Fix gcc43 warnings. Mark *csp as immutable
[privoxy.git] / urlmatch.c
1 const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.13 2006/12/06 19:50:54 fabiankeil Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/urlmatch.c,v $
5  *
6  * Purpose     :  Declares functions to match URLs against URL
7  *                patterns.
8  *
9  * Copyright   :  Written by and Copyright (C) 2001-2003, 2006 the SourceForge
10  *                Privoxy team. http://www.privoxy.org/
11  *
12  *                Based on the Internet Junkbuster originally written
13  *                by and Copyright (C) 1997 Anonymous Coders and
14  *                Junkbusters Corporation.  http://www.junkbusters.com
15  *
16  *                This program is free software; you can redistribute it
17  *                and/or modify it under the terms of the GNU General
18  *                Public License as published by the Free Software
19  *                Foundation; either version 2 of the License, or (at
20  *                your option) any later version.
21  *
22  *                This program is distributed in the hope that it will
23  *                be useful, but WITHOUT ANY WARRANTY; without even the
24  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
25  *                PARTICULAR PURPOSE.  See the GNU General Public
26  *                License for more details.
27  *
28  *                The GNU General Public License should be included with
29  *                this file.  If not, you can view it at
30  *                http://www.gnu.org/copyleft/gpl.html
31  *                or write to the Free Software Foundation, Inc., 59
32  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33  *
34  * Revisions   :
35  *    $Log: urlmatch.c,v $
36  *    Revision 1.13  2006/12/06 19:50:54  fabiankeil
37  *    parse_http_url() now handles intercepted
38  *    HTTP request lines as well. Moved parts
39  *    of parse_http_url()'s code into
40  *    init_domain_components() so that it can
41  *    be reused in chat().
42  *
43  *    Revision 1.12  2006/07/18 14:48:47  david__schmidt
44  *    Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch)
45  *    with what was really the latest development (the v_3_0_branch branch)
46  *
47  *    Revision 1.10.2.7  2003/05/17 15:57:24  oes
48  *     - parse_http_url now checks memory allocation failure for
49  *       duplication of "*" URL and rejects "*something" URLs
50  *       Closes bug #736344
51  *     - Added a comment to what might look like a bug in
52  *       create_url_spec (see !bug #736931)
53  *     - Comment cosmetics
54  *
55  *    Revision 1.10.2.6  2003/05/07 12:39:48  oes
56  *    Fix typo: Default port for https URLs is 443, not 143.
57  *    Thanks to Scott Tregear for spotting this one.
58  *
59  *    Revision 1.10.2.5  2003/02/28 13:09:29  oes
60  *    Fixed a rare double free condition as per Bug #694713
61  *
62  *    Revision 1.10.2.4  2003/02/28 12:57:44  oes
63  *    Moved freeing of http request structure to its owner
64  *    as per Dan Price's observations in Bug #694713
65  *
66  *    Revision 1.10.2.3  2002/11/12 16:50:40  oes
67  *    Fixed memory leak in parse_http_request() reported by Oliver Stoeneberg. Fixes bug #637073
68  *
69  *    Revision 1.10.2.2  2002/09/25 14:53:15  oes
70  *    Added basic support for OPTIONS and TRACE HTTP methods:
71  *    parse_http_url now recognizes the "*" URI as well as
72  *    the OPTIONS and TRACE method keywords.
73  *
74  *    Revision 1.10.2.1  2002/06/06 19:06:44  jongfoster
75  *    Adding support for proprietary Microsoft WebDAV extensions
76  *
77  *    Revision 1.10  2002/05/12 21:40:37  jongfoster
78  *    - Removing some unused code
79  *
80  *    Revision 1.9  2002/04/04 00:36:36  gliptak
81  *    always use pcre for matching
82  *
83  *    Revision 1.8  2002/04/03 23:32:47  jongfoster
84  *    Fixing memory leak on error
85  *
86  *    Revision 1.7  2002/03/26 22:29:55  swa
87  *    we have a new homepage!
88  *
89  *    Revision 1.6  2002/03/24 13:25:43  swa
90  *    name change related issues
91  *
92  *    Revision 1.5  2002/03/13 00:27:05  jongfoster
93  *    Killing warnings
94  *
95  *    Revision 1.4  2002/03/07 03:46:17  oes
96  *    Fixed compiler warnings
97  *
98  *    Revision 1.3  2002/03/03 14:51:11  oes
99  *    Fixed CLF logging: Added ocmd member for client's request to struct http_request
100  *
101  *    Revision 1.2  2002/01/21 00:14:09  jongfoster
102  *    Correcting comment style
103  *    Fixing an uninitialized memory bug in create_url_spec()
104  *
105  *    Revision 1.1  2002/01/17 20:53:46  jongfoster
106  *    Moving all our URL and URL pattern parsing code to the same file - it
107  *    was scattered around in filters.c, loaders.c and parsers.c.
108  *
109  *    Providing a single, simple url_match(pattern,url) function - rather than
110  *    the 3-line match routine which was repeated all over the place.
111  *
112  *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
113  *
114  *    Providing parse_http_url() so that URLs can be parsed without faking a
115  *    HTTP request line for parse_http_request() or repeating the parsing
116  *    code (both of which were techniques that were actually in use).
117  *
118  *    Standardizing that struct http_request is used to represent a URL, and
119  *    struct url_spec is used to represent a URL pattern.  (Before, URLs were
120  *    represented as seperate variables and a partially-filled-in url_spec).
121  *
122  *
123  *********************************************************************/
124 \f
125
126 #include "config.h"
127
128 #ifndef _WIN32
129 #include <stdio.h>
130 #include <sys/types.h>
131 #endif
132
133 #include <stdlib.h>
134 #include <ctype.h>
135 #include <assert.h>
136 #include <string.h>
137
138 #if !defined(_WIN32) && !defined(__OS2__)
139 #include <unistd.h>
140 #endif
141
142 #include "project.h"
143 #include "urlmatch.h"
144 #include "ssplit.h"
145 #include "miscutil.h"
146 #include "errlog.h"
147
148 const char urlmatch_h_rcs[] = URLMATCH_H_VERSION;
149
150
151 /*********************************************************************
152  *
153  * Function    :  free_http_request
154  *
155  * Description :  Freez a http_request structure
156  *
157  * Parameters  :
158  *          1  :  http = points to a http_request structure to free
159  *
160  * Returns     :  N/A
161  *
162  *********************************************************************/
163 void free_http_request(struct http_request *http)
164 {
165    assert(http);
166
167    freez(http->cmd);
168    freez(http->ocmd);
169    freez(http->gpc);
170    freez(http->host);
171    freez(http->url);
172    freez(http->hostport);
173    freez(http->path);
174    freez(http->ver);
175    freez(http->host_ip_addr_str);
176    freez(http->dbuffer);
177    freez(http->dvec);
178    http->dcount = 0;
179 }
180
181 /*********************************************************************
182  *
183  * Function    :  init_domain_components
184  *
185  * Description :  Splits the domain name so we can compare it
186  *                against wildcards. It used to be part of
187  *                parse_http_url, but was separated because the
188  *                same code is required in chat in case of
189  *                intercepted requests.
190  *
191  * Parameters  :
192  *          1  :  http = pointer to the http structure to hold elements.
193  *
194  * Returns     :  JB_ERR_OK on success
195  *                JB_ERR_MEMORY on out of memory
196  *                JB_ERR_PARSE on malformed command/URL
197  *                             or >100 domains deep.
198  *
199  *********************************************************************/
200 jb_err init_domain_components(struct http_request *http)
201 {
202    char *vec[BUFFER_SIZE];
203    size_t size;
204    char *p;
205
206    http->dbuffer = strdup(http->host);
207    if (NULL == http->dbuffer)
208    {
209       return JB_ERR_MEMORY;
210    }
211
212    /* map to lower case */
213    for (p = http->dbuffer; *p ; p++)
214    {
215       *p = (char)tolower((int)(unsigned char)*p);
216    }
217
218    /* split the domain name into components */
219    http->dcount = ssplit(http->dbuffer, ".", vec, SZ(vec), 1, 1);
220
221    if (http->dcount <= 0)
222    {
223       /*
224        * Error: More than SZ(vec) components in domain
225        *    or: no components in domain
226        */
227       log_error(LOG_LEVEL_ERROR, "More than SZ(vec) components in domain or none at all.");
228       return JB_ERR_PARSE;
229    }
230
231    /* save a copy of the pointers in dvec */
232    size = (size_t)http->dcount * sizeof(*http->dvec);
233
234    http->dvec = (char **)malloc(size);
235    if (NULL == http->dvec)
236    {
237       return JB_ERR_MEMORY;
238    }
239
240    memcpy(http->dvec, vec, size);
241
242    return JB_ERR_OK;
243 }
244
245 /*********************************************************************
246  *
247  * Function    :  parse_http_url
248  *
249  * Description :  Parse out the host and port from the URL.  Find the
250  *                hostname & path, port (if ':'), and/or password (if '@')
251  *
252  * Parameters  :
253  *          1  :  url = URL (or is it URI?) to break down
254  *          2  :  http = pointer to the http structure to hold elements.
255  *                       Will be zeroed before use.  Note that this
256  *                       function sets the http->gpc and http->ver
257  *                       members to NULL.
258  *          3  :  csp = Current client state (buffers, headers, etc...)
259  *
260  * Returns     :  JB_ERR_OK on success
261  *                JB_ERR_MEMORY on out of memory
262  *                JB_ERR_PARSE on malformed command/URL
263  *                             or >100 domains deep.
264  *
265  *********************************************************************/
266 jb_err parse_http_url(const char * url,
267                       struct http_request *http,
268                       const struct client_state *csp)
269 {
270    int host_available = 1; /* A proxy can dream. */
271
272    /*
273     * Zero out the results structure
274     */
275    memset(http, '\0', sizeof(*http));
276
277
278    /*
279     * Save our initial URL
280     */
281    http->url = strdup(url);
282    if (http->url == NULL)
283    {
284       return JB_ERR_MEMORY;
285    }
286
287
288    /*
289     * Check for * URI. If found, we're done.
290     */  
291    if (*http->url == '*')
292    {
293       if  ( NULL == (http->path = strdup("*"))
294          || NULL == (http->hostport = strdup("")) ) 
295       {
296          return JB_ERR_MEMORY;
297       }
298       if (http->url[1] != '\0')
299       {
300          return JB_ERR_PARSE;
301       }
302       return JB_ERR_OK;
303    }
304
305
306    /*
307     * Split URL into protocol,hostport,path.
308     */
309    {
310       char *buf;
311       char *url_noproto;
312       char *url_path;
313
314       buf = strdup(url);
315       if (buf == NULL)
316       {
317          return JB_ERR_MEMORY;
318       }
319
320       /* Find the start of the URL in our scratch space */
321       url_noproto = buf;
322       if (strncmpic(url_noproto, "http://",  7) == 0)
323       {
324          url_noproto += 7;
325          http->ssl = 0;
326       }
327       else if (strncmpic(url_noproto, "https://", 8) == 0)
328       {
329          url_noproto += 8;
330          http->ssl = 1;
331       }
332       else if (*url_noproto == '/')
333       {
334         /*
335          * Short request line without protocol and host.
336          * Most likely because the client's request
337          * was intercepted and redirected into Privoxy.
338          */
339          http->ssl = 0;
340          http->host = NULL;
341          host_available = 0;
342       }
343       else
344       {
345          http->ssl = 0;
346       }
347
348       url_path = strchr(url_noproto, '/');
349       if (url_path != NULL)
350       {
351          /*
352           * Got a path.
353           *
354           * NOTE: The following line ignores the path for HTTPS URLS.
355           * This means that you get consistent behaviour if you type a
356           * https URL in and it's parsed by the function.  (When the
357           * URL is actually retrieved, SSL hides the path part).
358           */
359          http->path = strdup(http->ssl ? "/" : url_path);
360          *url_path = '\0';
361          http->hostport = strdup(url_noproto);
362       }
363       else
364       {
365          /*
366           * Repair broken HTTP requests that don't contain a path,
367           * or CONNECT requests
368           */
369          http->path = strdup("/");
370          http->hostport = strdup(url_noproto);
371       }
372
373       freez(buf);
374
375       if ( (http->path == NULL)
376         || (http->hostport == NULL))
377       {
378          return JB_ERR_MEMORY;
379       }
380    }
381
382    if (!host_available)
383    {
384       /* Without host, there is nothing left to do here */
385       return JB_ERR_OK;
386    }
387
388    /*
389     * Split hostport into user/password (ignored), host, port.
390     */
391    {
392       char *buf;
393       char *host;
394       char *port;
395
396       buf = strdup(http->hostport);
397       if (buf == NULL)
398       {
399          return JB_ERR_MEMORY;
400       }
401
402       /* check if url contains username and/or password */
403       host = strchr(buf, '@');
404       if (host != NULL)
405       {
406          /* Contains username/password, skip it and the @ sign. */
407          host++;
408       }
409       else
410       {
411          /* No username or password. */
412          host = buf;
413       }
414
415       /* check if url contains port */
416       port = strchr(host, ':');
417       if (port != NULL)
418       {
419          /* Contains port */
420          /* Terminate hostname and point to start of port string */
421          *port++ = '\0';
422          http->port = atoi(port);
423       }
424       else
425       {
426          /* No port specified. */
427          http->port = (http->ssl ? 443 : 80);
428       }
429
430       http->host = strdup(host);
431
432       free(buf);
433
434       if (http->host == NULL)
435       {
436          return JB_ERR_MEMORY;
437       }
438    }
439
440    /*
441     * Split domain name so we can compare it against wildcards
442     */
443    return init_domain_components(http);
444
445 }
446
447
448 /*********************************************************************
449  *
450  * Function    :  parse_http_request
451  *
452  * Description :  Parse out the host and port from the URL.  Find the
453  *                hostname & path, port (if ':'), and/or password (if '@')
454  *
455  * Parameters  :
456  *          1  :  req = HTTP request line to break down
457  *          2  :  http = pointer to the http structure to hold elements
458  *          3  :  csp = Current client state (buffers, headers, etc...)
459  *
460  * Returns     :  JB_ERR_OK on success
461  *                JB_ERR_MEMORY on out of memory
462  *                JB_ERR_CGI_PARAMS on malformed command/URL
463  *                                  or >100 domains deep.
464  *
465  *********************************************************************/
466 jb_err parse_http_request(const char *req,
467                           struct http_request *http,
468                           const struct client_state *csp)
469 {
470    char *buf;
471    char *v[10];
472    int n;
473    jb_err err;
474    int is_connect = 0;
475
476    memset(http, '\0', sizeof(*http));
477
478    buf = strdup(req);
479    if (buf == NULL)
480    {
481       return JB_ERR_MEMORY;
482    }
483
484    n = ssplit(buf, " \r\n", v, SZ(v), 1, 1);
485    if (n != 3)
486    {
487       log_error(LOG_LEVEL_ERROR, "Trouble ssplitting: %s", buf);
488       free(buf);
489       return JB_ERR_PARSE;
490    }
491
492    /* this could be a CONNECT request */
493    if (strcmpic(v[0], "connect") == 0)
494    {
495       /* Secure */
496       is_connect = 1;
497    }
498    /* or it could be any other basic HTTP request type */
499    else if ((0 == strcmpic(v[0], "get"))
500          || (0 == strcmpic(v[0], "head"))
501          || (0 == strcmpic(v[0], "post"))
502          || (0 == strcmpic(v[0], "put"))
503          || (0 == strcmpic(v[0], "delete"))
504          || (0 == strcmpic(v[0], "options"))
505          || (0 == strcmpic(v[0], "trace"))
506  
507          /* or a webDAV extension (RFC2518) */
508          || (0 == strcmpic(v[0], "propfind"))
509          || (0 == strcmpic(v[0], "proppatch"))
510          || (0 == strcmpic(v[0], "move"))
511          || (0 == strcmpic(v[0], "copy"))
512          || (0 == strcmpic(v[0], "mkcol"))
513          || (0 == strcmpic(v[0], "lock"))
514          || (0 == strcmpic(v[0], "unlock"))
515
516          /* Or a Microsoft webDAV extension for Exchange 2000.  See: */
517          /*   http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html */
518          /*   http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ 
519          || (0 == strcmpic(v[0], "bcopy"))
520          || (0 == strcmpic(v[0], "bmove"))
521          || (0 == strcmpic(v[0], "bdelete"))
522          || (0 == strcmpic(v[0], "bpropfind"))
523          || (0 == strcmpic(v[0], "bproppatch"))
524
525          /* Or another Microsoft webDAV extension for Exchange 2000.  See: */
526          /*   http://systems.cs.colorado.edu/grunwald/MobileComputing/Papers/draft-cohen-gena-p-base-00.txt */
527          /*   http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html */
528          /*   http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ 
529          || (0 == strcmpic(v[0], "subscribe"))
530          || (0 == strcmpic(v[0], "unsubscribe"))
531          || (0 == strcmpic(v[0], "notify"))
532          || (0 == strcmpic(v[0], "poll"))
533          )
534    {
535       /* Normal */
536       is_connect = 0;
537    }
538    else
539    {
540       /* Unknown HTTP method */
541       log_error(LOG_LEVEL_ERROR, "Unknown HTTP method detected: %s", v[0]);
542       free(buf);
543       return JB_ERR_PARSE;
544    }
545
546    err = parse_http_url(v[1], http, csp);
547    if (err)
548    {
549       free(buf);
550       return err;
551    }
552
553    /*
554     * Copy the details into the structure
555     */
556    http->ssl = is_connect;
557    http->cmd = strdup(req);
558    http->gpc = strdup(v[0]);
559    http->ver = strdup(v[2]);
560
561    if ( (http->cmd == NULL)
562      || (http->gpc == NULL)
563      || (http->ver == NULL) )
564    {
565       free(buf);
566       return JB_ERR_MEMORY;
567    }
568
569    free(buf);
570    return JB_ERR_OK;
571
572 }
573
574
575 /*********************************************************************
576  *
577  * Function    :  simple_domaincmp
578  *
579  * Description :  Domain-wise Compare fqdn's.  The comparison is
580  *                both left- and right-anchored.  The individual
581  *                domain names are compared with simplematch().
582  *                This is only used by domain_match.
583  *
584  * Parameters  :
585  *          1  :  pv = array of patterns to compare
586  *          2  :  fv = array of domain components to compare
587  *          3  :  len = length of the arrays (both arrays are the
588  *                      same length - if they weren't, it couldn't
589  *                      possibly be a match).
590  *
591  * Returns     :  0 => domains are equivalent, else no match.
592  *
593  *********************************************************************/
594 static int simple_domaincmp(char **pv, char **fv, int len)
595 {
596    int n;
597
598    for (n = 0; n < len; n++)
599    {
600       if (simplematch(pv[n], fv[n]))
601       {
602          return 1;
603       }
604    }
605
606    return 0;
607
608 }
609
610
611 /*********************************************************************
612  *
613  * Function    :  domain_match
614  *
615  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
616  *                pattern->unachored, the comparison is un-, left-,
617  *                right-anchored, or both.
618  *                The individual domain names are compared with
619  *                simplematch().
620  *
621  * Parameters  :
622  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
623  *          2  :  fqdn = domain name against which the patterns are compared.
624  *
625  * Returns     :  0 => domains are equivalent, else no match.
626  *
627  *********************************************************************/
628 static int domain_match(const struct url_spec *pattern, const struct http_request *fqdn)
629 {
630    char **pv, **fv;  /* vectors  */
631    int    plen, flen;
632    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
633
634    plen = pattern->dcount;
635    flen = fqdn->dcount;
636
637    if (flen < plen)
638    {
639       /* fqdn is too short to match this pattern */
640       return 1;
641    }
642
643    pv   = pattern->dvec;
644    fv   = fqdn->dvec;
645
646    if (unanchored == ANCHOR_LEFT)
647    {
648       /*
649        * Right anchored.
650        *
651        * Convert this into a fully anchored pattern with
652        * the fqdn and pattern the same length
653        */
654       fv += (flen - plen); /* flen - plen >= 0 due to check above */
655       return simple_domaincmp(pv, fv, plen);
656    }
657    else if (unanchored == 0)
658    {
659       /* Fully anchored, check length */
660       if (flen != plen)
661       {
662          return 1;
663       }
664       return simple_domaincmp(pv, fv, plen);
665    }
666    else if (unanchored == ANCHOR_RIGHT)
667    {
668       /* Left anchored, ignore all extra in fqdn */
669       return simple_domaincmp(pv, fv, plen);
670    }
671    else
672    {
673       /* Unanchored */
674       int n;
675       int maxn = flen - plen;
676       for (n = 0; n <= maxn; n++)
677       {
678          if (!simple_domaincmp(pv, fv, plen))
679          {
680             return 0;
681          }
682          /*
683           * Doesn't match from start of fqdn
684           * Try skipping first part of fqdn
685           */
686          fv++;
687       }
688       return 1;
689    }
690
691 }
692
693
694 /*********************************************************************
695  *
696  * Function    :  create_url_spec
697  *
698  * Description :  Creates a "url_spec" structure from a string.
699  *                When finished, free with free_url_spec().
700  *
701  * Parameters  :
702  *          1  :  url = Target url_spec to be filled in.  Will be
703  *                      zeroed before use.
704  *          2  :  buf = Source pattern, null terminated.  NOTE: The
705  *                      contents of this buffer are destroyed by this
706  *                      function.  If this function succeeds, the
707  *                      buffer is copied to url->spec.  If this
708  *                      function fails, the contents of the buffer
709  *                      are lost forever.
710  *
711  * Returns     :  JB_ERR_OK - Success
712  *                JB_ERR_MEMORY - Out of memory
713  *                JB_ERR_PARSE - Cannot parse regex (Detailed message
714  *                               written to system log)
715  *
716  *********************************************************************/
717 jb_err create_url_spec(struct url_spec * url, const char * buf)
718 {
719    char *p;
720
721    assert(url);
722    assert(buf);
723
724    /*
725     * Zero memory
726     */
727    memset(url, '\0', sizeof(*url));
728
729    /*
730     * Save a copy of the orignal specification
731     */
732    if ((url->spec = strdup(buf)) == NULL)
733    {
734       return JB_ERR_MEMORY;
735    }
736
737    if ((p = strchr(buf, '/')) != NULL)
738    {
739       if (NULL == (url->path = strdup(p)))
740       {
741          freez(url->spec);
742          return JB_ERR_MEMORY;
743       }
744       url->pathlen = strlen(url->path);
745       *p = '\0';
746    }
747    else
748    {
749       url->path    = NULL;
750       url->pathlen = 0;
751    }
752    if (url->path)
753    {
754       int errcode;
755       char rebuf[BUFFER_SIZE];
756
757       if (NULL == (url->preg = zalloc(sizeof(*url->preg))))
758       {
759          freez(url->spec);
760          freez(url->path);
761          return JB_ERR_MEMORY;
762       }
763
764       snprintf(rebuf, sizeof(rebuf), "^(%s)", url->path);
765
766       errcode = regcomp(url->preg, rebuf,
767             (REG_EXTENDED|REG_NOSUB|REG_ICASE));
768       if (errcode)
769       {
770          size_t errlen = regerror(errcode,
771             url->preg, rebuf, sizeof(rebuf));
772
773          if (errlen > (sizeof(rebuf) - (size_t)1))
774          {
775             errlen = sizeof(rebuf) - (size_t)1;
776          }
777          rebuf[errlen] = '\0';
778
779          log_error(LOG_LEVEL_ERROR, "error compiling %s: %s",
780             url->spec, rebuf);
781
782          freez(url->spec);
783          freez(url->path);
784          regfree(url->preg);
785          freez(url->preg);
786
787          return JB_ERR_PARSE;
788       }
789    }
790    if ((p = strchr(buf, ':')) == NULL)
791    {
792       url->port = 0;
793    }
794    else
795    {
796       *p++ = '\0';
797       url->port = atoi(p);
798    }
799
800    if (buf[0] != '\0')
801    {
802       char *v[150];
803       size_t size;
804
805       /*
806        * Parse domain part
807        */
808       if (buf[strlen(buf) - 1] == '.')
809       {
810          url->unanchored |= ANCHOR_RIGHT;
811       }
812       if (buf[0] == '.')
813       {
814          url->unanchored |= ANCHOR_LEFT;
815       }
816
817       /* 
818        * Split domain into components
819        */
820       url->dbuffer = strdup(buf);
821       if (NULL == url->dbuffer)
822       {
823          freez(url->spec);
824          freez(url->path);
825          regfree(url->preg);
826          freez(url->preg);
827          return JB_ERR_MEMORY;
828       }
829
830       /* 
831        * Map to lower case
832        */
833       for (p = url->dbuffer; *p ; p++)
834       {
835          *p = (char)tolower((int)(unsigned char)*p);
836       }
837
838       /* 
839        * Split the domain name into components
840        */
841       url->dcount = ssplit(url->dbuffer, ".", v, SZ(v), 1, 1);
842
843       if (url->dcount < 0)
844       {
845          freez(url->spec);
846          freez(url->path);
847          regfree(url->preg);
848          freez(url->preg);
849          freez(url->dbuffer);
850          url->dcount = 0;
851          return JB_ERR_MEMORY;
852       }
853       else if (url->dcount != 0)
854       {
855
856          /* 
857           * Save a copy of the pointers in dvec
858           */
859          size = (size_t)url->dcount * sizeof(*url->dvec);
860
861          url->dvec = (char **)malloc(size);
862          if (NULL == url->dvec)
863          {
864             freez(url->spec);
865             freez(url->path);
866             regfree(url->preg);
867             freez(url->preg);
868             freez(url->dbuffer);
869             url->dcount = 0;
870             return JB_ERR_MEMORY;
871          }
872
873          memcpy(url->dvec, v, size);
874       }
875       /*
876        * else dcount == 0 in which case we needn't do anything,
877        * since dvec will never be accessed and the pattern will
878        * match all domains.
879        */
880    }
881
882    return JB_ERR_OK;
883
884 }
885
886
887 /*********************************************************************
888  *
889  * Function    :  free_url_spec
890  *
891  * Description :  Called from the "unloaders".  Freez the url
892  *                structure elements.
893  *
894  * Parameters  :
895  *          1  :  url = pointer to a url_spec structure.
896  *
897  * Returns     :  N/A
898  *
899  *********************************************************************/
900 void free_url_spec(struct url_spec *url)
901 {
902    if (url == NULL) return;
903
904    freez(url->spec);
905    freez(url->dbuffer);
906    freez(url->dvec);
907    freez(url->path);
908    if (url->preg)
909    {
910       regfree(url->preg);
911       freez(url->preg);
912    }
913 }
914
915
916 /*********************************************************************
917  *
918  * Function    :  url_match
919  *
920  * Description :  Compare a URL against a URL pattern.
921  *
922  * Parameters  :
923  *          1  :  pattern = a URL pattern
924  *          2  :  url = URL to match
925  *
926  * Returns     :  0 iff the URL matches the pattern, else nonzero.
927  *
928  *********************************************************************/
929 int url_match(const struct url_spec *pattern,
930               const struct http_request *url)
931 {
932    return ((pattern->port == 0) || (pattern->port == url->port))
933        && ((pattern->dbuffer == NULL) || (domain_match(pattern, url) == 0))
934        && ((pattern->path == NULL) ||
935             (regexec(pattern->preg, url->path, 0, NULL, 0) == 0)
936       );
937 }
938
939
940 /*
941   Local Variables:
942   tab-width: 3
943   end:
944 */