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