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