windows: Enable building Privoxy with OpenSSL
[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           * or when the request was https-inspected and the request
268           * line got rewritten.
269           */
270          url_noproto += 8;
271          http->ssl = 1;
272       }
273       else if (*url_noproto == '/')
274       {
275         /*
276          * Short request line without protocol and host.
277          * Most likely because the client's request
278          * was intercepted and redirected into Privoxy.
279          */
280          http->host = NULL;
281          host_available = 0;
282       }
283       else if (require_protocol)
284       {
285          freez(buf);
286          return JB_ERR_PARSE;
287       }
288
289       url_path = strchr(url_noproto, '/');
290       if (url_path != NULL)
291       {
292          /*
293           * Got a path.
294           *
295           * If FEATURE_HTTPS_INSPECTION isn't available, ignore the
296           * path for https URLs so that we get consistent behaviour
297           * if a https URL is parsed. When the URL is actually
298           * retrieved, https hides the path part.
299           */
300          http->path = strdup_or_die(
301 #ifndef FEATURE_HTTPS_INSPECTION
302             http->ssl ? "/" :
303 #endif
304             url_path
305          );
306          *url_path = '\0';
307          http->hostport = string_tolower(url_noproto);
308       }
309       else
310       {
311          /*
312           * Repair broken HTTP requests that don't contain a path,
313           * or CONNECT requests
314           */
315          http->path = strdup_or_die("/");
316          http->hostport = string_tolower(url_noproto);
317       }
318
319       freez(buf);
320
321       if (http->hostport == NULL)
322       {
323          return JB_ERR_PARSE;
324       }
325    }
326
327    if (!host_available)
328    {
329       /* Without host, there is nothing left to do here */
330       return JB_ERR_OK;
331    }
332
333    /*
334     * Split hostport into user/password (ignored), host, port.
335     */
336    {
337       char *buf;
338       char *host;
339       char *port;
340
341       buf = strdup_or_die(http->hostport);
342
343       /* check if url contains username and/or password */
344       host = strchr(buf, '@');
345       if (host != NULL)
346       {
347          /* Contains username/password, skip it and the @ sign. */
348          host++;
349       }
350       else
351       {
352          /* No username or password. */
353          host = buf;
354       }
355
356       /* Move after hostname before port number */
357       if (*host == '[')
358       {
359          /* Numeric IPv6 address delimited by brackets */
360          host++;
361          port = strchr(host, ']');
362
363          if (port == NULL)
364          {
365             /* Missing closing bracket */
366             freez(buf);
367             return JB_ERR_PARSE;
368          }
369
370          *port++ = '\0';
371
372          if (*port == '\0')
373          {
374             port = NULL;
375          }
376          else if (*port != ':')
377          {
378             /* Garbage after closing bracket */
379             freez(buf);
380             return JB_ERR_PARSE;
381          }
382       }
383       else
384       {
385          /* Plain non-escaped hostname */
386          port = strchr(host, ':');
387       }
388
389       /* check if url contains port */
390       if (port != NULL)
391       {
392          /* Contains port */
393          char *endptr;
394          long parsed_port;
395          /* Terminate hostname and point to start of port string */
396          *port++ = '\0';
397          parsed_port = strtol(port, &endptr, 10);
398          if ((parsed_port <= 0) || (parsed_port > 65535) || (*endptr != '\0'))
399          {
400             log_error(LOG_LEVEL_ERROR, "Invalid port in URL: %s.", url);
401             freez(buf);
402             return JB_ERR_PARSE;
403          }
404          http->port = (int)parsed_port;
405       }
406       else
407       {
408          /* No port specified. */
409          http->port = (http->ssl ? 443 : 80);
410       }
411
412       http->host = strdup_or_die(host);
413
414       freez(buf);
415    }
416
417    /* Split domain name so we can compare it against wildcards */
418    return init_domain_components(http);
419
420 }
421
422
423 /*********************************************************************
424  *
425  * Function    :  unknown_method
426  *
427  * Description :  Checks whether a method is unknown.
428  *
429  * Parameters  :
430  *          1  :  method = points to a http method
431  *
432  * Returns     :  TRUE if it's unknown, FALSE otherwise.
433  *
434  *********************************************************************/
435 static int unknown_method(const char *method)
436 {
437    static const char * const known_http_methods[] = {
438       /* Basic HTTP request type */
439       "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CONNECT",
440       /* webDAV extensions (RFC2518) */
441       "PROPFIND", "PROPPATCH", "MOVE", "COPY", "MKCOL", "LOCK", "UNLOCK",
442       /*
443        * Microsoft webDAV extension for Exchange 2000.  See:
444        * http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html
445        * http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp
446        */
447       "BCOPY", "BMOVE", "BDELETE", "BPROPFIND", "BPROPPATCH",
448       /*
449        * Another Microsoft webDAV extension for Exchange 2000.  See:
450        * http://systems.cs.colorado.edu/grunwald/MobileComputing/Papers/draft-cohen-gena-p-base-00.txt
451        * http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html
452        * http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp
453        */
454       "SUBSCRIBE", "UNSUBSCRIBE", "NOTIFY", "POLL",
455       /*
456        * Yet another WebDAV extension, this time for
457        * Web Distributed Authoring and Versioning (RFC3253)
458        */
459       "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
460       "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY",
461       /*
462        * The PATCH method is defined by RFC5789, the format of the
463        * actual patch in the body depends on the application, but from
464        * Privoxy's point of view it doesn't matter.
465        */
466       "PATCH",
467    };
468    int i;
469
470    for (i = 0; i < SZ(known_http_methods); i++)
471    {
472       if (0 == strcmpic(method, known_http_methods[i]))
473       {
474          return FALSE;
475       }
476    }
477
478    return TRUE;
479
480 }
481
482
483 /*********************************************************************
484  *
485  * Function    :  normalize_http_version
486  *
487  * Description :  Take a supported HTTP version string and remove
488  *                leading zeroes etc., reject unsupported versions.
489  *
490  *                This is an explicit RFC 2616 (3.1) MUST and
491  *                RFC 7230 mandates that intermediaries send their
492  *                own HTTP-version in forwarded messages.
493  *
494  * Parameters  :
495  *          1  :  http_version = HTTP version string
496  *
497  * Returns     :  JB_ERR_OK on success
498  *                JB_ERR_PARSE if the HTTP version is unsupported
499  *
500  *********************************************************************/
501 static jb_err normalize_http_version(char *http_version)
502 {
503    unsigned int major_version;
504    unsigned int minor_version;
505
506    if (2 != sscanf(http_version, "HTTP/%u.%u", &major_version, &minor_version))
507    {
508       log_error(LOG_LEVEL_ERROR, "Unsupported HTTP version: %s", http_version);
509       return JB_ERR_PARSE;
510    }
511
512    if (major_version != 1 || (minor_version != 0 && minor_version != 1))
513    {
514       log_error(LOG_LEVEL_ERROR, "The only supported HTTP "
515          "versions are 1.0 and 1.1. This rules out: %s", http_version);
516       return JB_ERR_PARSE;
517    }
518
519    assert(strlen(http_version) >= 8);
520    snprintf(http_version, 9, "HTTP/%u.%u", major_version, minor_version);
521
522    return JB_ERR_OK;
523
524 }
525
526
527 /*********************************************************************
528  *
529  * Function    :  parse_http_request
530  *
531  * Description :  Parse out the host and port from the URL.  Find the
532  *                hostname & path, port (if ':'), and/or password (if '@')
533  *
534  * Parameters  :
535  *          1  :  req = HTTP request line to break down
536  *          2  :  http = pointer to the http structure to hold elements
537  *
538  * Returns     :  JB_ERR_OK on success
539  *                JB_ERR_CGI_PARAMS on malformed command/URL
540  *                                  or >100 domains deep.
541  *
542  *********************************************************************/
543 jb_err parse_http_request(const char *req, struct http_request *http)
544 {
545    char *buf;
546    char *v[3];
547    int n;
548    jb_err err;
549
550    memset(http, '\0', sizeof(*http));
551
552    buf = strdup_or_die(req);
553
554    n = ssplit(buf, " \r\n", v, SZ(v));
555    if (n != 3)
556    {
557       freez(buf);
558       return JB_ERR_PARSE;
559    }
560
561    /*
562     * Fail in case of unknown methods
563     * which we might not handle correctly.
564     *
565     * XXX: There should be a config option
566     * to forward requests with unknown methods
567     * anyway. Most of them don't need special
568     * steps.
569     */
570    if (unknown_method(v[0]))
571    {
572       log_error(LOG_LEVEL_ERROR, "Unknown HTTP method detected: %s", v[0]);
573       freez(buf);
574       return JB_ERR_PARSE;
575    }
576
577    if (JB_ERR_OK != normalize_http_version(v[2]))
578    {
579       freez(buf);
580       return JB_ERR_PARSE;
581    }
582
583    http->ssl = !strcmpic(v[0], "CONNECT");
584
585    err = parse_http_url(v[1], http, !http->ssl);
586    if (err)
587    {
588       freez(buf);
589       return err;
590    }
591
592    /*
593     * Copy the details into the structure
594     */
595    http->cmd = strdup_or_die(req);
596    http->gpc = strdup_or_die(v[0]);
597    http->version = strdup_or_die(v[2]);
598    http->ocmd = strdup_or_die(http->cmd);
599
600    freez(buf);
601
602    return JB_ERR_OK;
603
604 }
605
606
607 #ifdef HAVE_PCRE2
608 /*********************************************************************
609  *
610  * Function    :  compile_pattern
611  *
612  * Description :  Compiles a host, domain or TAG pattern.
613  *
614  * Parameters  :
615  *          1  :  pattern = The pattern to compile.
616  *          2  :  anchoring = How the regex should be modified
617  *                            before compilation. Can be either
618  *                            one of NO_ANCHORING, LEFT_ANCHORED,
619  *                            RIGHT_ANCHORED or RIGHT_ANCHORED_HOST.
620  *          3  :  url     = In case of failures, the spec member is
621  *                          logged and the structure freed.
622  *          4  :  regex   = Where the compiled regex should be stored.
623  *
624  * Returns     :  JB_ERR_OK - Success
625  *                JB_ERR_PARSE - Cannot parse regex
626  *
627  *********************************************************************/
628 static jb_err compile_pattern(const char *pattern, enum regex_anchoring anchoring,
629                               struct pattern_spec *url, pcre2_code **regex)
630 {
631    int errcode;
632    const char *fmt = NULL;
633    char *rebuf;
634    size_t rebuf_size;
635    PCRE2_SIZE error_offset;
636    int ret;
637
638    assert(pattern);
639
640    if (pattern[0] == '\0')
641    {
642       *regex = NULL;
643       return JB_ERR_OK;
644    }
645
646    switch (anchoring)
647    {
648       case NO_ANCHORING:
649          fmt = "%s";
650          break;
651       case RIGHT_ANCHORED:
652          fmt = "%s$";
653          break;
654       case RIGHT_ANCHORED_HOST:
655          fmt = "%s\\.?$";
656          break;
657       case LEFT_ANCHORED:
658          fmt = "^%s";
659          break;
660       default:
661          log_error(LOG_LEVEL_FATAL,
662             "Invalid anchoring in compile_pattern %d", anchoring);
663    }
664    rebuf_size = strlen(pattern) + strlen(fmt);
665    rebuf = malloc_or_die(rebuf_size);
666
667    snprintf(rebuf, rebuf_size, fmt, pattern);
668
669    *regex = pcre2_compile((const unsigned char *)pattern,
670       PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &errcode,
671       &error_offset, NULL);
672    if (*regex == NULL)
673    {
674       log_error(LOG_LEVEL_ERROR, "error compiling %s from %s: %s",
675          pattern, url->spec, rebuf);
676       freez(rebuf);
677
678       return JB_ERR_PARSE;
679    }
680
681 #ifndef DISABLE_PCRE_JIT_COMPILATION
682    /* Try to enable JIT compilation but continue if it's unsupported. */
683    if ((ret = pcre2_jit_compile(*regex, PCRE2_JIT_COMPLETE)) &&
684        (ret != PCRE2_ERROR_JIT_BADOPTION))
685    {
686       log_error(LOG_LEVEL_ERROR,
687          "Unexpected error enabling JIT compilation for %s from %s: %s",
688          pattern, url->spec, rebuf);
689       freez(rebuf);
690
691       return JB_ERR_PARSE;
692    }
693 #endif
694
695    freez(rebuf);
696
697    return JB_ERR_OK;
698
699 }
700 #else
701 /*********************************************************************
702  *
703  * Function    :  compile_pattern
704  *
705  * Description :  Compiles a host, domain or TAG pattern.
706  *
707  * Parameters  :
708  *          1  :  pattern = The pattern to compile.
709  *          2  :  anchoring = How the regex should be modified
710  *                            before compilation. Can be either
711  *                            one of NO_ANCHORING, LEFT_ANCHORED,
712  *                            RIGHT_ANCHORED or RIGHT_ANCHORED_HOST.
713  *          3  :  url     = In case of failures, the spec member is
714  *                          logged and the structure freed.
715  *          4  :  regex   = Where the compiled regex should be stored.
716  *
717  * Returns     :  JB_ERR_OK - Success
718  *                JB_ERR_PARSE - Cannot parse regex
719  *
720  *********************************************************************/
721 static jb_err compile_pattern(const char *pattern, enum regex_anchoring anchoring,
722                               struct pattern_spec *url, regex_t **regex)
723 {
724    int errcode;
725    const char *fmt = NULL;
726    char *rebuf;
727    size_t rebuf_size;
728
729    assert(pattern);
730
731    if (pattern[0] == '\0')
732    {
733       *regex = NULL;
734       return JB_ERR_OK;
735    }
736
737    switch (anchoring)
738    {
739       case NO_ANCHORING:
740          fmt = "%s";
741          break;
742       case RIGHT_ANCHORED:
743          fmt = "%s$";
744          break;
745       case RIGHT_ANCHORED_HOST:
746          fmt = "%s\\.?$";
747          break;
748       case LEFT_ANCHORED:
749          fmt = "^%s";
750          break;
751       default:
752          log_error(LOG_LEVEL_FATAL,
753             "Invalid anchoring in compile_pattern %d", anchoring);
754    }
755    rebuf_size = strlen(pattern) + strlen(fmt);
756    rebuf = malloc_or_die(rebuf_size);
757    *regex = zalloc_or_die(sizeof(**regex));
758
759    snprintf(rebuf, rebuf_size, fmt, pattern);
760
761    errcode = regcomp(*regex, rebuf, (REG_EXTENDED|REG_NOSUB|REG_ICASE));
762
763    if (errcode)
764    {
765       size_t errlen = regerror(errcode, *regex, rebuf, rebuf_size);
766       if (errlen > (rebuf_size - (size_t)1))
767       {
768          errlen = rebuf_size - (size_t)1;
769       }
770       rebuf[errlen] = '\0';
771       log_error(LOG_LEVEL_ERROR, "error compiling %s from %s: %s",
772          pattern, url->spec, rebuf);
773       free_pattern_spec(url);
774       freez(rebuf);
775
776       return JB_ERR_PARSE;
777    }
778    freez(rebuf);
779
780    return JB_ERR_OK;
781
782 }
783 #endif
784
785
786 /*********************************************************************
787  *
788  * Function    :  compile_url_pattern
789  *
790  * Description :  Compiles the three parts of an URL pattern.
791  *
792  * Parameters  :
793  *          1  :  url = Target pattern_spec to be filled in.
794  *          2  :  buf = The url pattern to compile. Will be messed up.
795  *
796  * Returns     :  JB_ERR_OK - Success
797  *                JB_ERR_MEMORY - Out of memory
798  *                JB_ERR_PARSE - Cannot parse regex
799  *
800  *********************************************************************/
801 static jb_err compile_url_pattern(struct pattern_spec *url, char *buf)
802 {
803    char *p;
804    const size_t prefix_length = 18;
805
806 #ifdef FEATURE_PCRE_HOST_PATTERNS
807    if (strncmpic(buf, "PCRE-HOST-PATTERN:", prefix_length) == 0)
808    {
809       url->pattern.url_spec.host_regex_type = PCRE_HOST_PATTERN;
810       /* Overwrite the "PCRE-HOST-PATTERN:" prefix */
811       memmove(buf, buf+prefix_length, strlen(buf+prefix_length)+1);
812    }
813    else
814    {
815       url->pattern.url_spec.host_regex_type = VANILLA_HOST_PATTERN;
816    }
817 #else
818    if (strncmpic(buf, "PCRE-HOST-PATTERN:", prefix_length) == 0)
819    {
820       log_error(LOG_LEVEL_ERROR,
821          "PCRE-HOST-PATTERN detected while Privoxy has been compiled "
822          "without FEATURE_PCRE_HOST_PATTERNS: %s",
823          buf);
824       /* Overwrite the "PCRE-HOST-PATTERN:" prefix */
825       memmove(buf, buf+prefix_length, strlen(buf+prefix_length)+1);
826       /*
827        * The pattern will probably not work as expected.
828        * We don't simply return JB_ERR_PARSE here so the
829        * regression tests can be loaded with and without
830        * FEATURE_PCRE_HOST_PATTERNS.
831        */
832    }
833 #endif
834
835    p = strchr(buf, '/');
836    if (NULL != p)
837    {
838       /*
839        * Only compile the regex if it consists of more than
840        * a single slash, otherwise it wouldn't affect the result.
841        */
842       if (p[1] != '\0')
843       {
844          /*
845           * XXX: does it make sense to compile the slash at the beginning?
846           */
847          jb_err err = compile_pattern(p, LEFT_ANCHORED, url, &url->pattern.url_spec.preg);
848
849          if (JB_ERR_OK != err)
850          {
851             return err;
852          }
853       }
854       *p = '\0';
855    }
856
857    /*
858     * IPv6 numeric hostnames can contain colons, thus we need
859     * to delimit the hostname before the real port separator.
860     * As brackets are already used in the hostname pattern,
861     * we use angle brackets ('<', '>') instead.
862     */
863    if ((buf[0] == '<') && (NULL != (p = strchr(buf + 1, '>'))))
864    {
865       *p++ = '\0';
866       buf++;
867
868       if (*p == '\0')
869       {
870          /* IPv6 address without port number */
871          p = NULL;
872       }
873       else if (*p != ':')
874       {
875          /* Garbage after address delimiter */
876          return JB_ERR_PARSE;
877       }
878    }
879    else
880    {
881       p = strchr(buf, ':');
882    }
883
884    if (NULL != p)
885    {
886       *p++ = '\0';
887       url->pattern.url_spec.port_list = strdup_or_die(p);
888    }
889    else
890    {
891       url->pattern.url_spec.port_list = NULL;
892    }
893
894    if (buf[0] != '\0')
895    {
896 #ifdef FEATURE_PCRE_HOST_PATTERNS
897       if (url->pattern.url_spec.host_regex_type == PCRE_HOST_PATTERN)
898       {
899          return compile_pcre_host_pattern(url, buf);
900       }
901       else
902 #endif
903       {
904          return compile_vanilla_host_pattern(url, buf);
905       }
906    }
907
908    return JB_ERR_OK;
909
910 }
911
912
913 #ifdef FEATURE_PCRE_HOST_PATTERNS
914 /*********************************************************************
915  *
916  * Function    :  compile_pcre_host_pattern
917  *
918  * Description :  Parses and compiles a pcre host pattern.
919  *
920  * Parameters  :
921  *          1  :  url = Target pattern_spec to be filled in.
922  *          2  :  host_pattern = Host pattern to compile.
923  *
924  * Returns     :  JB_ERR_OK - Success
925  *                JB_ERR_MEMORY - Out of memory
926  *                JB_ERR_PARSE - Cannot parse regex
927  *
928  *********************************************************************/
929 static jb_err compile_pcre_host_pattern(struct pattern_spec *url, const char *host_pattern)
930 {
931    return compile_pattern(host_pattern, RIGHT_ANCHORED_HOST, url, &url->pattern.url_spec.host_regex);
932 }
933 #endif /* def FEATURE_PCRE_HOST_PATTERNS */
934
935
936 /*********************************************************************
937  *
938  * Function    :  compile_vanilla_host_pattern
939  *
940  * Description :  Parses and "compiles" an old-school host pattern.
941  *
942  * Parameters  :
943  *          1  :  url = Target pattern_spec to be filled in.
944  *          2  :  host_pattern = Host pattern to parse.
945  *
946  * Returns     :  JB_ERR_OK - Success
947  *                JB_ERR_PARSE - Cannot parse regex
948  *
949  *********************************************************************/
950 static jb_err compile_vanilla_host_pattern(struct pattern_spec *url, const char *host_pattern)
951 {
952    char *v[150];
953    size_t size;
954    char *p;
955
956    /*
957     * Parse domain part
958     */
959    if (host_pattern[strlen(host_pattern) - 1] == '.')
960    {
961       url->pattern.url_spec.unanchored |= ANCHOR_RIGHT;
962    }
963    if (host_pattern[0] == '.')
964    {
965       url->pattern.url_spec.unanchored |= ANCHOR_LEFT;
966    }
967
968    /*
969     * Split domain into components
970     */
971    url->pattern.url_spec.dbuffer = strdup_or_die(host_pattern);
972
973    /*
974     * Map to lower case
975     */
976    for (p = url->pattern.url_spec.dbuffer; *p ; p++)
977    {
978       *p = (char)privoxy_tolower(*p);
979    }
980
981    /*
982     * Split the domain name into components
983     */
984    url->pattern.url_spec.dcount = ssplit(url->pattern.url_spec.dbuffer, ".", v, SZ(v));
985
986    if (url->pattern.url_spec.dcount < 0)
987    {
988       free_pattern_spec(url);
989       return JB_ERR_PARSE;
990    }
991    else if (url->pattern.url_spec.dcount != 0)
992    {
993       /*
994        * Save a copy of the pointers in dvec
995        */
996       size = (size_t)url->pattern.url_spec.dcount * sizeof(*url->pattern.url_spec.dvec);
997
998       url->pattern.url_spec.dvec = malloc_or_die(size);
999
1000       memcpy(url->pattern.url_spec.dvec, v, size);
1001    }
1002    /*
1003     * else dcount == 0 in which case we needn't do anything,
1004     * since dvec will never be accessed and the pattern will
1005     * match all domains.
1006     */
1007    return JB_ERR_OK;
1008 }
1009
1010
1011 /*********************************************************************
1012  *
1013  * Function    :  simplematch
1014  *
1015  * Description :  String matching, with a (greedy) '*' wildcard that
1016  *                stands for zero or more arbitrary characters and
1017  *                character classes in [], which take both enumerations
1018  *                and ranges.
1019  *
1020  * Parameters  :
1021  *          1  :  pattern = pattern for matching
1022  *          2  :  text    = text to be matched
1023  *
1024  * Returns     :  0 if match, else nonzero
1025  *
1026  *********************************************************************/
1027 static int simplematch(const char *pattern, const char *text)
1028 {
1029    const unsigned char *pat = (const unsigned char *)pattern;
1030    const unsigned char *txt = (const unsigned char *)text;
1031    const unsigned char *fallback = pat;
1032    int wildcard = 0;
1033
1034    unsigned char lastchar = 'a';
1035    unsigned i;
1036    unsigned char charmap[32];
1037
1038    while (*txt)
1039    {
1040
1041       /* EOF pattern but !EOF text? */
1042       if (*pat == '\0')
1043       {
1044          if (wildcard)
1045          {
1046             pat = fallback;
1047          }
1048          else
1049          {
1050             return 1;
1051          }
1052       }
1053
1054       /* '*' in the pattern?  */
1055       if (*pat == '*')
1056       {
1057
1058          /* The pattern ends afterwards? Speed up the return. */
1059          if (*++pat == '\0')
1060          {
1061             return 0;
1062          }
1063
1064          /* Else, set wildcard mode and remember position after '*' */
1065          wildcard = 1;
1066          fallback = pat;
1067       }
1068
1069       /* Character range specification? */
1070       if (*pat == '[')
1071       {
1072          memset(charmap, '\0', sizeof(charmap));
1073
1074          while (*++pat != ']')
1075          {
1076             if (!*pat)
1077             {
1078                return 1;
1079             }
1080             else if (*pat == '-')
1081             {
1082                if ((*++pat == ']') || *pat == '\0')
1083                {
1084                   return(1);
1085                }
1086                for (i = lastchar; i <= *pat; i++)
1087                {
1088                   charmap[i / 8] |= (unsigned char)(1 << (i % 8));
1089                }
1090             }
1091             else
1092             {
1093                charmap[*pat / 8] |= (unsigned char)(1 << (*pat % 8));
1094                lastchar = *pat;
1095             }
1096          }
1097       } /* -END- if Character range specification */
1098
1099
1100       /*
1101        * Char match, or char range match?
1102        */
1103       if ((*pat == *txt)
1104        || (*pat == '?')
1105        || ((*pat == ']') && (charmap[*txt / 8] & (1 << (*txt % 8)))))
1106       {
1107          /*
1108           * Success: Go ahead
1109           */
1110          pat++;
1111       }
1112       else if (!wildcard)
1113       {
1114          /*
1115           * No match && no wildcard: No luck
1116           */
1117          return 1;
1118       }
1119       else if (pat != fallback)
1120       {
1121          /*
1122           * Increment text pointer if in char range matching
1123           */
1124          if (*pat == ']')
1125          {
1126             txt++;
1127          }
1128          /*
1129           * Wildcard mode && nonmatch beyond fallback: Rewind pattern
1130           */
1131          pat = fallback;
1132          /*
1133           * Restart matching from current text pointer
1134           */
1135          continue;
1136       }
1137       txt++;
1138    }
1139
1140    /* Cut off extra '*'s */
1141    if (*pat == '*') pat++;
1142
1143    /* If this is the pattern's end, fine! */
1144    return(*pat);
1145
1146 }
1147
1148
1149 #ifdef HAVE_PCRE2
1150 /*********************************************************************
1151  *
1152  * Function    :  pcre2_pattern_matches
1153  *
1154  * Description :  Checks if a compiled pcre2 pattern matches a string.
1155  *
1156  * Parameters  :
1157  *          1  :  pattern = The compiled pattern
1158  *          2  :  string = The string to check
1159  *
1160  * Returns     :  TRUE for yes, FALSE otherwise.
1161  *
1162  *********************************************************************/
1163 int pcre2_pattern_matches(const pcre2_code *pattern, const char *string)
1164 {
1165    PCRE2_SIZE offset;
1166    int ret;
1167    pcre2_match_data *pcre2_matches;
1168
1169    assert(pattern != NULL);
1170    assert(string != NULL);
1171
1172    offset = 0;
1173
1174    pcre2_matches = pcre2_match_data_create_from_pattern(pattern, NULL);
1175    if (NULL == pcre2_matches)
1176    {
1177       log_error(LOG_LEVEL_ERROR,
1178          "Out of memory while matching pattern against %s", string);
1179       return FALSE;
1180    }
1181
1182    ret = pcre2_match(pattern, (const unsigned char *)string, strlen(string),
1183       offset, 0, pcre2_matches, NULL);
1184
1185    pcre2_match_data_free(pcre2_matches);
1186
1187    return (ret >= 0);
1188 }
1189 #endif
1190
1191
1192 /*********************************************************************
1193  *
1194  * Function    :  simple_domaincmp
1195  *
1196  * Description :  Domain-wise Compare fqdn's.  The comparison is
1197  *                both left- and right-anchored.  The individual
1198  *                domain names are compared with simplematch().
1199  *                This is only used by domain_match.
1200  *
1201  * Parameters  :
1202  *          1  :  pv = array of patterns to compare
1203  *          2  :  fv = array of domain components to compare
1204  *          3  :  len = length of the arrays (both arrays are the
1205  *                      same length - if they weren't, it couldn't
1206  *                      possibly be a match).
1207  *
1208  * Returns     :  0 => domains are equivalent, else no match.
1209  *
1210  *********************************************************************/
1211 static int simple_domaincmp(char **pv, char **fv, int len)
1212 {
1213    int n;
1214
1215    for (n = 0; n < len; n++)
1216    {
1217       if (simplematch(pv[n], fv[n]))
1218       {
1219          return 1;
1220       }
1221    }
1222
1223    return 0;
1224
1225 }
1226
1227
1228 /*********************************************************************
1229  *
1230  * Function    :  domain_match
1231  *
1232  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
1233  *                p.pattern->unachored, the comparison is un-, left-,
1234  *                right-anchored, or both.
1235  *                The individual domain names are compared with
1236  *                simplematch().
1237  *
1238  * Parameters  :
1239  *          1  :  p = a domain that may contain a '*' as a wildcard.
1240  *          2  :  fqdn = domain name against which the patterns are compared.
1241  *
1242  * Returns     :  0 => domains are equivalent, else no match.
1243  *
1244  *********************************************************************/
1245 static int domain_match(const struct pattern_spec *p, const struct http_request *fqdn)
1246 {
1247    char **pv, **fv;  /* vectors  */
1248    int    plen, flen;
1249    int unanchored = p->pattern.url_spec.unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
1250
1251    plen = p->pattern.url_spec.dcount;
1252    flen = fqdn->dcount;
1253
1254    if (flen < plen)
1255    {
1256       /* fqdn is too short to match this pattern */
1257       return 1;
1258    }
1259
1260    pv   = p->pattern.url_spec.dvec;
1261    fv   = fqdn->dvec;
1262
1263    if (unanchored == ANCHOR_LEFT)
1264    {
1265       /*
1266        * Right anchored.
1267        *
1268        * Convert this into a fully anchored pattern with
1269        * the fqdn and pattern the same length
1270        */
1271       fv += (flen - plen); /* flen - plen >= 0 due to check above */
1272       return simple_domaincmp(pv, fv, plen);
1273    }
1274    else if (unanchored == 0)
1275    {
1276       /* Fully anchored, check length */
1277       if (flen != plen)
1278       {
1279          return 1;
1280       }
1281       return simple_domaincmp(pv, fv, plen);
1282    }
1283    else if (unanchored == ANCHOR_RIGHT)
1284    {
1285       /* Left anchored, ignore all extra in fqdn */
1286       return simple_domaincmp(pv, fv, plen);
1287    }
1288    else
1289    {
1290       /* Unanchored */
1291       int n;
1292       int maxn = flen - plen;
1293       for (n = 0; n <= maxn; n++)
1294       {
1295          if (!simple_domaincmp(pv, fv, plen))
1296          {
1297             return 0;
1298          }
1299          /*
1300           * Doesn't match from start of fqdn
1301           * Try skipping first part of fqdn
1302           */
1303          fv++;
1304       }
1305       return 1;
1306    }
1307
1308 }
1309
1310
1311 /*********************************************************************
1312  *
1313  * Function    :  create_pattern_spec
1314  *
1315  * Description :  Creates a "pattern_spec" structure from a string.
1316  *                When finished, free with free_pattern_spec().
1317  *
1318  * Parameters  :
1319  *          1  :  pattern = Target pattern_spec to be filled in.
1320  *                          Will be zeroed before use.
1321  *          2  :  buf = Source pattern, null terminated.  NOTE: The
1322  *                      contents of this buffer are destroyed by this
1323  *                      function.  If this function succeeds, the
1324  *                      buffer is copied to pattern->spec.  If this
1325  *                      function fails, the contents of the buffer
1326  *                      are lost forever.
1327  *
1328  * Returns     :  JB_ERR_OK - Success
1329  *                JB_ERR_PARSE - Cannot parse regex (Detailed message
1330  *                               written to system log)
1331  *
1332  *********************************************************************/
1333 jb_err create_pattern_spec(struct pattern_spec *pattern, char *buf)
1334 {
1335    static const struct
1336    {
1337       /** The tag pattern prefix to match */
1338       const char *prefix;
1339
1340       /** The length of the prefix to match */
1341       const size_t prefix_length;
1342
1343       /** The pattern flag */
1344       const unsigned flag;
1345    } tag_pattern[] = {
1346       { "TAG:",              4, PATTERN_SPEC_TAG_PATTERN},
1347 #ifdef FEATURE_CLIENT_TAGS
1348       { "CLIENT-TAG:",      11, PATTERN_SPEC_CLIENT_TAG_PATTERN},
1349 #endif
1350       { "NO-REQUEST-TAG:",  15, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN},
1351       { "NO-RESPONSE-TAG:", 16, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN}
1352    };
1353    int i;
1354
1355    assert(pattern);
1356    assert(buf);
1357
1358    memset(pattern, '\0', sizeof(*pattern));
1359
1360    /* Remember the original specification for the CGI pages. */
1361    pattern->spec = strdup_or_die(buf);
1362
1363    /* Check if it's a tag pattern */
1364    for (i = 0; i < SZ(tag_pattern); i++)
1365    {
1366       if (0 == strncmpic(pattern->spec, tag_pattern[i].prefix, tag_pattern[i].prefix_length))
1367       {
1368          /* The regex starts after the prefix */
1369          const char *tag_regex = buf + tag_pattern[i].prefix_length;
1370
1371          pattern->flags |= tag_pattern[i].flag;
1372
1373          return compile_pattern(tag_regex, NO_ANCHORING, pattern,
1374             &pattern->pattern.tag_regex);
1375       }
1376    }
1377
1378    /* If it isn't a tag pattern it must be an URL pattern. */
1379    pattern->flags |= PATTERN_SPEC_URL_PATTERN;
1380
1381    return compile_url_pattern(pattern, buf);
1382
1383 }
1384
1385
1386 /*********************************************************************
1387  *
1388  * Function    :  free_pattern_spec
1389  *
1390  * Description :  Called from the "unloaders".  Freez the pattern
1391  *                structure elements.
1392  *
1393  * Parameters  :
1394  *          1  :  pattern = pointer to a pattern_spec structure.
1395  *
1396  * Returns     :  N/A
1397  *
1398  *********************************************************************/
1399 void free_pattern_spec(struct pattern_spec *pattern)
1400 {
1401    if (pattern == NULL) return;
1402
1403    freez(pattern->spec);
1404
1405    if (!(pattern->flags & PATTERN_SPEC_URL_PATTERN))
1406    {
1407       if (pattern->pattern.tag_regex)
1408       {
1409 #ifdef HAVE_PCRE2
1410          pcre2_code_free(pattern->pattern.tag_regex);
1411 #else
1412          regfree(pattern->pattern.tag_regex);
1413          freez(pattern->pattern.tag_regex);
1414 #endif
1415       }
1416       return;
1417    }
1418
1419 #ifdef FEATURE_PCRE_HOST_PATTERNS
1420    if (pattern->pattern.url_spec.host_regex)
1421    {
1422 #ifdef HAVE_PCRE2
1423       pcre2_code_free(pattern->pattern.url_spec.host_regex);
1424 #else
1425       regfree(pattern->pattern.url_spec.host_regex);
1426       freez(pattern->pattern.url_spec.host_regex);
1427 #endif
1428    }
1429 #endif /* def FEATURE_PCRE_HOST_PATTERNS */
1430    freez(pattern->pattern.url_spec.dbuffer);
1431    freez(pattern->pattern.url_spec.dvec);
1432    pattern->pattern.url_spec.dcount = 0;
1433    freez(pattern->pattern.url_spec.port_list);
1434    if (pattern->pattern.url_spec.preg)
1435    {
1436 #ifdef HAVE_PCRE2
1437       pcre2_code_free(pattern->pattern.url_spec.preg);
1438 #else
1439       regfree(pattern->pattern.url_spec.preg);
1440       freez(pattern->pattern.url_spec.preg);
1441 #endif
1442    }
1443 }
1444
1445
1446 /*********************************************************************
1447  *
1448  * Function    :  port_matches
1449  *
1450  * Description :  Compares a port against a port list.
1451  *
1452  * Parameters  :
1453  *          1  :  port      = The port to check.
1454  *          2  :  port_list = The list of port to compare with.
1455  *
1456  * Returns     :  TRUE for yes, FALSE otherwise.
1457  *
1458  *********************************************************************/
1459 static int port_matches(const int port, const char *port_list)
1460 {
1461    return ((NULL == port_list) || match_portlist(port_list, port));
1462 }
1463
1464
1465 /*********************************************************************
1466  *
1467  * Function    :  host_matches
1468  *
1469  * Description :  Compares a host against a host pattern.
1470  *
1471  * Parameters  :
1472  *          1  :  url = The URL to match
1473  *          2  :  pattern = The URL pattern
1474  *
1475  * Returns     :  TRUE for yes, FALSE otherwise.
1476  *
1477  *********************************************************************/
1478 static int host_matches(const struct http_request *http,
1479                         const struct pattern_spec *pattern)
1480 {
1481    assert(http->host != NULL);
1482 #ifdef FEATURE_PCRE_HOST_PATTERNS
1483    if (pattern->pattern.url_spec.host_regex_type == PCRE_HOST_PATTERN)
1484    {
1485       return ((NULL == pattern->pattern.url_spec.host_regex)
1486 #ifdef HAVE_PCRE2
1487          || pcre2_pattern_matches(pattern->pattern.url_spec.host_regex,
1488                http->host));
1489 #else
1490          || (0 == regexec(pattern->pattern.url_spec.host_regex,
1491                http->host, 0, NULL, 0)));
1492 #endif
1493    }
1494 #endif
1495    return ((NULL == pattern->pattern.url_spec.dbuffer) || (0 == domain_match(pattern, http)));
1496 }
1497
1498
1499 /*********************************************************************
1500  *
1501  * Function    :  path_matches
1502  *
1503  * Description :  Compares a path against a path pattern.
1504  *
1505  * Parameters  :
1506  *          1  :  path = The path to match
1507  *          2  :  pattern = The URL pattern
1508  *
1509  * Returns     :  TRUE for yes, FALSE otherwise.
1510  *
1511  *********************************************************************/
1512 static int path_matches(const char *path, const struct pattern_spec *pattern)
1513 {
1514    return ((NULL == pattern->pattern.url_spec.preg)
1515 #ifdef HAVE_PCRE2
1516       || (pcre2_pattern_matches(pattern->pattern.url_spec.preg, path)));
1517 #else
1518       || (0 == regexec(pattern->pattern.url_spec.preg, path, 0, NULL, 0)));
1519 #endif
1520 }
1521
1522
1523 /*********************************************************************
1524  *
1525  * Function    :  url_match
1526  *
1527  * Description :  Compare a URL against a URL pattern.
1528  *
1529  * Parameters  :
1530  *          1  :  pattern = a URL pattern
1531  *          2  :  url = URL to match
1532  *
1533  * Returns     :  Nonzero if the URL matches the pattern, else 0.
1534  *
1535  *********************************************************************/
1536 int url_match(const struct pattern_spec *pattern,
1537               const struct http_request *http)
1538 {
1539    if (!(pattern->flags & PATTERN_SPEC_URL_PATTERN))
1540    {
1541       /* It's not an URL pattern and thus shouldn't be matched against URLs */
1542       return 0;
1543    }
1544
1545    return (port_matches(http->port, pattern->pattern.url_spec.port_list)
1546       && host_matches(http, pattern) && path_matches(http->path, pattern));
1547
1548 }
1549
1550
1551 /*********************************************************************
1552  *
1553  * Function    :  match_portlist
1554  *
1555  * Description :  Check if a given number is covered by a comma
1556  *                separated list of numbers and ranges (a,b-c,d,..)
1557  *
1558  * Parameters  :
1559  *          1  :  portlist = String with list
1560  *          2  :  port = port to check
1561  *
1562  * Returns     :  0 => no match
1563  *                1 => match
1564  *
1565  *********************************************************************/
1566 int match_portlist(const char *portlist, int port)
1567 {
1568    char *min, *max, *next, *portlist_copy;
1569
1570    min = portlist_copy = strdup_or_die(portlist);
1571
1572    /*
1573     * Zero-terminate first item and remember offset for next
1574     */
1575    if (NULL != (next = strchr(portlist_copy, (int) ',')))
1576    {
1577       *next++ = '\0';
1578    }
1579
1580    /*
1581     * Loop through all items, checking for match
1582     */
1583    while (NULL != min)
1584    {
1585       if (NULL == (max = strchr(min, (int) '-')))
1586       {
1587          /*
1588           * No dash, check for equality
1589           */
1590          if (port == atoi(min))
1591          {
1592             freez(portlist_copy);
1593             return(1);
1594          }
1595       }
1596       else
1597       {
1598          /*
1599           * This is a range, so check if between min and max,
1600           * or, if max was omitted, between min and 65K
1601           */
1602          *max++ = '\0';
1603          if (port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535))
1604          {
1605             freez(portlist_copy);
1606             return(1);
1607          }
1608
1609       }
1610
1611       /*
1612        * Jump to next item
1613        */
1614       min = next;
1615
1616       /*
1617        * Zero-terminate next item and remember offset for n+1
1618        */
1619       if ((NULL != next) && (NULL != (next = strchr(next, (int) ','))))
1620       {
1621          *next++ = '\0';
1622       }
1623    }
1624
1625    freez(portlist_copy);
1626    return 0;
1627
1628 }
1629
1630
1631 /*********************************************************************
1632  *
1633  * Function    :  parse_forwarder_address
1634  *
1635  * Description :  Parse out the username, password, host and port from
1636  *                a forwarder address.
1637  *
1638  * Parameters  :
1639  *          1  :  address = The forwarder address to parse.
1640  *          2  :  hostname = Used to return the hostname. NULL on error.
1641  *          3  :  port = Used to return the port. Untouched if no port
1642  *                       is specified.
1643  *          4  :  username = Used to return the username if any.
1644  *          5  :  password = Used to return the password if any.
1645  *
1646  * Returns     :  JB_ERR_OK on success
1647  *                JB_ERR_MEMORY on out of memory
1648  *                JB_ERR_PARSE on malformed address.
1649  *
1650  *********************************************************************/
1651 jb_err parse_forwarder_address(char *address, char **hostname, int *port,
1652                                char **username, char **password)
1653 {
1654    char *p;
1655    char *tmp;
1656
1657    tmp = *hostname = strdup_or_die(address);
1658
1659    /* Parse username and password */
1660    if (username && password && (NULL != (p = strchr(*hostname, '@'))))
1661    {
1662       *p++ = '\0';
1663       *username = strdup_or_die(*hostname);
1664       *hostname = strdup_or_die(p);
1665
1666       if (NULL != (p = strchr(*username, ':')))
1667       {
1668          *p++ = '\0';
1669          *password = strdup_or_die(p);
1670       }
1671       freez(tmp);
1672    }
1673
1674    /* Parse hostname and port */
1675    p = *hostname;
1676    if ((*p == '[') && (NULL == strchr(p, ']')))
1677    {
1678       /* XXX: Should do some more validity checks here. */
1679       return JB_ERR_PARSE;
1680    }
1681
1682    if ((**hostname == '[') && (NULL != (p = strchr(*hostname, ']'))))
1683    {
1684       *p++ = '\0';
1685       memmove(*hostname, (*hostname + 1), (size_t)(p - *hostname));
1686       if (*p == ':')
1687       {
1688          *port = (int)strtol(++p, NULL, 0);
1689       }
1690    }
1691    else if (NULL != (p = strchr(*hostname, ':')))
1692    {
1693       *p++ = '\0';
1694       *port = (int)strtol(p, NULL, 0);
1695    }
1696
1697    return JB_ERR_OK;
1698
1699 }
1700
1701
1702 /*
1703   Local Variables:
1704   tab-width: 3
1705   end:
1706 */