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