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