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