+
+ if (!err) err = string_append(&s, "</td></tr>\n");
+ }
+ }
+ if (*s != '\0')
+ {
+ if (!err) err = map(exports, "actions-filenames", 1, s, 0);
+ }
+ else
+ {
+ if (!err) err = map(exports, "actions-filenames", 1, "<tr><td>None specified</td></tr>", 1);
+ }
+
+ /*
+ * List all re_filterfiles in use, together with view options.
+ * FIXME: Shouldn't include hardwired HTML here, use line template instead!
+ */
+ s = strdup("");
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ if (csp->rlist[i] != NULL)
+ {
+ if (!err) err = string_append(&s, "<tr><td>");
+ if (!err) err = string_join(&s, html_encode(csp->rlist[i]->filename));
+ snprintf(buf, sizeof(buf),
+ "</td><td class=\"buttons\"><a href=\"/show-status?file=filter&index=%u\">View</a>", i);
+ if (!err) err = string_append(&s, buf);
+ if (!err) err = string_append(&s, "</td></tr>\n");
+ }
+ }
+ if (*s != '\0')
+ {
+ if (!err) err = map(exports, "re-filter-filenames", 1, s, 0);
+ }
+ else
+ {
+ if (!err) err = map(exports, "re-filter-filenames", 1, "<tr><td>None specified</td></tr>", 1);
+ if (!err) err = map_block_killer(exports, "have-filterfile");
+ }
+
+#ifdef FEATURE_TRUST
+ if (csp->tlist)
+ {
+ if (!err) err = map(exports, "trust-filename", 1, html_encode(csp->tlist->filename), 0);
+ }
+ else
+ {
+ if (!err) err = map(exports, "trust-filename", 1, "None specified", 1);
+ if (!err) err = map_block_killer(exports, "have-trustfile");
+ }
+#else
+ if (!err) err = map_block_killer(exports, "trust-support");
+#endif /* ndef FEATURE_TRUST */
+
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+ if (!err && (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+ {
+ err = map_block_killer(exports, "cgi-editor-is-disabled");
+ }
+#endif /* ndef CGI_EDIT_ACTIONS */
+
+ if (!err) err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
+
+ if (err)
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ return template_fill_for_cgi(csp, "show-status", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function : cgi_show_url_info
+ *
+ * Description : CGI function that determines and shows which actions
+ * Privoxy will perform for a given url, and which
+ * matches starting from the defaults have lead to that.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : rsp = http_response data structure for output
+ * 3 : parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ * url : The url whose actions are to be determined.
+ * If url is unset, the url-given conditional will be
+ * set, so that all but the form can be suppressed in
+ * the template.
+ *
+ * Returns : JB_ERR_OK on success
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_show_url_info(struct client_state *csp,
+ struct http_response *rsp,
+ const struct map *parameters)
+{
+ char *url_param;
+ struct map *exports;
+ char buf[150];
+
+ assert(csp);
+ assert(rsp);
+ assert(parameters);
+
+ if (NULL == (exports = default_exports(csp, "show-url-info")))
+ {
+ return JB_ERR_MEMORY;
+ }
+
+ /*
+ * Get the url= parameter (if present) and remove any leading/trailing spaces.
+ */
+ url_param = strdup_or_die(lookup(parameters, "url"));
+ chomp(url_param);
+
+ /*
+ * Handle prefixes. 4 possibilities:
+ * 1) "http://" or "https://" prefix present and followed by URL - OK
+ * 2) Only the "http://" or "https://" part is present, no URL - change
+ * to empty string so it will be detected later as "no URL".
+ * 3) Parameter specified but doesn't start with "http(s?)://" - add a
+ * "http://" prefix.
+ * 4) Parameter not specified or is empty string - let this fall through
+ * for now, next block of code will handle it.
+ */
+ if (0 == strncmp(url_param, "http://", 7))
+ {
+ if (url_param[7] == '\0')
+ {
+ /*
+ * Empty URL (just prefix).
+ * Make it totally empty so it's caught by the next if ()
+ */
+ url_param[0] = '\0';
+ }
+ }
+ else if (0 == strncmp(url_param, "https://", 8))
+ {
+ if (url_param[8] == '\0')
+ {
+ /*
+ * Empty URL (just prefix).
+ * Make it totally empty so it's caught by the next if ()
+ */
+ url_param[0] = '\0';
+ }
+ }
+ else if ((url_param[0] != '\0')
+ && ((NULL == strstr(url_param, "://")
+ || (strstr(url_param, "://") > strstr(url_param, "/")))))
+ {
+ /*
+ * No prefix or at least no prefix before
+ * the first slash - assume http://
+ */
+ char *url_param_prefixed = strdup_or_die("http://");
+
+ if (JB_ERR_OK != string_join(&url_param_prefixed, url_param))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ url_param = url_param_prefixed;
+ }
+
+ /*
+ * Hide "toggle off" warning if Privoxy is toggled on.
+ */
+ if (
+#ifdef FEATURE_TOGGLE
+ (global_toggle_state == 1) &&
+#endif /* def FEATURE_TOGGLE */
+ map_block_killer(exports, "privoxy-is-toggled-off")
+ )
+ {
+ freez(url_param);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ if (url_param[0] == '\0')
+ {
+ /* URL paramater not specified, display query form only. */
+ free(url_param);
+ if (map_block_killer(exports, "url-given")
+ || map(exports, "url", 1, "", 1))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ }
+ else
+ {
+ /* Given a URL, so query it. */
+ jb_err err;
+ char *matches;
+ char *s;
+ int hits = 0;
+ struct file_list *fl;
+ struct url_actions *b;
+ struct http_request url_to_query[1];
+ struct current_action_spec action[1];
+ int i;
+
+ if (map(exports, "url", 1, html_encode(url_param), 0))
+ {
+ free(url_param);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ init_current_action(action);
+
+ if (map(exports, "default", 1, current_action_to_html(csp, action), 0))
+ {
+ free_current_action(action);
+ free(url_param);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ memset(url_to_query, '\0', sizeof(url_to_query));
+ err = parse_http_url(url_param, url_to_query, REQUIRE_PROTOCOL);
+ assert((err != JB_ERR_OK) || (url_to_query->ssl == !strncmpic(url_param, "https://", 8)));
+
+ free(url_param);
+
+ if (err == JB_ERR_MEMORY)
+ {
+ free_http_request(url_to_query);
+ free_current_action(action);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ else if (err)
+ {
+ /* Invalid URL */
+
+ err = map(exports, "matches", 1, "<b>[Invalid URL specified!]</b>" , 1);
+ if (!err) err = map(exports, "final", 1, lookup(exports, "default"), 1);
+ if (!err) err = map_block_killer(exports, "valid-url");
+
+ free_current_action(action);
+ free_http_request(url_to_query);
+
+ if (err)
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
+ }
+
+ /*
+ * We have a warning about SSL paths. Hide it for unencrypted sites.
+ */
+ if (!url_to_query->ssl)
+ {
+ if (map_block_killer(exports, "https"))
+ {
+ free_current_action(action);
+ free_map(exports);
+ free_http_request(url_to_query);
+ return JB_ERR_MEMORY;
+ }
+ }
+
+ matches = strdup_or_die("<table summary=\"\" class=\"transparent\">");
+
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ if (NULL == csp->config->actions_file_short[i]
+ || !strcmp(csp->config->actions_file_short[i], "standard.action")) continue;
+
+ b = NULL;
+ hits = 1;
+ if ((fl = csp->actions_list[i]) != NULL)
+ {
+ if ((b = fl->f) != NULL)
+ {
+ /* FIXME: Hardcoded HTML! */
+ string_append(&matches, "<tr><th>In file: ");
+ string_join (&matches, html_encode(csp->config->actions_file_short[i]));
+ snprintf(buf, sizeof(buf), " <a class=\"cmd\" href=\"/show-status?file=actions&index=%d\">", i);
+ string_append(&matches, buf);
+ string_append(&matches, "View</a>");
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+ if (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)
+ {
+#ifdef HAVE_ACCESS
+ if (access(csp->config->actions_file[i], W_OK) == 0)
+ {
+#endif /* def HAVE_ACCESS */
+ snprintf(buf, sizeof(buf),
+ " <a class=\"cmd\" href=\"/edit-actions-list?f=%d\">", i);
+ string_append(&matches, buf);
+ string_append(&matches, "Edit</a>");
+#ifdef HAVE_ACCESS
+ }
+ else
+ {
+ string_append(&matches, " <strong>No write access.</strong>");
+ }
+#endif /* def HAVE_ACCESS */
+ }
+#endif /* FEATURE_CGI_EDIT_ACTIONS */
+
+ string_append(&matches, "</th></tr>\n");
+
+ hits = 0;
+ b = b->next;
+ }
+ }
+
+ for (; (b != NULL) && (matches != NULL); b = b->next)
+ {
+ if (url_match(b->url, url_to_query))
+ {
+ string_append(&matches, "<tr><td>{");
+ string_join (&matches, actions_to_html(csp, b->action));
+ string_append(&matches, " }<br>\n<code>");
+ string_join (&matches, html_encode(b->url->spec));
+ string_append(&matches, "</code></td></tr>\n");
+
+ if (merge_current_action(action, b->action))
+ {
+ freez(matches);
+ free_http_request(url_to_query);
+ free_current_action(action);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ hits++;
+ }
+ }
+
+ if (!hits)
+ {
+ string_append(&matches, "<tr><td>(no matches in this file)</td></tr>\n");
+ }
+ }
+ string_append(&matches, "</table>\n");
+
+ /*
+ * XXX: Kludge to make sure the "Forward settings" section
+ * shows what forward-override{} would do with the requested URL.
+ * No one really cares how the CGI request would be forwarded
+ * if it wasn't intercepted as CGI request in the first place.
+ *
+ * From here on the action bitmask will no longer reflect
+ * the real url (http://config.privoxy.org/show-url-info?url=.*),
+ * but luckily it's no longer required later on anyway.
+ */
+ free_current_action(csp->action);
+ get_url_actions(csp, url_to_query);
+
+ /*
+ * Fill in forwarding settings.
+ *
+ * The possibilities are:
+ * - no forwarding
+ * - http forwarding only
+ * - socks4(a) forwarding only
+ * - socks4(a) and http forwarding.
+ *
+ * XXX: Parts of this code could be reused for the
+ * "forwarding-failed" template which currently doesn't
+ * display the proxy port and an eventual second forwarder.
+ */
+ {
+ const struct forward_spec *fwd = forward_url(csp, url_to_query);
+
+ if ((fwd->gateway_host == NULL) && (fwd->forward_host == NULL))
+ {
+ if (!err) err = map_block_killer(exports, "socks-forwarder");
+ if (!err) err = map_block_killer(exports, "http-forwarder");
+ }
+ else
+ {
+ char port[10]; /* We save proxy ports as int but need a string here */
+
+ if (!err) err = map_block_killer(exports, "no-forwarder");
+
+ if (fwd->gateway_host != NULL)
+ {
+ char *socks_type = NULL;
+
+ switch (fwd->type)
+ {
+ case SOCKS_4:
+ socks_type = "socks4";
+ break;
+ case SOCKS_4A:
+ socks_type = "socks4a";
+ break;
+ case SOCKS_5:
+ socks_type = "socks5";
+ break;
+ case SOCKS_5T:
+ socks_type = "socks5t";
+ break;
+ default:
+ log_error(LOG_LEVEL_FATAL, "Unknown socks type: %d.", fwd->type);
+ }
+
+ if (!err) err = map(exports, "socks-type", 1, socks_type, 1);
+ if (!err) err = map(exports, "gateway-host", 1, fwd->gateway_host, 1);
+ snprintf(port, sizeof(port), "%d", fwd->gateway_port);
+ if (!err) err = map(exports, "gateway-port", 1, port, 1);
+ }
+ else
+ {
+ if (!err) err = map_block_killer(exports, "socks-forwarder");
+ }
+
+ if (fwd->forward_host != NULL)
+ {
+ if (!err) err = map(exports, "forward-host", 1, fwd->forward_host, 1);
+ snprintf(port, sizeof(port), "%d", fwd->forward_port);
+ if (!err) err = map(exports, "forward-port", 1, port, 1);
+ }
+ else
+ {
+ if (!err) err = map_block_killer(exports, "http-forwarder");
+ }
+ }
+ }
+
+ free_http_request(url_to_query);
+
+ if (err || matches == NULL)
+ {
+ free_current_action(action);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+ if ((csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+ {
+ err = map_block_killer(exports, "cgi-editor-is-disabled");
+ }
+#endif /* FEATURE_CGI_EDIT_ACTIONS */
+
+ /*
+ * If zlib support is available, if no content filters
+ * are enabled or if the prevent-compression action is enabled,
+ * suppress the "compression could prevent filtering" warning.
+ */
+#ifndef FEATURE_ZLIB
+ if (!content_filters_enabled(action) ||
+ (action->flags & ACTION_NO_COMPRESSION))
+#endif
+ {
+ if (!err) err = map_block_killer(exports, "filters-might-be-ineffective");
+ }
+
+ if (err || map(exports, "matches", 1, matches , 0))
+ {
+ free_current_action(action);
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ s = current_action_to_html(csp, action);
+
+ free_current_action(action);
+
+ if (map(exports, "final", 1, s, 0))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ }
+
+ return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function : cgi_robots_txt
+ *
+ * Description : CGI function to return "/robots.txt".
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : rsp = http_response data structure for output
+ * 3 : parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns : JB_ERR_OK on success
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_robots_txt(struct client_state *csp,
+ struct http_response *rsp,
+ const struct map *parameters)
+{
+ char buf[100];
+ jb_err err;
+
+ (void)csp;
+ (void)parameters;
+
+ rsp->body = strdup_or_die(
+ "# This is the Privoxy control interface.\n"
+ "# It isn't very useful to index it, and you're likely to break stuff.\n"
+ "# So go away!\n"
+ "\n"
+ "User-agent: *\n"
+ "Disallow: /\n"
+ "\n");
+
+ err = enlist_unique(rsp->headers, "Content-Type: text/plain", 13);
+
+ rsp->is_static = 1;
+
+ get_http_time(7 * 24 * 60 * 60, buf, sizeof(buf)); /* 7 days into future */
+ if (!err) err = enlist_unique_header(rsp->headers, "Expires", buf);
+
+ return (err ? JB_ERR_MEMORY : JB_ERR_OK);
+}
+
+
+/*********************************************************************
+ *
+ * Function : show_defines
+ *
+ * Description : Add to a map the state od all conditional #defines
+ * used when building
+ *
+ * Parameters :
+ * 1 : exports = map to extend
+ *
+ * Returns : JB_ERR_OK on success
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+static jb_err show_defines(struct map *exports)
+{
+ jb_err err = JB_ERR_OK;
+ int i;
+ struct feature {
+ const char name[31];
+ const unsigned char is_available;
+ };
+
+ static const struct feature features[] = {
+ {
+ "FEATURE_64_BIT_TIME_T",
+#if (SIZEOF_TIME_T == 8)
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_ACCEPT_FILTER",
+#ifdef FEATURE_ACCEPT_FILTER
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_ACL",
+#ifdef FEATURE_ACL
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_CGI_EDIT_ACTIONS",
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_CLIENT_TAGS",
+#ifdef FEATURE_CLIENT_TAGS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_COMPRESSION",
+#ifdef FEATURE_COMPRESSION
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_CONNECTION_KEEP_ALIVE",
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_CONNECTION_SHARING",
+#ifdef FEATURE_CONNECTION_SHARING
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_EXTERNAL_FILTERS",
+#ifdef FEATURE_EXTERNAL_FILTERS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_FAST_REDIRECTS",
+#ifdef FEATURE_FAST_REDIRECTS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_FORCE_LOAD",
+#ifdef FEATURE_FORCE_LOAD
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_GRACEFUL_TERMINATION",
+#ifdef FEATURE_GRACEFUL_TERMINATION
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_HTTPS_INSPECTION",
+#ifdef FEATURE_HTTPS_INSPECTION
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_IMAGE_BLOCKING",
+#ifdef FEATURE_IMAGE_BLOCKING
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_IPV6_SUPPORT",
+#ifdef HAVE_RFC2553
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_NO_GIFS",
+#ifdef FEATURE_NO_GIFS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_PTHREAD",
+#ifdef FEATURE_PTHREAD
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_STATISTICS",
+#ifdef FEATURE_STATISTICS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_STRPTIME_SANITY_CHECKS",
+#ifdef FEATURE_STRPTIME_SANITY_CHECKS
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_TOGGLE",
+#ifdef FEATURE_TOGGLE
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_TRUST",
+#ifdef FEATURE_TRUST
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_ZLIB",
+#ifdef FEATURE_ZLIB
+ 1,
+#else
+ 0,
+#endif
+ },
+ {
+ "FEATURE_DYNAMIC_PCRE",
+#ifdef FEATURE_DYNAMIC_PCRE
+ 1,
+#else
+ 0,
+#endif
+ }
+ };
+
+ for (i = 0; i < SZ(features); i++)
+ {
+ err = map_conditional(exports, features[i].name, features[i].is_available);
+ if (err)
+ {
+ break;
+ }
+ }
+
+ return err;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : cgi_show_file
+ *
+ * Description : CGI function that shows the content of a
+ * configuration file.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : rsp = http_response data structure for output
+ * 3 : parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ * file : Which file to show. Only first letter is checked,
+ * valid values are:
+ * - "a"ction file
+ * - "r"egex
+ * - "t"rust
+ * Default is to show menu and other information.
+ *
+ * Returns : JB_ERR_OK on success
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+static jb_err cgi_show_file(struct client_state *csp,
+ struct http_response *rsp,
+ const struct map *parameters)
+{
+ unsigned i;
+ const char * filename = NULL;
+ char * file_description = NULL;
+
+ assert(csp);
+ assert(rsp);
+ assert(parameters);
+
+ switch (*(lookup(parameters, "file")))
+ {
+ case 'a':
+ if (!get_number_param(csp, parameters, "index", &i) && i < MAX_AF_FILES && csp->actions_list[i])
+ {
+ filename = csp->actions_list[i]->filename;
+ file_description = "Actions File";
+ }
+ break;
+
+ case 'f':
+ if (!get_number_param(csp, parameters, "index", &i) && i < MAX_AF_FILES && csp->rlist[i])
+ {
+ filename = csp->rlist[i]->filename;
+ file_description = "Filter File";
+ }
+ break;
+
+#ifdef FEATURE_TRUST
+ case 't':
+ if (csp->tlist)
+ {
+ filename = csp->tlist->filename;
+ file_description = "Trust File";
+ }
+ break;
+#endif /* def FEATURE_TRUST */
+ }
+
+ if (NULL != filename)
+ {
+ struct map *exports;
+ char *s;
+ jb_err err;
+ size_t length;
+
+ exports = default_exports(csp, "show-status");
+ if (NULL == exports)
+ {
+ return JB_ERR_MEMORY;
+ }
+
+ if (map(exports, "file-description", 1, file_description, 1)
+ || map(exports, "filepath", 1, html_encode(filename), 0))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ err = load_file(filename, &s, &length);
+ if (JB_ERR_OK != err)
+ {
+ if (map(exports, "contents", 1, "<h1>ERROR OPENING FILE!</h1>", 1))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ }
+ else
+ {
+ s = html_encode_and_free_original(s);
+ if (NULL == s)
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+
+ if (map(exports, "contents", 1, s, 0))
+ {
+ free_map(exports);
+ return JB_ERR_MEMORY;
+ }
+ }
+
+ return template_fill_for_cgi(csp, "show-status-file", exports, rsp);
+ }
+
+ return JB_ERR_CGI_PARAMS;
+}
+
+
+/*********************************************************************
+ *
+ * Function : load_file
+ *
+ * Description : Loads a file into a buffer.
+ *
+ * Parameters :
+ * 1 : filename = Name of the file to be loaded.
+ * 2 : buffer = Used to return the file's content.
+ * 3 : length = Used to return the size of the file.
+ *
+ * Returns : JB_ERR_OK in case of success,
+ * JB_ERR_FILE in case of ordinary file loading errors
+ * (fseek() and ftell() errors are fatal)
+ * JB_ERR_MEMORY in case of out-of-memory.
+ *
+ *********************************************************************/
+static jb_err load_file(const char *filename, char **buffer, size_t *length)
+{
+ FILE *fp;
+ long ret;
+ jb_err err = JB_ERR_OK;
+
+ fp = fopen(filename, "rb");
+ if (NULL == fp)
+ {
+ log_error(LOG_LEVEL_ERROR, "Failed to open %s: %E", filename);
+ return JB_ERR_FILE;
+ }
+
+ /* Get file length */
+ if (fseek(fp, 0, SEEK_END))
+ {
+ log_error(LOG_LEVEL_FATAL,
+ "Unexpected error while fseek()ing to the end of %s: %E",
+ filename);
+ }
+ ret = ftell(fp);
+ if (-1 == ret)
+ {
+ log_error(LOG_LEVEL_FATAL,
+ "Unexpected ftell() error while loading %s: %E",
+ filename);
+ }
+ *length = (size_t)ret;
+
+ /* Go back to the beginning. */
+ if (fseek(fp, 0, SEEK_SET))
+ {
+ log_error(LOG_LEVEL_FATAL,
+ "Unexpected error while fseek()ing to the beginning of %s: %E",
+ filename);
+ }
+
+ *buffer = zalloc_or_die(*length + 1);
+
+ if (1 != fread(*buffer, *length, 1, fp))
+ {
+ /*
+ * May theoretically happen if the file size changes between
+ * fseek() and fread() because it's edited in-place. Privoxy
+ * and common text editors don't do that, thus we just fail.
+ */
+ log_error(LOG_LEVEL_ERROR,
+ "Couldn't completely read file %s.", filename);
+ freez(*buffer);
+ err = JB_ERR_FILE;
+ }
+
+ fclose(fp);
+
+ return err;