Add privoxy-runtests.pm
[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 *)rebuf,
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 static 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    :  regex_matches
1195  *
1196  * Description :  Checks if a compiled regex pattern matches a string
1197  *                using either pcre2 or pcre1 code.
1198  *
1199  * Parameters  :
1200  *          1  :  pattern = The compiled pattern
1201  *          2  :  string = The string to check
1202  *
1203  * Returns     :  TRUE for yes, FALSE otherwise.
1204  *
1205  *********************************************************************/
1206 int regex_matches(const REGEX_TYPE *pattern, const char *string)
1207 {
1208 #ifdef HAVE_PCRE2
1209    return pcre2_pattern_matches(pattern, string);
1210 #else
1211    return (0 == regexec(pattern, string, 0, NULL, 0));
1212 #endif
1213 }
1214
1215 /*********************************************************************
1216  *
1217  * Function    :  simple_domaincmp
1218  *
1219  * Description :  Domain-wise Compare fqdn's.  The comparison is
1220  *                both left- and right-anchored.  The individual
1221  *                domain names are compared with simplematch().
1222  *                This is only used by domain_match.
1223  *
1224  * Parameters  :
1225  *          1  :  pv = array of patterns to compare
1226  *          2  :  fv = array of domain components to compare
1227  *          3  :  len = length of the arrays (both arrays are the
1228  *                      same length - if they weren't, it couldn't
1229  *                      possibly be a match).
1230  *
1231  * Returns     :  0 => domains are equivalent, else no match.
1232  *
1233  *********************************************************************/
1234 static int simple_domaincmp(char **pv, char **fv, int len)
1235 {
1236    int n;
1237
1238    for (n = 0; n < len; n++)
1239    {
1240       if (simplematch(pv[n], fv[n]))
1241       {
1242          return 1;
1243       }
1244    }
1245
1246    return 0;
1247
1248 }
1249
1250
1251 /*********************************************************************
1252  *
1253  * Function    :  domain_match
1254  *
1255  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
1256  *                p.pattern->unachored, the comparison is un-, left-,
1257  *                right-anchored, or both.
1258  *                The individual domain names are compared with
1259  *                simplematch().
1260  *
1261  * Parameters  :
1262  *          1  :  p = a domain that may contain a '*' as a wildcard.
1263  *          2  :  fqdn = domain name against which the patterns are compared.
1264  *
1265  * Returns     :  0 => domains are equivalent, else no match.
1266  *
1267  *********************************************************************/
1268 static int domain_match(const struct pattern_spec *p, const struct http_request *fqdn)
1269 {
1270    char **pv, **fv;  /* vectors  */
1271    int    plen, flen;
1272    int unanchored = p->pattern.url_spec.unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
1273
1274    plen = p->pattern.url_spec.dcount;
1275    flen = fqdn->dcount;
1276
1277    if (flen < plen)
1278    {
1279       /* fqdn is too short to match this pattern */
1280       return 1;
1281    }
1282
1283    pv   = p->pattern.url_spec.dvec;
1284    fv   = fqdn->dvec;
1285
1286    if (unanchored == ANCHOR_LEFT)
1287    {
1288       /*
1289        * Right anchored.
1290        *
1291        * Convert this into a fully anchored pattern with
1292        * the fqdn and pattern the same length
1293        */
1294       fv += (flen - plen); /* flen - plen >= 0 due to check above */
1295       return simple_domaincmp(pv, fv, plen);
1296    }
1297    else if (unanchored == 0)
1298    {
1299       /* Fully anchored, check length */
1300       if (flen != plen)
1301       {
1302          return 1;
1303       }
1304       return simple_domaincmp(pv, fv, plen);
1305    }
1306    else if (unanchored == ANCHOR_RIGHT)
1307    {
1308       /* Left anchored, ignore all extra in fqdn */
1309       return simple_domaincmp(pv, fv, plen);
1310    }
1311    else
1312    {
1313       /* Unanchored */
1314       int n;
1315       int maxn = flen - plen;
1316       for (n = 0; n <= maxn; n++)
1317       {
1318          if (!simple_domaincmp(pv, fv, plen))
1319          {
1320             return 0;
1321          }
1322          /*
1323           * Doesn't match from start of fqdn
1324           * Try skipping first part of fqdn
1325           */
1326          fv++;
1327       }
1328       return 1;
1329    }
1330
1331 }
1332
1333
1334 /*********************************************************************
1335  *
1336  * Function    :  create_pattern_spec
1337  *
1338  * Description :  Creates a "pattern_spec" structure from a string.
1339  *                When finished, free with free_pattern_spec().
1340  *
1341  * Parameters  :
1342  *          1  :  pattern = Target pattern_spec to be filled in.
1343  *                          Will be zeroed before use.
1344  *          2  :  buf = Source pattern, null terminated.  NOTE: The
1345  *                      contents of this buffer are destroyed by this
1346  *                      function.  If this function succeeds, the
1347  *                      buffer is copied to pattern->spec.  If this
1348  *                      function fails, the contents of the buffer
1349  *                      are lost forever.
1350  *
1351  * Returns     :  JB_ERR_OK - Success
1352  *                JB_ERR_PARSE - Cannot parse regex (Detailed message
1353  *                               written to system log)
1354  *
1355  *********************************************************************/
1356 jb_err create_pattern_spec(struct pattern_spec *pattern, char *buf)
1357 {
1358    static const struct
1359    {
1360       /** The tag pattern prefix to match */
1361       const char *prefix;
1362
1363       /** The length of the prefix to match */
1364       const size_t prefix_length;
1365
1366       /** The pattern flag */
1367       const unsigned flag;
1368    } tag_pattern[] = {
1369       { "TAG:",              4, PATTERN_SPEC_TAG_PATTERN},
1370 #ifdef FEATURE_CLIENT_TAGS
1371       { "CLIENT-TAG:",      11, PATTERN_SPEC_CLIENT_TAG_PATTERN},
1372 #endif
1373       { "NO-REQUEST-TAG:",  15, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN},
1374       { "NO-RESPONSE-TAG:", 16, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN}
1375    };
1376    int i;
1377
1378    assert(pattern);
1379    assert(buf);
1380
1381    memset(pattern, '\0', sizeof(*pattern));
1382
1383    /* Remember the original specification for the CGI pages. */
1384    pattern->spec = strdup_or_die(buf);
1385
1386    /* Check if it's a tag pattern */
1387    for (i = 0; i < SZ(tag_pattern); i++)
1388    {
1389       if (0 == strncmpic(pattern->spec, tag_pattern[i].prefix, tag_pattern[i].prefix_length))
1390       {
1391          /* The regex starts after the prefix */
1392          const char *tag_regex = buf + tag_pattern[i].prefix_length;
1393
1394          pattern->flags |= tag_pattern[i].flag;
1395
1396          return compile_pattern(tag_regex, NO_ANCHORING, pattern,
1397             &pattern->pattern.tag_regex);
1398       }
1399    }
1400
1401    /* If it isn't a tag pattern it must be an URL pattern. */
1402    pattern->flags |= PATTERN_SPEC_URL_PATTERN;
1403
1404    return compile_url_pattern(pattern, buf);
1405
1406 }
1407
1408
1409 /*********************************************************************
1410  *
1411  * Function    :  free_pattern_spec
1412  *
1413  * Description :  Called from the "unloaders".  Freez the pattern
1414  *                structure elements.
1415  *
1416  * Parameters  :
1417  *          1  :  pattern = pointer to a pattern_spec structure.
1418  *
1419  * Returns     :  N/A
1420  *
1421  *********************************************************************/
1422 void free_pattern_spec(struct pattern_spec *pattern)
1423 {
1424    if (pattern == NULL) return;
1425
1426    freez(pattern->spec);
1427
1428    if (!(pattern->flags & PATTERN_SPEC_URL_PATTERN))
1429    {
1430       if (pattern->pattern.tag_regex)
1431       {
1432 #ifdef HAVE_PCRE2
1433          pcre2_code_free(pattern->pattern.tag_regex);
1434 #else
1435          regfree(pattern->pattern.tag_regex);
1436          freez(pattern->pattern.tag_regex);
1437 #endif
1438       }
1439       return;
1440    }
1441
1442 #ifdef FEATURE_PCRE_HOST_PATTERNS
1443    if (pattern->pattern.url_spec.host_regex)
1444    {
1445 #ifdef HAVE_PCRE2
1446       pcre2_code_free(pattern->pattern.url_spec.host_regex);
1447 #else
1448       regfree(pattern->pattern.url_spec.host_regex);
1449       freez(pattern->pattern.url_spec.host_regex);
1450 #endif
1451    }
1452 #endif /* def FEATURE_PCRE_HOST_PATTERNS */
1453    freez(pattern->pattern.url_spec.dbuffer);
1454    freez(pattern->pattern.url_spec.dvec);
1455    pattern->pattern.url_spec.dcount = 0;
1456    freez(pattern->pattern.url_spec.port_list);
1457    if (pattern->pattern.url_spec.preg)
1458    {
1459 #ifdef HAVE_PCRE2
1460       pcre2_code_free(pattern->pattern.url_spec.preg);
1461 #else
1462       regfree(pattern->pattern.url_spec.preg);
1463       freez(pattern->pattern.url_spec.preg);
1464 #endif
1465    }
1466 }
1467
1468
1469 /*********************************************************************
1470  *
1471  * Function    :  port_matches
1472  *
1473  * Description :  Compares a port against a port list.
1474  *
1475  * Parameters  :
1476  *          1  :  port      = The port to check.
1477  *          2  :  port_list = The list of port to compare with.
1478  *
1479  * Returns     :  TRUE for yes, FALSE otherwise.
1480  *
1481  *********************************************************************/
1482 static int port_matches(const int port, const char *port_list)
1483 {
1484    return ((NULL == port_list) || match_portlist(port_list, port));
1485 }
1486
1487
1488 /*********************************************************************
1489  *
1490  * Function    :  host_matches
1491  *
1492  * Description :  Compares a host against a host pattern.
1493  *
1494  * Parameters  :
1495  *          1  :  url = The URL to match
1496  *          2  :  pattern = The URL pattern
1497  *
1498  * Returns     :  TRUE for yes, FALSE otherwise.
1499  *
1500  *********************************************************************/
1501 static int host_matches(const struct http_request *http,
1502                         const struct pattern_spec *pattern)
1503 {
1504    assert(http->host != NULL);
1505 #ifdef FEATURE_PCRE_HOST_PATTERNS
1506    if (pattern->pattern.url_spec.host_regex_type == PCRE_HOST_PATTERN)
1507    {
1508       return ((NULL == pattern->pattern.url_spec.host_regex)
1509          || regex_matches(pattern->pattern.url_spec.host_regex, http->host));
1510    }
1511 #endif
1512    return ((NULL == pattern->pattern.url_spec.dbuffer) || (0 == domain_match(pattern, http)));
1513 }
1514
1515
1516 /*********************************************************************
1517  *
1518  * Function    :  path_matches
1519  *
1520  * Description :  Compares a path against a path pattern.
1521  *
1522  * Parameters  :
1523  *          1  :  path = The path to match
1524  *          2  :  pattern = The URL pattern
1525  *
1526  * Returns     :  TRUE for yes, FALSE otherwise.
1527  *
1528  *********************************************************************/
1529 static int path_matches(const char *path, const struct pattern_spec *pattern)
1530 {
1531    return ((NULL == pattern->pattern.url_spec.preg)
1532       || regex_matches(pattern->pattern.url_spec.preg, path));
1533 }
1534
1535
1536 /*********************************************************************
1537  *
1538  * Function    :  url_match
1539  *
1540  * Description :  Compare a URL against a URL pattern.
1541  *
1542  * Parameters  :
1543  *          1  :  pattern = a URL pattern
1544  *          2  :  url = URL to match
1545  *
1546  * Returns     :  Nonzero if the URL matches the pattern, else 0.
1547  *
1548  *********************************************************************/
1549 int url_match(const struct pattern_spec *pattern,
1550               const struct http_request *http)
1551 {
1552    if (!(pattern->flags & PATTERN_SPEC_URL_PATTERN))
1553    {
1554       /* It's not an URL pattern and thus shouldn't be matched against URLs */
1555       return 0;
1556    }
1557
1558    return (port_matches(http->port, pattern->pattern.url_spec.port_list)
1559       && host_matches(http, pattern) && path_matches(http->path, pattern));
1560
1561 }
1562
1563
1564 /*********************************************************************
1565  *
1566  * Function    :  match_portlist
1567  *
1568  * Description :  Check if a given number is covered by a comma
1569  *                separated list of numbers and ranges (a,b-c,d,..)
1570  *
1571  * Parameters  :
1572  *          1  :  portlist = String with list
1573  *          2  :  port = port to check
1574  *
1575  * Returns     :  0 => no match
1576  *                1 => match
1577  *
1578  *********************************************************************/
1579 int match_portlist(const char *portlist, int port)
1580 {
1581    char *min, *max, *next, *portlist_copy;
1582
1583    min = portlist_copy = strdup_or_die(portlist);
1584
1585    /*
1586     * Zero-terminate first item and remember offset for next
1587     */
1588    if (NULL != (next = strchr(portlist_copy, (int) ',')))
1589    {
1590       *next++ = '\0';
1591    }
1592
1593    /*
1594     * Loop through all items, checking for match
1595     */
1596    while (NULL != min)
1597    {
1598       if (NULL == (max = strchr(min, (int) '-')))
1599       {
1600          /*
1601           * No dash, check for equality
1602           */
1603          if (port == atoi(min))
1604          {
1605             freez(portlist_copy);
1606             return(1);
1607          }
1608       }
1609       else
1610       {
1611          /*
1612           * This is a range, so check if between min and max,
1613           * or, if max was omitted, between min and 65K
1614           */
1615          *max++ = '\0';
1616          if (port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535))
1617          {
1618             freez(portlist_copy);
1619             return(1);
1620          }
1621
1622       }
1623
1624       /*
1625        * Jump to next item
1626        */
1627       min = next;
1628
1629       /*
1630        * Zero-terminate next item and remember offset for n+1
1631        */
1632       if ((NULL != next) && (NULL != (next = strchr(next, (int) ','))))
1633       {
1634          *next++ = '\0';
1635       }
1636    }
1637
1638    freez(portlist_copy);
1639    return 0;
1640
1641 }
1642
1643
1644 /*********************************************************************
1645  *
1646  * Function    :  parse_forwarder_address
1647  *
1648  * Description :  Parse out the username, password, host and port from
1649  *                a forwarder address.
1650  *
1651  * Parameters  :
1652  *          1  :  address = The forwarder address to parse.
1653  *          2  :  hostname = Used to return the hostname. NULL on error.
1654  *          3  :  port = Used to return the port. Untouched if no port
1655  *                       is specified.
1656  *          4  :  username = Used to return the username if any.
1657  *          5  :  password = Used to return the password if any.
1658  *
1659  * Returns     :  JB_ERR_OK on success
1660  *                JB_ERR_MEMORY on out of memory
1661  *                JB_ERR_PARSE on malformed address.
1662  *
1663  *********************************************************************/
1664 jb_err parse_forwarder_address(char *address, char **hostname, int *port,
1665                                char **username, char **password)
1666 {
1667    char *p;
1668    char *tmp;
1669
1670    tmp = *hostname = strdup_or_die(address);
1671
1672    /* Parse username and password */
1673    if (username && password && (NULL != (p = strchr(*hostname, '@'))))
1674    {
1675       *p++ = '\0';
1676       *username = strdup_or_die(*hostname);
1677       *hostname = strdup_or_die(p);
1678
1679       if (NULL != (p = strchr(*username, ':')))
1680       {
1681          *p++ = '\0';
1682          *password = strdup_or_die(p);
1683       }
1684       freez(tmp);
1685    }
1686
1687    /* Parse hostname and port */
1688    p = *hostname;
1689    if ((*p == '[') && (NULL == strchr(p, ']')))
1690    {
1691       /* XXX: Should do some more validity checks here. */
1692       return JB_ERR_PARSE;
1693    }
1694
1695    if ((**hostname == '[') && (NULL != (p = strchr(*hostname, ']'))))
1696    {
1697       *p++ = '\0';
1698       memmove(*hostname, (*hostname + 1), (size_t)(p - *hostname));
1699       if (*p == ':')
1700       {
1701          *port = (int)strtol(++p, NULL, 0);
1702       }
1703    }
1704    else if (NULL != (p = strchr(*hostname, ':')))
1705    {
1706       *p++ = '\0';
1707       *port = (int)strtol(p, NULL, 0);
1708    }
1709
1710    return JB_ERR_OK;
1711
1712 }
1713
1714
1715 /*
1716   Local Variables:
1717   tab-width: 3
1718   end:
1719 */