Now #defining the URLs of the config interface
[privoxy.git] / urlmatch.c
1 const char urlmatch_rcs[] = "$Id: urlmatch.c JGF $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/urlmatch.c,v $
5  *
6  * Purpose     :  Declares functions to match URLs against URL
7  *                patterns.
8  *
9  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
10  *                IJBSWA team.  http://ijbswa.sourceforge.net
11  *
12  *                Based on the Internet Junkbuster originally written
13  *                by and Copyright (C) 1997 Anonymous Coders and
14  *                Junkbusters Corporation.  http://www.junkbusters.com
15  *
16  *                This program is free software; you can redistribute it
17  *                and/or modify it under the terms of the GNU General
18  *                Public License as published by the Free Software
19  *                Foundation; either version 2 of the License, or (at
20  *                your option) any later version.
21  *
22  *                This program is distributed in the hope that it will
23  *                be useful, but WITHOUT ANY WARRANTY; without even the
24  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
25  *                PARTICULAR PURPOSE.  See the GNU General Public
26  *                License for more details.
27  *
28  *                The GNU General Public License should be included with
29  *                this file.  If not, you can view it at
30  *                http://www.gnu.org/copyleft/gpl.html
31  *                or write to the Free Software Foundation, Inc., 59
32  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33  *
34  * Revisions   :
35  *    $Log: urlmatch.c,v $
36  *
37  *********************************************************************/
38 \f
39
40 #include "config.h"
41
42 #ifndef _WIN32
43 #include <stdio.h>
44 #include <sys/types.h>
45 #endif
46
47 #include <stdlib.h>
48 #include <ctype.h>
49 #include <assert.h>
50 #include <string.h>
51
52 #if !defined(_WIN32) && !defined(__OS2__)
53 #include <unistd.h>
54 #endif
55
56 #include "project.h"
57 #include "urlmatch.h"
58 #include "ssplit.h"
59 #include "miscutil.h"
60 #include "errlog.h"
61
62 const char urlmatch_h_rcs[] = URLMATCH_H_VERSION;
63
64 /* Fix a problem with Solaris.  There should be no effect on other
65  * platforms.
66  * Solaris's isspace() is a macro which uses it's argument directly
67  * as an array index.  Therefore we need to make sure that high-bit
68  * characters generate +ve values, and ideally we also want to make
69  * the argument match the declared parameter type of "int".
70  *
71  * Why did they write a character function that can't take a simple
72  * "char" argument?  Doh!
73  */
74 #define ijb_isupper(__X) isupper((int)(unsigned char)(__X))
75 #define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
76
77
78 /*********************************************************************
79  *
80  * Function    :  free_http_request
81  *
82  * Description :  Freez a http_request structure
83  *
84  * Parameters  :
85  *          1  :  http = points to a http_request structure to free
86  *
87  * Returns     :  N/A
88  *
89  *********************************************************************/
90 void free_http_request(struct http_request *http)
91 {
92    assert(http);
93
94    freez(http->cmd);
95    freez(http->gpc);
96    freez(http->host);
97    freez(http->url);
98    freez(http->hostport);
99    freez(http->path);
100    freez(http->ver);
101    freez(http->host_ip_addr_str);
102    freez(http->dbuffer);
103    freez(http->dvec);
104    http->dcount = 0;
105 }
106
107
108 /*********************************************************************
109  *
110  * Function    :  parse_http_url
111  *
112  * Description :  Parse out the host and port from the URL.  Find the
113  *                hostname & path, port (if ':'), and/or password (if '@')
114  *
115  * Parameters  :
116  *          1  :  url = URL (or is it URI?) to break down
117  *          2  :  http = pointer to the http structure to hold elements.
118  *                       Will be zeroed before use.  Note that this
119  *                       function sets the http->gpc and http->ver
120  *                       members to NULL.
121  *          3  :  csp = Current client state (buffers, headers, etc...)
122  *
123  * Returns     :  JB_ERR_OK on success
124  *                JB_ERR_MEMORY on out of memory
125  *                JB_ERR_CGI_PARAMS on malformed command/URL
126  *                                  or >100 domains deep.
127  *
128  *********************************************************************/
129 jb_err parse_http_url(const char * url,
130                       struct http_request *http,
131                       struct client_state *csp)
132 {
133    /*
134     * Zero out the results structure
135     */
136    memset(http, '\0', sizeof(*http));
137
138
139    /*
140     * Save our initial URL
141     */
142    http->url = strdup(url);
143    if (http->url == NULL)
144    {
145       return JB_ERR_MEMORY;
146    }
147
148
149    /*
150     * Split URL into protocol,hostport,path.
151     */
152    {
153       char *buf;
154       char *url_noproto;
155       char *url_path;
156
157       buf = strdup(url);
158       if (buf == NULL)
159       {
160          return JB_ERR_MEMORY;
161       }
162
163       /* Find the start of the URL in our scratch space */
164       url_noproto = buf;
165       if (strncmpic(url_noproto, "http://",  7) == 0)
166       {
167          url_noproto += 7;
168          http->ssl = 0;
169       }
170       else if (strncmpic(url_noproto, "https://", 8) == 0)
171       {
172          url_noproto += 8;
173          http->ssl = 1;
174       }
175       else
176       {
177          http->ssl = 0;
178       }
179
180       url_path = strchr(url_noproto, '/');
181       if (url_path != NULL)
182       {
183          /*
184           * Got a path.
185           *
186           * NOTE: The following line ignores the path for HTTPS URLS.
187           * This means that you get consistent behaviour if you type a
188           * https URL in and it's parsed by the function.  (When the
189           * URL is actually retrieved, SSL hides the path part).
190           */
191          http->path = strdup(http->ssl ? "/" : url_path);
192          *url_path = '\0';
193          http->hostport = strdup(url_noproto);
194       }
195       else
196       {
197          /*
198           * Repair broken HTTP requests that don't contain a path,
199           * or CONNECT requests
200           */
201          http->path = strdup("/");
202          http->hostport = strdup(url_noproto);
203       }
204
205       free(buf);
206
207       if ( (http->path == NULL)
208         || (http->hostport == NULL))
209       {
210          free(buf);
211          free_http_request(http);
212          return JB_ERR_MEMORY;
213       }
214    }
215
216
217    /*
218     * Split hostport into user/password (ignored), host, port.
219     */
220    {
221       char *buf;
222       char *host;
223       char *port;
224
225       buf = strdup(http->hostport);
226       if (buf == NULL)
227       {
228          free_http_request(http);
229          return JB_ERR_MEMORY;
230       }
231
232       /* check if url contains username and/or password */
233       host = strchr(buf, '@');
234       if (host != NULL)
235       {
236          /* Contains username/password, skip it and the @ sign. */
237          host++;
238       }
239       else
240       {
241          /* No username or password. */
242          host = buf;
243       }
244
245       /* check if url contains port */
246       port = strchr(host, ':');
247       if (port != NULL)
248       {
249          /* Contains port */
250          /* Terminate hostname and point to start of port string */
251          *port++ = '\0';
252          http->port = atoi(port);
253       }
254       else
255       {
256          /* No port specified. */
257          http->port = (http->ssl ? 143 : 80);
258       }
259
260       http->host = strdup(host);
261
262       free(buf);
263
264       if (http->host == NULL)
265       {
266          free_http_request(http);
267          return JB_ERR_MEMORY;
268       }
269    }
270
271
272    /*
273     * Split domain name so we can compare it against wildcards
274     */
275    {
276       char *vec[BUFFER_SIZE];
277       int size;
278       char *p;
279
280       http->dbuffer = strdup(http->host);
281       if (NULL == http->dbuffer)
282       {
283          free_http_request(http);
284          return JB_ERR_MEMORY;
285       }
286
287       /* map to lower case */
288       for (p = http->dbuffer; *p ; p++)
289       {
290          *p = tolower((int)(unsigned char)*p);
291       }
292
293       /* split the domain name into components */
294       http->dcount = ssplit(http->dbuffer, ".", vec, SZ(vec), 1, 1);
295
296       if (http->dcount <= 0)
297       {
298          // Error: More than SZ(vec) components in domain
299          //    or: no components in domain
300          free_http_request(http);
301          return JB_ERR_PARSE;
302       }
303
304       /* save a copy of the pointers in dvec */
305       size = http->dcount * sizeof(*http->dvec);
306
307       http->dvec = (char **)malloc(size);
308       if (NULL == http->dvec)
309       {
310          free_http_request(http);
311          return JB_ERR_MEMORY;
312       }
313
314       memcpy(http->dvec, vec, size);
315    }
316
317
318    return JB_ERR_OK;
319 }
320
321
322 /*********************************************************************
323  *
324  * Function    :  parse_http_request
325  *
326  * Description :  Parse out the host and port from the URL.  Find the
327  *                hostname & path, port (if ':'), and/or password (if '@')
328  *
329  * Parameters  :
330  *          1  :  req = HTTP request line to break down
331  *          2  :  http = pointer to the http structure to hold elements
332  *          3  :  csp = Current client state (buffers, headers, etc...)
333  *
334  * Returns     :  JB_ERR_OK on success
335  *                JB_ERR_MEMORY on out of memory
336  *                JB_ERR_CGI_PARAMS on malformed command/URL
337  *                                  or >100 domains deep.
338  *
339  *********************************************************************/
340 jb_err parse_http_request(const char *req,
341                           struct http_request *http,
342                           struct client_state *csp)
343 {
344    char *buf;
345    char *v[10];
346    int n;
347    jb_err err;
348    int is_connect = 0;
349
350    memset(http, '\0', sizeof(*http));
351
352    buf = strdup(req);
353    if (buf == NULL)
354    {
355       return JB_ERR_MEMORY;
356    }
357
358    n = ssplit(buf, " \r\n", v, SZ(v), 1, 1);
359    if (n != 3)
360    {
361       free(buf);
362       return JB_ERR_PARSE;
363    }
364
365    /* this could be a CONNECT request */
366    if (strcmpic(v[0], "connect") == 0)
367    {
368       /* Secure */
369       is_connect = 1;
370    }
371    /* or it could be any other basic HTTP request type */
372    else if ((0 == strcmpic(v[0], "get"))
373          || (0 == strcmpic(v[0], "head"))
374          || (0 == strcmpic(v[0], "post"))
375          || (0 == strcmpic(v[0], "put"))
376          || (0 == strcmpic(v[0], "delete"))
377  
378          /* or a webDAV extension (RFC2518) */
379          || (0 == strcmpic(v[0], "propfind"))
380          || (0 == strcmpic(v[0], "proppatch"))
381          || (0 == strcmpic(v[0], "move"))
382          || (0 == strcmpic(v[0], "copy"))
383          || (0 == strcmpic(v[0], "mkcol"))
384          || (0 == strcmpic(v[0], "lock"))
385          || (0 == strcmpic(v[0], "unlock"))
386          )
387    {
388       /* Normal */
389       is_connect = 0;
390    }
391    else
392    {
393       /* Unknown HTTP method */
394       free(buf);
395       return JB_ERR_PARSE;
396    }
397
398    err = parse_http_url(v[1], http, csp);
399    if (err)
400    {
401       free(buf);
402       return err;
403    }
404
405    /*
406     * Copy the details into the structure
407     */
408    http->ssl = is_connect;
409    http->cmd = strdup(req);
410    http->gpc = strdup(v[0]);
411    http->ver = strdup(v[2]);
412
413    if ( (http->cmd == NULL)
414      || (http->gpc == NULL)
415      || (http->ver == NULL) )
416    {
417       free(buf);
418       free_http_request(http);
419       return JB_ERR_MEMORY;
420    }
421
422    return JB_ERR_OK;
423 }
424
425
426 /*********************************************************************
427  *
428  * Function    :  simple_domaincmp
429  *
430  * Description :  Domain-wise Compare fqdn's.  The comparison is
431  *                both left- and right-anchored.  The individual
432  *                domain names are compared with simplematch().
433  *                This is only used by domain_match.
434  *
435  * Parameters  :
436  *          1  :  pv = array of patterns to compare
437  *          2  :  fv = array of domain components to compare
438  *          3  :  len = length of the arrays (both arrays are the
439  *                      same length - if they weren't, it couldn't
440  *                      possibly be a match).
441  *
442  * Returns     :  0 => domains are equivalent, else no match.
443  *
444  *********************************************************************/
445 static int simple_domaincmp(char **pv, char **fv, int len)
446 {
447    int n;
448
449    for (n = 0; n < len; n++)
450    {
451       if (simplematch(pv[n], fv[n]))
452       {
453          return 1;
454       }
455    }
456
457    return 0;
458
459 }
460
461
462 /*********************************************************************
463  *
464  * Function    :  domain_match
465  *
466  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
467  *                pattern->unachored, the comparison is un-, left-,
468  *                right-anchored, or both.
469  *                The individual domain names are compared with
470  *                simplematch().
471  *
472  * Parameters  :
473  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
474  *          2  :  fqdn = domain name against which the patterns are compared.
475  *
476  * Returns     :  0 => domains are equivalent, else no match.
477  *
478  *********************************************************************/
479 static int domain_match(const struct url_spec *pattern, const struct http_request *fqdn)
480 {
481    char **pv, **fv;  /* vectors  */
482    int    plen, flen;
483    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
484
485    plen = pattern->dcount;
486    flen = fqdn->dcount;
487
488    if (flen < plen)
489    {
490       /* fqdn is too short to match this pattern */
491       return 1;
492    }
493
494    pv   = pattern->dvec;
495    fv   = fqdn->dvec;
496
497    if (unanchored == ANCHOR_LEFT)
498    {
499       /*
500        * Right anchored.
501        *
502        * Convert this into a fully anchored pattern with
503        * the fqdn and pattern the same length
504        */
505       fv += (flen - plen); /* flen - plen >= 0 due to check above */
506       return simple_domaincmp(pv, fv, plen);
507    }
508    else if (unanchored == 0)
509    {
510       /* Fully anchored, check length */
511       if (flen != plen)
512       {
513          return 1;
514       }
515       return simple_domaincmp(pv, fv, plen);
516    }
517    else if (unanchored == ANCHOR_RIGHT)
518    {
519       /* Left anchored, ignore all extra in fqdn */
520       return simple_domaincmp(pv, fv, plen);
521    }
522    else
523    {
524       /* Unanchored */
525       int n;
526       int maxn = flen - plen;
527       for (n = 0; n <= maxn; n++)
528       {
529          if (!simple_domaincmp(pv, fv, plen))
530          {
531             return 0;
532          }
533          /*
534           * Doesn't match from start of fqdn
535           * Try skipping first part of fqdn
536           */
537          fv++;
538       }
539       return 1;
540    }
541
542 }
543
544
545 /*********************************************************************
546  *
547  * Function    :  create_url_spec
548  *
549  * Description :  Creates a "url_spec" structure from a string.
550  *                When finished, free with unload_url().
551  *
552  * Parameters  :
553  *          1  :  url = Target url_spec to be filled in.  Must be
554  *                      zeroed out before the call (e.g. using zalloc).
555  *          2  :  buf = Source pattern, null terminated.  NOTE: The
556  *                      contents of this buffer are destroyed by this
557  *                      function.  If this function succeeds, the
558  *                      buffer is copied to url->spec.  If this
559  *                      function fails, the contents of the buffer
560  *                      are lost forever.
561  *
562  * Returns     :  JB_ERR_OK - Success
563  *                JB_ERR_MEMORY - Out of memory
564  *                JB_ERR_PARSE - Cannot parse regex (Detailed message
565  *                               written to system log)
566  *
567  *********************************************************************/
568 jb_err create_url_spec(struct url_spec * url, const char * buf)
569 {
570    char *p;
571
572    assert(url);
573    assert(buf);
574
575    /* save a copy of the orignal specification */
576    if ((url->spec = strdup(buf)) == NULL)
577    {
578       return JB_ERR_MEMORY;
579    }
580
581    if ((p = strchr(buf, '/')))
582    {
583       if (NULL == (url->path = strdup(p)))
584       {
585          freez(url->spec);
586          return JB_ERR_MEMORY;
587       }
588       url->pathlen = strlen(url->path);
589       *p = '\0';
590    }
591    else
592    {
593       url->path    = NULL;
594       url->pathlen = 0;
595    }
596 #ifdef REGEX
597    if (url->path)
598    {
599       int errcode;
600       char rebuf[BUFFER_SIZE];
601
602       if (NULL == (url->preg = zalloc(sizeof(*url->preg))))
603       {
604          freez(url->spec);
605          freez(url->path);
606          return JB_ERR_MEMORY;
607       }
608
609       sprintf(rebuf, "^(%s)", url->path);
610
611       errcode = regcomp(url->preg, rebuf,
612             (REG_EXTENDED|REG_NOSUB|REG_ICASE));
613       if (errcode)
614       {
615          size_t errlen = regerror(errcode,
616             url->preg, rebuf, sizeof(rebuf));
617
618          if (errlen > (sizeof(rebuf) - (size_t)1))
619          {
620             errlen = sizeof(rebuf) - (size_t)1;
621          }
622          rebuf[errlen] = '\0';
623
624          log_error(LOG_LEVEL_ERROR, "error compiling %s: %s",
625             url->spec, rebuf);
626
627          freez(url->spec);
628          freez(url->path);
629          freez(url->preg);
630
631          return JB_ERR_PARSE;
632       }
633    }
634 #endif
635    if ((p = strchr(buf, ':')) == NULL)
636    {
637       url->port = 0;
638    }
639    else
640    {
641       *p++ = '\0';
642       url->port = atoi(p);
643    }
644
645    if (buf[0] != '\0')
646    {
647       char *v[150];
648       int size;
649
650       /* Parse domain part */
651       if (buf[strlen(buf) - 1] == '.')
652       {
653          url->unanchored |= ANCHOR_RIGHT;
654       }
655       if (buf[0] == '.')
656       {
657          url->unanchored |= ANCHOR_LEFT;
658       }
659
660       /* split domain into components */
661
662       url->dbuffer = strdup(buf);
663       if (NULL == url->dbuffer)
664       {
665          freez(url->spec);
666          freez(url->path);
667 #ifdef REGEX
668          freez(url->preg);
669 #endif /* def REGEX */
670          return JB_ERR_MEMORY;
671       }
672
673       /* map to lower case */
674       for (p = url->dbuffer; *p ; p++)
675       {
676          *p = tolower((int)(unsigned char)*p);
677       }
678
679       /* split the domain name into components */
680       url->dcount = ssplit(url->dbuffer, ".", v, SZ(v), 1, 1);
681
682       if (url->dcount < 0)
683       {
684          freez(url->spec);
685          freez(url->path);
686 #ifdef REGEX
687          freez(url->preg);
688 #endif /* def REGEX */
689          freez(url->dbuffer);
690          url->dcount = 0;
691          return JB_ERR_MEMORY;
692       }
693       else if (url->dcount != 0)
694       {
695
696          /* save a copy of the pointers in dvec */
697          size = url->dcount * sizeof(*url->dvec);
698
699          url->dvec = (char **)malloc(size);
700          if (NULL == url->dvec)
701          {
702             freez(url->spec);
703             freez(url->path);
704 #ifdef REGEX
705             freez(url->preg);
706 #endif /* def REGEX */
707             freez(url->dbuffer);
708             url->dcount = 0;
709             return JB_ERR_MEMORY;
710          }
711
712          memcpy(url->dvec, v, size);
713       }
714    }
715
716    return JB_ERR_OK;
717
718 }
719
720
721 /*********************************************************************
722  *
723  * Function    :  free_url_spec
724  *
725  * Description :  Called from the "unloaders".  Freez the url
726  *                structure elements.
727  *
728  * Parameters  :
729  *          1  :  url = pointer to a url_spec structure.
730  *
731  * Returns     :  N/A
732  *
733  *********************************************************************/
734 void free_url_spec(struct url_spec *url)
735 {
736    if (url == NULL) return;
737
738    freez(url->spec);
739    freez(url->dbuffer);
740    freez(url->dvec);
741    freez(url->path);
742 #ifdef REGEX
743    if (url->preg)
744    {
745       regfree(url->preg);
746       freez(url->preg);
747    }
748 #endif
749
750 }
751
752
753 /*********************************************************************
754  *
755  * Function    :  url_match
756  *
757  * Description :  Compare a URL against a URL pattern.
758  *
759  * Parameters  :
760  *          1  :  pattern = a URL pattern
761  *          2  :  url = URL to match
762  *
763  * Returns     :  0 iff the URL matches the pattern, else nonzero.
764  *
765  *********************************************************************/
766 int url_match(const struct url_spec *pattern,
767               const struct http_request *url)
768 {
769    return ((pattern->port == 0) || (pattern->port == url->port))
770        && ((pattern->dbuffer == NULL) || (domain_match(pattern, url) == 0))
771        && ((pattern->path == NULL) ||
772 #ifdef REGEX
773             (regexec(pattern->preg, url->path, 0, NULL, 0) == 0)
774 #else
775             (strncmp(pattern->path, url->path, pattern->pathlen) == 0)
776 #endif
777       );
778 }
779
780
781 /*
782   Local Variables:
783   tab-width: 3
784   end:
785 */