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