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