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