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