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