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