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