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