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