Adding support for proprietary Microsoft WebDAV extensions
[privoxy.git] / src / urlmatch.c
1 const char urlmatch_rcs[] = "$Id: urlmatch.c,v 2.0 2002/06/04 14:34:21 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.0  2002/06/04 14:34:21  jongfoster
37  *    Moving source files to src/
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_CGI_PARAMS 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     * Split URL into protocol,hostport,path.
187     */
188    {
189       char *buf;
190       char *url_noproto;
191       char *url_path;
192
193       buf = strdup(url);
194       if (buf == NULL)
195       {
196          return JB_ERR_MEMORY;
197       }
198
199       /* Find the start of the URL in our scratch space */
200       url_noproto = buf;
201       if (strncmpic(url_noproto, "http://",  7) == 0)
202       {
203          url_noproto += 7;
204          http->ssl = 0;
205       }
206       else if (strncmpic(url_noproto, "https://", 8) == 0)
207       {
208          url_noproto += 8;
209          http->ssl = 1;
210       }
211       else
212       {
213          http->ssl = 0;
214       }
215
216       url_path = strchr(url_noproto, '/');
217       if (url_path != NULL)
218       {
219          /*
220           * Got a path.
221           *
222           * NOTE: The following line ignores the path for HTTPS URLS.
223           * This means that you get consistent behaviour if you type a
224           * https URL in and it's parsed by the function.  (When the
225           * URL is actually retrieved, SSL hides the path part).
226           */
227          http->path = strdup(http->ssl ? "/" : url_path);
228          *url_path = '\0';
229          http->hostport = strdup(url_noproto);
230       }
231       else
232       {
233          /*
234           * Repair broken HTTP requests that don't contain a path,
235           * or CONNECT requests
236           */
237          http->path = strdup("/");
238          http->hostport = strdup(url_noproto);
239       }
240
241       free(buf);
242
243       if ( (http->path == NULL)
244         || (http->hostport == NULL))
245       {
246          free(buf);
247          free_http_request(http);
248          return JB_ERR_MEMORY;
249       }
250    }
251
252
253    /*
254     * Split hostport into user/password (ignored), host, port.
255     */
256    {
257       char *buf;
258       char *host;
259       char *port;
260
261       buf = strdup(http->hostport);
262       if (buf == NULL)
263       {
264          free_http_request(http);
265          return JB_ERR_MEMORY;
266       }
267
268       /* check if url contains username and/or password */
269       host = strchr(buf, '@');
270       if (host != NULL)
271       {
272          /* Contains username/password, skip it and the @ sign. */
273          host++;
274       }
275       else
276       {
277          /* No username or password. */
278          host = buf;
279       }
280
281       /* check if url contains port */
282       port = strchr(host, ':');
283       if (port != NULL)
284       {
285          /* Contains port */
286          /* Terminate hostname and point to start of port string */
287          *port++ = '\0';
288          http->port = atoi(port);
289       }
290       else
291       {
292          /* No port specified. */
293          http->port = (http->ssl ? 143 : 80);
294       }
295
296       http->host = strdup(host);
297
298       free(buf);
299
300       if (http->host == NULL)
301       {
302          free_http_request(http);
303          return JB_ERR_MEMORY;
304       }
305    }
306
307
308    /*
309     * Split domain name so we can compare it against wildcards
310     */
311    {
312       char *vec[BUFFER_SIZE];
313       size_t size;
314       char *p;
315
316       http->dbuffer = strdup(http->host);
317       if (NULL == http->dbuffer)
318       {
319          free_http_request(http);
320          return JB_ERR_MEMORY;
321       }
322
323       /* map to lower case */
324       for (p = http->dbuffer; *p ; p++)
325       {
326          *p = tolower((int)(unsigned char)*p);
327       }
328
329       /* split the domain name into components */
330       http->dcount = ssplit(http->dbuffer, ".", vec, SZ(vec), 1, 1);
331
332       if (http->dcount <= 0)
333       {
334          /*
335           * Error: More than SZ(vec) components in domain
336           *    or: no components in domain
337           */
338          free_http_request(http);
339          return JB_ERR_PARSE;
340       }
341
342       /* save a copy of the pointers in dvec */
343       size = http->dcount * sizeof(*http->dvec);
344
345       http->dvec = (char **)malloc(size);
346       if (NULL == http->dvec)
347       {
348          free_http_request(http);
349          return JB_ERR_MEMORY;
350       }
351
352       memcpy(http->dvec, vec, size);
353    }
354
355
356    return JB_ERR_OK;
357 }
358
359
360 /*********************************************************************
361  *
362  * Function    :  parse_http_request
363  *
364  * Description :  Parse out the host and port from the URL.  Find the
365  *                hostname & path, port (if ':'), and/or password (if '@')
366  *
367  * Parameters  :
368  *          1  :  req = HTTP request line to break down
369  *          2  :  http = pointer to the http structure to hold elements
370  *          3  :  csp = Current client state (buffers, headers, etc...)
371  *
372  * Returns     :  JB_ERR_OK on success
373  *                JB_ERR_MEMORY on out of memory
374  *                JB_ERR_CGI_PARAMS on malformed command/URL
375  *                                  or >100 domains deep.
376  *
377  *********************************************************************/
378 jb_err parse_http_request(const char *req,
379                           struct http_request *http,
380                           struct client_state *csp)
381 {
382    char *buf;
383    char *v[10];
384    int n;
385    jb_err err;
386    int is_connect = 0;
387
388    memset(http, '\0', sizeof(*http));
389
390    buf = strdup(req);
391    if (buf == NULL)
392    {
393       return JB_ERR_MEMORY;
394    }
395
396    n = ssplit(buf, " \r\n", v, SZ(v), 1, 1);
397    if (n != 3)
398    {
399       free(buf);
400       return JB_ERR_PARSE;
401    }
402
403    /* this could be a CONNECT request */
404    if (strcmpic(v[0], "connect") == 0)
405    {
406       /* Secure */
407       is_connect = 1;
408    }
409    /* or it could be any other basic HTTP request type */
410    else if ((0 == strcmpic(v[0], "get"))
411          || (0 == strcmpic(v[0], "head"))
412          || (0 == strcmpic(v[0], "post"))
413          || (0 == strcmpic(v[0], "put"))
414          || (0 == strcmpic(v[0], "delete"))
415  
416          /* or a webDAV extension (RFC2518) */
417          || (0 == strcmpic(v[0], "propfind"))
418          || (0 == strcmpic(v[0], "proppatch"))
419          || (0 == strcmpic(v[0], "move"))
420          || (0 == strcmpic(v[0], "copy"))
421          || (0 == strcmpic(v[0], "mkcol"))
422          || (0 == strcmpic(v[0], "lock"))
423          || (0 == strcmpic(v[0], "unlock"))
424
425          /* Or a Microsoft webDAV extension for Exchange 2000.  See: */
426          /*   http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html */
427          /*   http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ 
428          || (0 == strcmpic(v[0], "bcopy"))
429          || (0 == strcmpic(v[0], "bmove"))
430          || (0 == strcmpic(v[0], "bdelete"))
431          || (0 == strcmpic(v[0], "bpropfind"))
432          || (0 == strcmpic(v[0], "bproppatch"))
433
434          /* Or another Microsoft webDAV extension for Exchange 2000.  See: */
435          /*   http://systems.cs.colorado.edu/grunwald/MobileComputing/Papers/draft-cohen-gena-p-base-00.txt */
436          /*   http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html */
437          /*   http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ 
438          || (0 == strcmpic(v[0], "subscribe"))
439          || (0 == strcmpic(v[0], "unsubscribe"))
440          || (0 == strcmpic(v[0], "notify"))
441          || (0 == strcmpic(v[0], "poll"))
442          )
443    {
444       /* Normal */
445       is_connect = 0;
446    }
447    else
448    {
449       /* Unknown HTTP method */
450       /* Maybe one of: trace options purge */
451       free(buf);
452       return JB_ERR_PARSE;
453    }
454
455    err = parse_http_url(v[1], http, csp);
456    if (err)
457    {
458       free(buf);
459       return err;
460    }
461
462    /*
463     * Copy the details into the structure
464     */
465    http->ssl = is_connect;
466    http->cmd = strdup(req);
467    http->gpc = strdup(v[0]);
468    http->ver = strdup(v[2]);
469
470    if ( (http->cmd == NULL)
471      || (http->gpc == NULL)
472      || (http->ver == NULL) )
473    {
474       free(buf);
475       free_http_request(http);
476       return JB_ERR_MEMORY;
477    }
478
479    return JB_ERR_OK;
480 }
481
482
483 /*********************************************************************
484  *
485  * Function    :  simple_domaincmp
486  *
487  * Description :  Domain-wise Compare fqdn's.  The comparison is
488  *                both left- and right-anchored.  The individual
489  *                domain names are compared with simplematch().
490  *                This is only used by domain_match.
491  *
492  * Parameters  :
493  *          1  :  pv = array of patterns to compare
494  *          2  :  fv = array of domain components to compare
495  *          3  :  len = length of the arrays (both arrays are the
496  *                      same length - if they weren't, it couldn't
497  *                      possibly be a match).
498  *
499  * Returns     :  0 => domains are equivalent, else no match.
500  *
501  *********************************************************************/
502 static int simple_domaincmp(char **pv, char **fv, int len)
503 {
504    int n;
505
506    for (n = 0; n < len; n++)
507    {
508       if (simplematch(pv[n], fv[n]))
509       {
510          return 1;
511       }
512    }
513
514    return 0;
515
516 }
517
518
519 /*********************************************************************
520  *
521  * Function    :  domain_match
522  *
523  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
524  *                pattern->unachored, the comparison is un-, left-,
525  *                right-anchored, or both.
526  *                The individual domain names are compared with
527  *                simplematch().
528  *
529  * Parameters  :
530  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
531  *          2  :  fqdn = domain name against which the patterns are compared.
532  *
533  * Returns     :  0 => domains are equivalent, else no match.
534  *
535  *********************************************************************/
536 static int domain_match(const struct url_spec *pattern, const struct http_request *fqdn)
537 {
538    char **pv, **fv;  /* vectors  */
539    int    plen, flen;
540    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
541
542    plen = pattern->dcount;
543    flen = fqdn->dcount;
544
545    if (flen < plen)
546    {
547       /* fqdn is too short to match this pattern */
548       return 1;
549    }
550
551    pv   = pattern->dvec;
552    fv   = fqdn->dvec;
553
554    if (unanchored == ANCHOR_LEFT)
555    {
556       /*
557        * Right anchored.
558        *
559        * Convert this into a fully anchored pattern with
560        * the fqdn and pattern the same length
561        */
562       fv += (flen - plen); /* flen - plen >= 0 due to check above */
563       return simple_domaincmp(pv, fv, plen);
564    }
565    else if (unanchored == 0)
566    {
567       /* Fully anchored, check length */
568       if (flen != plen)
569       {
570          return 1;
571       }
572       return simple_domaincmp(pv, fv, plen);
573    }
574    else if (unanchored == ANCHOR_RIGHT)
575    {
576       /* Left anchored, ignore all extra in fqdn */
577       return simple_domaincmp(pv, fv, plen);
578    }
579    else
580    {
581       /* Unanchored */
582       int n;
583       int maxn = flen - plen;
584       for (n = 0; n <= maxn; n++)
585       {
586          if (!simple_domaincmp(pv, fv, plen))
587          {
588             return 0;
589          }
590          /*
591           * Doesn't match from start of fqdn
592           * Try skipping first part of fqdn
593           */
594          fv++;
595       }
596       return 1;
597    }
598
599 }
600
601
602 /*********************************************************************
603  *
604  * Function    :  create_url_spec
605  *
606  * Description :  Creates a "url_spec" structure from a string.
607  *                When finished, free with unload_url().
608  *
609  * Parameters  :
610  *          1  :  url = Target url_spec to be filled in.  Will be
611  *                      zeroed before use.
612  *          2  :  buf = Source pattern, null terminated.  NOTE: The
613  *                      contents of this buffer are destroyed by this
614  *                      function.  If this function succeeds, the
615  *                      buffer is copied to url->spec.  If this
616  *                      function fails, the contents of the buffer
617  *                      are lost forever.
618  *
619  * Returns     :  JB_ERR_OK - Success
620  *                JB_ERR_MEMORY - Out of memory
621  *                JB_ERR_PARSE - Cannot parse regex (Detailed message
622  *                               written to system log)
623  *
624  *********************************************************************/
625 jb_err create_url_spec(struct url_spec * url, const char * buf)
626 {
627    char *p;
628
629    assert(url);
630    assert(buf);
631
632    /* Zero memory */
633    memset(url, '\0', sizeof(*url));
634
635    /* save a copy of the orignal specification */
636    if ((url->spec = strdup(buf)) == NULL)
637    {
638       return JB_ERR_MEMORY;
639    }
640
641    if ((p = strchr(buf, '/')) != NULL)
642    {
643       if (NULL == (url->path = strdup(p)))
644       {
645          freez(url->spec);
646          return JB_ERR_MEMORY;
647       }
648       url->pathlen = strlen(url->path);
649       *p = '\0';
650    }
651    else
652    {
653       url->path    = NULL;
654       url->pathlen = 0;
655    }
656    if (url->path)
657    {
658       int errcode;
659       char rebuf[BUFFER_SIZE];
660
661       if (NULL == (url->preg = zalloc(sizeof(*url->preg))))
662       {
663          freez(url->spec);
664          freez(url->path);
665          return JB_ERR_MEMORY;
666       }
667
668       sprintf(rebuf, "^(%s)", url->path);
669
670       errcode = regcomp(url->preg, rebuf,
671             (REG_EXTENDED|REG_NOSUB|REG_ICASE));
672       if (errcode)
673       {
674          size_t errlen = regerror(errcode,
675             url->preg, rebuf, sizeof(rebuf));
676
677          if (errlen > (sizeof(rebuf) - (size_t)1))
678          {
679             errlen = sizeof(rebuf) - (size_t)1;
680          }
681          rebuf[errlen] = '\0';
682
683          log_error(LOG_LEVEL_ERROR, "error compiling %s: %s",
684             url->spec, rebuf);
685
686          freez(url->spec);
687          freez(url->path);
688          regfree(url->preg);
689          freez(url->preg);
690
691          return JB_ERR_PARSE;
692       }
693    }
694    if ((p = strchr(buf, ':')) == NULL)
695    {
696       url->port = 0;
697    }
698    else
699    {
700       *p++ = '\0';
701       url->port = atoi(p);
702    }
703
704    if (buf[0] != '\0')
705    {
706       char *v[150];
707       size_t size;
708
709       /* Parse domain part */
710       if (buf[strlen(buf) - 1] == '.')
711       {
712          url->unanchored |= ANCHOR_RIGHT;
713       }
714       if (buf[0] == '.')
715       {
716          url->unanchored |= ANCHOR_LEFT;
717       }
718
719       /* split domain into components */
720
721       url->dbuffer = strdup(buf);
722       if (NULL == url->dbuffer)
723       {
724          freez(url->spec);
725          freez(url->path);
726          regfree(url->preg);
727          freez(url->preg);
728          return JB_ERR_MEMORY;
729       }
730
731       /* map to lower case */
732       for (p = url->dbuffer; *p ; p++)
733       {
734          *p = tolower((int)(unsigned char)*p);
735       }
736
737       /* split the domain name into components */
738       url->dcount = ssplit(url->dbuffer, ".", v, SZ(v), 1, 1);
739
740       if (url->dcount < 0)
741       {
742          freez(url->spec);
743          freez(url->path);
744          regfree(url->preg);
745          freez(url->preg);
746          freez(url->dbuffer);
747          url->dcount = 0;
748          return JB_ERR_MEMORY;
749       }
750       else if (url->dcount != 0)
751       {
752
753          /* save a copy of the pointers in dvec */
754          size = url->dcount * sizeof(*url->dvec);
755
756          url->dvec = (char **)malloc(size);
757          if (NULL == url->dvec)
758          {
759             freez(url->spec);
760             freez(url->path);
761             regfree(url->preg);
762             freez(url->preg);
763             freez(url->dbuffer);
764             url->dcount = 0;
765             return JB_ERR_MEMORY;
766          }
767
768          memcpy(url->dvec, v, size);
769       }
770    }
771
772    return JB_ERR_OK;
773
774 }
775
776
777 /*********************************************************************
778  *
779  * Function    :  free_url_spec
780  *
781  * Description :  Called from the "unloaders".  Freez the url
782  *                structure elements.
783  *
784  * Parameters  :
785  *          1  :  url = pointer to a url_spec structure.
786  *
787  * Returns     :  N/A
788  *
789  *********************************************************************/
790 void free_url_spec(struct url_spec *url)
791 {
792    if (url == NULL) return;
793
794    freez(url->spec);
795    freez(url->dbuffer);
796    freez(url->dvec);
797    freez(url->path);
798    if (url->preg)
799    {
800       regfree(url->preg);
801       freez(url->preg);
802    }
803 }
804
805
806 /*********************************************************************
807  *
808  * Function    :  url_match
809  *
810  * Description :  Compare a URL against a URL pattern.
811  *
812  * Parameters  :
813  *          1  :  pattern = a URL pattern
814  *          2  :  url = URL to match
815  *
816  * Returns     :  0 iff the URL matches the pattern, else nonzero.
817  *
818  *********************************************************************/
819 int url_match(const struct url_spec *pattern,
820               const struct http_request *url)
821 {
822    return ((pattern->port == 0) || (pattern->port == url->port))
823        && ((pattern->dbuffer == NULL) || (domain_match(pattern, url) == 0))
824        && ((pattern->path == NULL) ||
825             (regexec(pattern->preg, url->path, 0, NULL, 0) == 0)
826       );
827 }
828
829
830 /*
831   Local Variables:
832   tab-width: 3
833   end:
834 */