cgi_send_user_manual(): Also reject requests if the user-manual
[privoxy.git] / cgisimple.c
1 /*********************************************************************
2  *
3  * File        :  $Source: /cvsroot/ijbswa/current/cgisimple.c,v $
4  *
5  * Purpose     :  Simple CGIs to get information about Privoxy's
6  *                status.
7  *
8  * Copyright   :  Written by and Copyright (C) 2001-2020 the
9  *                Privoxy team. https://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 #if defined (HAVE_ACCESS) && defined (HAVE_UNISTD_H)
46 #include <unistd.h>
47 #endif /* def HAVE_ACCESS && HAVE_UNISTD_H */
48
49 #include "project.h"
50 #include "cgi.h"
51 #include "cgisimple.h"
52 #include "list.h"
53 #include "encode.h"
54 #include "jcc.h"
55 #include "filters.h"
56 #include "actions.h"
57 #include "miscutil.h"
58 #include "loadcfg.h"
59 #include "parsers.h"
60 #include "urlmatch.h"
61 #include "errlog.h"
62 #ifdef FEATURE_CLIENT_TAGS
63 #include "client-tags.h"
64 #endif
65
66 static jb_err show_defines(struct map *exports);
67 static jb_err cgi_show_file(struct client_state *csp,
68                             struct http_response *rsp,
69                             const struct map *parameters);
70 static jb_err load_file(const char *filename, char **buffer, size_t *length);
71
72 /*********************************************************************
73  *
74  * Function    :  cgi_default
75  *
76  * Description :  CGI function that is called for the CGI_SITE_1_HOST
77  *                and CGI_SITE_2_HOST/CGI_SITE_2_PATH base URLs.
78  *                Boring - only exports the default exports.
79  *
80  * Parameters  :
81  *          1  :  csp = Current client state (buffers, headers, etc...)
82  *          2  :  rsp = http_response data structure for output
83  *          3  :  parameters = map of cgi parameters
84  *
85  * CGI Parameters : none
86  *
87  * Returns     :  JB_ERR_OK on success
88  *                JB_ERR_MEMORY on out-of-memory
89  *
90  *********************************************************************/
91 jb_err cgi_default(struct client_state *csp,
92                    struct http_response *rsp,
93                    const struct map *parameters)
94 {
95    struct map *exports;
96
97    (void)parameters;
98
99    assert(csp);
100    assert(rsp);
101
102    if (NULL == (exports = default_exports(csp, "")))
103    {
104       return JB_ERR_MEMORY;
105    }
106
107    return template_fill_for_cgi(csp, "default", exports, rsp);
108 }
109
110
111 /*********************************************************************
112  *
113  * Function    :  cgi_error_404
114  *
115  * Description :  CGI function that is called if an unknown action was
116  *                given.
117  *
118  * Parameters  :
119  *          1  :  csp = Current client state (buffers, headers, etc...)
120  *          2  :  rsp = http_response data structure for output
121  *          3  :  parameters = map of cgi parameters
122  *
123  * CGI Parameters : none
124  *
125  * Returns     :  JB_ERR_OK on success
126  *                JB_ERR_MEMORY on out-of-memory error.
127  *
128  *********************************************************************/
129 jb_err cgi_error_404(struct client_state *csp,
130                      struct http_response *rsp,
131                      const struct map *parameters)
132 {
133    struct map *exports;
134
135    assert(csp);
136    assert(rsp);
137    assert(parameters);
138
139    if (NULL == (exports = default_exports(csp, NULL)))
140    {
141       return JB_ERR_MEMORY;
142    }
143
144    rsp->status = strdup_or_die("404 Privoxy configuration page not found");
145
146    return template_fill_for_cgi(csp, "cgi-error-404", exports, rsp);
147 }
148
149
150 #ifdef FEATURE_GRACEFUL_TERMINATION
151 /*********************************************************************
152  *
153  * Function    :  cgi_die
154  *
155  * Description :  CGI function to shut down Privoxy.
156  *                NOTE: Turning this on in a production build
157  *                would be a BAD idea.  An EXTREMELY BAD idea.
158  *                In short, don't do it.
159  *
160  * Parameters  :
161  *          1  :  csp = Current client state (buffers, headers, etc...)
162  *          2  :  rsp = http_response data structure for output
163  *          3  :  parameters = map of cgi parameters
164  *
165  * CGI Parameters : none
166  *
167  * Returns     :  JB_ERR_OK on success
168  *
169  *********************************************************************/
170 jb_err cgi_die (struct client_state *csp,
171                 struct http_response *rsp,
172                 const struct map *parameters)
173 {
174    static const char status[] = "200 OK Privoxy shutdown request received";
175    static const char body[] =
176       "<html>\n"
177       "<head>\n"
178       " <title>Privoxy shutdown request received</title>\n"
179       " <link rel=\"shortcut icon\" href=\"" CGI_PREFIX "error-favicon.ico\" type=\"image/x-icon\">\n"
180       " <link rel=\"stylesheet\" type=\"text/css\" href=\"" CGI_PREFIX "send-stylesheet\">\n"
181       "</head>\n"
182       "<body>\n"
183       "<h1>Privoxy shutdown request received</h1>\n"
184       "<p>Privoxy is going to shut down after the next request.</p>\n"
185       "</body>\n"
186       "</html>\n";
187
188    assert(csp);
189    assert(rsp);
190    assert(parameters);
191
192    /* quit */
193    g_terminate = 1;
194
195    csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
196
197    rsp->content_length = 0;
198    rsp->head_length = 0;
199    rsp->is_static = 0;
200
201    rsp->body = strdup_or_die(body);
202    rsp->status = strdup_or_die(status);
203
204    return JB_ERR_OK;
205 }
206 #endif /* def FEATURE_GRACEFUL_TERMINATION */
207
208
209 /*********************************************************************
210  *
211  * Function    :  cgi_show_request
212  *
213  * Description :  Show the client's request and what sed() would have
214  *                made of it.
215  *
216  * Parameters  :
217  *          1  :  csp = Current client state (buffers, headers, etc...)
218  *          2  :  rsp = http_response data structure for output
219  *          3  :  parameters = map of cgi parameters
220  *
221  * CGI Parameters : none
222  *
223  * Returns     :  JB_ERR_OK on success
224  *                JB_ERR_MEMORY on out-of-memory error.
225  *
226  *********************************************************************/
227 jb_err cgi_show_request(struct client_state *csp,
228                         struct http_response *rsp,
229                         const struct map *parameters)
230 {
231    char *p;
232    struct map *exports;
233
234    assert(csp);
235    assert(rsp);
236    assert(parameters);
237
238    if (NULL == (exports = default_exports(csp, "show-request")))
239    {
240       return JB_ERR_MEMORY;
241    }
242
243    /*
244     * Repair the damage done to the IOB by get_header()
245     */
246    for (p = csp->client_iob->buf; p < csp->client_iob->cur; p++)
247    {
248       if (*p == '\0') *p = '\n';
249    }
250
251    /*
252     * Export the original client's request and the one we would
253     * be sending to the server if this wasn't a CGI call
254     */
255
256    if (map(exports, "client-request", 1, html_encode(csp->client_iob->buf), 0))
257    {
258       free_map(exports);
259       return JB_ERR_MEMORY;
260    }
261
262    if (map(exports, "processed-request", 1,
263          html_encode_and_free_original(
264 #ifdef FEATURE_HTTPS_INSPECTION
265                                        csp->http->ssl ?
266                                        list_to_text(csp->https_headers) :
267 #endif
268                                        list_to_text(csp->headers)
269                                        ), 0))
270    {
271       free_map(exports);
272       return JB_ERR_MEMORY;
273    }
274
275    return template_fill_for_cgi(csp, "show-request", exports, rsp);
276 }
277
278
279 #ifdef FEATURE_CLIENT_TAGS
280 /*********************************************************************
281  *
282  * Function    :  cgi_create_client_tag_form
283  *
284  * Description :  Creates a HTML form to enable or disable a given
285  *                client tag.
286  *                XXX: Could use a template.
287  *
288  * Parameters  :
289  *          1  :  form = Buffer to fill with the generated form
290  *          2  :  size = Size of the form buffer
291  *          3  :  tag = Name of the tag this form should affect
292  *          4  :  toggle_state = Desired state after the button pressed 0
293  *          5  :  expires = Whether or not the tag should be enabled.
294  *                          Only checked if toggle_state is 1.
295  *
296  * Returns     :  void
297  *
298  *********************************************************************/
299 static void cgi_create_client_tag_form(char *form, size_t size,
300    const char *tag, int toggle_state, int expires)
301 {
302    char *button_name;
303
304    if (toggle_state == 1)
305    {
306       button_name = (expires == 1) ? "Enable" : "Enable temporarily";
307    }
308    else
309    {
310       assert(toggle_state == 0);
311       button_name = "Disable";
312    }
313
314    snprintf(form, size,
315       "<form method=\"GET\" action=\""CGI_PREFIX"toggle-client-tag\" style=\"display: inline\">\n"
316       " <input type=\"hidden\" name=\"tag\" value=\"%s\">\n"
317       " <input type=\"hidden\" name=\"toggle-state\" value=\"%i\">\n"
318       " <input type=\"hidden\" name=\"expires\" value=\"%u\">\n"
319       " <input type=\"submit\" value=\"%s\">\n"
320       "</form>", tag, toggle_state, !expires, button_name);
321 }
322
323 /*********************************************************************
324  *
325  * Function    :  cgi_show_client_tags
326  *
327  * Description :  Shows the tags that can be set based on the client
328  *                address (opt-in).
329  *
330  * Parameters  :
331  *          1  :  csp = Current client state (buffers, headers, etc...)
332  *          2  :  rsp = http_response data structure for output
333  *          3  :  parameters = map of cgi parameters
334  *
335  * CGI Parameters : none
336  *
337  * Returns     :  JB_ERR_OK on success
338  *                JB_ERR_MEMORY on out-of-memory error.
339  *
340  *********************************************************************/
341 jb_err cgi_show_client_tags(struct client_state *csp,
342                         struct http_response *rsp,
343                         const struct map *parameters)
344 {
345    struct map *exports;
346    struct client_tag_spec *this_tag;
347    jb_err err = JB_ERR_OK;
348    char *client_tag_status;
349    char buf[1000];
350    time_t refresh_delay;
351
352    assert(csp);
353    assert(rsp);
354    assert(parameters);
355
356    if (NULL == (exports = default_exports(csp, "client-tags")))
357    {
358       return JB_ERR_MEMORY;
359    }
360    assert(csp->client_address != NULL);
361
362    this_tag = csp->config->client_tags;
363    if (this_tag->name == NULL)
364    {
365       client_tag_status = strdup_or_die("<p>No tags have been configured.</p>\n");
366    }
367    else
368    {
369       client_tag_status = strdup_or_die("<table border=\"1\">\n"
370          "<tr><th>Tag name</th>\n"
371          "<th>Current state</th><th>Change state</th><th>Description</th></tr>\n");
372       while ((this_tag != NULL) && (this_tag->name != NULL))
373       {
374          int tag_state;
375
376          privoxy_mutex_lock(&client_tags_mutex);
377          tag_state = client_has_requested_tag(csp->client_address, this_tag->name);
378          privoxy_mutex_unlock(&client_tags_mutex);
379          if (!err) err = string_append(&client_tag_status, "<tr><td>");
380          if (!err) err = string_append(&client_tag_status, this_tag->name);
381          if (!err) err = string_append(&client_tag_status, "</td><td>");
382          if (!err) err = string_append(&client_tag_status, tag_state == 1 ? "Enabled" : "Disabled");
383          if (!err) err = string_append(&client_tag_status, "</td><td>");
384          cgi_create_client_tag_form(buf, sizeof(buf), this_tag->name, !tag_state, 1);
385          if (!err) err = string_append(&client_tag_status, buf);
386          if (tag_state == 0)
387          {
388             cgi_create_client_tag_form(buf, sizeof(buf), this_tag->name, !tag_state, 0);
389             if (!err) err = string_append(&client_tag_status, buf);
390          }
391          if (!err) err = string_append(&client_tag_status, "</td><td>");
392          if (!err) err = string_append(&client_tag_status, this_tag->description);
393          if (!err) err = string_append(&client_tag_status, "</td></tr>\n");
394          if (err)
395          {
396             break;
397          }
398          this_tag = this_tag->next;
399       }
400       if (!err) err = string_append(&client_tag_status, "</table>\n");
401       if (err)
402       {
403          free_map(exports);
404          return JB_ERR_MEMORY;
405       }
406    }
407    refresh_delay = get_next_tag_timeout_for_client(csp->client_address);
408    if (refresh_delay != 0)
409    {
410       snprintf(buf, sizeof(buf), "%u", csp->config->client_tag_lifetime);
411       if (map(exports, "refresh-delay", 1, buf, 1))
412       {
413          freez(client_tag_status);
414          free_map(exports);
415          return JB_ERR_MEMORY;
416       }
417    }
418    else
419    {
420       err = map_block_killer(exports, "tags-expire");
421       if (err != JB_ERR_OK)
422       {
423          freez(client_tag_status);
424          return err;
425       }
426    }
427
428    if (map(exports, "client-tags", 1, client_tag_status, 0))
429    {
430       free_map(exports);
431       return JB_ERR_MEMORY;
432    }
433
434    if (map(exports, "client-ip-addr", 1, csp->client_address, 1))
435    {
436       free_map(exports);
437       return JB_ERR_MEMORY;
438    }
439
440    return template_fill_for_cgi(csp, "client-tags", exports, rsp);
441 }
442
443
444 /*********************************************************************
445  *
446  * Function    :  cgi_toggle_client_tag
447  *
448  * Description :  Toggles a client tag and redirects to the show-tags
449  *                page
450  *
451  * Parameters  :
452  *          1  :  csp = Current client state (buffers, headers, etc...)
453  *          2  :  rsp = http_response data structure for output
454  *          3  :  parameters = map of cgi parameters
455  *
456  * CGI Parameters : none
457  *          1  :  tag = Name of the tag to enable or disable
458  *          2  :  toggle-state = How to toggle the tag (0/1)
459  *          3  :  expires = Set to 1 if the tag should be enabled
460  *                          temporarily, otherwise set to 0
461  *
462  * Returns     :  JB_ERR_OK on success
463  *                JB_ERR_MEMORY on out-of-memory error.
464  *
465  *********************************************************************/
466 jb_err cgi_toggle_client_tag(struct client_state *csp,
467                              struct http_response *rsp,
468                              const struct map *parameters)
469 {
470    const char *toggled_tag;
471    const char *toggle_state;
472    const char *tag_expires;
473    time_t time_to_live;
474
475    assert(csp);
476    assert(rsp);
477    assert(parameters);
478
479    toggled_tag = lookup(parameters, "tag");
480    if (*toggled_tag == '\0')
481    {
482       log_error(LOG_LEVEL_ERROR, "Received tag toggle request without tag");
483    }
484    else
485    {
486       tag_expires = lookup(parameters, "expires");
487       if (*tag_expires == '0')
488       {
489          time_to_live = 0;
490       }
491       else
492       {
493          time_to_live = csp->config->client_tag_lifetime;
494       }
495       toggle_state = lookup(parameters, "toggle-state");
496       if (*toggle_state == '1')
497       {
498          enable_client_specific_tag(csp, toggled_tag, time_to_live);
499       }
500       else
501       {
502          disable_client_specific_tag(csp, toggled_tag);
503       }
504    }
505    rsp->status = strdup_or_die("302 Done dealing with toggle request");
506    if (enlist_unique_header(rsp->headers,
507          "Location", CGI_PREFIX "client-tags"))
508    {
509          return JB_ERR_MEMORY;
510    }
511    return JB_ERR_OK;
512
513 }
514 #endif /* def FEATURE_CLIENT_TAGS */
515
516
517 /*********************************************************************
518  *
519  * Function    :  cgi_send_banner
520  *
521  * Description :  CGI function that returns a banner.
522  *
523  * Parameters  :
524  *          1  :  csp = Current client state (buffers, headers, etc...)
525  *          2  :  rsp = http_response data structure for output
526  *          3  :  parameters = map of cgi parameters
527  *
528  * CGI Parameters :
529  *           type : Selects the type of banner between "trans", "logo",
530  *                  and "auto". Defaults to "logo" if absent or invalid.
531  *                  "auto" means to select as if we were image-blocking.
532  *                  (Only the first character really counts; b and t are
533  *                  equivalent).
534  *
535  * Returns     :  JB_ERR_OK on success
536  *                JB_ERR_MEMORY on out-of-memory error.
537  *
538  *********************************************************************/
539 jb_err cgi_send_banner(struct client_state *csp,
540                        struct http_response *rsp,
541                        const struct map *parameters)
542 {
543    char imagetype = lookup(parameters, "type")[0];
544
545    /*
546     * If type is auto, then determine the right thing
547     * to do from the set-image-blocker action
548     */
549    if (imagetype == 'a')
550    {
551       /*
552        * Default to pattern
553        */
554       imagetype = 'p';
555
556 #ifdef FEATURE_IMAGE_BLOCKING
557       if ((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
558       {
559          static const char prefix1[] = CGI_PREFIX "send-banner?type=";
560          static const char prefix2[] = "http://" CGI_SITE_1_HOST "/send-banner?type=";
561          const char *p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
562
563          if (p == NULL)
564          {
565             /* Use default - nothing to do here. */
566          }
567          else if (0 == strcmpic(p, "blank"))
568          {
569             imagetype = 'b';
570          }
571          else if (0 == strcmpic(p, "pattern"))
572          {
573             imagetype = 'p';
574          }
575
576          /*
577           * If the action is to call this CGI, determine
578           * the argument:
579           */
580          else if (0 == strncmpic(p, prefix1, sizeof(prefix1) - 1))
581          {
582             imagetype = p[sizeof(prefix1) - 1];
583          }
584          else if (0 == strncmpic(p, prefix2, sizeof(prefix2) - 1))
585          {
586             imagetype = p[sizeof(prefix2) - 1];
587          }
588
589          /*
590           * Everything else must (should) be a URL to
591           * redirect to.
592           */
593          else
594          {
595             imagetype = 'r';
596          }
597       }
598 #endif /* def FEATURE_IMAGE_BLOCKING */
599    }
600
601    /*
602     * Now imagetype is either the non-auto type we were called with,
603     * or it was auto and has since been determined. In any case, we
604     * can proceed to actually answering the request by sending a redirect
605     * or an image as appropriate:
606     */
607    if (imagetype == 'r')
608    {
609       rsp->status = strdup_or_die("302 Local Redirect from Privoxy");
610       if (enlist_unique_header(rsp->headers, "Location",
611                                csp->action->string[ACTION_STRING_IMAGE_BLOCKER]))
612       {
613          return JB_ERR_MEMORY;
614       }
615    }
616    else
617    {
618       if ((imagetype == 'b') || (imagetype == 't'))
619       {
620          rsp->body = bindup(image_blank_data, image_blank_length);
621          rsp->content_length = image_blank_length;
622       }
623       else
624       {
625          rsp->body = bindup(image_pattern_data, image_pattern_length);
626          rsp->content_length = image_pattern_length;
627       }
628
629       if (rsp->body == NULL)
630       {
631          return JB_ERR_MEMORY;
632       }
633       if (enlist(rsp->headers, "Content-Type: " BUILTIN_IMAGE_MIMETYPE))
634       {
635          return JB_ERR_MEMORY;
636       }
637
638       rsp->is_static = 1;
639    }
640
641    return JB_ERR_OK;
642
643 }
644
645
646 /*********************************************************************
647  *
648  * Function    :  cgi_transparent_image
649  *
650  * Description :  CGI function that sends a 1x1 transparent image.
651  *
652  * Parameters  :
653  *          1  :  csp = Current client state (buffers, headers, etc...)
654  *          2  :  rsp = http_response data structure for output
655  *          3  :  parameters = map of cgi parameters
656  *
657  * CGI Parameters : None
658  *
659  * Returns     :  JB_ERR_OK on success
660  *                JB_ERR_MEMORY on out-of-memory error.
661  *
662  *********************************************************************/
663 jb_err cgi_transparent_image(struct client_state *csp,
664                              struct http_response *rsp,
665                              const struct map *parameters)
666 {
667    (void)csp;
668    (void)parameters;
669
670    rsp->body = bindup(image_blank_data, image_blank_length);
671    rsp->content_length = image_blank_length;
672
673    if (rsp->body == NULL)
674    {
675       return JB_ERR_MEMORY;
676    }
677
678    if (enlist(rsp->headers, "Content-Type: " BUILTIN_IMAGE_MIMETYPE))
679    {
680       return JB_ERR_MEMORY;
681    }
682
683    rsp->is_static = 1;
684
685    return JB_ERR_OK;
686
687 }
688
689
690 /*********************************************************************
691  *
692  * Function    :  cgi_send_default_favicon
693  *
694  * Description :  CGI function that sends the standard favicon.
695  *
696  * Parameters  :
697  *          1  :  csp = Current client state (buffers, headers, etc...)
698  *          2  :  rsp = http_response data structure for output
699  *          3  :  parameters = map of cgi parameters
700  *
701  * CGI Parameters : None
702  *
703  * Returns     :  JB_ERR_OK on success
704  *                JB_ERR_MEMORY on out-of-memory error.
705  *
706  *********************************************************************/
707 jb_err cgi_send_default_favicon(struct client_state *csp,
708                                 struct http_response *rsp,
709                                 const struct map *parameters)
710 {
711    static const char default_favicon_data[] =
712       "\000\000\001\000\001\000\020\020\002\000\000\000\000\000\260"
713       "\000\000\000\026\000\000\000\050\000\000\000\020\000\000\000"
714       "\040\000\000\000\001\000\001\000\000\000\000\000\100\000\000"
715       "\000\000\000\000\000\000\000\000\000\002\000\000\000\000\000"
716       "\000\000\377\377\377\000\377\000\052\000\017\360\000\000\077"
717       "\374\000\000\161\376\000\000\161\376\000\000\361\377\000\000"
718       "\361\377\000\000\360\017\000\000\360\007\000\000\361\307\000"
719       "\000\361\307\000\000\361\307\000\000\360\007\000\000\160\036"
720       "\000\000\177\376\000\000\077\374\000\000\017\360\000\000\360"
721       "\017\000\000\300\003\000\000\200\001\000\000\200\001\000\000"
722       "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
723       "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
724       "\000\000\200\001\000\000\200\001\000\000\300\003\000\000\360"
725       "\017\000\000";
726    static const size_t favicon_length = sizeof(default_favicon_data) - 1;
727
728    (void)csp;
729    (void)parameters;
730
731    rsp->body = bindup(default_favicon_data, favicon_length);
732    rsp->content_length = favicon_length;
733
734    if (rsp->body == NULL)
735    {
736       return JB_ERR_MEMORY;
737    }
738
739    if (enlist(rsp->headers, "Content-Type: image/x-icon"))
740    {
741       return JB_ERR_MEMORY;
742    }
743
744    rsp->is_static = 1;
745
746    return JB_ERR_OK;
747
748 }
749
750
751 /*********************************************************************
752  *
753  * Function    :  cgi_send_error_favicon
754  *
755  * Description :  CGI function that sends the favicon for error pages.
756  *
757  * Parameters  :
758  *          1  :  csp = Current client state (buffers, headers, etc...)
759  *          2  :  rsp = http_response data structure for output
760  *          3  :  parameters = map of cgi parameters
761  *
762  * CGI Parameters : None
763  *
764  * Returns     :  JB_ERR_OK on success
765  *                JB_ERR_MEMORY on out-of-memory error.
766  *
767  *********************************************************************/
768 jb_err cgi_send_error_favicon(struct client_state *csp,
769                               struct http_response *rsp,
770                               const struct map *parameters)
771 {
772    static const char error_favicon_data[] =
773       "\000\000\001\000\001\000\020\020\002\000\000\000\000\000\260"
774       "\000\000\000\026\000\000\000\050\000\000\000\020\000\000\000"
775       "\040\000\000\000\001\000\001\000\000\000\000\000\100\000\000"
776       "\000\000\000\000\000\000\000\000\000\002\000\000\000\000\000"
777       "\000\000\377\377\377\000\000\000\377\000\017\360\000\000\077"
778       "\374\000\000\161\376\000\000\161\376\000\000\361\377\000\000"
779       "\361\377\000\000\360\017\000\000\360\007\000\000\361\307\000"
780       "\000\361\307\000\000\361\307\000\000\360\007\000\000\160\036"
781       "\000\000\177\376\000\000\077\374\000\000\017\360\000\000\360"
782       "\017\000\000\300\003\000\000\200\001\000\000\200\001\000\000"
783       "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
784       "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
785       "\000\000\200\001\000\000\200\001\000\000\300\003\000\000\360"
786       "\017\000\000";
787    static const size_t favicon_length = sizeof(error_favicon_data) - 1;
788
789    (void)csp;
790    (void)parameters;
791
792    rsp->body = bindup(error_favicon_data, favicon_length);
793    rsp->content_length = favicon_length;
794
795    if (rsp->body == NULL)
796    {
797       return JB_ERR_MEMORY;
798    }
799
800    if (enlist(rsp->headers, "Content-Type: image/x-icon"))
801    {
802       return JB_ERR_MEMORY;
803    }
804
805    rsp->is_static = 1;
806
807    return JB_ERR_OK;
808
809 }
810
811
812 /*********************************************************************
813  *
814  * Function    :  cgi_send_stylesheet
815  *
816  * Description :  CGI function that sends a css stylesheet found
817  *                in the cgi-style.css template
818  *
819  * Parameters  :
820  *          1  :  csp = Current client state (buffers, headers, etc...)
821  *          2  :  rsp = http_response data structure for output
822  *          3  :  parameters = map of cgi parameters
823  *
824  * CGI Parameters : None
825  *
826  * Returns     :  JB_ERR_OK on success
827  *                JB_ERR_MEMORY on out-of-memory error.
828  *
829  *********************************************************************/
830 jb_err cgi_send_stylesheet(struct client_state *csp,
831                            struct http_response *rsp,
832                            const struct map *parameters)
833 {
834    jb_err err;
835
836    assert(csp);
837    assert(rsp);
838
839    (void)parameters;
840
841    err = template_load(csp, &rsp->body, "cgi-style.css", 0);
842
843    if (err == JB_ERR_FILE)
844    {
845       /*
846        * No way to tell user; send empty stylesheet
847        */
848       log_error(LOG_LEVEL_ERROR, "Could not find cgi-style.css template");
849    }
850    else if (err)
851    {
852       return err; /* JB_ERR_MEMORY */
853    }
854
855    if (enlist(rsp->headers, "Content-Type: text/css"))
856    {
857       return JB_ERR_MEMORY;
858    }
859
860    return JB_ERR_OK;
861
862 }
863
864
865 /*********************************************************************
866  *
867  * Function    :  cgi_send_url_info_osd
868  *
869  * Description :  CGI function that sends the OpenSearch Description
870  *                template for the show-url-info page. It allows to
871  *                access the page through "search engine plugins".
872  *
873  * Parameters  :
874  *          1  :  csp = Current client state (buffers, headers, etc...)
875  *          2  :  rsp = http_response data structure for output
876  *          3  :  parameters = map of cgi parameters
877  *
878  * CGI Parameters : None
879  *
880  * Returns     :  JB_ERR_OK on success
881  *                JB_ERR_MEMORY on out-of-memory error.
882  *
883  *********************************************************************/
884 jb_err cgi_send_url_info_osd(struct client_state *csp,
885                                struct http_response *rsp,
886                                const struct map *parameters)
887 {
888    jb_err err = JB_ERR_MEMORY;
889    struct map *exports = default_exports(csp, NULL);
890
891    (void)csp;
892    (void)parameters;
893
894    if (NULL != exports)
895    {
896       err = template_fill_for_cgi(csp, "url-info-osd.xml", exports, rsp);
897       if (JB_ERR_OK == err)
898       {
899          err = enlist(rsp->headers,
900             "Content-Type: application/opensearchdescription+xml");
901       }
902    }
903
904    return err;
905
906 }
907
908
909 /*********************************************************************
910  *
911  * Function    :  get_content_type
912  *
913  * Description :  Use the file extension to guess the content type
914  *                header we should use to serve the file.
915  *
916  * Parameters  :
917  *          1  :  filename = Name of the file whose content type
918  *                           we care about
919  *
920  * Returns     :  The guessed content type.
921  *
922  *********************************************************************/
923 static const char *get_content_type(const char *filename)
924 {
925    int i;
926    struct content_type
927    {
928       const char extension[6];
929       const char content_type[11];
930    };
931    static const struct content_type content_types[] =
932    {
933       {".css",  "text/css"},
934       {".jpg",  "image/jpeg"},
935       {".jpeg", "image/jpeg"},
936       {".png",  "image/png"},
937    };
938
939    for (i = 0; i < SZ(content_types); i++)
940    {
941       if (strstr(filename, content_types[i].extension))
942       {
943          return content_types[i].content_type;
944       }
945    }
946
947    /* No match by extension, default to html */
948    return "text/html";
949 }
950
951 /*********************************************************************
952  *
953  * Function    :  cgi_send_user_manual
954  *
955  * Description :  CGI function that sends a file in the user
956  *                manual directory.
957  *
958  * Parameters  :
959  *          1  :  csp = Current client state (buffers, headers, etc...)
960  *          2  :  rsp = http_response data structure for output
961  *          3  :  parameters = map of cgi parameters
962  *
963  * CGI Parameters : file=name.html, the name of the HTML file
964  *                  (relative to user-manual from config)
965  *
966  * Returns     :  JB_ERR_OK on success
967  *                JB_ERR_MEMORY on out-of-memory error.
968  *
969  *********************************************************************/
970 jb_err cgi_send_user_manual(struct client_state *csp,
971                             struct http_response *rsp,
972                             const struct map *parameters)
973 {
974    const char *filename;
975    char *full_path;
976    jb_err err = JB_ERR_OK;
977    const char *content_type;
978
979    assert(csp);
980    assert(rsp);
981    assert(parameters);
982
983    if (0 == strncmpic(csp->config->usermanual, "http://", 7) ||
984        0 == strncmpic(csp->config->usermanual, "https://", 8))
985    {
986       log_error(LOG_LEVEL_CGI, "Request for local user-manual "
987          "received while user-manual delivery is disabled.");
988       return cgi_error_404(csp, rsp, parameters);
989    }
990
991    if (!parameters->first)
992    {
993       /* requested http://p.p/user-manual (without trailing slash) */
994       return cgi_redirect(rsp, CGI_PREFIX "user-manual/");
995    }
996
997    get_string_param(parameters, "file", &filename);
998    if (filename == NULL)
999    {
1000       /* It's '/' so serve the index.html if there is one.  */
1001       filename = "index.html";
1002    }
1003    else if (NULL != strchr(filename, '/') || NULL != strstr(filename, ".."))
1004    {
1005       /*
1006        * We currently only support a flat file
1007        * hierarchy for the documentation.
1008        */
1009       log_error(LOG_LEVEL_ERROR,
1010          "Rejecting the request to serve '%s' as it contains '/' or '..'",
1011          filename);
1012       return JB_ERR_CGI_PARAMS;
1013    }
1014
1015    full_path = make_path(csp->config->usermanual, filename);
1016    if (full_path == NULL)
1017    {
1018       return JB_ERR_MEMORY;
1019    }
1020
1021    err = load_file(full_path, &rsp->body, &rsp->content_length);
1022    if (JB_ERR_OK != err)
1023    {
1024       assert((JB_ERR_FILE == err) || (JB_ERR_MEMORY == err));
1025       if (JB_ERR_FILE == err)
1026       {
1027          err = cgi_error_no_template(csp, rsp, full_path);
1028       }
1029       freez(full_path);
1030       return err;
1031    }
1032    freez(full_path);
1033
1034    content_type = get_content_type(filename);
1035    log_error(LOG_LEVEL_CGI,
1036       "Content-Type guessed for %s: %s", filename, content_type);
1037
1038    return enlist_unique_header(rsp->headers, "Content-Type", content_type);
1039
1040 }
1041
1042
1043 #ifdef FEATURE_EXTENDED_STATISTICS
1044 /*********************************************************************
1045  *
1046  * Function    :  get_block_reason_statistics_table
1047  *
1048  * Description :  Produces the block reason statistic table content.
1049  *
1050  * Parameters  :
1051  *          1  :  csp = Current client state (buffers, headers, etc...)
1052  *
1053  * Returns     :  Pointer to the HTML statistic table content or
1054  *                NULL on out of memory
1055  *
1056  *********************************************************************/
1057 static char *get_block_reason_statistics_table(const struct client_state *csp)
1058 {
1059    char buf[BUFFER_SIZE];
1060    char *statistics;
1061    int i;
1062    struct file_list *fl;
1063    jb_err err = JB_ERR_OK;
1064
1065    statistics = strdup_or_die("");
1066
1067    /* Run through all action files. */
1068    for (i = 0; i < MAX_AF_FILES; i++)
1069    {
1070       struct url_actions *b;
1071       struct action_spec *last_action = NULL;
1072
1073       if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
1074       {
1075          /* Skip empty files */
1076          continue;
1077       }
1078
1079       /* Go through all the actions. */
1080       for (b = b->next; NULL != b; b = b->next)
1081       {
1082          if (last_action == b->action)
1083          {
1084             continue;
1085          }
1086          if ((b->action->add & ACTION_BLOCK))
1087          {
1088             unsigned long long count;
1089             const char *block_reason = b->action->string[ACTION_STRING_BLOCK];
1090             const char *encoded_block_reason = html_encode(block_reason);
1091
1092             if (encoded_block_reason == NULL)
1093             {
1094                freez(statistics);
1095                return NULL;
1096             }
1097             get_block_reason_count(block_reason, &count);
1098             snprintf(buf, sizeof(buf),
1099                "<tr><td>%s</td><td style=\"text-align: right\">%llu</td>\n",
1100                encoded_block_reason, count);
1101             freez(encoded_block_reason);
1102
1103             if (!err) err = string_append(&statistics, buf);
1104          }
1105          last_action = b->action;
1106       }
1107    }
1108
1109    return statistics;
1110
1111 }
1112
1113
1114 /*********************************************************************
1115  *
1116  * Function    :  get_filter_statistics_table
1117  *
1118  * Description :  Produces the filter statistic table content.
1119  *
1120  * Parameters  :
1121  *          1  :  csp = Current client state (buffers, headers, etc...)
1122  *
1123  * Returns     :  Pointer to the HTML statistic table content or
1124  *                NULL on out of memory
1125  *
1126  *********************************************************************/
1127 static char *get_filter_statistics_table(const struct client_state *csp)
1128 {
1129    char buf[BUFFER_SIZE];
1130    char *statistics;
1131    int i;
1132    struct file_list *fl;
1133    struct re_filterfile_spec *b;
1134    jb_err err = JB_ERR_OK;
1135
1136    statistics = strdup_or_die("");
1137
1138    for (i = 0; i < MAX_AF_FILES; i++)
1139    {
1140      fl = csp->rlist[i];
1141      if ((NULL == fl) || (NULL == fl->f))
1142      {
1143         /*
1144          * Either there are no filter files left or this
1145          * filter file just contains no valid filters.
1146          *
1147          * Continue to be sure we don't miss valid filter
1148          * files that are chained after empty or invalid ones.
1149          */
1150         continue;
1151      }
1152
1153      for (b = fl->f; b != NULL; b = b->next)
1154      {
1155         if (b->type == FT_CONTENT_FILTER)
1156         {
1157            unsigned long long executions;
1158            unsigned long long response_bodies_modified;
1159            unsigned long long hits;
1160
1161            get_filter_statistics(b->name, &executions, &response_bodies_modified, &hits);
1162            snprintf(buf, sizeof(buf),
1163               "<tr><td>%s</td><td style=\"text-align: right\">%llu</td>"
1164               "<td style=\"text-align: right\">%llu</td>"
1165               "<td style=\"text-align: right\">%llu</td><tr>\n",
1166               b->name, executions, response_bodies_modified, hits);
1167
1168            if (!err) err = string_append(&statistics, buf);
1169         }
1170      }
1171    }
1172
1173    return statistics;
1174
1175 }
1176 #endif /* def FEATURE_EXTENDED_STATISTICS */
1177
1178
1179 /*********************************************************************
1180  *
1181  * Function    :  cgi_show_status
1182  *
1183  * Description :  CGI function that returns a web page describing the
1184  *                current status of Privoxy.
1185  *
1186  * Parameters  :
1187  *          1  :  csp = Current client state (buffers, headers, etc...)
1188  *          2  :  rsp = http_response data structure for output
1189  *          3  :  parameters = map of cgi parameters
1190  *
1191  * CGI Parameters :
1192  *        file :  Which file to show.  Only first letter is checked,
1193  *                valid values are:
1194  *                - "a"ction file
1195  *                - "r"egex
1196  *                - "t"rust
1197  *                Default is to show menu and other information.
1198  *
1199  * Returns     :  JB_ERR_OK on success
1200  *                JB_ERR_MEMORY on out-of-memory error.
1201  *
1202  *********************************************************************/
1203 jb_err cgi_show_status(struct client_state *csp,
1204                        struct http_response *rsp,
1205                        const struct map *parameters)
1206 {
1207    char *s = NULL;
1208    unsigned i;
1209    int j;
1210
1211    char buf[BUFFER_SIZE];
1212 #ifdef FEATURE_STATISTICS
1213    float perc_rej;   /* Percentage of http requests rejected */
1214    int local_urls_read;
1215    int local_urls_rejected;
1216 #endif /* ndef FEATURE_STATISTICS */
1217    jb_err err = JB_ERR_OK;
1218
1219    struct map *exports;
1220
1221    assert(csp);
1222    assert(rsp);
1223    assert(parameters);
1224
1225    if ('\0' != *(lookup(parameters, "file")))
1226    {
1227       return cgi_show_file(csp, rsp, parameters);
1228    }
1229
1230    if (NULL == (exports = default_exports(csp, "show-status")))
1231    {
1232       return JB_ERR_MEMORY;
1233    }
1234
1235    s = strdup("");
1236    for (j = 0; (s != NULL) && (j < Argc); j++)
1237    {
1238       if (!err) err = string_join  (&s, html_encode(Argv[j]));
1239       if (!err) err = string_append(&s, " ");
1240    }
1241    if (!err) err = map(exports, "invocation", 1, s, 0);
1242
1243    if (!err) err = map(exports, "options", 1, csp->config->proxy_args, 1);
1244    if (!err) err = show_defines(exports);
1245
1246    if (err)
1247    {
1248       free_map(exports);
1249       return JB_ERR_MEMORY;
1250    }
1251
1252 #ifdef FEATURE_STATISTICS
1253    local_urls_read     = urls_read;
1254    local_urls_rejected = urls_rejected;
1255
1256    /*
1257     * Need to alter the stats not to include the fetch of this
1258     * page.
1259     *
1260     * Can't do following thread safely! doh!
1261     *
1262     * urls_read--;
1263     * urls_rejected--; * This will be incremented subsequently *
1264     */
1265
1266    if (local_urls_read == 0)
1267    {
1268       if (!err) err = map_block_killer(exports, "have-stats");
1269    }
1270    else
1271    {
1272       if (!err) err = map_block_killer(exports, "have-no-stats");
1273
1274       perc_rej = (float)local_urls_rejected * 100.0F /
1275             (float)local_urls_read;
1276
1277       snprintf(buf, sizeof(buf), "%d", local_urls_read);
1278       if (!err) err = map(exports, "requests-received", 1, buf, 1);
1279
1280       snprintf(buf, sizeof(buf), "%d", local_urls_rejected);
1281       if (!err) err = map(exports, "requests-blocked", 1, buf, 1);
1282
1283       snprintf(buf, sizeof(buf), "%6.2f", perc_rej);
1284       if (!err) err = map(exports, "percent-blocked", 1, buf, 1);
1285    }
1286
1287 #else /* ndef FEATURE_STATISTICS */
1288    if (!err) err = map_block_killer(exports, "statistics");
1289 #endif /* ndef FEATURE_STATISTICS */
1290
1291 #ifdef FEATURE_EXTENDED_STATISTICS
1292    if (!err)
1293    {
1294       char *block_reason_statistics = get_block_reason_statistics_table(csp);
1295       if (block_reason_statistics != NULL)
1296       {
1297          err = map(exports, "block-reason-statistics", 1, block_reason_statistics, 0);
1298       }
1299       else
1300       {
1301          err = map_block_killer(exports, "extended-statistics");
1302       }
1303    }
1304    if (!err)
1305    {
1306       char *filter_statistics = get_filter_statistics_table(csp);
1307       if (filter_statistics != NULL)
1308       {
1309          err = map(exports, "filter-statistics", 1, filter_statistics, 0);
1310       }
1311       else
1312       {
1313          err = map_block_killer(exports, "extended-statistics");
1314       }
1315    }
1316 #else /* ndef FEATURE_EXTENDED_STATISTICS */
1317    if (!err) err = map_block_killer(exports, "extended-statistics");
1318 #endif /* def FEATURE_EXTENDED_STATISTICS */
1319
1320    /*
1321     * List all action files in use, together with view and edit links,
1322     * except for standard.action, which should only be viewable. (Not
1323     * enforced in the editor itself)
1324     * FIXME: Shouldn't include hardwired HTML here, use line template instead!
1325     */
1326    s = strdup("");
1327    for (i = 0; i < MAX_AF_FILES; i++)
1328    {
1329       if (csp->actions_list[i] != NULL)
1330       {
1331          if (!err) err = string_append(&s, "<tr><td>");
1332          if (!err) err = string_join(&s, html_encode(csp->actions_list[i]->filename));
1333          snprintf(buf, sizeof(buf),
1334             "</td><td class=\"buttons\"><a href=\"/show-status?file=actions&amp;index=%u\">View</a>", i);
1335          if (!err) err = string_append(&s, buf);
1336
1337 #ifdef FEATURE_CGI_EDIT_ACTIONS
1338          if ((csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)
1339             && (NULL != csp->config->actions_file_short[i]))
1340          {
1341 #ifdef HAVE_ACCESS
1342             if (access(csp->config->actions_file[i], W_OK) == 0)
1343             {
1344 #endif /* def HAVE_ACCESS */
1345                snprintf(buf, sizeof(buf), "&nbsp;&nbsp;<a href=\"/edit-actions-list?f=%u\">Edit</a>", i);
1346                if (!err) err = string_append(&s, buf);
1347 #ifdef HAVE_ACCESS
1348             }
1349             else
1350             {
1351                if (!err) err = string_append(&s, "&nbsp;&nbsp;<strong>No write access.</strong>");
1352             }
1353 #endif /* def HAVE_ACCESS */
1354          }
1355 #endif
1356
1357          if (!err) err = string_append(&s, "</td></tr>\n");
1358       }
1359    }
1360    if (!err && *s != '\0')
1361    {
1362       err = map(exports, "actions-filenames", 1, s, 0);
1363    }
1364    else
1365    {
1366       if (!err) err = map(exports, "actions-filenames", 1, "<tr><td>None specified</td></tr>", 1);
1367       freez(s);
1368    }
1369
1370    /*
1371     * List all re_filterfiles in use, together with view options.
1372     * FIXME: Shouldn't include hardwired HTML here, use line template instead!
1373     */
1374    s = strdup("");
1375    for (i = 0; i < MAX_AF_FILES; i++)
1376    {
1377       if (csp->rlist[i] != NULL)
1378       {
1379          if (!err) err = string_append(&s, "<tr><td>");
1380          if (!err) err = string_join(&s, html_encode(csp->rlist[i]->filename));
1381          snprintf(buf, sizeof(buf),
1382             "</td><td class=\"buttons\"><a href=\"/show-status?file=filter&amp;index=%u\">View</a>", i);
1383          if (!err) err = string_append(&s, buf);
1384          if (!err) err = string_append(&s, "</td></tr>\n");
1385       }
1386    }
1387    if (!err && *s != '\0')
1388    {
1389       err = map(exports, "re-filter-filenames", 1, s, 0);
1390    }
1391    else
1392    {
1393       if (!err) err = map(exports, "re-filter-filenames", 1, "<tr><td>None specified</td></tr>", 1);
1394       if (!err) err = map_block_killer(exports, "have-filterfile");
1395       freez(s);
1396    }
1397
1398 #ifdef FEATURE_TRUST
1399    if (csp->tlist)
1400    {
1401       if (!err) err = map(exports, "trust-filename", 1, html_encode(csp->tlist->filename), 0);
1402    }
1403    else
1404    {
1405       if (!err) err = map(exports, "trust-filename", 1, "None specified", 1);
1406       if (!err) err = map_block_killer(exports, "have-trustfile");
1407    }
1408 #else
1409    if (!err) err = map_block_killer(exports, "trust-support");
1410 #endif /* ndef FEATURE_TRUST */
1411
1412 #ifdef FEATURE_CGI_EDIT_ACTIONS
1413    if (!err && (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
1414    {
1415       err = map_block_killer(exports, "cgi-editor-is-disabled");
1416    }
1417 #endif /* ndef CGI_EDIT_ACTIONS */
1418
1419    if (!err) err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
1420
1421    if (err)
1422    {
1423       free_map(exports);
1424       return JB_ERR_MEMORY;
1425    }
1426
1427    return template_fill_for_cgi(csp, "show-status", exports, rsp);
1428 }
1429
1430
1431 /*********************************************************************
1432  *
1433  * Function    :  cgi_show_url_info
1434  *
1435  * Description :  CGI function that determines and shows which actions
1436  *                Privoxy will perform for a given url, and which
1437  *                matches starting from the defaults have lead to that.
1438  *
1439  * Parameters  :
1440  *          1  :  csp = Current client state (buffers, headers, etc...)
1441  *          2  :  rsp = http_response data structure for output
1442  *          3  :  parameters = map of cgi parameters
1443  *
1444  * CGI Parameters :
1445  *            url : The url whose actions are to be determined.
1446  *                  If url is unset, the url-given conditional will be
1447  *                  set, so that all but the form can be suppressed in
1448  *                  the template.
1449  *
1450  * Returns     :  JB_ERR_OK on success
1451  *                JB_ERR_MEMORY on out-of-memory error.
1452  *
1453  *********************************************************************/
1454 jb_err cgi_show_url_info(struct client_state *csp,
1455                          struct http_response *rsp,
1456                          const struct map *parameters)
1457 {
1458    char *url_param;
1459    struct map *exports;
1460    char buf[150];
1461
1462    assert(csp);
1463    assert(rsp);
1464    assert(parameters);
1465
1466    if (NULL == (exports = default_exports(csp, "show-url-info")))
1467    {
1468       return JB_ERR_MEMORY;
1469    }
1470
1471    /*
1472     * Get the url= parameter (if present) and remove any leading/trailing spaces.
1473     */
1474    url_param = strdup_or_die(lookup(parameters, "url"));
1475    chomp(url_param);
1476
1477    /*
1478     * Handle prefixes.  4 possibilities:
1479     * 1) "http://" or "https://" prefix present and followed by URL - OK
1480     * 2) Only the "http://" or "https://" part is present, no URL - change
1481     *    to empty string so it will be detected later as "no URL".
1482     * 3) Parameter specified but doesn't start with "http(s?)://" - add a
1483     *    "http://" prefix.
1484     * 4) Parameter not specified or is empty string - let this fall through
1485     *    for now, next block of code will handle it.
1486     */
1487    if (0 == strncmp(url_param, "http://", 7))
1488    {
1489       if (url_param[7] == '\0')
1490       {
1491          /*
1492           * Empty URL (just prefix).
1493           * Make it totally empty so it's caught by the next if ()
1494           */
1495          url_param[0] = '\0';
1496       }
1497    }
1498    else if (0 == strncmp(url_param, "https://", 8))
1499    {
1500       if (url_param[8] == '\0')
1501       {
1502          /*
1503           * Empty URL (just prefix).
1504           * Make it totally empty so it's caught by the next if ()
1505           */
1506          url_param[0] = '\0';
1507       }
1508    }
1509    else if ((url_param[0] != '\0')
1510       && ((NULL == strstr(url_param, "://")
1511             || (strstr(url_param, "://") > strstr(url_param, "/")))))
1512    {
1513       /*
1514        * No prefix or at least no prefix before
1515        * the first slash - assume http://
1516        */
1517       char *url_param_prefixed = strdup_or_die("http://");
1518
1519       if (JB_ERR_OK != string_join(&url_param_prefixed, url_param))
1520       {
1521          free_map(exports);
1522          return JB_ERR_MEMORY;
1523       }
1524       url_param = url_param_prefixed;
1525    }
1526
1527    /*
1528     * Hide "toggle off" warning if Privoxy is toggled on.
1529     */
1530    if (
1531 #ifdef FEATURE_TOGGLE
1532        (global_toggle_state == 1) &&
1533 #endif /* def FEATURE_TOGGLE */
1534        map_block_killer(exports, "privoxy-is-toggled-off")
1535       )
1536    {
1537       freez(url_param);
1538       free_map(exports);
1539       return JB_ERR_MEMORY;
1540    }
1541
1542    if (url_param[0] == '\0')
1543    {
1544       /* URL parameter not specified, display query form only. */
1545       free(url_param);
1546       if (map_block_killer(exports, "url-given")
1547         || map(exports, "url", 1, "", 1))
1548       {
1549          free_map(exports);
1550          return JB_ERR_MEMORY;
1551       }
1552    }
1553    else
1554    {
1555       /* Given a URL, so query it. */
1556       jb_err err;
1557       char *matches;
1558       char *s;
1559       int hits = 0;
1560       struct file_list *fl;
1561       struct url_actions *b;
1562       struct http_request url_to_query[1];
1563       struct current_action_spec action[1];
1564       int i;
1565
1566       if (map(exports, "url", 1, html_encode(url_param), 0))
1567       {
1568          free(url_param);
1569          free_map(exports);
1570          return JB_ERR_MEMORY;
1571       }
1572
1573       init_current_action(action);
1574
1575       if (map(exports, "default", 1, current_action_to_html(csp, action), 0))
1576       {
1577          free_current_action(action);
1578          free(url_param);
1579          free_map(exports);
1580          return JB_ERR_MEMORY;
1581       }
1582
1583       memset(url_to_query, '\0', sizeof(url_to_query));
1584       err = parse_http_url(url_param, url_to_query, REQUIRE_PROTOCOL);
1585       assert((err != JB_ERR_OK) || (url_to_query->ssl == !strncmpic(url_param, "https://", 8)));
1586
1587       free(url_param);
1588
1589       if (err == JB_ERR_MEMORY)
1590       {
1591          free_http_request(url_to_query);
1592          free_current_action(action);
1593          free_map(exports);
1594          return JB_ERR_MEMORY;
1595       }
1596       else if (err)
1597       {
1598          /* Invalid URL */
1599
1600          err = map(exports, "matches", 1, "<b>[Invalid URL specified!]</b>" , 1);
1601          if (!err) err = map(exports, "final", 1, lookup(exports, "default"), 1);
1602          if (!err) err = map_block_killer(exports, "valid-url");
1603
1604          free_current_action(action);
1605          free_http_request(url_to_query);
1606
1607          if (err)
1608          {
1609             free_map(exports);
1610             return JB_ERR_MEMORY;
1611          }
1612
1613          return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
1614       }
1615
1616       /*
1617        * We have a warning about SSL paths. Hide it for unencrypted sites
1618        * and unconditionally if https inspection is enabled.
1619        */
1620 #ifndef FEATURE_HTTPS_INSPECTION
1621       if (!url_to_query->ssl)
1622 #endif
1623       {
1624          if (map_block_killer(exports, "https-and-no-https-inspection"))
1625          {
1626             free_current_action(action);
1627             free_map(exports);
1628             free_http_request(url_to_query);
1629             return JB_ERR_MEMORY;
1630          }
1631       }
1632
1633       matches = strdup_or_die("<table summary=\"\" class=\"transparent\">");
1634
1635       for (i = 0; i < MAX_AF_FILES; i++)
1636       {
1637          if (NULL == csp->config->actions_file_short[i]
1638              || !strcmp(csp->config->actions_file_short[i], "standard.action")) continue;
1639
1640          b = NULL;
1641          hits = 1;
1642          if ((fl = csp->actions_list[i]) != NULL)
1643          {
1644             if ((b = fl->f) != NULL)
1645             {
1646                /* FIXME: Hardcoded HTML! */
1647                string_append(&matches, "<tr><th>In file: ");
1648                string_join  (&matches, html_encode(csp->config->actions_file_short[i]));
1649                snprintf(buf, sizeof(buf), " <a class=\"cmd\" href=\"/show-status?file=actions&amp;index=%d\">", i);
1650                string_append(&matches, buf);
1651                string_append(&matches, "View</a>");
1652 #ifdef FEATURE_CGI_EDIT_ACTIONS
1653                if (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)
1654                {
1655 #ifdef HAVE_ACCESS
1656                   if (access(csp->config->actions_file[i], W_OK) == 0)
1657                   {
1658 #endif /* def HAVE_ACCESS */
1659                      snprintf(buf, sizeof(buf),
1660                         " <a class=\"cmd\" href=\"/edit-actions-list?f=%d\">", i);
1661                      string_append(&matches, buf);
1662                      string_append(&matches, "Edit</a>");
1663 #ifdef HAVE_ACCESS
1664                   }
1665                   else
1666                   {
1667                      string_append(&matches, " <strong>No write access.</strong>");
1668                   }
1669 #endif /* def HAVE_ACCESS */
1670                }
1671 #endif /* FEATURE_CGI_EDIT_ACTIONS */
1672
1673                string_append(&matches, "</th></tr>\n");
1674
1675                hits = 0;
1676                b = b->next;
1677             }
1678          }
1679
1680          for (; (b != NULL) && (matches != NULL); b = b->next)
1681          {
1682             if (url_match(b->url, url_to_query))
1683             {
1684                string_append(&matches, "<tr><td>{");
1685                string_join  (&matches, actions_to_html(csp, b->action));
1686                string_append(&matches, " }<br>\n<code>");
1687                string_join  (&matches, html_encode(b->url->spec));
1688                string_append(&matches, "</code></td></tr>\n");
1689
1690                if (merge_current_action(action, b->action))
1691                {
1692                   freez(matches);
1693                   free_http_request(url_to_query);
1694                   free_current_action(action);
1695                   free_map(exports);
1696                   return JB_ERR_MEMORY;
1697                }
1698                hits++;
1699             }
1700          }
1701
1702          if (!hits)
1703          {
1704             string_append(&matches, "<tr><td>(no matches in this file)</td></tr>\n");
1705          }
1706       }
1707       string_append(&matches, "</table>\n");
1708
1709       /*
1710        * XXX: Kludge to make sure the "Forward settings" section
1711        * shows what forward-override{} would do with the requested URL.
1712        * No one really cares how the CGI request would be forwarded
1713        * if it wasn't intercepted as CGI request in the first place.
1714        *
1715        * From here on the action bitmask will no longer reflect
1716        * the real url (http://config.privoxy.org/show-url-info?url=.*),
1717        * but luckily it's no longer required later on anyway.
1718        */
1719       free_current_action(csp->action);
1720       get_url_actions(csp, url_to_query);
1721
1722       /*
1723        * Fill in forwarding settings.
1724        *
1725        * The possibilities are:
1726        *  - no forwarding
1727        *  - http forwarding only
1728        *  - socks4(a) forwarding only
1729        *  - socks4(a) and http forwarding.
1730        *
1731        * XXX: Parts of this code could be reused for the
1732        * "forwarding-failed" template which currently doesn't
1733        * display the proxy port and an eventual second forwarder.
1734        */
1735       {
1736          const struct forward_spec *fwd = forward_url(csp, url_to_query);
1737
1738          if ((fwd->gateway_host == NULL) && (fwd->forward_host == NULL))
1739          {
1740             if (!err) err = map_block_killer(exports, "socks-forwarder");
1741             if (!err) err = map_block_killer(exports, "http-forwarder");
1742          }
1743          else
1744          {
1745             char port[10]; /* We save proxy ports as int but need a string here */
1746
1747             if (!err) err = map_block_killer(exports, "no-forwarder");
1748
1749             if (fwd->gateway_host != NULL)
1750             {
1751                char *socks_type = NULL;
1752
1753                switch (fwd->type)
1754                {
1755                   case SOCKS_4:
1756                      socks_type = "socks4";
1757                      break;
1758                   case SOCKS_4A:
1759                      socks_type = "socks4a";
1760                      break;
1761                   case SOCKS_5:
1762                      socks_type = "socks5";
1763                      break;
1764                   case SOCKS_5T:
1765                      socks_type = "socks5t";
1766                      break;
1767                   default:
1768                      log_error(LOG_LEVEL_FATAL, "Unknown socks type: %d.", fwd->type);
1769                }
1770
1771                if (!err) err = map(exports, "socks-type", 1, socks_type, 1);
1772                if (!err) err = map(exports, "gateway-host", 1, fwd->gateway_host, 1);
1773                snprintf(port, sizeof(port), "%d", fwd->gateway_port);
1774                if (!err) err = map(exports, "gateway-port", 1, port, 1);
1775             }
1776             else
1777             {
1778                if (!err) err = map_block_killer(exports, "socks-forwarder");
1779             }
1780
1781             if (fwd->forward_host != NULL)
1782             {
1783                if (!err) err = map(exports, "forward-host", 1, fwd->forward_host, 1);
1784                snprintf(port, sizeof(port), "%d", fwd->forward_port);
1785                if (!err) err = map(exports, "forward-port", 1, port, 1);
1786             }
1787             else
1788             {
1789                if (!err) err = map_block_killer(exports, "http-forwarder");
1790             }
1791          }
1792       }
1793
1794       free_http_request(url_to_query);
1795
1796       if (err || matches == NULL)
1797       {
1798          free_current_action(action);
1799          free_map(exports);
1800          return JB_ERR_MEMORY;
1801       }
1802
1803 #ifdef FEATURE_CGI_EDIT_ACTIONS
1804       if ((csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
1805       {
1806          err = map_block_killer(exports, "cgi-editor-is-disabled");
1807       }
1808 #endif /* FEATURE_CGI_EDIT_ACTIONS */
1809
1810       /*
1811        * If zlib support is available, if no content filters
1812        * are enabled or if the prevent-compression action is enabled,
1813        * suppress the "compression could prevent filtering" warning.
1814        */
1815 #ifndef FEATURE_ZLIB
1816       if (!content_filters_enabled(action) ||
1817          (action->flags & ACTION_NO_COMPRESSION))
1818 #endif
1819       {
1820          if (!err) err = map_block_killer(exports, "filters-might-be-ineffective");
1821       }
1822
1823       if (err || map(exports, "matches", 1, matches , 0))
1824       {
1825          free_current_action(action);
1826          free_map(exports);
1827          return JB_ERR_MEMORY;
1828       }
1829
1830       s = current_action_to_html(csp, action);
1831
1832       free_current_action(action);
1833
1834       if (map(exports, "final", 1, s, 0))
1835       {
1836          free_map(exports);
1837          return JB_ERR_MEMORY;
1838       }
1839    }
1840
1841    return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
1842 }
1843
1844
1845 /*********************************************************************
1846  *
1847  * Function    :  cgi_robots_txt
1848  *
1849  * Description :  CGI function to return "/robots.txt".
1850  *
1851  * Parameters  :
1852  *          1  :  csp = Current client state (buffers, headers, etc...)
1853  *          2  :  rsp = http_response data structure for output
1854  *          3  :  parameters = map of cgi parameters
1855  *
1856  * CGI Parameters : None
1857  *
1858  * Returns     :  JB_ERR_OK on success
1859  *                JB_ERR_MEMORY on out-of-memory error.
1860  *
1861  *********************************************************************/
1862 jb_err cgi_robots_txt(struct client_state *csp,
1863                       struct http_response *rsp,
1864                       const struct map *parameters)
1865 {
1866    char buf[100];
1867    jb_err err;
1868
1869    (void)csp;
1870    (void)parameters;
1871
1872    rsp->body = strdup_or_die(
1873       "# This is the Privoxy control interface.\n"
1874       "# It isn't very useful to index it, and you're likely to break stuff.\n"
1875       "# So go away!\n"
1876       "\n"
1877       "User-agent: *\n"
1878       "Disallow: /\n"
1879       "\n");
1880
1881    err = enlist_unique(rsp->headers, "Content-Type: text/plain", 13);
1882
1883    rsp->is_static = 1;
1884
1885    get_http_time(7 * 24 * 60 * 60, buf, sizeof(buf)); /* 7 days into future */
1886    if (!err) err = enlist_unique_header(rsp->headers, "Expires", buf);
1887
1888    return (err ? JB_ERR_MEMORY : JB_ERR_OK);
1889 }
1890
1891
1892 /*********************************************************************
1893  *
1894  * Function    :  show_defines
1895  *
1896  * Description :  Add to a map the state of all conditional #defines
1897  *                used when building
1898  *
1899  * Parameters  :
1900  *          1  :  exports = map to extend
1901  *
1902  * Returns     :  JB_ERR_OK on success
1903  *                JB_ERR_MEMORY on out-of-memory error.
1904  *
1905  *********************************************************************/
1906 static jb_err show_defines(struct map *exports)
1907 {
1908    jb_err err = JB_ERR_OK;
1909    int i;
1910    struct feature {
1911       const char name[31];
1912       const unsigned char is_available;
1913    };
1914
1915    static const struct feature features[] = {
1916       {
1917          "FEATURE_64_BIT_TIME_T",
1918 #if (SIZEOF_TIME_T == 8)
1919          1,
1920 #else
1921          0,
1922 #endif
1923       },
1924       {
1925          "FEATURE_ACCEPT_FILTER",
1926 #ifdef FEATURE_ACCEPT_FILTER
1927          1,
1928 #else
1929          0,
1930 #endif
1931       },
1932       {
1933          "FEATURE_ACL",
1934 #ifdef FEATURE_ACL
1935          1,
1936 #else
1937          0,
1938 #endif
1939       },
1940       {
1941          "FEATURE_BROTLI",
1942 #ifdef FEATURE_BROTLI
1943          1,
1944 #else
1945          0,
1946 #endif
1947       },
1948       {
1949          "FEATURE_CGI_EDIT_ACTIONS",
1950 #ifdef FEATURE_CGI_EDIT_ACTIONS
1951          1,
1952 #else
1953          0,
1954 #endif
1955       },
1956       {
1957          "FEATURE_CLIENT_TAGS",
1958 #ifdef FEATURE_CLIENT_TAGS
1959          1,
1960 #else
1961          0,
1962 #endif
1963       },
1964       {
1965          "FEATURE_COMPRESSION",
1966 #ifdef FEATURE_COMPRESSION
1967          1,
1968 #else
1969          0,
1970 #endif
1971       },
1972       {
1973          "FEATURE_CONNECTION_KEEP_ALIVE",
1974 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
1975          1,
1976 #else
1977          0,
1978 #endif
1979       },
1980       {
1981          "FEATURE_CONNECTION_SHARING",
1982 #ifdef FEATURE_CONNECTION_SHARING
1983          1,
1984 #else
1985          0,
1986 #endif
1987       },
1988       {
1989          "FEATURE_EXTERNAL_FILTERS",
1990 #ifdef FEATURE_EXTERNAL_FILTERS
1991          1,
1992 #else
1993          0,
1994 #endif
1995       },
1996       {
1997          "FEATURE_FAST_REDIRECTS",
1998 #ifdef FEATURE_FAST_REDIRECTS
1999          1,
2000 #else
2001          0,
2002 #endif
2003       },
2004       {
2005          "FEATURE_FORCE_LOAD",
2006 #ifdef FEATURE_FORCE_LOAD
2007          1,
2008 #else
2009          0,
2010 #endif
2011       },
2012       {
2013          "FEATURE_GRACEFUL_TERMINATION",
2014 #ifdef FEATURE_GRACEFUL_TERMINATION
2015          1,
2016 #else
2017          0,
2018 #endif
2019       },
2020       {
2021          "FEATURE_HTTPS_INSPECTION",
2022 #ifdef FEATURE_HTTPS_INSPECTION
2023          1,
2024 #else
2025          0,
2026 #endif
2027       },
2028       {
2029          "FEATURE_IMAGE_BLOCKING",
2030 #ifdef FEATURE_IMAGE_BLOCKING
2031          1,
2032 #else
2033          0,
2034 #endif
2035       },
2036       {
2037          "FEATURE_IPV6_SUPPORT",
2038 #ifdef HAVE_RFC2553
2039          1,
2040 #else
2041          0,
2042 #endif
2043       },
2044       {
2045          "FEATURE_NO_GIFS",
2046 #ifdef FEATURE_NO_GIFS
2047          1,
2048 #else
2049          0,
2050 #endif
2051       },
2052       {
2053          "FEATURE_PTHREAD",
2054 #ifdef FEATURE_PTHREAD
2055          1,
2056 #else
2057          0,
2058 #endif
2059       },
2060       {
2061          "FEATURE_STATISTICS",
2062 #ifdef FEATURE_STATISTICS
2063          1,
2064 #else
2065          0,
2066 #endif
2067       },
2068       {
2069          "FEATURE_STRPTIME_SANITY_CHECKS",
2070 #ifdef FEATURE_STRPTIME_SANITY_CHECKS
2071          1,
2072 #else
2073          0,
2074 #endif
2075       },
2076       {
2077          "FEATURE_TOGGLE",
2078 #ifdef FEATURE_TOGGLE
2079          1,
2080 #else
2081          0,
2082 #endif
2083       },
2084       {
2085          "FEATURE_TRUST",
2086 #ifdef FEATURE_TRUST
2087          1,
2088 #else
2089          0,
2090 #endif
2091       },
2092       {
2093          "FEATURE_ZLIB",
2094 #ifdef FEATURE_ZLIB
2095          1,
2096 #else
2097          0,
2098 #endif
2099       },
2100       {
2101          "FEATURE_DYNAMIC_PCRE",
2102 #ifdef FEATURE_DYNAMIC_PCRE
2103          1,
2104 #else
2105          0,
2106 #endif
2107       },
2108       {
2109          "FEATURE_EXTENDED_STATISTICS",
2110 #ifdef FEATURE_EXTENDED_STATISTICS
2111          1,
2112 #else
2113          0,
2114 #endif
2115       },
2116       {
2117          "FEATURE_PCRE_HOST_PATTERNS",
2118 #ifdef FEATURE_PCRE_HOST_PATTERNS
2119          1,
2120 #else
2121          0,
2122 #endif
2123       }
2124    };
2125
2126    for (i = 0; i < SZ(features); i++)
2127    {
2128       err = map_conditional(exports, features[i].name, features[i].is_available);
2129       if (err)
2130       {
2131          break;
2132       }
2133    }
2134
2135    return err;
2136
2137 }
2138
2139
2140 /*********************************************************************
2141  *
2142  * Function    :  cgi_show_file
2143  *
2144  * Description :  CGI function that shows the content of a
2145  *                configuration file.
2146  *
2147  * Parameters  :
2148  *          1  :  csp = Current client state (buffers, headers, etc...)
2149  *          2  :  rsp = http_response data structure for output
2150  *          3  :  parameters = map of cgi parameters
2151  *
2152  * CGI Parameters :
2153  *        file :  Which file to show.  Only first letter is checked,
2154  *                valid values are:
2155  *                - "a"ction file
2156  *                - "r"egex
2157  *                - "t"rust
2158  *                Default is to show menu and other information.
2159  *
2160  * Returns     :  JB_ERR_OK on success
2161  *                JB_ERR_MEMORY on out-of-memory error.
2162  *
2163  *********************************************************************/
2164 static jb_err cgi_show_file(struct client_state *csp,
2165                             struct http_response *rsp,
2166                             const struct map *parameters)
2167 {
2168    unsigned i;
2169    const char * filename = NULL;
2170    char * file_description = NULL;
2171
2172    assert(csp);
2173    assert(rsp);
2174    assert(parameters);
2175
2176    switch (*(lookup(parameters, "file")))
2177    {
2178    case 'a':
2179       if (!get_number_param(csp, parameters, "index", &i) && i < MAX_AF_FILES && csp->actions_list[i])
2180       {
2181          filename = csp->actions_list[i]->filename;
2182          file_description = "Actions File";
2183       }
2184       break;
2185
2186    case 'f':
2187       if (!get_number_param(csp, parameters, "index", &i) && i < MAX_AF_FILES && csp->rlist[i])
2188       {
2189          filename = csp->rlist[i]->filename;
2190          file_description = "Filter File";
2191       }
2192       break;
2193
2194 #ifdef FEATURE_TRUST
2195    case 't':
2196       if (csp->tlist)
2197       {
2198          filename = csp->tlist->filename;
2199          file_description = "Trust File";
2200       }
2201       break;
2202 #endif /* def FEATURE_TRUST */
2203    }
2204
2205    if (NULL != filename)
2206    {
2207       struct map *exports;
2208       char *s;
2209       jb_err err;
2210       size_t length;
2211
2212       exports = default_exports(csp, "show-status");
2213       if (NULL == exports)
2214       {
2215          return JB_ERR_MEMORY;
2216       }
2217
2218       if (map(exports, "file-description", 1, file_description, 1)
2219         || map(exports, "filepath", 1, html_encode(filename), 0))
2220       {
2221          free_map(exports);
2222          return JB_ERR_MEMORY;
2223       }
2224
2225       err = load_file(filename, &s, &length);
2226       if (JB_ERR_OK != err)
2227       {
2228          if (map(exports, "contents", 1, "<h1>ERROR OPENING FILE!</h1>", 1))
2229          {
2230             free_map(exports);
2231             return JB_ERR_MEMORY;
2232          }
2233       }
2234       else
2235       {
2236          s = html_encode_and_free_original(s);
2237          if (NULL == s)
2238          {
2239             free_map(exports);
2240             return JB_ERR_MEMORY;
2241          }
2242
2243          if (map(exports, "contents", 1, s, 0))
2244          {
2245             free_map(exports);
2246             return JB_ERR_MEMORY;
2247          }
2248       }
2249
2250       return template_fill_for_cgi(csp, "show-status-file", exports, rsp);
2251    }
2252
2253    return JB_ERR_CGI_PARAMS;
2254 }
2255
2256
2257 /*********************************************************************
2258  *
2259  * Function    :  load_file
2260  *
2261  * Description :  Loads a file into a buffer.
2262  *
2263  * Parameters  :
2264  *          1  :  filename = Name of the file to be loaded.
2265  *          2  :  buffer   = Used to return the file's content.
2266  *          3  :  length   = Used to return the size of the file.
2267  *
2268  * Returns     :  JB_ERR_OK in case of success,
2269  *                JB_ERR_FILE in case of ordinary file loading errors
2270  *                            (fseek() and ftell() errors are fatal)
2271  *                JB_ERR_MEMORY in case of out-of-memory.
2272  *
2273  *********************************************************************/
2274 static jb_err load_file(const char *filename, char **buffer, size_t *length)
2275 {
2276    FILE *fp;
2277    long ret;
2278    jb_err err = JB_ERR_OK;
2279
2280    fp = fopen(filename, "rb");
2281    if (NULL == fp)
2282    {
2283       log_error(LOG_LEVEL_ERROR, "Failed to open %s: %E", filename);
2284       return JB_ERR_FILE;
2285    }
2286
2287    /* Get file length */
2288    if (fseek(fp, 0, SEEK_END))
2289    {
2290       log_error(LOG_LEVEL_FATAL,
2291          "Unexpected error while fseek()ing to the end of %s: %E",
2292          filename);
2293    }
2294    ret = ftell(fp);
2295    if (-1 == ret)
2296    {
2297       log_error(LOG_LEVEL_FATAL,
2298          "Unexpected ftell() error while loading %s: %E",
2299          filename);
2300    }
2301    *length = (size_t)ret;
2302
2303    /* Go back to the beginning. */
2304    if (fseek(fp, 0, SEEK_SET))
2305    {
2306       log_error(LOG_LEVEL_FATAL,
2307          "Unexpected error while fseek()ing to the beginning of %s: %E",
2308          filename);
2309    }
2310
2311    *buffer = zalloc_or_die(*length + 1);
2312
2313    if (1 != fread(*buffer, *length, 1, fp))
2314    {
2315       /*
2316        * May theoretically happen if the file size changes between
2317        * fseek() and fread() because it's edited in-place. Privoxy
2318        * and common text editors don't do that, thus we just fail.
2319        */
2320       log_error(LOG_LEVEL_ERROR,
2321          "Couldn't completely read file %s.", filename);
2322       freez(*buffer);
2323       err = JB_ERR_FILE;
2324    }
2325
2326    fclose(fp);
2327
2328    return err;
2329
2330 }
2331
2332
2333 /*
2334   Local Variables:
2335   tab-width: 3
2336   end:
2337 */