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