Remove parts of get_filter_function() that have been moved to content_requires_filter...
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.133 2010/09/14 07:16:07 fabiankeil 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', `domain_split',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
12  *                   `ijb_send_banner', `trust_url', `gif_deanimate_response',
13  *                   `execute_single_pcrs_command', `rewrite_url',
14  *                   `get_last_url'
15  *
16  * Copyright   :  Written by and Copyright (C) 2001-2010 the
17  *                Privoxy team. http://www.privoxy.org/
18  *
19  *                Based on the Internet Junkbuster originally written
20  *                by and Copyright (C) 1997 Anonymous Coders and
21  *                Junkbusters Corporation.  http://www.junkbusters.com
22  *
23  *                This program is free software; you can redistribute it
24  *                and/or modify it under the terms of the GNU General
25  *                Public License as published by the Free Software
26  *                Foundation; either version 2 of the License, or (at
27  *                your option) any later version.
28  *
29  *                This program is distributed in the hope that it will
30  *                be useful, but WITHOUT ANY WARRANTY; without even the
31  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
32  *                PARTICULAR PURPOSE.  See the GNU General Public
33  *                License for more details.
34  *
35  *                The GNU General Public License should be included with
36  *                this file.  If not, you can view it at
37  *                http://www.gnu.org/copyleft/gpl.html
38  *                or write to the Free Software Foundation, Inc., 59
39  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
40  *
41  *********************************************************************/
42
43
44 #include "config.h"
45
46 #include <stdio.h>
47 #include <sys/types.h>
48 #include <stdlib.h>
49 #include <ctype.h>
50 #include <string.h>
51 #include <assert.h>
52
53 #ifdef HAVE_RFC2553
54 #include <netdb.h>
55 #include <sys/socket.h>
56 #endif /* def HAVE_RFC2553 */
57
58 #ifndef _WIN32
59 #ifndef __OS2__
60 #include <unistd.h>
61 #endif /* ndef __OS2__ */
62 #include <netinet/in.h>
63 #else
64 #include <winsock2.h>
65 #endif /* ndef _WIN32 */
66
67 #ifdef __OS2__
68 #include <utils.h>
69 #endif /* def __OS2__ */
70
71 #include "project.h"
72 #include "filters.h"
73 #include "encode.h"
74 #include "parsers.h"
75 #include "ssplit.h"
76 #include "errlog.h"
77 #include "jbsockets.h"
78 #include "miscutil.h"
79 #include "actions.h"
80 #include "cgi.h"
81 #include "list.h"
82 #include "deanimate.h"
83 #include "urlmatch.h"
84 #include "loaders.h"
85
86 #ifdef _WIN32
87 #include "win32.h"
88 #endif
89
90 const char filters_h_rcs[] = FILTERS_H_VERSION;
91
92 /* Fix a problem with Solaris.  There should be no effect on other
93  * platforms.
94  * Solaris's isspace() is a macro which uses it's argument directly
95  * as an array index.  Therefore we need to make sure that high-bit
96  * characters generate +ve values, and ideally we also want to make
97  * the argument match the declared parameter type of "int".
98  */
99 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
100
101 typedef char *(*filter_function_ptr)();
102 static filter_function_ptr get_filter_function(const struct client_state *csp);
103 static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size);
104 static jb_err prepare_for_filtering(struct client_state *csp);
105
106 #ifdef FEATURE_ACL
107 #ifdef HAVE_RFC2553
108 /*********************************************************************
109  *
110  * Function    :  sockaddr_storage_to_ip
111  *
112  * Description :  Access internal structure of sockaddr_storage
113  *
114  * Parameters  :
115  *          1  :  addr = socket address
116  *          2  :  ip   = IP address as array of octets in network order
117  *                       (it points into addr)
118  *          3  :  len  = length of IP address in octets
119  *          4  :  port = port number in network order;
120  *
121  * Returns     :  0 = no errror; -1 otherwise.
122  *
123  *********************************************************************/
124 static int sockaddr_storage_to_ip(const struct sockaddr_storage *addr,
125                                   uint8_t **ip, unsigned int *len,
126                                   in_port_t **port)
127 {
128    if (NULL == addr)
129    {
130       return(-1);
131    }
132
133    switch (addr->ss_family)
134    {
135       case AF_INET:
136          if (NULL != len)
137          {
138             *len = 4;
139          }
140          if (NULL != ip)
141          {
142             *ip = (uint8_t *)
143                &(((struct sockaddr_in *)addr)->sin_addr.s_addr);
144          }
145          if (NULL != port)
146          {
147             *port = &((struct sockaddr_in *)addr)->sin_port;
148          }
149          break;
150
151       case AF_INET6:
152          if (NULL != len)
153          {
154             *len = 16;
155          }
156          if (NULL != ip)
157          {
158             *ip = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr;
159          }
160          if (NULL != port)
161          {
162             *port = &((struct sockaddr_in6 *)addr)->sin6_port;
163          }
164          break;
165
166       default:
167          /* Unsupported address family */
168          return(-1);
169    }
170
171    return(0);
172 }
173
174
175 /*********************************************************************
176  *
177  * Function    :  match_sockaddr
178  *
179  * Description :  Check whether address matches network (IP address and port)
180  *
181  * Parameters  :
182  *          1  :  network = socket address of subnework
183  *          2  :  netmask = network mask as socket address
184  *          3  :  address = checked socket address against given network
185  *
186  * Returns     :  0 = doesn't match; 1 = does match
187  *
188  *********************************************************************/
189 static int match_sockaddr(const struct sockaddr_storage *network,
190                           const struct sockaddr_storage *netmask,
191                           const struct sockaddr_storage *address)
192 {
193    uint8_t *network_addr, *netmask_addr, *address_addr;
194    unsigned int addr_len;
195    in_port_t *network_port, *netmask_port, *address_port;
196    int i;
197
198    if (network->ss_family != netmask->ss_family)
199    {
200       /* This should never happen */
201       log_error(LOG_LEVEL_ERROR,
202          "Internal error at %s:%llu: network and netmask differ in family",
203          __FILE__, __LINE__);
204       return 0;
205    }
206
207    sockaddr_storage_to_ip(network, &network_addr, &addr_len, &network_port);
208    sockaddr_storage_to_ip(netmask, &netmask_addr, NULL, &netmask_port);
209    sockaddr_storage_to_ip(address, &address_addr, NULL, &address_port);
210
211    /* Check for family */
212    if ((network->ss_family == AF_INET) && (address->ss_family == AF_INET6)
213       && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)address_addr))
214    {
215       /* Map AF_INET6 V4MAPPED address into AF_INET */
216       address_addr += 12;
217       addr_len = 4;
218    }
219    else if ((network->ss_family == AF_INET6) && (address->ss_family == AF_INET)
220       && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)network_addr))
221    {
222       /* Map AF_INET6 V4MAPPED network into AF_INET */
223       network_addr += 12;
224       netmask_addr += 12;
225       addr_len = 4;
226    }
227    else if (network->ss_family != address->ss_family)
228    {
229       return 0;
230    }
231
232    /* XXX: Port check is signaled in netmask */
233    if (*netmask_port && *network_port != *address_port)
234    {
235       return 0;
236    }
237
238    /* TODO: Optimize by checking by words insted of octets */
239    for (i = 0; (i < addr_len) && netmask_addr[i]; i++)
240    {
241       if ((network_addr[i] & netmask_addr[i]) !=
242           (address_addr[i] & netmask_addr[i]))
243       {
244          return 0;
245       }
246    }
247
248    return 1;
249 }
250 #endif /* def HAVE_RFC2553 */
251
252
253 /*********************************************************************
254  *
255  * Function    :  block_acl
256  *
257  * Description :  Block this request?
258  *                Decide yes or no based on ACL file.
259  *
260  * Parameters  :
261  *          1  :  dst = The proxy or gateway address this is going to.
262  *                      Or NULL to check all possible targets.
263  *          2  :  csp = Current client state (buffers, headers, etc...)
264  *                      Also includes the client IP address.
265  *
266  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
267  *
268  *********************************************************************/
269 int block_acl(const struct access_control_addr *dst, const struct client_state *csp)
270 {
271    struct access_control_list *acl = csp->config->acl;
272
273    /* if not using an access control list, then permit the connection */
274    if (acl == NULL)
275    {
276       return(0);
277    }
278
279    /* search the list */
280    while (acl != NULL)
281    {
282       if (
283 #ifdef HAVE_RFC2553
284             match_sockaddr(&acl->src->addr, &acl->src->mask, &csp->tcp_addr)
285 #else
286             (csp->ip_addr_long & acl->src->mask) == acl->src->addr
287 #endif
288             )
289       {
290          if (dst == NULL)
291          {
292             /* Just want to check if they have any access */
293             if (acl->action == ACL_PERMIT)
294             {
295                return(0);
296             }
297          }
298          else if (
299 #ifdef HAVE_RFC2553
300                /*
301                 * XXX: An undefined acl->dst is full of zeros and should be
302                 * considered a wildcard address. sockaddr_storage_to_ip()
303                 * fails on such destinations because of unknown sa_familly
304                 * (glibc only?). However this test is not portable.
305                 *
306                 * So, we signal the acl->dst is wildcard in wildcard_dst.
307                 */
308                acl->wildcard_dst ||
309                   match_sockaddr(&acl->dst->addr, &acl->dst->mask, &dst->addr)
310 #else
311                ((dst->addr & acl->dst->mask) == acl->dst->addr)
312            && ((dst->port == acl->dst->port) || (acl->dst->port == 0))
313 #endif
314            )
315          {
316             if (acl->action == ACL_PERMIT)
317             {
318                return(0);
319             }
320             else
321             {
322                return(1);
323             }
324          }
325       }
326       acl = acl->next;
327    }
328
329    return(1);
330
331 }
332
333
334 /*********************************************************************
335  *
336  * Function    :  acl_addr
337  *
338  * Description :  Called from `load_config' to parse an ACL address.
339  *
340  * Parameters  :
341  *          1  :  aspec = String specifying ACL address.
342  *          2  :  aca = struct access_control_addr to fill in.
343  *
344  * Returns     :  0 => Ok, everything else is an error.
345  *
346  *********************************************************************/
347 int acl_addr(const char *aspec, struct access_control_addr *aca)
348 {
349    int i, masklength;
350 #ifdef HAVE_RFC2553
351    struct addrinfo hints, *result;
352    uint8_t *mask_data;
353    in_port_t *mask_port;
354    unsigned int addr_len;
355 #else
356    long port;
357 #endif /* def HAVE_RFC2553 */
358    char *p;
359    char *acl_spec = NULL;
360
361 #ifdef HAVE_RFC2553
362    /* XXX: Depend on ai_family */
363    masklength = 128;
364 #else
365    masklength = 32;
366    port       =  0;
367 #endif
368
369    /*
370     * Use a temporary acl spec copy so we can log
371     * the unmodified original in case of parse errors.
372     */
373    acl_spec = strdup(aspec);
374    if (acl_spec == NULL)
375    {
376       /* XXX: This will be logged as parse error. */
377       return(-1);
378    }
379
380    if ((p = strchr(acl_spec, '/')) != NULL)
381    {
382       *p++ = '\0';
383       if (ijb_isdigit(*p) == 0)
384       {
385          freez(acl_spec);
386          return(-1);
387       }
388       masklength = atoi(p);
389    }
390
391    if ((masklength < 0) ||
392 #ifdef HAVE_RFC2553
393          (masklength > 128)
394 #else
395          (masklength > 32)
396 #endif
397          )
398    {
399       freez(acl_spec);
400       return(-1);
401    }
402
403    if ((*acl_spec == '[') && (NULL != (p = strchr(acl_spec, ']'))))
404    {
405       *p = '\0';
406       memmove(acl_spec, acl_spec + 1, (size_t)(p - acl_spec));
407
408       if (*++p != ':')
409       {
410          p = NULL;
411       }
412    }
413    else
414    {
415       p = strchr(acl_spec, ':');
416    }
417
418 #ifdef HAVE_RFC2553
419    memset(&hints, 0, sizeof(struct addrinfo));
420    hints.ai_family = AF_UNSPEC;
421    hints.ai_socktype = SOCK_STREAM;
422
423    i = getaddrinfo(acl_spec, ((p) ? ++p : NULL), &hints, &result);
424
425    if (i != 0)
426    {
427       log_error(LOG_LEVEL_ERROR, "Can not resolve [%s]:%s: %s",
428          acl_spec, p, gai_strerror(i));
429       freez(acl_spec);
430       return(-1);
431    }
432    freez(acl_spec);
433
434    /* TODO: Allow multihomed hostnames */
435    memcpy(&(aca->addr), result->ai_addr, result->ai_addrlen);
436    freeaddrinfo(result);
437 #else
438    if (p != NULL)
439    {
440       char *endptr;
441
442       *p++ = '\0';
443       port = strtol(p, &endptr, 10);
444
445       if (port <= 0 || port > 65535 || *endptr != '\0')
446       {
447          freez(acl_spec);
448          return(-1);
449       }
450    }
451
452    aca->port = (unsigned long)port;
453
454    aca->addr = ntohl(resolve_hostname_to_ip(acl_spec));
455    freez(acl_spec);
456
457    if (aca->addr == INADDR_NONE)
458    {
459       /* XXX: This will be logged as parse error. */
460       return(-1);
461    }
462 #endif /* def HAVE_RFC2553 */
463
464    /* build the netmask */
465 #ifdef HAVE_RFC2553
466    /* Clip masklength according to current family. */
467    if ((aca->addr.ss_family == AF_INET) && (masklength > 32))
468    {
469       masklength = 32;
470    }
471
472    aca->mask.ss_family = aca->addr.ss_family;
473    if (sockaddr_storage_to_ip(&aca->mask, &mask_data, &addr_len, &mask_port))
474    {
475       return(-1);
476    }
477
478    if (p)
479    {
480       /* ACL contains a port number, check ports in the future. */
481       *mask_port = 1;
482    }
483
484    /*
485     * XXX: This could be optimized to operate on whole words instead
486     * of octets (128-bit CPU could do it in one iteration).
487     */
488    /*
489     * Octets after prefix can be ommitted because of
490     * previous initialization to zeros.
491     */
492    for (i = 0; (i < addr_len) && masklength; i++)
493    {
494       if (masklength >= 8)
495       {
496          mask_data[i] = 0xFF;
497          masklength -= 8;
498       }
499       else
500       {
501          /*
502           * XXX: This assumes MSB of octet is on the left side.
503           * This should be true for all architectures or solved
504           * by the link layer.
505           */
506          mask_data[i] = (uint8_t)~((1 << (8 - masklength)) - 1);
507          masklength = 0;
508       }
509    }
510
511 #else
512    aca->mask = 0;
513    for (i=1; i <= masklength ; i++)
514    {
515       aca->mask |= (1U << (32 - i));
516    }
517
518    /* now mask off the host portion of the ip address
519     * (i.e. save on the network portion of the address).
520     */
521    aca->addr = aca->addr & aca->mask;
522 #endif /* def HAVE_RFC2553 */
523
524    return(0);
525
526 }
527 #endif /* def FEATURE_ACL */
528
529
530 /*********************************************************************
531  *
532  * Function    :  connect_port_is_forbidden
533  *
534  * Description :  Check to see if CONNECT requests to the destination
535  *                port of this request are forbidden. The check is
536  *                independend of the actual request method.
537  *
538  * Parameters  :
539  *          1  :  csp = Current client state (buffers, headers, etc...)
540  *
541  * Returns     :  True if yes, false otherwise.
542  *
543  *********************************************************************/
544 int connect_port_is_forbidden(const struct client_state *csp)
545 {
546    return ((csp->action->flags & ACTION_LIMIT_CONNECT) &&
547      !match_portlist(csp->action->string[ACTION_STRING_LIMIT_CONNECT],
548         csp->http->port));
549 }
550
551
552 /*********************************************************************
553  *
554  * Function    :  block_url
555  *
556  * Description :  Called from `chat'.  Check to see if we need to block this.
557  *
558  * Parameters  :
559  *          1  :  csp = Current client state (buffers, headers, etc...)
560  *
561  * Returns     :  NULL => unblocked, else HTTP block response
562  *
563  *********************************************************************/
564 struct http_response *block_url(struct client_state *csp)
565 {
566    struct http_response *rsp;
567    const char *new_content_type = NULL;
568
569    /*
570     * If it's not blocked, don't block it ;-)
571     */
572    if ((csp->action->flags & ACTION_BLOCK) == 0)
573    {
574       return NULL;
575    }
576    if (csp->action->flags & ACTION_REDIRECT)
577    {
578       log_error(LOG_LEVEL_ERROR, "redirect{} overruled by block.");     
579    }
580    /*
581     * Else, prepare a response
582     */
583    if (NULL == (rsp = alloc_http_response()))
584    {
585       return cgi_error_memory();
586    }
587
588    /*
589     * If it's an image-url, send back an image or redirect
590     * as specified by the relevant +image action
591     */
592 #ifdef FEATURE_IMAGE_BLOCKING
593    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
594         && is_imageurl(csp))
595    {
596       char *p;
597       /* determine HOW images should be blocked */
598       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
599
600       if(csp->action->flags & ACTION_HANDLE_AS_EMPTY_DOCUMENT)
601       {
602          log_error(LOG_LEVEL_ERROR, "handle-as-empty-document overruled by handle-as-image.");
603       }
604
605       /* and handle accordingly: */
606       if ((p == NULL) || (0 == strcmpic(p, "pattern")))
607       {
608          rsp->status = strdup("403 Request blocked by Privoxy");
609          if (rsp->status == NULL)
610          {
611             free_http_response(rsp);
612             return cgi_error_memory();
613          }
614          rsp->body = bindup(image_pattern_data, image_pattern_length);
615          if (rsp->body == NULL)
616          {
617             free_http_response(rsp);
618             return cgi_error_memory();
619          }
620          rsp->content_length = image_pattern_length;
621
622          if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
623          {
624             free_http_response(rsp);
625             return cgi_error_memory();
626          }
627       }
628       else if (0 == strcmpic(p, "blank"))
629       {
630          rsp->status = strdup("403 Request blocked by Privoxy");
631          if (rsp->status == NULL)
632          {
633             free_http_response(rsp);
634             return cgi_error_memory();
635          }
636          rsp->body = bindup(image_blank_data, image_blank_length);
637          if (rsp->body == NULL)
638          {
639             free_http_response(rsp);
640             return cgi_error_memory();
641          }
642          rsp->content_length = image_blank_length;
643
644          if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
645          {
646             free_http_response(rsp);
647             return cgi_error_memory();
648          }
649       }
650       else
651       {
652          rsp->status = strdup("302 Local Redirect from Privoxy");
653          if (rsp->status == NULL)
654          {
655             free_http_response(rsp);
656             return cgi_error_memory();
657          }
658
659          if (enlist_unique_header(rsp->headers, "Location", p))
660          {
661             free_http_response(rsp);
662             return cgi_error_memory();
663          }
664       }
665
666    }
667    else
668 #endif /* def FEATURE_IMAGE_BLOCKING */
669    if(csp->action->flags & ACTION_HANDLE_AS_EMPTY_DOCUMENT)
670    {
671      /*
672       *  Send empty document.               
673       */
674       new_content_type = csp->action->string[ACTION_STRING_CONTENT_TYPE];
675
676       freez(rsp->body);
677       rsp->body = strdup(" ");
678       rsp->content_length = 1;
679
680       if (csp->config->feature_flags & RUNTIME_FEATURE_EMPTY_DOC_RETURNS_OK)
681       {
682          /*
683           * Workaround for firefox bug 492459
684           *   https://bugzilla.mozilla.org/show_bug.cgi?id=492459
685           * Return a 200 OK status for pages blocked with +handle-as-empty-document
686           * if the "handle-as-empty-doc-returns-ok" runtime config option is set.
687           */
688          rsp->status = strdup("200 Request blocked by Privoxy");
689       }
690       else
691       {
692          rsp->status = strdup("403 Request blocked by Privoxy");
693       }
694
695       if (rsp->status == NULL)
696       {
697          free_http_response(rsp);
698          return cgi_error_memory();
699       }
700       if (new_content_type != 0)
701       {
702          log_error(LOG_LEVEL_HEADER, "Overwriting Content-Type with %s", new_content_type);
703          if (enlist_unique_header(rsp->headers, "Content-Type", new_content_type))
704          {
705             free_http_response(rsp);
706             return cgi_error_memory();
707          }
708       }
709    }
710    else
711
712    /*
713     * Else, generate an HTML "blocked" message:
714     */
715    {
716       jb_err err;
717       struct map * exports;
718       char *p;
719
720       /*
721        * Workaround for stupid Netscape bug which prevents
722        * pages from being displayed if loading a referenced
723        * JavaScript or style sheet fails. So make it appear
724        * as if it succeeded.
725        */
726       if ( NULL != (p = get_header_value(csp->headers, "User-Agent:"))
727            && !strncmpic(p, "mozilla", 7) /* Catch Netscape but */
728            && !strstr(p, "Gecko")         /* save Mozilla, */
729            && !strstr(p, "compatible")    /* MSIE */
730            && !strstr(p, "Opera"))        /* and Opera. */
731       {
732          rsp->status = strdup("200 Request for blocked URL");
733       }
734       else
735       {
736          rsp->status = strdup("403 Request for blocked URL");
737       }
738
739       if (rsp->status == NULL)
740       {
741          free_http_response(rsp);
742          return cgi_error_memory();
743       }
744
745       exports = default_exports(csp, NULL);
746       if (exports == NULL)
747       {
748          free_http_response(rsp);
749          return cgi_error_memory();
750       }
751
752 #ifdef FEATURE_FORCE_LOAD
753       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
754       /*
755        * Export the force conditional block killer if
756        *
757        * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
758        * - Privoxy is configured to enforce blocks, or
759        * - it's a CONNECT request and enforcing wouldn't work anyway.
760        */
761       if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
762        || (0 == strcmpic(csp->http->gpc, "connect")))
763 #endif /* ndef FEATURE_FORCE_LOAD */
764       {
765          err = map_block_killer(exports, "force-support");
766       }
767
768       if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1);
769       if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
770       if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
771       if (!err) err = map(exports, "path-ue", 1, url_encode(csp->http->path), 0);
772       if (!err)
773       {
774          const char *block_reason;
775          if (csp->action->string[ACTION_STRING_BLOCK] != NULL)
776          {
777             block_reason = csp->action->string[ACTION_STRING_BLOCK];
778          }
779          else
780          {
781             assert(connect_port_is_forbidden(csp));
782             block_reason = "Forbidden CONNECT port.";
783          }
784          err = map(exports, "block-reason", 1, html_encode(block_reason), 0);
785       }
786       if (err)
787       {
788          free_map(exports);
789          free_http_response(rsp);
790          return cgi_error_memory();
791       }
792
793       err = template_fill_for_cgi(csp, "blocked", exports, rsp);
794       if (err)
795       {
796          free_http_response(rsp);
797          return cgi_error_memory();
798       }
799    }
800    rsp->crunch_reason = BLOCKED;
801
802    return finish_http_response(csp, rsp);
803
804 }
805
806
807 #ifdef FEATURE_TRUST
808 /*********************************************************************
809  *
810  * Function    :  trust_url FIXME: I should be called distrust_url
811  *
812  * Description :  Calls is_untrusted_url to determine if the URL is trusted
813  *                and if not, returns a HTTP 403 response with a reject message.
814  *
815  * Parameters  :
816  *          1  :  csp = Current client state (buffers, headers, etc...)
817  *
818  * Returns     :  NULL => trusted, else http_response.
819  *
820  *********************************************************************/
821 struct http_response *trust_url(struct client_state *csp)
822 {
823    struct http_response *rsp;
824    struct map * exports;
825    char buf[BUFFER_SIZE];
826    char *p;
827    struct url_spec **tl;
828    struct url_spec *t;
829    jb_err err;
830
831    /*
832     * Don't bother to work on trusted URLs
833     */
834    if (!is_untrusted_url(csp))
835    {
836       return NULL;
837    }
838
839    /*
840     * Else, prepare a response:
841     */
842    if (NULL == (rsp = alloc_http_response()))
843    {
844       return cgi_error_memory();
845    }
846
847    rsp->status = strdup("403 Request blocked by Privoxy");
848    exports = default_exports(csp, NULL);
849    if (exports == NULL || rsp->status == NULL)
850    {
851       free_http_response(rsp);
852       return cgi_error_memory();
853    }
854
855    /*
856     * Export the protocol, host, port, and referrer information
857     */
858    err = map(exports, "hostport", 1, csp->http->hostport, 1);
859    if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
860    if (!err) err = map(exports, "path", 1, csp->http->path, 1);
861
862    if (NULL != (p = get_header_value(csp->headers, "Referer:")))
863    {
864       if (!err) err = map(exports, "referrer", 1, html_encode(p), 0);
865    }
866    else
867    {
868       if (!err) err = map(exports, "referrer", 1, "none set", 1);
869    }
870
871    if (err)
872    {
873       free_map(exports);
874       free_http_response(rsp);
875       return cgi_error_memory();
876    }
877
878    /*
879     * Export the trust list
880     */
881    p = strdup("");
882    for (tl = csp->config->trust_list; (t = *tl) != NULL ; tl++)
883    {
884       snprintf(buf, sizeof(buf), "<li>%s</li>\n", t->spec);
885       string_append(&p, buf);
886    }
887    err = map(exports, "trusted-referrers", 1, p, 0);
888
889    if (err)
890    {
891       free_map(exports);
892       free_http_response(rsp);
893       return cgi_error_memory();
894    }
895
896    /*
897     * Export the trust info, if available
898     */
899    if (csp->config->trust_info->first)
900    {
901       struct list_entry *l;
902
903       p = strdup("");
904       for (l = csp->config->trust_info->first; l ; l = l->next)
905       {
906          snprintf(buf, sizeof(buf), "<li> <a href=\"%s\">%s</a><br>\n", l->str, l->str);
907          string_append(&p, buf);
908       }
909       err = map(exports, "trust-info", 1, p, 0);
910    }
911    else
912    {
913       err = map_block_killer(exports, "have-trust-info");
914    }
915
916    if (err)
917    {
918       free_map(exports);
919       free_http_response(rsp);
920       return cgi_error_memory();
921    }
922
923    /*
924     * Export the force conditional block killer if
925     *
926     * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
927     * - Privoxy is configured to enforce blocks, or
928     * - it's a CONNECT request and enforcing wouldn't work anyway.
929     */
930 #ifdef FEATURE_FORCE_LOAD
931    if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
932     || (0 == strcmpic(csp->http->gpc, "connect")))
933    {
934       err = map_block_killer(exports, "force-support");
935    }
936    else
937    {
938       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
939    }
940 #else /* ifndef FEATURE_FORCE_LOAD */
941    err = map_block_killer(exports, "force-support");
942 #endif /* ndef FEATURE_FORCE_LOAD */
943
944    if (err)
945    {
946       free_map(exports);
947       free_http_response(rsp);
948       return cgi_error_memory();
949    }
950
951    /*
952     * Build the response
953     */
954    err = template_fill_for_cgi(csp, "untrusted", exports, rsp);
955    if (err)
956    {
957       free_http_response(rsp);
958       return cgi_error_memory();
959    }
960    rsp->crunch_reason = UNTRUSTED;
961
962    return finish_http_response(csp, rsp);
963 }
964 #endif /* def FEATURE_TRUST */
965
966
967 /*********************************************************************
968  *
969  * Function    :  compile_dynamic_pcrs_job_list
970  *
971  * Description :  Compiles a dynamic pcrs job list (one with variables
972  *                resolved at request time)
973  *
974  * Parameters  :
975  *          1  :  csp = Current client state (buffers, headers, etc...)
976  *          2  :  b = The filter list to compile
977  *
978  * Returns     :  NULL in case of errors, otherwise the
979  *                pcrs job list.  
980  *
981  *********************************************************************/
982 pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, const struct re_filterfile_spec *b)
983 {
984    struct list_entry *pattern;
985    pcrs_job *job_list = NULL;
986    pcrs_job *dummy = NULL;
987    pcrs_job *lastjob = NULL;
988    int error = 0;
989
990    const struct pcrs_variable variables[] =
991    {
992       {"url",    csp->http->url,   1},
993       {"path",   csp->http->path,  1},
994       {"host",   csp->http->host,  1},
995       {"origin", csp->ip_addr_str, 1},
996       {NULL,     NULL,             1}
997    };
998
999    for (pattern = b->patterns->first; pattern != NULL; pattern = pattern->next)
1000    {
1001       assert(pattern->str != NULL);
1002
1003       dummy = pcrs_compile_dynamic_command(pattern->str, variables, &error);
1004       if (NULL == dummy)
1005       {
1006          assert(error < 0);
1007          log_error(LOG_LEVEL_ERROR,
1008             "Adding filter job \'%s\' to dynamic filter %s failed: %s",
1009             pattern->str, b->name, pcrs_strerror(error));
1010          continue;
1011       }
1012       else
1013       {
1014          if (error == PCRS_WARN_TRUNCATION)
1015          {
1016             log_error(LOG_LEVEL_ERROR,
1017                "At least one of the variables in \'%s\' had to "
1018                "be truncated before compilation", pattern->str);
1019          }
1020          if (job_list == NULL)
1021          {
1022             job_list = dummy;
1023          }
1024          else
1025          {
1026             lastjob->next = dummy;
1027          }
1028          lastjob = dummy;
1029       }
1030    }
1031
1032    return job_list;
1033 }
1034
1035
1036 /*********************************************************************
1037  *
1038  * Function    :  rewrite_url
1039  *
1040  * Description :  Rewrites a URL with a single pcrs command
1041  *                and returns the result if it differs from the
1042  *                original and isn't obviously invalid.
1043  *
1044  * Parameters  :
1045  *          1  :  old_url = URL to rewrite.
1046  *          2  :  pcrs_command = pcrs command formatted as string (s@foo@bar@)
1047  *
1048  *
1049  * Returns     :  NULL if the pcrs_command didn't change the url, or 
1050  *                the result of the modification.
1051  *
1052  *********************************************************************/
1053 char *rewrite_url(char *old_url, const char *pcrs_command)
1054 {
1055    char *new_url = NULL;
1056    int hits;
1057
1058    assert(old_url);
1059    assert(pcrs_command);
1060
1061    new_url = pcrs_execute_single_command(old_url, pcrs_command, &hits);
1062
1063    if (hits == 0)
1064    {
1065       log_error(LOG_LEVEL_REDIRECTS,
1066          "pcrs command \"%s\" didn't change \"%s\".",
1067          pcrs_command, old_url);
1068       freez(new_url);
1069    }
1070    else if (hits < 0)
1071    {
1072       log_error(LOG_LEVEL_REDIRECTS,
1073          "executing pcrs command \"%s\" to rewrite %s failed: %s",
1074          pcrs_command, old_url, pcrs_strerror(hits));
1075       freez(new_url);
1076    }
1077    else if (strncmpic(new_url, "http://", 7) && strncmpic(new_url, "https://", 8))
1078    {
1079       log_error(LOG_LEVEL_ERROR,
1080          "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s), "
1081          "but the result doesn't look like a valid URL and will be ignored.",
1082          pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
1083       freez(new_url);
1084    }
1085    else
1086    {
1087       log_error(LOG_LEVEL_REDIRECTS,
1088          "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s).",
1089          pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
1090    }
1091
1092    return new_url;
1093
1094 }
1095
1096
1097 #ifdef FEATURE_FAST_REDIRECTS
1098 /*********************************************************************
1099  *
1100  * Function    :  get_last_url
1101  *
1102  * Description :  Search for the last URL inside a string.
1103  *                If the string already is a URL, it will
1104  *                be the first URL found.
1105  *
1106  * Parameters  :
1107  *          1  :  subject = the string to check
1108  *          2  :  redirect_mode = +fast-redirect{} mode 
1109  *
1110  * Returns     :  NULL if no URL was found, or
1111  *                the last URL found.
1112  *
1113  *********************************************************************/
1114 char *get_last_url(char *subject, const char *redirect_mode)
1115 {
1116    char *new_url = NULL;
1117    char *tmp;
1118
1119    assert(subject);
1120    assert(redirect_mode);
1121
1122    subject = strdup(subject);
1123    if (subject == NULL)
1124    {
1125       log_error(LOG_LEVEL_ERROR, "Out of memory while searching for redirects.");
1126       return NULL;
1127    }
1128
1129    if (0 == strcmpic(redirect_mode, "check-decoded-url"))
1130    {  
1131       log_error(LOG_LEVEL_REDIRECTS, "Decoding \"%s\" if necessary.", subject);
1132       new_url = url_decode(subject);
1133       if (new_url != NULL)
1134       {
1135          freez(subject);
1136          subject = new_url;
1137       }
1138       else
1139       {
1140          log_error(LOG_LEVEL_ERROR, "Unable to decode \"%s\".", subject);
1141       }
1142    }
1143
1144    log_error(LOG_LEVEL_REDIRECTS, "Checking \"%s\" for redirects.", subject);
1145
1146    /*
1147     * Find the last URL encoded in the request
1148     */
1149    tmp = subject;
1150    while ((tmp = strstr(tmp, "http://")) != NULL)
1151    {
1152       new_url = tmp++;
1153    }
1154    tmp = (new_url != NULL) ? new_url : subject;
1155    while ((tmp = strstr(tmp, "https://")) != NULL)
1156    {
1157       new_url = tmp++;
1158    }
1159
1160    if ((new_url != NULL)
1161       && (  (new_url != subject)
1162          || (0 == strncmpic(subject, "http://", 7))
1163          || (0 == strncmpic(subject, "https://", 8))
1164          ))
1165    {
1166       /*
1167        * Return new URL if we found a redirect 
1168        * or if the subject already was a URL.
1169        *
1170        * The second case makes sure that we can
1171        * chain get_last_url after another redirection check
1172        * (like rewrite_url) without losing earlier redirects.
1173        */
1174       new_url = strdup(new_url);
1175       freez(subject);
1176       return new_url;
1177    }
1178
1179    freez(subject);
1180    return NULL;
1181
1182 }
1183 #endif /* def FEATURE_FAST_REDIRECTS */
1184
1185
1186 /*********************************************************************
1187  *
1188  * Function    :  redirect_url
1189  *
1190  * Description :  Checks if Privoxy should answer the request with
1191  *                a HTTP redirect and generates the redirect if
1192  *                necessary.
1193  *
1194  * Parameters  :
1195  *          1  :  csp = Current client state (buffers, headers, etc...)
1196  *
1197  * Returns     :  NULL if the request can pass, HTTP redirect otherwise.
1198  *
1199  *********************************************************************/
1200 struct http_response *redirect_url(struct client_state *csp)
1201 {
1202    struct http_response *rsp;
1203 #ifdef FEATURE_FAST_REDIRECTS
1204    /*
1205     * XXX: Do we still need FEATURE_FAST_REDIRECTS
1206     * as compile-time option? The user can easily disable
1207     * it in his action file.
1208     */
1209    char * redirect_mode;
1210 #endif /* def FEATURE_FAST_REDIRECTS */
1211    char *old_url = NULL;
1212    char *new_url = NULL;
1213    char *redirection_string;
1214
1215    if ((csp->action->flags & ACTION_REDIRECT))
1216    {
1217       redirection_string = csp->action->string[ACTION_STRING_REDIRECT];
1218
1219       /*
1220        * If the redirection string begins with 's',
1221        * assume it's a pcrs command, otherwise treat it as
1222        * properly formatted URL and use it for the redirection
1223        * directly.
1224        *
1225        * According to RFC 2616 section 14.30 the URL
1226        * has to be absolute and if the user tries:
1227        * +redirect{shit/this/will/be/parsed/as/pcrs_command.html}
1228        * she would get undefined results anyway.
1229        *
1230        */
1231
1232       if (*redirection_string == 's')
1233       {
1234          old_url = csp->http->url;
1235          new_url = rewrite_url(old_url, redirection_string);
1236       }
1237       else
1238       {
1239          log_error(LOG_LEVEL_REDIRECTS,
1240             "No pcrs command recognized, assuming that \"%s\" is already properly formatted.",
1241             redirection_string);
1242          new_url = strdup(redirection_string);
1243       }
1244    }
1245
1246 #ifdef FEATURE_FAST_REDIRECTS
1247    if ((csp->action->flags & ACTION_FAST_REDIRECTS))
1248    {
1249       redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS];
1250
1251       /*
1252        * If it exists, use the previously rewritten URL as input
1253        * otherwise just use the old path.
1254        */
1255       old_url = (new_url != NULL) ? new_url : strdup(csp->http->path);
1256       new_url = get_last_url(old_url, redirect_mode);
1257       freez(old_url);
1258    }
1259
1260    /*
1261     * Disable redirect checkers, so that they
1262     * will be only run more than once if the user
1263     * also enables them through tags.
1264     *
1265     * From a performance point of view
1266     * it doesn't matter, but the duplicated
1267     * log messages are annoying.
1268     */
1269    csp->action->flags &= ~ACTION_FAST_REDIRECTS;
1270 #endif /* def FEATURE_FAST_REDIRECTS */
1271    csp->action->flags &= ~ACTION_REDIRECT;
1272
1273    /* Did any redirect action trigger? */   
1274    if (new_url)
1275    {
1276       if (0 == strcmpic(new_url, csp->http->url))
1277       {
1278          log_error(LOG_LEVEL_ERROR,
1279             "New URL \"%s\" and old URL \"%s\" are the same. Redirection loop prevented.",
1280             csp->http->url, new_url);
1281             freez(new_url);
1282       }
1283       else
1284       {
1285          log_error(LOG_LEVEL_REDIRECTS, "New URL is: %s", new_url);
1286
1287          if (NULL == (rsp = alloc_http_response()))
1288          {
1289             freez(new_url);
1290             return cgi_error_memory();
1291          }
1292
1293          if ( enlist_unique_header(rsp->headers, "Location", new_url)
1294            || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) )
1295          {
1296             freez(new_url);
1297             free_http_response(rsp);
1298             return cgi_error_memory();
1299          }
1300          rsp->crunch_reason = REDIRECTED;
1301          freez(new_url);
1302
1303          return finish_http_response(csp, rsp);
1304       }
1305    }
1306
1307    /* Only reached if no redirect is required */
1308    return NULL;
1309
1310 }
1311
1312
1313 #ifdef FEATURE_IMAGE_BLOCKING
1314 /*********************************************************************
1315  *
1316  * Function    :  is_imageurl
1317  *
1318  * Description :  Given a URL, decide whether it is an image or not,
1319  *                using either the info from a previous +image action
1320  *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, and the browser
1321  *                is MSIE and not on a Mac, tell from the browser's accept
1322  *                header.
1323  *
1324  * Parameters  :
1325  *          1  :  csp = Current client state (buffers, headers, etc...)
1326  *
1327  * Returns     :  True (nonzero) if URL is an image, false (0)
1328  *                otherwise
1329  *
1330  *********************************************************************/
1331 int is_imageurl(const struct client_state *csp)
1332 {
1333 #ifdef FEATURE_IMAGE_DETECT_MSIE
1334    char *tmp;
1335
1336    tmp = get_header_value(csp->headers, "User-Agent:");
1337    if (tmp && strstr(tmp, "MSIE") && !strstr(tmp, "Mac_"))
1338    {
1339       tmp = get_header_value(csp->headers, "Accept:");
1340       if (tmp && strstr(tmp, "image/gif"))
1341       {
1342          /* Client will accept HTML.  If this seems counterintuitive,
1343           * blame Microsoft.
1344           */
1345          return(0);
1346       }
1347       else
1348       {
1349          return(1);
1350       }
1351    }
1352 #endif /* def FEATURE_IMAGE_DETECT_MSIE */
1353
1354    return ((csp->action->flags & ACTION_IMAGE) != 0);
1355
1356 }
1357 #endif /* def FEATURE_IMAGE_BLOCKING */
1358
1359
1360 #ifdef FEATURE_TRUST
1361 /*********************************************************************
1362  *
1363  * Function    :  is_untrusted_url
1364  *
1365  * Description :  Should we "distrust" this URL (and block it)?
1366  *
1367  *                Yes if it matches a line in the trustfile, or if the
1368  *                    referrer matches a line starting with "+" in the
1369  *                    trustfile.
1370  *                No  otherwise.
1371  *
1372  * Parameters  :
1373  *          1  :  csp = Current client state (buffers, headers, etc...)
1374  *
1375  * Returns     :  0 => trusted, 1 => untrusted
1376  *
1377  *********************************************************************/
1378 int is_untrusted_url(const struct client_state *csp)
1379 {
1380    struct file_list *fl;
1381    struct block_spec *b;
1382    struct url_spec **trusted_url;
1383    struct http_request rhttp[1];
1384    const char * referer;
1385    jb_err err;
1386
1387    /*
1388     * If we don't have a trustlist, we trust everybody
1389     */
1390    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
1391    {
1392       return 0;
1393    }
1394
1395    memset(rhttp, '\0', sizeof(*rhttp));
1396
1397    /*
1398     * Do we trust the request URL itself?
1399     */
1400    for (b = b->next; b ; b = b->next)
1401    {
1402       if (url_match(b->url, csp->http))
1403       {
1404          return b->reject;
1405       }
1406    }
1407
1408    if (NULL == (referer = get_header_value(csp->headers, "Referer:")))
1409    {
1410       /* no referrer was supplied */
1411       return 1;
1412    }
1413
1414
1415    /*
1416     * If not, do we maybe trust its referrer?
1417     */
1418    err = parse_http_url(referer, rhttp, REQUIRE_PROTOCOL);
1419    if (err)
1420    {
1421       return 1;
1422    }
1423
1424    for (trusted_url = csp->config->trust_list; *trusted_url != NULL; trusted_url++)
1425    {
1426       if (url_match(*trusted_url, rhttp))
1427       {
1428          /* if the URL's referrer is from a trusted referrer, then
1429           * add the target spec to the trustfile as an unblocked
1430           * domain and return 0 (which means it's OK).
1431           */
1432
1433          FILE *fp;
1434
1435          if (NULL != (fp = fopen(csp->config->trustfile, "a")))
1436          {
1437             char * path;
1438             char * path_end;
1439             char * new_entry = strdup("~");
1440
1441             string_append(&new_entry, csp->http->hostport);
1442
1443             path = csp->http->path;
1444             if ( (path[0] == '/')
1445               && (path[1] == '~')
1446               && ((path_end = strchr(path + 2, '/')) != NULL))
1447             {
1448                /* since this path points into a user's home space
1449                 * be sure to include this spec in the trustfile.
1450                 */
1451                long path_len = path_end - path; /* save offset */
1452                path = strdup(path); /* Copy string */
1453                if (path != NULL)
1454                {
1455                   path_end = path + path_len; /* regenerate ptr to new buffer */
1456                   *(path_end + 1) = '\0'; /* Truncate path after '/' */
1457                }
1458                string_join(&new_entry, path);
1459             }
1460
1461             /*
1462              * Give a reason for generating this entry.
1463              */
1464             string_append(&new_entry, " # Trusted referrer was: ");
1465             string_append(&new_entry, referer);
1466
1467             if (new_entry != NULL)
1468             {
1469                if (-1 == fprintf(fp, "%s\n", new_entry))
1470                {
1471                   log_error(LOG_LEVEL_ERROR, "Failed to append \'%s\' to trustfile \'%s\': %E",
1472                      new_entry, csp->config->trustfile);
1473                }
1474                freez(new_entry);
1475             }
1476             else
1477             {
1478                /* FIXME: No way to handle out-of memory, so mostly ignoring it */
1479                log_error(LOG_LEVEL_ERROR, "Out of memory adding pattern to trust file");
1480             }
1481
1482             fclose(fp);
1483          }
1484          else
1485          {
1486             log_error(LOG_LEVEL_ERROR, "Failed to append new entry for \'%s\' to trustfile \'%s\': %E",
1487                csp->http->hostport, csp->config->trustfile);
1488          }
1489          return 0;
1490       }
1491    }
1492
1493    return 1;
1494 }
1495 #endif /* def FEATURE_TRUST */
1496
1497
1498 /*********************************************************************
1499  *
1500  * Function    :  pcrs_filter_response
1501  *
1502  * Description :  Execute all text substitutions from all applying
1503  *                +filter actions on the text buffer that's been
1504  *                accumulated in csp->iob->buf.
1505  *
1506  * Parameters  :
1507  *          1  :  csp = Current client state (buffers, headers, etc...)
1508  *
1509  * Returns     :  a pointer to the (newly allocated) modified buffer.
1510  *                or NULL if there were no hits or something went wrong
1511  *
1512  *********************************************************************/
1513 static char *pcrs_filter_response(struct client_state *csp)
1514 {
1515    int hits = 0;
1516    int i;
1517    size_t size, prev_size;
1518
1519    char *old = NULL;
1520    char *new = NULL;
1521    pcrs_job *job;
1522
1523    struct file_list *fl;
1524    struct re_filterfile_spec *b;
1525    struct list_entry *filtername;
1526
1527    /* 
1528     * Sanity first
1529     */
1530    if (csp->iob->cur >= csp->iob->eod)
1531    {
1532       return(NULL);
1533    }
1534
1535    if (filters_available(csp) == FALSE)
1536    {
1537       log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
1538          "content filtering enabled, but no content filters available.");
1539       return(NULL);
1540    }
1541
1542    size = (size_t)(csp->iob->eod - csp->iob->cur);
1543    old = csp->iob->cur;
1544
1545    for (i = 0; i < MAX_AF_FILES; i++)
1546    {
1547      fl = csp->rlist[i];
1548      if ((NULL == fl) || (NULL == fl->f))
1549      {
1550         /*
1551          * Either there are no filter files
1552          * left, or this filter file just
1553          * contains no valid filters.
1554          *
1555          * Continue to be sure we don't miss
1556          * valid filter files that are chained
1557          * after empty or invalid ones.
1558          */
1559         continue;
1560      }
1561    /*
1562     * For all applying +filter actions, look if a filter by that
1563     * name exists and if yes, execute it's pcrs_joblist on the
1564     * buffer.
1565     */
1566    for (b = fl->f; b; b = b->next)
1567    {
1568       if (b->type != FT_CONTENT_FILTER)
1569       {
1570          /* Skip header filters */
1571          continue;
1572       }
1573
1574       for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
1575            filtername ; filtername = filtername->next)
1576       {
1577          if (strcmp(b->name, filtername->str) == 0)
1578          {
1579             int current_hits = 0; /* Number of hits caused by this filter */
1580             int job_number   = 0; /* Which job we're currently executing  */
1581             int job_hits     = 0; /* How many hits the current job caused */
1582             pcrs_job *joblist = b->joblist;
1583
1584             if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
1585
1586             if (NULL == joblist)
1587             {
1588                log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
1589                continue;
1590             }
1591
1592             prev_size = size;
1593             /* Apply all jobs from the joblist */
1594             for (job = joblist; NULL != job; job = job->next)
1595             {
1596                job_number++;
1597                job_hits = pcrs_execute(job, old, size, &new, &size);
1598
1599                if (job_hits >= 0)
1600                {
1601                   /*
1602                    * That went well. Continue filtering
1603                    * and use the result of this job as
1604                    * input for the next one.
1605                    */
1606                   current_hits += job_hits;
1607                   if (old != csp->iob->cur)
1608                   {
1609                      freez(old);
1610                   }
1611                   old = new;
1612                }
1613                else
1614                {
1615                   /*
1616                    * This job caused an unexpected error. Inform the user
1617                    * and skip the rest of the jobs in this filter. We could
1618                    * continue with the next job, but usually the jobs
1619                    * depend on each other or are similar enough to
1620                    * fail for the same reason.
1621                    *
1622                    * At the moment our pcrs expects the error codes of pcre 3.4,
1623                    * but newer pcre versions can return additional error codes.
1624                    * As a result pcrs_strerror()'s error message might be
1625                    * "Unknown error ...", therefore we print the numerical value
1626                    * as well.
1627                    *
1628                    * XXX: Is this important enough for LOG_LEVEL_ERROR or
1629                    * should we use LOG_LEVEL_RE_FILTER instead?
1630                    */
1631                   log_error(LOG_LEVEL_ERROR, "Skipped filter \'%s\' after job number %u: %s (%d)",
1632                      b->name, job_number, pcrs_strerror(job_hits), job_hits);
1633                   break;
1634                }
1635             }
1636
1637             if (b->dynamic) pcrs_free_joblist(joblist);
1638
1639             log_error(LOG_LEVEL_RE_FILTER,
1640                "filtering %s%s (size %d) with \'%s\' produced %d hits (new size %d).",
1641                csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size);
1642
1643             hits += current_hits;
1644          }
1645       }
1646    }
1647    }
1648
1649    /*
1650     * If there were no hits, destroy our copy and let
1651     * chat() use the original in csp->iob
1652     */
1653    if (!hits)
1654    {
1655       freez(new);
1656       return(NULL);
1657    }
1658
1659    csp->flags |= CSP_FLAG_MODIFIED;
1660    csp->content_length = size;
1661    IOB_RESET(csp);
1662
1663    return(new);
1664
1665 }
1666
1667
1668 /*********************************************************************
1669  *
1670  * Function    :  gif_deanimate_response
1671  *
1672  * Description :  Deanimate the GIF image that has been accumulated in
1673  *                csp->iob->buf, set csp->content_length to the modified
1674  *                size and raise the CSP_FLAG_MODIFIED flag.
1675  *
1676  * Parameters  :
1677  *          1  :  csp = Current client state (buffers, headers, etc...)
1678  *
1679  * Returns     :  a pointer to the (newly allocated) modified buffer.
1680  *                or NULL in case something went wrong.
1681  *
1682  *********************************************************************/
1683 static char *gif_deanimate_response(struct client_state *csp)
1684 {
1685    struct binbuffer *in, *out;
1686    char *p;
1687    size_t size;
1688
1689    size = (size_t)(csp->iob->eod - csp->iob->cur);
1690
1691    if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
1692       || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
1693    {
1694       log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
1695       return NULL;
1696    }
1697
1698    in->buffer = csp->iob->cur;
1699    in->size = size;
1700
1701    if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
1702    {
1703       log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
1704       freez(in);
1705       buf_free(out);
1706       return(NULL);
1707    }
1708    else
1709    {
1710       if ((int)size == out->offset)
1711       {
1712          log_error(LOG_LEVEL_DEANIMATE, "GIF not changed.");
1713       }
1714       else
1715       {
1716          log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
1717       }
1718       csp->content_length = out->offset;
1719       csp->flags |= CSP_FLAG_MODIFIED;
1720       p = out->buffer;
1721       freez(in);
1722       freez(out);
1723       return(p);
1724    }
1725
1726 }
1727
1728
1729 /*********************************************************************
1730  *
1731  * Function    :  get_filter_function
1732  *
1733  * Description :  Decides which content filter function has
1734  *                to be applied (if any).
1735  *
1736  * Parameters  :
1737  *          1  :  csp = Current client state (buffers, headers, etc...)
1738  *
1739  * Returns     :  The content filter function to run, or
1740  *                NULL if no content filter is active
1741  *
1742  *********************************************************************/
1743 static filter_function_ptr get_filter_function(const struct client_state *csp)
1744 {
1745    filter_function_ptr filter_function = NULL;
1746
1747    /*
1748     * Choose the applying filter function based on
1749     * the content type and action settings.
1750     */
1751    if ((csp->content_type & CT_TEXT) &&
1752        (csp->rlist != NULL) &&
1753        (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
1754    {
1755       filter_function = pcrs_filter_response;
1756    }
1757    else if ((csp->content_type & CT_GIF)  &&
1758             (csp->action->flags & ACTION_DEANIMATE))
1759    {
1760       filter_function = gif_deanimate_response;
1761    }
1762
1763    return filter_function;
1764 }
1765
1766
1767 /*********************************************************************
1768  *
1769  * Function    :  remove_chunked_transfer_coding
1770  *
1771  * Description :  In-situ remove the "chunked" transfer coding as defined
1772  *                in rfc2616 from a buffer.
1773  *
1774  * Parameters  :
1775  *          1  :  buffer = Pointer to the text buffer
1776  *          2  :  size =  In: Number of bytes to be processed,
1777  *                       Out: Number of bytes after de-chunking.
1778  *                       (undefined in case of errors)
1779  *
1780  * Returns     :  JB_ERR_OK for success,
1781  *                JB_ERR_PARSE otherwise
1782  *
1783  *********************************************************************/
1784 static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size)
1785 {
1786    size_t newsize = 0;
1787    unsigned int chunksize = 0;
1788    char *from_p, *to_p;
1789
1790    assert(buffer);
1791    from_p = to_p = buffer;
1792
1793    if (sscanf(buffer, "%x", &chunksize) != 1)
1794    {
1795       log_error(LOG_LEVEL_ERROR, "Invalid first chunksize while stripping \"chunked\" transfer coding");
1796       return JB_ERR_PARSE;
1797    }
1798
1799    while (chunksize > 0U)
1800    {
1801       if (NULL == (from_p = strstr(from_p, "\r\n")))
1802       {
1803          log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
1804          return JB_ERR_PARSE;
1805       }
1806
1807       if ((newsize += chunksize) >= *size)
1808       {
1809          log_error(LOG_LEVEL_ERROR,
1810             "Chunk size %d exceeds buffer size %d in  \"chunked\" transfer coding",
1811             chunksize, *size);
1812          return JB_ERR_PARSE;
1813       }
1814       from_p += 2;
1815
1816       memmove(to_p, from_p, (size_t) chunksize);
1817       to_p = buffer + newsize;
1818       from_p += chunksize + 2;
1819
1820       if (sscanf(from_p, "%x", &chunksize) != 1)
1821       {
1822          log_error(LOG_LEVEL_INFO, "Invalid \"chunked\" transfer encoding detected and ignored.");
1823          break;
1824       }
1825    }
1826    
1827    /* XXX: Should get its own loglevel. */
1828    log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d", *size, newsize);
1829
1830    *size = newsize;
1831
1832    return JB_ERR_OK;
1833
1834 }
1835
1836
1837 /*********************************************************************
1838  *
1839  * Function    :  prepare_for_filtering
1840  *
1841  * Description :  If necessary, de-chunks and decompresses
1842  *                the content so it can get filterd.
1843  *
1844  * Parameters  :
1845  *          1  :  csp = Current client state (buffers, headers, etc...)
1846  *
1847  * Returns     :  JB_ERR_OK for success,
1848  *                JB_ERR_PARSE otherwise
1849  *
1850  *********************************************************************/
1851 static jb_err prepare_for_filtering(struct client_state *csp)
1852 {
1853    jb_err err = JB_ERR_OK;
1854
1855    /*
1856     * If the body has a "chunked" transfer-encoding,
1857     * get rid of it, adjusting size and iob->eod
1858     */
1859    if (csp->flags & CSP_FLAG_CHUNKED)
1860    {
1861       size_t size = (size_t)(csp->iob->eod - csp->iob->cur);
1862
1863       log_error(LOG_LEVEL_RE_FILTER, "Need to de-chunk first");
1864       err = remove_chunked_transfer_coding(csp->iob->cur, &size);
1865       if (JB_ERR_OK == err)
1866       {
1867          csp->iob->eod = csp->iob->cur + size;
1868          csp->flags |= CSP_FLAG_MODIFIED;
1869       }
1870       else
1871       {
1872          return JB_ERR_PARSE;
1873       }
1874    }
1875
1876 #ifdef FEATURE_ZLIB
1877    /*
1878     * If the body has a supported transfer-encoding,
1879     * decompress it, adjusting size and iob->eod.
1880     */
1881    if (csp->content_type & (CT_GZIP|CT_DEFLATE))
1882    {
1883       if (0 == csp->iob->eod - csp->iob->cur)
1884       {
1885          /* Nothing left after de-chunking. */
1886          return JB_ERR_OK;
1887       }
1888
1889       err = decompress_iob(csp);
1890
1891       if (JB_ERR_OK == err)
1892       {
1893          csp->flags |= CSP_FLAG_MODIFIED;
1894          csp->content_type &= ~CT_TABOO;
1895       }
1896       else
1897       {
1898          /*
1899           * Unset CT_GZIP and CT_DEFLATE to remember not
1900           * to modify the Content-Encoding header later.
1901           */
1902          csp->content_type &= ~CT_GZIP;
1903          csp->content_type &= ~CT_DEFLATE;
1904       }
1905    }
1906 #endif
1907
1908    return err;
1909 }
1910
1911
1912 /*********************************************************************
1913  *
1914  * Function    :  execute_content_filters
1915  *
1916  * Description :  Executes a given content filter.
1917  *
1918  * Parameters  :
1919  *          1  :  csp = Current client state (buffers, headers, etc...)
1920  *
1921  * Returns     :  Pointer to the modified buffer, or
1922  *                NULL if filtering failed or wasn't necessary.
1923  *
1924  *********************************************************************/
1925 char *execute_content_filters(struct client_state *csp)
1926 {
1927    filter_function_ptr content_filter;
1928
1929    assert(content_filters_enabled(csp->action));
1930
1931    if (0 == csp->iob->eod - csp->iob->cur)
1932    {
1933       /*
1934        * No content (probably status code 301, 302 ...),
1935        * no filtering necessary.
1936        */
1937       return NULL;
1938    }
1939
1940    if (JB_ERR_OK != prepare_for_filtering(csp))
1941    {
1942       /*
1943        * failed to de-chunk or decompress.
1944        */
1945       return NULL;
1946    }
1947
1948    if (0 == csp->iob->eod - csp->iob->cur)
1949    {
1950       /*
1951        * Clown alarm: chunked and/or compressed nothing delivered.
1952        */
1953       return NULL;
1954    }
1955
1956    content_filter = get_filter_function(csp);
1957
1958    return ((*content_filter)(csp));
1959 }
1960
1961
1962 /*********************************************************************
1963  *
1964  * Function    :  get_url_actions
1965  *
1966  * Description :  Gets the actions for this URL.
1967  *
1968  * Parameters  :
1969  *          1  :  csp = Current client state (buffers, headers, etc...)
1970  *          2  :  http = http_request request for blocked URLs
1971  *
1972  * Returns     :  N/A
1973  *
1974  *********************************************************************/
1975 void get_url_actions(struct client_state *csp, struct http_request *http)
1976 {
1977    struct file_list *fl;
1978    struct url_actions *b;
1979    int i;
1980
1981    init_current_action(csp->action);
1982
1983    for (i = 0; i < MAX_AF_FILES; i++)
1984    {
1985       if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
1986       {
1987          return;
1988       }
1989
1990       apply_url_actions(csp->action, http, b);
1991    }
1992
1993    return;
1994 }
1995
1996
1997 /*********************************************************************
1998  *
1999  * Function    :  apply_url_actions
2000  *
2001  * Description :  Applies a list of URL actions.
2002  *
2003  * Parameters  :
2004  *          1  :  action = Destination.
2005  *          2  :  http = Current URL
2006  *          3  :  b = list of URL actions to apply
2007  *
2008  * Returns     :  N/A
2009  *
2010  *********************************************************************/
2011 void apply_url_actions(struct current_action_spec *action,
2012                        struct http_request *http,
2013                        struct url_actions *b)
2014 {
2015    if (b == NULL)
2016    {
2017       /* Should never happen */
2018       return;
2019    }
2020
2021    for (b = b->next; NULL != b; b = b->next)
2022    {
2023       if (url_match(b->url, http))
2024       {
2025          merge_current_action(action, b->action);
2026       }
2027    }
2028 }
2029
2030
2031 /*********************************************************************
2032  *
2033  * Function    :  get_forward_override_settings
2034  *
2035  * Description :  Returns forward settings as specified with the
2036  *                forward-override{} action. forward-override accepts
2037  *                forward lines similar to the one used in the
2038  *                configuration file, but without the URL pattern.
2039  *
2040  *                For example:
2041  *
2042  *                   forward / .
2043  *
2044  *                in the configuration file can be replaced with
2045  *                the action section:
2046  *
2047  *                 {+forward-override{forward .}}
2048  *                 /
2049  *
2050  * Parameters  :
2051  *          1  :  csp = Current client state (buffers, headers, etc...)
2052  *
2053  * Returns     :  Pointer to forwarding structure in case of success.
2054  *                Invalid syntax is fatal.
2055  *
2056  *********************************************************************/
2057 const static struct forward_spec *get_forward_override_settings(struct client_state *csp)
2058 {
2059    const char *forward_override_line = csp->action->string[ACTION_STRING_FORWARD_OVERRIDE];
2060    char forward_settings[BUFFER_SIZE];
2061    char *http_parent = NULL;
2062    /* variable names were chosen for consistency reasons. */
2063    struct forward_spec *fwd = NULL;
2064    int vec_count;
2065    char *vec[3];
2066
2067    assert(csp->action->flags & ACTION_FORWARD_OVERRIDE);
2068    /* Should be enforced by load_one_actions_file() */
2069    assert(strlen(forward_override_line) < sizeof(forward_settings) - 1);
2070
2071    /* Create a copy ssplit can modify */
2072    strlcpy(forward_settings, forward_override_line, sizeof(forward_settings));
2073
2074    if (NULL != csp->fwd)
2075    {
2076       /*
2077        * XXX: Currently necessary to prevent memory
2078        * leaks when the show-url-info cgi page is visited.
2079        */
2080       unload_forward_spec(csp->fwd);
2081    }
2082
2083    /*
2084     * allocate a new forward node, valid only for
2085     * the lifetime of this request. Save its location
2086     * in csp as well, so sweep() can free it later on.
2087     */
2088    fwd = csp->fwd = zalloc(sizeof(*fwd));
2089    if (NULL == fwd)
2090    {
2091       log_error(LOG_LEVEL_FATAL,
2092          "can't allocate memory for forward-override{%s}", forward_override_line);
2093       /* Never get here - LOG_LEVEL_FATAL causes program exit */
2094       return NULL;
2095    }
2096
2097    vec_count = ssplit(forward_settings, " \t", vec, SZ(vec), 1, 1);
2098    if ((vec_count == 2) && !strcasecmp(vec[0], "forward"))
2099    {
2100       fwd->type = SOCKS_NONE;
2101
2102       /* Parse the parent HTTP proxy host:port */
2103       http_parent = vec[1];
2104
2105    }
2106    else if (vec_count == 3)
2107    {
2108       char *socks_proxy = NULL;
2109
2110       if  (!strcasecmp(vec[0], "forward-socks4"))
2111       {
2112          fwd->type = SOCKS_4;
2113          socks_proxy = vec[1];
2114       }
2115       else if (!strcasecmp(vec[0], "forward-socks4a"))
2116       {
2117          fwd->type = SOCKS_4A;
2118          socks_proxy = vec[1];
2119       }
2120       else if (!strcasecmp(vec[0], "forward-socks5"))
2121       {
2122          fwd->type = SOCKS_5;
2123          socks_proxy = vec[1];
2124       }
2125
2126       if (NULL != socks_proxy)
2127       {
2128          /* Parse the SOCKS proxy host[:port] */
2129          fwd->gateway_port = 1080;
2130          parse_forwarder_address(socks_proxy,
2131             &fwd->gateway_host, &fwd->gateway_port);
2132
2133          http_parent = vec[2];
2134       }
2135    }
2136
2137    if (NULL == http_parent)
2138    {
2139       log_error(LOG_LEVEL_FATAL,
2140          "Invalid forward-override syntax in: %s", forward_override_line);
2141       /* Never get here - LOG_LEVEL_FATAL causes program exit */
2142    }
2143
2144    /* Parse http forwarding settings */
2145    if (strcmp(http_parent, ".") != 0)
2146    {
2147       fwd->forward_port = 8000;
2148       parse_forwarder_address(http_parent,
2149          &fwd->forward_host, &fwd->forward_port);
2150    }
2151
2152    assert (NULL != fwd);
2153
2154    log_error(LOG_LEVEL_CONNECT,
2155       "Overriding forwarding settings based on \'%s\'", forward_override_line);
2156
2157    return fwd;
2158 }
2159
2160
2161 /*********************************************************************
2162  *
2163  * Function    :  forward_url
2164  *
2165  * Description :  Should we forward this to another proxy?
2166  *
2167  * Parameters  :
2168  *          1  :  csp = Current client state (buffers, headers, etc...)
2169  *          2  :  http = http_request request for current URL
2170  *
2171  * Returns     :  Pointer to forwarding information.
2172  *
2173  *********************************************************************/
2174 const struct forward_spec *forward_url(struct client_state *csp,
2175                                        const struct http_request *http)
2176 {
2177    static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER };
2178    struct forward_spec *fwd = csp->config->forward;
2179
2180    if (csp->action->flags & ACTION_FORWARD_OVERRIDE)
2181    {
2182       return get_forward_override_settings(csp);
2183    }
2184
2185    if (fwd == NULL)
2186    {
2187       return fwd_default;
2188    }
2189
2190    while (fwd != NULL)
2191    {
2192       if (url_match(fwd->url, http))
2193       {
2194          return fwd;
2195       }
2196       fwd = fwd->next;
2197    }
2198
2199    return fwd_default;
2200 }
2201
2202
2203 /*********************************************************************
2204  *
2205  * Function    :  direct_response 
2206  *
2207  * Description :  Check if Max-Forwards == 0 for an OPTIONS or TRACE
2208  *                request and if so, return a HTTP 501 to the client.
2209  *
2210  *                FIXME: I have a stupid name and I should handle the
2211  *                requests properly. Still, what we do here is rfc-
2212  *                compliant, whereas ignoring or forwarding are not.
2213  *
2214  * Parameters  :  
2215  *          1  :  csp = Current client state (buffers, headers, etc...)
2216  *
2217  * Returns     :  http_response if , NULL if nonmatch or handler fail
2218  *
2219  *********************************************************************/
2220 struct http_response *direct_response(struct client_state *csp)
2221 {
2222    struct http_response *rsp;
2223    struct list_entry *p;
2224
2225    if ((0 == strcmpic(csp->http->gpc, "trace"))
2226       || (0 == strcmpic(csp->http->gpc, "options")))
2227    {
2228       for (p = csp->headers->first; (p != NULL) ; p = p->next)
2229       {
2230          if (!strncmpic("Max-Forwards:", p->str, 13))
2231          {
2232             unsigned int max_forwards;
2233
2234             /*
2235              * If it's a Max-Forwards value of zero,
2236              * we have to intercept the request.
2237              */
2238             if (1 == sscanf(p->str+12, ": %u", &max_forwards) && max_forwards == 0)
2239             {
2240                /*
2241                 * FIXME: We could handle at least TRACE here,
2242                 * but that would require a verbatim copy of
2243                 * the request which we don't have anymore
2244                 */
2245                 log_error(LOG_LEVEL_HEADER,
2246                   "Detected header \'%s\' in OPTIONS or TRACE request. Returning 501.",
2247                   p->str);
2248
2249                /* Get mem for response or fail*/
2250                if (NULL == (rsp = alloc_http_response()))
2251                {
2252                   return cgi_error_memory();
2253                }
2254             
2255                if (NULL == (rsp->status = strdup("501 Not Implemented")))
2256                {
2257                   free_http_response(rsp);
2258                   return cgi_error_memory();
2259                }
2260
2261                rsp->is_static = 1;
2262                rsp->crunch_reason = UNSUPPORTED;
2263
2264                return(finish_http_response(csp, rsp));
2265             }
2266          }
2267       }
2268    }
2269    return NULL;
2270 }
2271
2272
2273 /*********************************************************************
2274  *
2275  * Function    :  content_requires_filtering
2276  *
2277  * Description :  Checks whether there are any content filters
2278  *                enabled for the current request and if they
2279  *                can actually be applied..
2280  *
2281  * Parameters  :
2282  *          1  :  csp = Current client state (buffers, headers, etc...)
2283  *
2284  * Returns     :  TRUE for yes, FALSE otherwise
2285  *
2286  *********************************************************************/
2287 int content_requires_filtering(struct client_state *csp)
2288 {
2289    if ((csp->content_type & CT_TABOO)
2290       && !(csp->action->flags & ACTION_FORCE_TEXT_MODE))
2291    {
2292       return FALSE;
2293    }
2294
2295    /*
2296     * Are we enabling text mode by force?
2297     */
2298    if (csp->action->flags & ACTION_FORCE_TEXT_MODE)
2299    {
2300       /*
2301        * Do we really have to?
2302        */
2303       if (csp->content_type & CT_TEXT)
2304       {
2305          log_error(LOG_LEVEL_HEADER, "Text mode is already enabled.");
2306       }
2307       else
2308       {
2309          csp->content_type |= CT_TEXT;
2310          log_error(LOG_LEVEL_HEADER, "Text mode enabled by force. Take cover!");
2311       }
2312    }
2313
2314    if (!(csp->content_type & CT_DECLARED))
2315    {
2316       /*
2317        * The server didn't bother to declare a MIME-Type.
2318        * Assume it's text that can be filtered.
2319        *
2320        * This also regulary happens with 304 responses,
2321        * therefore logging anything here would cause
2322        * too much noise.
2323        */
2324       csp->content_type |= CT_TEXT;
2325    }
2326
2327    /*
2328     * Choose the applying filter function based on
2329     * the content type and action settings.
2330     */
2331    if ((csp->content_type & CT_TEXT) &&
2332        (csp->rlist != NULL) &&
2333        (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
2334    {
2335       return TRUE;
2336    }
2337    else if ((csp->content_type & CT_GIF)  &&
2338             (csp->action->flags & ACTION_DEANIMATE))
2339    {
2340       return TRUE;
2341    }
2342
2343    return FALSE;
2344
2345 }
2346
2347
2348 /*********************************************************************
2349  *
2350  * Function    :  content_filters_enabled
2351  *
2352  * Description :  Checks whether there are any content filters
2353  *                enabled for the current request.
2354  *
2355  * Parameters  :  
2356  *          1  :  action = Action spec to check.
2357  *
2358  * Returns     :  TRUE for yes, FALSE otherwise
2359  *
2360  *********************************************************************/
2361 int content_filters_enabled(const struct current_action_spec *action)
2362 {
2363    return ((action->flags & ACTION_DEANIMATE) ||
2364       !list_is_empty(action->multi[ACTION_MULTI_FILTER]));
2365 }
2366
2367
2368 /*********************************************************************
2369  *
2370  * Function    :  filters_available
2371  *
2372  * Description :  Checks whether there are any filters available.
2373  *
2374  * Parameters  :
2375  *          1  :  csp = Current client state (buffers, headers, etc...)
2376  *
2377  * Returns     :  TRUE for yes, FALSE otherwise.
2378  *
2379  *********************************************************************/
2380 int filters_available(const struct client_state *csp)
2381 {
2382    int i;
2383    for (i = 0; i < MAX_AF_FILES; i++)
2384    {
2385       const struct file_list *fl = csp->rlist[i];
2386       if ((NULL != fl) && (NULL != fl->f))
2387       {
2388          return TRUE;
2389       }
2390    }
2391    return FALSE;
2392 }
2393
2394
2395 /*
2396   Local Variables:
2397   tab-width: 3
2398   end:
2399 */