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