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