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