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