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