Moving file version information to a separate CGI page.
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.25 2001/07/26 10:09:46 oes Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
5  *
6  * Purpose     :  Declares functions to parse/crunch headers and pages.
7  *                Functions declared include:
8  *                   `acl_addr', `add_stats', `block_acl', `block_imageurl',
9  *                   `block_url', `url_actions', `domaincmp', `dsplit',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
12  *                   `show_proxy_args', 'ijb_send_banner', and `trust_url'
13  *
14  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
15  *                IJBSWA team.  http://ijbswa.sourceforge.net
16  *
17  *                Based on the Internet Junkbuster originally written
18  *                by and Copyright (C) 1997 Anonymous Coders and 
19  *                Junkbusters Corporation.  http://www.junkbusters.com
20  *
21  *                This program is free software; you can redistribute it 
22  *                and/or modify it under the terms of the GNU General
23  *                Public License as published by the Free Software
24  *                Foundation; either version 2 of the License, or (at
25  *                your option) any later version.
26  *
27  *                This program is distributed in the hope that it will
28  *                be useful, but WITHOUT ANY WARRANTY; without even the
29  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
30  *                PARTICULAR PURPOSE.  See the GNU General Public
31  *                License for more details.
32  *
33  *                The GNU General Public License should be included with
34  *                this file.  If not, you can view it at
35  *                http://www.gnu.org/copyleft/gpl.html
36  *                or write to the Free Software Foundation, Inc., 59
37  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
38  *
39  * Revisions   :
40  *    $Log: filters.c,v $
41  *    Revision 1.25  2001/07/26 10:09:46  oes
42  *    Made browser detection a little less naive
43  *
44  *    Revision 1.24  2001/07/25 17:22:51  oes
45  *    Added workaround for Netscape bug that prevents display of page when loading a component fails.
46  *
47  *    Revision 1.23  2001/07/23 13:40:12  oes
48  *    Fixed bug that caused document body to be dropped when pcrs joblist was empty.
49  *
50  *    Revision 1.22  2001/07/18 12:29:34  oes
51  *    - Made gif_deanimate_response respect
52  *      csp->action->string[ACTION_STRING_DEANIMATE]
53  *    - Logging cosmetics
54  *
55  *    Revision 1.21  2001/07/13 13:59:53  oes
56  *     - Introduced gif_deanimate_response which shares the
57  *       generic content modification interface of pcrs_filter_response
58  *       and acts as a wrapper to deanimate.c:gif_deanimate()
59  *     - Renamed re_process_buffer to pcrs_filter_response
60  *     - pcrs_filter_response now returns NULL on failiure
61  *     - Removed all #ifdef PCRS
62  *
63  *    Revision 1.20  2001/07/01 17:01:04  oes
64  *    Added comments and missing return statement in is_untrusted_url()
65  *
66  *    Revision 1.19  2001/06/29 21:45:41  oes
67  *    Indentation, CRLF->LF, Tab-> Space
68  *
69  *    Revision 1.18  2001/06/29 13:27:38  oes
70  *    - Cleaned up, renamed and reorderd functions
71  *      and improved comments
72  *
73  *    - block_url:
74  *      - Ported to CGI platform. Now delivers
75  *        http_response or NULL
76  *      - Unified HTML and GIF generation (moved image detection
77  *        and GIF generation here from jcc.c:chat())
78  *      - Fixed HTTP status to:
79  *       -  403 (Forbidden) for the "blocked" HTML message
80  *       -  200 (OK) for GIF answers
81  *       -  302 (Redirect) for redirect to GIF
82  *
83  *    - trust_url:
84  *      - Ported to CGI platform. Now delivers
85  *        http_response or NULL
86  *      - Separated detection of untrusted URL into
87  *        (bool)is_untrusted_url
88  *      - Added enforcement of untrusted requests
89  *
90  *    - Moved redirect_url() from cgi.c to here
91  *      and ported it to the CGI platform
92  *
93  *    - Removed logentry from cancelled commit
94  *
95  *    Revision 1.17  2001/06/09 10:55:28  jongfoster
96  *    Changing BUFSIZ ==> BUFFER_SIZE
97  *
98  *    Revision 1.16  2001/06/07 23:10:26  jongfoster
99  *    Allowing unanchored domain patterns to back off and retry
100  *    if they partially match.  Optimized right-anchored patterns.
101  *    Moving ACL and forward files into config file.
102  *    Replacing struct gateway with struct forward_spec
103  *
104  *    Revision 1.15  2001/06/03 19:12:00  oes
105  *    extracted-CGI relevant stuff
106  *
107  *    Revision 1.14  2001/06/01 10:30:55  oes
108  *    Added optional left-anchoring to domaincmp
109  *
110  *    Revision 1.13  2001/05/31 21:21:30  jongfoster
111  *    Permissionsfile / actions file changes:
112  *    - Changed "permission" to "action" throughout
113  *    - changes to file format to allow string parameters
114  *    - Moved helper functions to actions.c
115  *
116  *    Revision 1.12  2001/05/31 17:35:20  oes
117  *
118  *     - Enhanced domain part globbing with infix and prefix asterisk
119  *       matching and optional unanchored operation
120  *
121  *    Revision 1.11  2001/05/29 11:53:23  oes
122  *    "See why" link added to "blocked" page
123  *
124  *    Revision 1.10  2001/05/29 09:50:24  jongfoster
125  *    Unified blocklist/imagelist/permissionslist.
126  *    File format is still under discussion, but the internal changes
127  *    are (mostly) done.
128  *
129  *    Also modified interceptor behaviour:
130  *    - We now intercept all URLs beginning with one of the following
131  *      prefixes (and *only* these prefixes):
132  *        * http://i.j.b/
133  *        * http://ijbswa.sf.net/config/
134  *        * http://ijbswa.sourceforge.net/config/
135  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
136  *    - Internal changes so that intercepted and fast redirect pages
137  *      are not replaced with an image.
138  *    - Interceptors now have the option to send a binary page direct
139  *      to the client. (i.e. ijb-send-banner uses this)
140  *    - Implemented show-url-info interceptor.  (Which is why I needed
141  *      the above interceptors changes - a typical URL is
142  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
143  *      The previous mechanism would not have intercepted that, and
144  *      if it had been intercepted then it then it would have replaced
145  *      it with an image.)
146  *
147  *    Revision 1.9  2001/05/27 22:17:04  oes
148  *
149  *    - re_process_buffer no longer writes the modified buffer
150  *      to the client, which was very ugly. It now returns the
151  *      buffer, which it is then written by chat.
152  *
153  *    - content_length now adjusts the Content-Length: header
154  *      for modified documents rather than crunch()ing it.
155  *      (Length info in csp->content_length, which is 0 for
156  *      unmodified documents)
157  *
158  *    - For this to work, sed() is called twice when filtering.
159  *
160  *    Revision 1.8  2001/05/26 17:13:28  jongfoster
161  *    Filled in a function comment.
162  *
163  *    Revision 1.7  2001/05/26 15:26:15  jongfoster
164  *    ACL feature now provides more security by immediately dropping
165  *    connections from untrusted hosts.
166  *
167  *    Revision 1.6  2001/05/26 00:28:36  jongfoster
168  *    Automatic reloading of config file.
169  *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
170  *    Most of the global variables have been moved to a new
171  *    struct configuration_spec, accessed through csp->config->globalname
172  *    Most of the globals remaining are used by the Win32 GUI.
173  *
174  *    Revision 1.5  2001/05/25 22:34:30  jongfoster
175  *    Hard tabs->Spaces
176  *
177  *    Revision 1.4  2001/05/22 18:46:04  oes
178  *
179  *    - Enabled filtering banners by size rather than URL
180  *      by adding patterns that replace all standard banner
181  *      sizes with the "Junkbuster" gif to the re_filterfile
182  *
183  *    - Enabled filtering WebBugs by providing a pattern
184  *      which kills all 1x1 images
185  *
186  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
187  *      which is selected by the (nonstandard and therefore
188  *      capital) letter 'U' in the option string.
189  *      It causes the quantifiers to be ungreedy by default.
190  *      Appending a ? turns back to greedy (!).
191  *
192  *    - Added a new interceptor ijb-send-banner, which
193  *      sends back the "Junkbuster" gif. Without imagelist or
194  *      MSIE detection support, or if tinygif = 1, or the
195  *      URL isn't recognized as an imageurl, a lame HTML
196  *      explanation is sent instead.
197  *
198  *    - Added new feature, which permits blocking remote
199  *      script redirects and firing back a local redirect
200  *      to the browser.
201  *      The feature is conditionally compiled, i.e. it
202  *      can be disabled with --disable-fast-redirects,
203  *      plus it must be activated by a "fast-redirects"
204  *      line in the config file, has its own log level
205  *      and of course wants to be displayed by show-proxy-args
206  *      Note: Boy, all the #ifdefs in 1001 locations and
207  *      all the fumbling with configure.in and acconfig.h
208  *      were *way* more work than the feature itself :-(
209  *
210  *    - Because a generic redirect template was needed for
211  *      this, tinygif = 3 now uses the same.
212  *
213  *    - Moved GIFs, and other static HTTP response templates
214  *      to project.h
215  *
216  *    - Some minor fixes
217  *
218  *    - Removed some >400 CRs again (Jon, you really worked
219  *      a lot! ;-)
220  *
221  *    Revision 1.3  2001/05/20 16:44:47  jongfoster
222  *    Removing last hardcoded JunkBusters.com URLs.
223  *
224  *    Revision 1.2  2001/05/20 01:21:20  jongfoster
225  *    Version 2.9.4 checkin.
226  *    - Merged popupfile and cookiefile, and added control over PCRS
227  *      filtering, in new "permissionsfile".
228  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
229  *      file error you now get a message box (in the Win32 GUI) rather
230  *      than the program exiting with no explanation.
231  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
232  *      skipping.
233  *    - Removed tabs from "config"
234  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
235  *    - Bumped up version number.
236  *
237  *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
238  *    Initial import of version 2.9.3 source tree
239  *
240  *
241  *********************************************************************/
242 \f
243
244 #include "config.h"
245
246 #include <stdio.h>
247 #include <sys/types.h>
248 #include <stdlib.h>
249 #include <ctype.h>
250 #include <string.h>
251
252 #ifndef _WIN32
253 #include <unistd.h>
254 #include <netinet/in.h>
255 #else
256 #include <winsock2.h>
257 #endif
258
259 #include "project.h"
260 #include "filters.h"
261 #include "encode.h"
262 #include "jcc.h"
263 #include "showargs.h"
264 #include "parsers.h"
265 #include "ssplit.h"
266 #include "gateway.h"
267 #include "jbsockets.h"
268 #include "errlog.h"
269 #include "jbsockets.h"
270 #include "miscutil.h"
271 #include "actions.h"
272 #include "cgi.h"
273 #include "list.h"
274 #include "deanimate.h"
275
276 #ifdef _WIN32
277 #include "win32.h"
278 #endif
279
280 const char filters_h_rcs[] = FILTERS_H_VERSION;
281
282 /* Fix a problem with Solaris.  There should be no effect on other
283  * platforms.
284  * Solaris's isspace() is a macro which uses it's argument directly
285  * as an array index.  Therefore we need to make sure that high-bit
286  * characters generate +ve values, and ideally we also want to make
287  * the argument match the declared parameter type of "int".
288  */
289 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
290
291
292 #ifdef FEATURE_ACL
293 /*********************************************************************
294  *
295  * Function    :  block_acl
296  *
297  * Description :  Block this request?
298  *                Decide yes or no based on ACL file.
299  *
300  * Parameters  :
301  *          1  :  dst = The proxy or gateway address this is going to.
302  *                      Or NULL to check all possible targets.
303  *          2  :  csp = Current client state (buffers, headers, etc...)
304  *                      Also includes the client IP address.
305  *
306  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
307  *
308  *********************************************************************/
309 int block_acl(struct access_control_addr *dst, struct client_state *csp)
310 {
311    struct access_control_list *acl = csp->config->acl;
312
313    /* if not using an access control list, then permit the connection */
314    if (acl == NULL)
315    {
316       return(0);
317    }
318
319    /* search the list */
320    while (acl != NULL)
321    {
322       if ((csp->ip_addr_long & acl->src->mask) == acl->src->addr)
323       {
324          if (dst == NULL)
325          {
326             /* Just want to check if they have any access */
327             if (acl->action == ACL_PERMIT)
328             {
329                return(0);
330             }
331          }
332          else if ( ((dst->addr & acl->dst->mask) == acl->dst->addr)
333            && ((dst->port == acl->dst->port) || (acl->dst->port == 0)))
334          {
335             if (acl->action == ACL_PERMIT)
336             {
337                return(0);
338             }
339             else
340             {
341                return(1);
342             }
343          }
344       }
345       acl = acl->next;
346    }
347
348    return(1);
349
350 }
351
352
353 /*********************************************************************
354  *
355  * Function    :  acl_addr
356  *
357  * Description :  Called from `load_aclfile' to parse an ACL address.
358  *
359  * Parameters  :
360  *          1  :  aspec = String specifying ACL address.
361  *          2  :  aca = struct access_control_addr to fill in.
362  *
363  * Returns     :  0 => Ok, everything else is an error.
364  *
365  *********************************************************************/
366 int acl_addr(char *aspec, struct access_control_addr *aca)
367 {
368    int i, masklength, port;
369    char *p;
370
371    masklength = 32;
372    port       =  0;
373
374    if ((p = strchr(aspec, '/')))
375    {
376       *p++ = '\0';
377
378       if (ijb_isdigit(*p) == 0)
379       {
380          return(-1);
381       }
382       masklength = atoi(p);
383    }
384
385    if ((masklength < 0) || (masklength > 32))
386    {
387       return(-1);
388    }
389
390    if ((p = strchr(aspec, ':')))
391    {
392       *p++ = '\0';
393
394       if (ijb_isdigit(*p) == 0)
395       {
396          return(-1);
397       }
398       port = atoi(p);
399    }
400
401    aca->port = port;
402
403    aca->addr = ntohl(resolve_hostname_to_ip(aspec));
404
405    if (aca->addr == -1)
406    {
407       log_error(LOG_LEVEL_ERROR, "can't resolve address for %s", aspec);
408       return(-1);
409    }
410
411    /* build the netmask */
412    aca->mask = 0;
413    for (i=1; i <= masklength ; i++)
414    {
415       aca->mask |= (1 << (32 - i));
416    }
417
418    /* now mask off the host portion of the ip address
419     * (i.e. save on the network portion of the address).
420     */
421    aca->addr = aca->addr & aca->mask;
422
423    return(0);
424
425 }
426 #endif /* def FEATURE_ACL */
427
428
429 /*********************************************************************
430  *
431  * Function    :  block_url
432  *
433  * Description :  Called from `chat'.  Check to see if we need to block this.
434  *
435  * Parameters  :
436  *          1  :  csp = Current client state (buffers, headers, etc...)
437  *
438  * Returns     :  NULL => unblocked, else HTTP block response
439  *
440  *********************************************************************/
441 struct http_response *block_url(struct client_state *csp)
442 {
443 #ifdef FEATURE_IMAGE_BLOCKING
444    char *p;
445 #endif /* def FEATURE_IMAGE_BLOCKING */
446    struct http_response *rsp;
447    struct map *exports = NULL;
448
449    /* 
450     * If it's not blocked, don't block it ;-)
451     */
452    if ((csp->action->flags & ACTION_BLOCK) == 0)
453    {
454       return(NULL);
455    }
456
457    /* 
458     * Else, prepare a response
459     */
460    if (NULL == ( rsp = (struct http_response *)zalloc(sizeof(*rsp))))
461    {
462       return NULL;
463    }
464
465    /*
466     * If it's an image-url, send back an image or redirect
467     * as specified by the relevant +image action
468     */
469 #ifdef FEATURE_IMAGE_BLOCKING
470    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
471         && is_imageurl(csp))
472    {
473       /* determine HOW images should be blocked */
474       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
475
476       /* and handle accordingly: */
477       if ((p == NULL) || (0 == strcmpic(p, "logo")))
478       {
479          rsp->body = bindup(JBGIF, sizeof(JBGIF));
480          rsp->content_length = sizeof(JBGIF);
481          enlist_unique_header(rsp->headers, "Content-Type", "image/gif");
482       }
483
484       else if (0 == strcmpic(p, "blank"))
485       {
486          rsp->body = bindup(BLANKGIF, sizeof(BLANKGIF));
487          rsp->content_length = sizeof(BLANKGIF);
488          enlist_unique_header(rsp->headers, "Content-Type", "image/gif");
489       }
490
491       else
492       {
493          rsp->status = strdup("302 Local Redirect from Junkbuster");
494          enlist_unique_header(rsp->headers, "Location", p);
495       }
496    }  
497    else
498 #endif /* def FEATURE_IMAGE_BLOCKING */
499
500    /* 
501     * Else, generate an HTML "blocked" message:
502     */
503    {
504
505       exports = default_exports(csp, NULL);        
506 #ifdef FEATURE_FORCE_LOAD
507       exports = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
508 #else /* ifndef FEATURE_FORCE_LOAD */
509       exports = map_block_killer(exports, "force-support");
510 #endif /* ndef FEATURE_FORCE_LOAD */
511
512       exports = map(exports, "hostport", 1, csp->http->hostport, 1);
513       exports = map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
514       exports = map(exports, "path", 1, csp->http->path, 1);
515       exports = map(exports, "path-html", 1, html_encode(csp->http->path), 0);
516
517       rsp->body = fill_template(csp, "blocked", exports);
518       free_map(exports);
519   
520       /*
521        * Workaround for stupid Netscape bug which prevents
522        * pages from being displayed if loading a referenced
523        * JavaScript or style sheet fails. So make it appear
524        * as if it succeeded.
525        */
526       if (csp->http->user_agent
527           && !strncmpic(csp->http->user_agent, "mozilla", 7)
528           && !strstr(csp->http->user_agent, "compatible")
529           && !strstr(csp->http->user_agent, "Opera"))
530       {
531          rsp->status = strdup("200 Request for blocked URL"); 
532       }
533       else
534       {
535          rsp->status = strdup("404 Request for blocked URL"); 
536       }
537
538    }
539
540    return(finish_http_response(rsp));
541
542 }
543
544
545 #ifdef FEATURE_TRUST
546 /*********************************************************************
547  *
548  * Function    :  trust_url FIXME: I should be called distrust_url
549  *
550  * Description :  Calls is_untrusted_url to determine if the URL is trusted
551  *                and if not, returns a HTTP 304 response with a reject message.
552  *
553  * Parameters  :
554  *          1  :  csp = Current client state (buffers, headers, etc...)
555  *
556  * Returns     :  NULL => trusted, else http_response.
557  *
558  *********************************************************************/
559 struct http_response *trust_url(struct client_state *csp)
560 {
561    struct http_response *rsp;
562    struct map *exports = NULL;
563    char buf[BUFFER_SIZE], *p = NULL;
564    struct url_spec **tl, *t;
565
566    /*
567     * Don't bother to work on trusted URLs
568     */
569    if (!is_untrusted_url(csp))
570    {
571       return NULL;
572    }
573
574    /* 
575     * Else, prepare a response:
576     */
577    if (NULL == ( rsp = (struct http_response *)zalloc(sizeof(*rsp))))
578    {
579       return NULL;
580    }
581    exports = default_exports(csp, NULL);
582
583    /* 
584     * Export the host, port, and referrer information
585     */
586    exports = map(exports, "hostport", 1, csp->http->hostport, 1);
587    exports = map(exports, "path", 1, csp->http->path, 1);
588    exports = map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
589    exports = map(exports, "path-html", 1, html_encode(csp->http->path), 0);
590
591    if (csp->referrer && strlen(csp->referrer) > 9)
592    {
593       exports = map(exports, "referrer", 1, csp->referrer + 9, 1);
594       exports = map(exports, "referrer-html", 1, html_encode(csp->referrer + 9), 0);
595    }
596    else
597    {
598       exports = map(exports, "referrer", 1, "unknown", 1);
599       exports = map(exports, "referrer-html", 1, "unknown", 1);
600    }
601
602    /*
603     * Export the trust list
604     */
605    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
606    {
607       sprintf(buf, "<li>%s</li>\n", t->spec);
608       p = strsav(p, buf);
609    }
610    exports = map(exports, "trusted-referrers", 1, p, 0);
611    p = NULL;
612
613    /*
614     * Export the trust info, if available
615     */
616    if (csp->config->trust_info->next)
617    {
618       struct list *l;
619
620       for (l = csp->config->trust_info->next; l ; l = l->next)
621       {
622          sprintf(buf, "<li> <a href=%s>%s</a><br>\n",l->str, l->str);
623          p = strsav(p, buf);
624       }
625       exports = map(exports, "trust-info", 1, p, 0);
626    }
627    else
628    {
629       exports = map_block_killer(exports, "have-trust-info");
630    }
631    
632    /*
633     * Export the force prefix or the force conditional block killer
634     */
635 #ifdef FEATURE_FORCE_LOAD
636    exports = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
637 #else /* ifndef FEATURE_FORCE_LOAD */
638    exports = map_block_killer(exports, "force-support");
639 #endif /* ndef FEATURE_FORCE_LOAD */
640
641    /*
642     * Build the response
643     */
644    rsp->body = fill_template(csp, "untrusted", exports);
645    free_map(exports);
646
647    return(finish_http_response(rsp));
648
649 }
650 #endif /* def FEATURE_TRUST */
651
652
653 #ifdef FEATURE_FAST_REDIRECTS
654 /*********************************************************************
655  *
656  * Function    :  redirect_url
657  *
658  * Description :  Checks for redirection URLs and returns a HTTP redirect
659  *                to the destination URL, if necessary
660  *
661  * Parameters  :
662  *          1  :  csp = Current client state (buffers, headers, etc...)
663  *
664  * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
665  *
666  *********************************************************************/
667 struct http_response *redirect_url(struct client_state *csp)
668 {
669    char *p, *q;
670    struct http_response *rsp;
671
672    p = q = csp->http->path;
673    log_error(LOG_LEVEL_REDIRECTS, "checking path for redirects: %s", p);
674
675    /* 
676     * find the last URL encoded in the request
677     */
678    while (p = strstr(p, "http://"))
679    {
680       q = p++;
681    }
682
683    /* 
684     * if there was any, generate and return a HTTP redirect
685     */
686    if (q != csp->http->path)
687    {
688       log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
689
690       if (NULL == ( rsp = zalloc(sizeof(*rsp))))
691       {
692          return NULL;
693       }
694
695       rsp->status = strdup("302 Local Redirect from Junkbuster");
696       enlist_unique_header(rsp->headers, "Location", q);
697
698       return(finish_http_response(rsp));
699    }
700    else
701    {
702       return(NULL);
703    }
704
705 }
706 #endif /* def FEATURE_FAST_REDIRECTS */
707
708
709 #ifdef FEATURE_IMAGE_BLOCKING
710 /*********************************************************************
711  *
712  * Function    :  is_imageurl
713  *
714  * Description :  Given a URL, decide whether it is an image or not,
715  *                using either the info from a previous +image action
716  *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, the info from
717  *                the browser's accept header.
718  *                
719  * Parameters  :
720  *          1  :  csp = Current client state (buffers, headers, etc...)
721  *
722  * Returns     :  True (nonzero) if URL is an image, false (0)
723  *                otherwise
724  *
725  *********************************************************************/
726 int is_imageurl(struct client_state *csp)
727 {
728 #ifdef FEATURE_IMAGE_DETECT_MSIE
729    if ((csp->accept_types 
730        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
731        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE))
732    {
733       return 1;
734    }
735    else if ((csp->accept_types 
736        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
737        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_HTML))
738    {
739       return 0;
740    }
741 #endif /* def FEATURE_IMAGE_DETECT_MSIE */
742
743    return ((csp->action->flags & ACTION_IMAGE) != 0);
744
745 }
746 #endif /* def FEATURE_IMAGE_BLOCKING */
747
748
749 #ifdef FEATURE_COOKIE_JAR
750 /*********************************************************************
751  *
752  * Function    :  is_untrusted_url
753  *
754  * Description :  Should we "distrust" this URL (and block it)?
755  *
756  *                Yes if it matches a line in the trustfile, or if the
757  *                    referrer matches a line starting with "+" in the
758  *                    trustfile.
759  *                No  otherwise.
760  *
761  * Parameters  :
762  *          1  :  csp = Current client state (buffers, headers, etc...)
763  *
764  * Returns     :  0 => trusted, 1 => untrusted
765  *
766  *********************************************************************/
767 int is_untrusted_url(struct client_state *csp)
768 {
769    struct file_list *fl;
770    struct block_spec *b;
771    struct url_spec url[1], **tl, *t;
772    struct http_request rhttp[1];
773    char *p, *h;
774
775    /*
776     * If we don't have a trustlist, we trust everybody
777     */
778    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
779    {
780       return(0);
781    }
782
783
784    /*
785     * Do we trust the request URL itself?
786     */
787    *url = dsplit(csp->http->host);
788
789    /* if splitting the domain fails, punt */
790    if (url->dbuf == NULL) return(0);
791
792    memset(rhttp, '\0', sizeof(*rhttp));
793
794    for (b = b->next; b ; b = b->next)
795    {
796       if ((b->url->port == 0) || (b->url->port == csp->http->port))
797       {
798          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
799          {
800             if ((b->url->path == NULL) ||
801 #ifdef REGEX
802                (regexec(b->url->preg, csp->http->path, 0, NULL, 0) == 0)
803 #else
804                (strncmp(b->url->path, csp->http->path, b->url->pathlen) == 0)
805 #endif
806             )
807             {
808                freez(url->dbuf);
809                freez(url->dvec);
810
811                if (b->reject == 0) return(0);
812
813                return(1);
814             }
815          }
816       }
817    }
818
819    freez(url->dbuf);
820    freez(url->dvec);
821
822    if ((csp->referrer == NULL)|| (strlen(csp->referrer) <= 9))
823    {
824       /* no referrer was supplied */
825       return(1);
826    }
827
828    /* forge a URL from the referrer so we can use
829     * convert_url() to parse it into its components.
830     */
831
832    p = NULL;
833    p = strsav(p, "GET ");
834    p = strsav(p, csp->referrer + 9);   /* skip over "Referer: " */
835    p = strsav(p, " HTTP/1.0");
836
837    parse_http_request(p, rhttp, csp);
838    freez(p);
839
840    if (rhttp->cmd == NULL)
841    {
842       return(1);
843    }
844
845
846    /*
847     * If not, do we maybe trust its referrer?
848     */
849    *url = dsplit(rhttp->host);
850
851    /* if splitting the domain fails, punt */
852    if (url->dbuf == NULL) return(1);
853
854    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
855    {
856       if ((t->port == 0) || (t->port == rhttp->port))
857       {
858          if ((t->domain[0] == '\0') || domaincmp(t, url) == 0)
859          {
860             if ((t->path == NULL) ||
861 #ifdef REGEX
862                (regexec(t->preg, rhttp->path, 0, NULL, 0) == 0)
863 #else
864                (strncmp(t->path, rhttp->path, t->pathlen) == 0)
865 #endif
866             )
867             {
868                /* if the URL's referrer is from a trusted referrer, then
869                 * add the target spec to the trustfile as an unblocked
870                 * domain and return NULL (which means it's OK).
871                 */
872
873                FILE *fp;
874
875                freez(url->dbuf);
876                freez(url->dvec);
877
878                if ((fp = fopen(csp->config->trustfile, "a")))
879                {
880                   h = NULL;
881
882                   h = strsav(h, "~");
883                   h = strsav(h, csp->http->hostport);
884
885                   p = csp->http->path;
886                   if ((*p++ == '/')
887                   && (*p++ == '~'))
888                   {
889                   /* since this path points into a user's home space
890                    * be sure to include this spec in the trustfile.
891                    */
892                      if ((p = strchr(p, '/')))
893                      {
894                         *p = '\0';
895                         h = strsav(h, csp->http->path); /* FIXME: p?! */
896                         h = strsav(h, "/");
897                      }
898                   }
899
900                   fprintf(fp, "%s\n", h);
901                   freez(h);
902                   fclose(fp);
903                }
904                return(0);
905             }
906          }
907       }
908    }
909    return(1);
910 }
911 #endif /* def FEATURE_COOKIE_JAR */
912
913
914 /*********************************************************************
915  *
916  * Function    :  pcrs_filter_response
917  *
918  * Description :  Apply all the pcrs jobs from the joblist (re_filterfile)
919  *                to the text buffer that's been accumulated in 
920  *                csp->iob->buf and set csp->content_length to the modified
921  *                size.
922  *
923  * Parameters  :
924  *          1  :  csp = Current client state (buffers, headers, etc...)
925  *
926  * Returns     :  a pointer to the (newly allocated) modified buffer.
927  *                or NULL in case something went wrong
928  *                
929  *********************************************************************/
930 char *pcrs_filter_response(struct client_state *csp)
931 {
932    int hits=0;
933    int size = csp->iob->eod - csp->iob->cur;
934
935    char *old = csp->iob->cur, *new = NULL;
936    pcrs_job *job;
937
938    struct file_list *fl;
939    struct re_filterfile_spec *b;
940
941    /* Sanity first ;-) */
942    if (size <= 0)
943    {
944       return(NULL);
945    }
946
947    if ( ( NULL == (fl = csp->rlist) ) || ( NULL == (b = fl->f) ) )
948    {
949       log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering.");
950       return(NULL);
951    }
952
953    if ( NULL == b->joblist )
954    {
955       log_error(LOG_LEVEL_RE_FILTER, "Empty joblist. Nothing to do.");
956       return(NULL);
957    }
958
959    log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) ...",
960               csp->http->hostport, csp->http->path, size);
961
962    /* Apply all jobs from the joblist */
963    for (job = b->joblist; NULL != job; job = job->next)
964    {
965       hits += pcrs_execute(job, old, size, &new, &size);
966       if (old != csp->iob->cur) free(old);
967       old=new;
968    }
969
970    log_error(LOG_LEVEL_RE_FILTER, " produced %d hits (new size %d).", hits, size);
971
972    csp->content_length = size;
973
974    /* fwiw, reset the iob */
975    IOB_RESET(csp);
976    return(new);
977
978 }
979
980
981 /*********************************************************************
982  *
983  * Function    :  gif_deanimate_response
984  *
985  * Description :  Deanimate the GIF image that has been accumulated in 
986  *                csp->iob->buf and set csp->content_length to the modified
987  *                size.
988  *
989  * Parameters  :
990  *          1  :  csp = Current client state (buffers, headers, etc...)
991  *
992  * Returns     :  a pointer to the (newly allocated) modified buffer.
993  *                or NULL in case something went wrong.
994  *                
995  *********************************************************************/
996 char *gif_deanimate_response(struct client_state *csp)
997 {
998    struct binbuffer *in, *out;
999    char *p;
1000    int size = csp->iob->eod - csp->iob->cur;
1001
1002    if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
1003       || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
1004    {
1005       log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
1006       return NULL;
1007    }
1008
1009    in->buffer = csp->iob->cur;
1010    in->size = size;
1011
1012    if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
1013    {
1014       log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
1015       free(in);
1016       buf_free(out);
1017       return(NULL);
1018    }
1019    else
1020    {
1021       log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
1022       csp->content_length = out->offset;
1023       p = out->buffer;
1024       free(in);
1025       free(out);
1026       return(p);
1027    }  
1028
1029 }
1030
1031
1032 /*********************************************************************
1033  *
1034  * Function    :  url_actions
1035  *
1036  * Description :  Gets the actions for this URL.
1037  *
1038  * Parameters  :
1039  *          1  :  http = http_request request for blocked URLs
1040  *          2  :  csp = Current client state (buffers, headers, etc...)
1041  *
1042  * Returns     :  N/A
1043  *
1044  *********************************************************************/
1045 void url_actions(struct http_request *http, 
1046                  struct client_state *csp)
1047 {
1048    struct file_list *fl;
1049    struct url_actions *b;
1050
1051    init_current_action(csp->action);
1052
1053    if (((fl = csp->actions_list) == NULL) || ((b = fl->f) == NULL))
1054    {
1055       return;
1056    }
1057
1058    apply_url_actions(csp->action, http, b);
1059
1060 }
1061
1062
1063 /*********************************************************************
1064  *
1065  * Function    :  apply_url_actions
1066  *
1067  * Description :  Applies a list of URL actions.
1068  *
1069  * Parameters  :
1070  *          1  :  action = Destination.
1071  *          2  :  http = Current URL
1072  *          3  :  b = list of URL actions to apply
1073  *
1074  * Returns     :  N/A
1075  *
1076  *********************************************************************/
1077 void apply_url_actions(struct current_action_spec *action, 
1078                        struct http_request *http, 
1079                        struct url_actions *b)
1080 {
1081    struct url_spec url[1];
1082
1083    if (b == NULL)
1084    {
1085       /* Should never happen */
1086       return;
1087    }
1088
1089    *url = dsplit(http->host);
1090
1091    /* if splitting the domain fails, punt */
1092    if (url->dbuf == NULL)
1093    {
1094       return;
1095    }
1096
1097    for (b = b->next; NULL != b; b = b->next)
1098    {
1099       if ((b->url->port == 0) || (b->url->port == http->port))
1100       {
1101          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
1102          {
1103             if ((b->url->path == NULL) ||
1104 #ifdef REGEX
1105                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
1106 #else
1107                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
1108 #endif
1109             )
1110             {
1111                merge_current_action(action, b->action);
1112             }
1113          }
1114       }
1115    }
1116
1117    freez(url->dbuf);
1118    freez(url->dvec);
1119 }
1120
1121
1122 /*********************************************************************
1123  *
1124  * Function    :  forward_url
1125  *
1126  * Description :  Should we forward this to another proxy?
1127  *
1128  * Parameters  :
1129  *          1  :  http = http_request request for current URL
1130  *          2  :  csp = Current client state (buffers, headers, etc...)
1131  *
1132  * Returns     :  Pointer to forwarding information.
1133  *
1134  *********************************************************************/
1135 const struct forward_spec * forward_url(struct http_request *http,
1136                                         struct client_state *csp)
1137 {
1138    static const struct forward_spec fwd_default[1] = { 0 }; /* All zeroes */
1139    struct forward_spec *fwd = csp->config->forward;
1140    struct url_spec url[1];
1141
1142    if (fwd == NULL)
1143    {
1144       return(fwd_default);
1145    }
1146
1147    *url = dsplit(http->host);
1148
1149    /* if splitting the domain fails, punt */
1150    if (url->dbuf == NULL)
1151    {
1152       return(fwd_default);
1153    }
1154
1155    while (fwd != NULL)
1156    {
1157       if ((fwd->url->port == 0) || (fwd->url->port == http->port))
1158       {
1159          if ((fwd->url->domain[0] == '\0') || (domaincmp(fwd->url, url) == 0))
1160          {
1161             if ((fwd->url->path == NULL) ||
1162 #ifdef REGEX
1163                (regexec(fwd->url->preg, http->path, 0, NULL, 0) == 0)
1164 #else
1165                (strncmp(fwd->url->path, http->path, fwd->url->pathlen) == 0)
1166 #endif
1167             )
1168             {
1169                freez(url->dbuf);
1170                freez(url->dvec);
1171                return(fwd);
1172             }
1173          }
1174       }
1175       fwd = fwd->next;
1176    }
1177
1178    freez(url->dbuf);
1179    freez(url->dvec);
1180    return(fwd_default);
1181
1182 }
1183
1184
1185 /*********************************************************************
1186  *
1187  * Function    :  dsplit
1188  *
1189  * Description :  Takes a domain and returns a pointer to a url_spec
1190  *                structure populated with dbuf, dcnt and dvec.  The
1191  *                other fields in the structure that is returned are zero.
1192  *
1193  * Parameters  :
1194  *          1  :  domain = a URL address
1195  *
1196  * Returns     :  url_spec structure populated with dbuf, dcnt and dvec.
1197  *
1198  *********************************************************************/
1199 struct url_spec dsplit(char *domain)
1200 {
1201    struct url_spec ret[1];
1202    char *v[BUFFER_SIZE];
1203    int size;
1204    char *p;
1205
1206    memset(ret, '\0', sizeof(*ret));
1207
1208    if (domain[strlen(domain) - 1] == '.')
1209    {
1210       ret->unanchored |= ANCHOR_RIGHT;
1211    }
1212
1213    if (domain[0] == '.')
1214    {
1215       ret->unanchored |= ANCHOR_LEFT;
1216    }
1217
1218    ret->dbuf = strdup(domain);
1219
1220    /* map to lower case */
1221    for (p = ret->dbuf; *p ; p++) *p = tolower(*p);
1222
1223    /* split the domain name into components */
1224    ret->dcnt = ssplit(ret->dbuf, ".", v, SZ(v), 1, 1);
1225
1226    if (ret->dcnt <= 0)
1227    {
1228       memset(ret, '\0', sizeof(ret));
1229       return(*ret);
1230    }
1231
1232    /* save a copy of the pointers in dvec */
1233    size = ret->dcnt * sizeof(*ret->dvec);
1234
1235    if ((ret->dvec = (char **)malloc(size)))
1236    {
1237       memcpy(ret->dvec, v, size);
1238    }
1239
1240    return(*ret);
1241
1242 }
1243
1244
1245 /*********************************************************************
1246  *
1247  * Function    :  simple_domaincmp
1248  *
1249  * Description :  Domain-wise Compare fqdn's.  The comparison is 
1250  *                both left- and right-anchored.  The individual
1251  *                domain names are compared with simplematch().
1252  *                This is only used by domaincmp.
1253  *
1254  * Parameters  :
1255  *          1  :  pv = array of patterns to compare
1256  *          2  :  fv = array of domain components to compare
1257  *          3  :  len = length of the arrays (both arrays are the
1258  *                      same length - if they weren't, it couldn't
1259  *                      possibly be a match).
1260  *
1261  * Returns     :  0 => domains are equivalent, else no match.
1262  *
1263  *********************************************************************/
1264 static int simple_domaincmp(char **pv, char **fv, int len)
1265 {
1266    int n;
1267
1268    for (n = 0; n < len; n++)
1269    {
1270       if (simplematch(pv[n], fv[n]))
1271       {
1272          return 1;
1273       }
1274    }
1275
1276    return 0;
1277
1278 }
1279
1280
1281 /*********************************************************************
1282  *
1283  * Function    :  domaincmp
1284  *
1285  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
1286  *                pattern->unachored, the comparison is un-, left-,
1287  *                right-anchored, or both.
1288  *                The individual domain names are compared with
1289  *                simplematch().
1290  *
1291  * Parameters  :
1292  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
1293  *          2  :  fqdn = domain name against which the patterns are compared.
1294  *
1295  * Returns     :  0 => domains are equivalent, else no match.
1296  *
1297  *********************************************************************/
1298 int domaincmp(struct url_spec *pattern, struct url_spec *fqdn)
1299 {
1300    char **pv, **fv;  /* vectors  */
1301    int    plen, flen;
1302    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
1303
1304    plen = pattern->dcnt;
1305    flen = fqdn->dcnt;
1306
1307    if (flen < plen)
1308    {
1309       /* fqdn is too short to match this pattern */
1310       return 1;
1311    }
1312
1313    pv   = pattern->dvec;
1314    fv   = fqdn->dvec;
1315
1316    if (unanchored == ANCHOR_LEFT)
1317    {
1318       /*
1319        * Right anchored.
1320        *
1321        * Convert this into a fully anchored pattern with
1322        * the fqdn and pattern the same length
1323        */
1324       fv += (flen - plen); /* flen - plen >= 0 due to check above */
1325       return simple_domaincmp(pv, fv, plen);
1326    }
1327    else if (unanchored == 0)
1328    {
1329       /* Fully anchored, check length */
1330       if (flen != plen)
1331       {
1332          return 1;
1333       }
1334       return simple_domaincmp(pv, fv, plen);
1335    }
1336    else if (unanchored == ANCHOR_RIGHT)
1337    {
1338       /* Left anchored, ignore all extra in fqdn */
1339       return simple_domaincmp(pv, fv, plen);
1340    }
1341    else
1342    {
1343       /* Unanchored */
1344       int n;
1345       int maxn = flen - plen;
1346       for (n = 0; n <= maxn; n++)
1347       {
1348          if (!simple_domaincmp(pv, fv, plen))
1349          {
1350             return 0;
1351          }
1352          /*
1353           * Doesn't match from start of fqdn
1354           * Try skipping first part of fqdn
1355           */
1356          fv++;
1357       }
1358       return 1;
1359    }
1360
1361 }
1362
1363
1364 /*
1365   Local Variables:
1366   tab-width: 3
1367   end:
1368 */