summaryrefslogtreecommitdiff
path: root/content/fetchers
diff options
context:
space:
mode:
Diffstat (limited to 'content/fetchers')
-rw-r--r--content/fetchers/Makefile13
-rw-r--r--content/fetchers/about.c1473
-rw-r--r--content/fetchers/about/Makefile20
-rw-r--r--content/fetchers/about/about.c758
-rw-r--r--content/fetchers/about/about.h (renamed from content/fetchers/about.h)9
-rw-r--r--content/fetchers/about/atestament.h35
-rw-r--r--content/fetchers/about/blank.c60
-rw-r--r--content/fetchers/about/blank.h35
-rw-r--r--content/fetchers/about/certificate.c1291
-rw-r--r--content/fetchers/about/certificate.h35
-rw-r--r--content/fetchers/about/chart.c630
-rw-r--r--content/fetchers/about/chart.h37
-rw-r--r--content/fetchers/about/choices.c93
-rw-r--r--content/fetchers/about/choices.h35
-rw-r--r--content/fetchers/about/config.c133
-rw-r--r--content/fetchers/about/config.h35
-rw-r--r--content/fetchers/about/imagecache.c188
-rw-r--r--content/fetchers/about/imagecache.h37
-rw-r--r--content/fetchers/about/nscolours.c81
-rw-r--r--content/fetchers/about/nscolours.h35
-rw-r--r--content/fetchers/about/private.h77
-rw-r--r--content/fetchers/about/query.c60
-rw-r--r--content/fetchers/about/query.h36
-rw-r--r--content/fetchers/about/query_auth.c246
-rw-r--r--content/fetchers/about/query_auth.h35
-rw-r--r--content/fetchers/about/query_fetcherror.c158
-rw-r--r--content/fetchers/about/query_fetcherror.h35
-rw-r--r--content/fetchers/about/query_privacy.c174
-rw-r--r--content/fetchers/about/query_privacy.h35
-rw-r--r--content/fetchers/about/query_timeout.c158
-rw-r--r--content/fetchers/about/query_timeout.h35
-rw-r--r--content/fetchers/about/testament.c129
-rw-r--r--content/fetchers/curl.c1088
-rw-r--r--content/fetchers/data.c92
-rw-r--r--content/fetchers/file/Makefile3
-rw-r--r--content/fetchers/file/dirlist.c407
-rw-r--r--content/fetchers/file/dirlist.h50
-rw-r--r--content/fetchers/file/file.c (renamed from content/fetchers/file.c)80
-rw-r--r--content/fetchers/file/file.h (renamed from content/fetchers/file.h)0
-rw-r--r--content/fetchers/resource.c77
40 files changed, 6104 insertions, 1904 deletions
diff --git a/content/fetchers/Makefile b/content/fetchers/Makefile
index 9c8479320..8f6e5211f 100644
--- a/content/fetchers/Makefile
+++ b/content/fetchers/Makefile
@@ -1,10 +1,15 @@
# Content fetchers sources
-S_FETCHERS_YES := data.c file.c about.c resource.c
+S_FETCHERS_YES := data.c resource.c
S_FETCHERS_NO :=
S_FETCHERS_$(NETSURF_USE_CURL) += curl.c
-S_FETCHERS := $(addprefix content/fetchers/,$(S_FETCHERS_YES))
+S_FETCHERS := $(addprefix fetchers/,$(S_FETCHERS_YES))
-# The following files depend on the testament
-content/fetchers/about.c: testament $(OBJROOT)/testament.h
+# about fetcher
+include content/fetchers/about/Makefile
+S_FETCHERS += $(addprefix fetchers/about/,$(S_FETCHER_ABOUT))
+
+# file fetcher
+include content/fetchers/file/Makefile
+S_FETCHERS += $(addprefix fetchers/file/,$(S_FETCHER_FILE))
diff --git a/content/fetchers/about.c b/content/fetchers/about.c
deleted file mode 100644
index df514108c..000000000
--- a/content/fetchers/about.c
+++ /dev/null
@@ -1,1473 +0,0 @@
-/*
- * Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
- *
- * This file is part of NetSurf.
- *
- * NetSurf is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * NetSurf is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * \file
- *
- * URL handling for the "about" scheme.
- *
- * Based on the data fetcher by Rob Kendrick
- * This fetcher provides a simple scheme for the user to access
- * information from the browser from a known, fixed URL.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdarg.h>
-
-#include "utils/log.h"
-#include "testament.h"
-#include "utils/corestrings.h"
-#include "utils/nsoption.h"
-#include "utils/utils.h"
-#include "utils/messages.h"
-#include "utils/ring.h"
-
-#include "content/fetch.h"
-#include "content/fetchers.h"
-#include "content/fetchers/about.h"
-#include "image/image_cache.h"
-
-struct fetch_about_context;
-
-typedef bool (*fetch_about_handler)(struct fetch_about_context *);
-
-/**
- * Context for an about fetch
- */
-struct fetch_about_context {
- struct fetch_about_context *r_next, *r_prev;
-
- struct fetch *fetchh; /**< Handle for this fetch */
-
- bool aborted; /**< Flag indicating fetch has been aborted */
- bool locked; /**< Flag indicating entry is already entered */
-
- nsurl *url; /**< The full url the fetch refers to */
-
- const struct fetch_multipart_data *multipart; /**< post data */
-
- fetch_about_handler handler;
-};
-
-static struct fetch_about_context *ring = NULL;
-
-/**
- * handler info for about scheme
- */
-struct about_handlers {
- const char *name; /**< name to match in url */
- int name_len;
- lwc_string *lname; /**< Interned name */
- fetch_about_handler handler; /**< handler for the url */
- bool hidden; /**< If entry should be hidden in listing */
-};
-
-/**
- * authentication query description if messages fails to retrieve usable text
- */
-static const char *authentication_description_fallback = "The site %s is requesting your username and password. The realm is \"%s\"";
-
-/**
- * privacy query description if messages fails to retrieve usable text
- */
-static const char *privacy_description_fallback = "A privacy error occurred while communicating with %s this may be a site configuration error or an attempt to steal private information (passwords, messages or credit cards)";
-
-/**
- * issue fetch callbacks with locking
- */
-static inline bool
-fetch_about_send_callback(const fetch_msg *msg, struct fetch_about_context *ctx)
-{
- ctx->locked = true;
- fetch_send_callback(msg, ctx->fetchh);
- ctx->locked = false;
-
- return ctx->aborted;
-}
-
-static inline bool
-fetch_about_send_finished(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
- msg.type = FETCH_FINISHED;
- return fetch_about_send_callback(&msg, ctx);
-}
-
-static bool
-fetch_about_send_header(struct fetch_about_context *ctx, const char *fmt, ...)
-{
- char header[64];
- fetch_msg msg;
- va_list ap;
-
- va_start(ap, fmt);
-
- vsnprintf(header, sizeof header, fmt, ap);
-
- va_end(ap);
-
- msg.type = FETCH_HEADER;
- msg.data.header_or_data.buf = (const uint8_t *) header;
- msg.data.header_or_data.len = strlen(header);
-
- return fetch_about_send_callback(&msg, ctx);
-}
-
-/**
- * send formatted data on a fetch
- */
-static nserror ssenddataf(struct fetch_about_context *ctx, const char *fmt, ...)
-{
- char buffer[1024];
- fetch_msg msg;
- va_list ap;
- int slen;
-
- va_start(ap, fmt);
-
- slen = vsnprintf(buffer, sizeof(buffer), fmt, ap);
-
- va_end(ap);
-
- if (slen >= (int)sizeof(buffer)) {
- return NSERROR_NOSPACE;
- }
-
- msg.type = FETCH_DATA;
- msg.data.header_or_data.buf = (const uint8_t *) buffer;
- msg.data.header_or_data.len = slen;
-
- if (fetch_about_send_callback(&msg, ctx)) {
- return NSERROR_INVALID;
- }
-
- return NSERROR_OK;
-}
-
-
-/**
- * Generate a 500 server error respnse
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_srverror(struct fetch_about_context *ctx)
-{
- nserror res;
-
- fetch_set_http_code(ctx->fetchh, 500);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
- return false;
-
- res = ssenddataf(ctx, "Server error 500");
- if (res != NSERROR_OK) {
- return false;
- }
-
- fetch_about_send_finished(ctx);
-
- return true;
-}
-
-
-/**
- * Handler to generate about scheme cache page.
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_blank_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
- const char buffer[2] = { ' ', '\0' };
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html"))
- goto fetch_about_blank_handler_aborted;
-
- msg.type = FETCH_DATA;
- msg.data.header_or_data.buf = (const uint8_t *) buffer;
- msg.data.header_or_data.len = strlen(buffer);
-
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_blank_handler_aborted;
-
- msg.type = FETCH_FINISHED;
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-
-fetch_about_blank_handler_aborted:
- return false;
-}
-
-
-/**
- * Handler to generate about scheme credits page.
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_credits_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
-
- /* content is going to return redirect */
- fetch_set_http_code(ctx->fetchh, 302);
-
- msg.type = FETCH_REDIRECT;
- msg.data.redirect = "resource:credits.html";
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-}
-
-
-/**
- * Handler to generate about scheme licence page.
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_licence_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
-
- /* content is going to return redirect */
- fetch_set_http_code(ctx->fetchh, 302);
-
- msg.type = FETCH_REDIRECT;
- msg.data.redirect = "resource:licence.html";
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-}
-
-
-/**
- * Handler to generate about:cache page.
- *
- * Shows details of current image cache.
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_imagecache_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
- char buffer[2048]; /* output buffer */
- int code = 200;
- int slen;
- unsigned int cent_loop = 0;
- int elen = 0; /* entry length */
- nserror res;
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, code);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html"))
- goto fetch_about_imagecache_handler_aborted;
-
- /* page head */
- res = ssenddataf(ctx,
- "<html>\n<head>\n"
- "<title>NetSurf Browser Image Cache Status</title>\n"
- "<link rel=\"stylesheet\" type=\"text/css\" "
- "href=\"resource:internal.css\">\n"
- "</head>\n"
- "<body id =\"cachelist\">\n"
- "<p class=\"banner\">"
- "<a href=\"http://www.netsurf-browser.org/\">"
- "<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
- "</p>\n"
- "<h1>NetSurf Browser Image Cache Status</h1>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_imagecache_handler_aborted;
- }
-
- /* image cache summary */
- slen = image_cache_snsummaryf(buffer, sizeof(buffer),
- "<p>Configured limit of %a hysteresis of %b</p>\n"
- "<p>Total bitmap size in use %c (in %d)</p>\n"
- "<p>Age %es</p>\n"
- "<p>Peak size %f (in %g)</p>\n"
- "<p>Peak image count %h (size %i)</p>\n"
- "<p>Cache total/hit/miss/fail (counts) %j/%k/%l/%m "
- "(%pj%%/%pk%%/%pl%%/%pm%%)</p>\n"
- "<p>Cache total/hit/miss/fail (size) %n/%o/%q/%r "
- "(%pn%%/%po%%/%pq%%/%pr%%)</p>\n"
- "<p>Total images never rendered: %s "
- "(includes %t that were converted)</p>\n"
- "<p>Total number of excessive conversions: %u "
- "(from %v images converted more than once)"
- "</p>\n"
- "<p>Bitmap of size %w had most (%x) conversions</p>\n"
- "<h2>Current image cache contents</h2>\n");
- if (slen >= (int) (sizeof(buffer))) {
- goto fetch_about_imagecache_handler_aborted; /* overflow */
- }
-
- /* send image cache summary */
- msg.type = FETCH_DATA;
- msg.data.header_or_data.buf = (const uint8_t *) buffer;
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx)) {
- goto fetch_about_imagecache_handler_aborted;
- }
-
- /* image cache entry table */
- res = ssenddataf(ctx, "<p class=\"imagecachelist\">\n"
- "<strong>"
- "<span>Entry</span>"
- "<span>Content Key</span>"
- "<span>Redraw Count</span>"
- "<span>Conversion Count</span>"
- "<span>Last Redraw</span>"
- "<span>Bitmap Age</span>"
- "<span>Bitmap Size</span>"
- "<span>Source</span>"
- "</strong>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_imagecache_handler_aborted;
- }
-
- slen = 0;
- do {
- elen = image_cache_snentryf(buffer + slen,
- sizeof buffer - slen,
- cent_loop,
- "<a href=\"%U\">"
- "<span>%e</span>"
- "<span>%k</span>"
- "<span>%r</span>"
- "<span>%c</span>"
- "<span>%a</span>"
- "<span>%g</span>"
- "<span>%s</span>"
- "<span>%o</span>"
- "</a>\n");
- if (elen <= 0)
- break; /* last option */
-
- if (elen >= (int) (sizeof buffer - slen)) {
- /* last entry would not fit in buffer, submit buffer */
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_imagecache_handler_aborted;
- slen = 0;
- } else {
- /* normal addition */
- slen += elen;
- cent_loop++;
- }
- } while (elen > 0);
-
- slen += snprintf(buffer + slen, sizeof buffer - slen,
- "</p>\n</body>\n</html>\n");
-
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_imagecache_handler_aborted;
-
- fetch_about_send_finished(ctx);
-
- return true;
-
-fetch_about_imagecache_handler_aborted:
- return false;
-}
-
-
-/**
- * Handler to generate about scheme config page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_config_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
- char buffer[1024];
- int slen = 0;
- unsigned int opt_loop = 0;
- int elen = 0; /* entry length */
- nserror res;
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html")) {
- goto fetch_about_config_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<html>\n<head>\n"
- "<title>NetSurf Browser Config</title>\n"
- "<link rel=\"stylesheet\" type=\"text/css\" "
- "href=\"resource:internal.css\">\n"
- "</head>\n"
- "<body id =\"configlist\">\n"
- "<p class=\"banner\">"
- "<a href=\"http://www.netsurf-browser.org/\">"
- "<img src=\"resource:netsurf.png\""
- " alt=\"NetSurf\"></a>"
- "</p>\n"
- "<h1>NetSurf Browser Config</h1>\n"
- "<table class=\"config\">\n"
- "<tr><th>Option</th>"
- "<th>Type</th>"
- "<th>Provenance</th>"
- "<th>Setting</th></tr>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_config_handler_aborted;
- }
-
- msg.type = FETCH_DATA;
- msg.data.header_or_data.buf = (const uint8_t *) buffer;
-
- do {
- elen = nsoption_snoptionf(buffer + slen,
- sizeof buffer - slen,
- opt_loop,
- "<tr><th>%k</th><td>%t</td><td>%p</td><td>%V</td></tr>\n");
- if (elen <= 0)
- break; /* last option */
-
- if (elen >= (int) (sizeof buffer - slen)) {
- /* last entry would not fit in buffer, submit buffer */
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_config_handler_aborted;
- slen = 0;
- } else {
- /* normal addition */
- slen += elen;
- opt_loop++;
- }
- } while (elen > 0);
-
- slen += snprintf(buffer + slen, sizeof buffer - slen,
- "</table>\n</body>\n</html>\n");
-
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_config_handler_aborted;
-
- fetch_about_send_finished(ctx);
-
- return true;
-
-fetch_about_config_handler_aborted:
- return false;
-}
-
-
-/**
- * Generate the text of a Choices file which represents the current
- * in use options.
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_choices_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
- char buffer[1024];
- int code = 200;
- int slen;
- unsigned int opt_loop = 0;
- int res = 0;
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, code);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
- goto fetch_about_choices_handler_aborted;
-
- msg.type = FETCH_DATA;
- msg.data.header_or_data.buf = (const uint8_t *) buffer;
-
- slen = snprintf(buffer, sizeof buffer,
- "# Automatically generated current NetSurf browser Choices\n");
-
- do {
- res = nsoption_snoptionf(buffer + slen,
- sizeof buffer - slen,
- opt_loop,
- "%k:%v\n");
- if (res <= 0)
- break; /* last option */
-
- if (res >= (int) (sizeof buffer - slen)) {
- /* last entry would not fit in buffer, submit buffer */
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_choices_handler_aborted;
- slen = 0;
- } else {
- /* normal addition */
- slen += res;
- opt_loop++;
- }
- } while (res > 0);
-
- msg.data.header_or_data.len = slen;
- if (fetch_about_send_callback(&msg, ctx))
- goto fetch_about_choices_handler_aborted;
-
- fetch_about_send_finished(ctx);
-
- return true;
-
-fetch_about_choices_handler_aborted:
- return false;
-}
-
-
-typedef struct {
- const char *leaf;
- const char *modtype;
-} modification_t;
-
-/**
- * Generate the text of an svn testament which represents the current
- * build-tree status
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_testament_handler(struct fetch_about_context *ctx)
-{
- nserror res;
- static modification_t modifications[] = WT_MODIFICATIONS;
- int modidx; /* midification index */
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
- goto fetch_about_testament_handler_aborted;
-
- res = ssenddataf(ctx,
- "# Automatically generated by NetSurf build system\n\n");
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
-
- res = ssenddataf(ctx,
-#if defined(WT_BRANCHISTRUNK) || defined(WT_BRANCHISMASTER)
- "# This is a *DEVELOPMENT* build from the main line.\n\n"
-#elif defined(WT_BRANCHISTAG) && (WT_MODIFIED == 0)
- "# This is a tagged build of NetSurf\n"
-#ifdef WT_TAGIS
- "# The tag used was '" WT_TAGIS "'\n\n"
-#else
- "\n"
-#endif
-#elif defined(WT_NO_SVN) || defined(WT_NO_GIT)
- "# This NetSurf was built outside of our revision "
- "control environment.\n"
- "# This testament is therefore not very useful.\n\n"
-#else
- "# This NetSurf was built from a branch (" WT_BRANCHPATH ").\n\n"
-#endif
-#if defined(CI_BUILD)
- "# This build carries the CI build number '" CI_BUILD "'\n\n"
-#endif
- );
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "Built by %s (%s) from %s at revision %s on %s\n\n",
- GECOS, USERNAME, WT_BRANCHPATH, WT_REVID, WT_COMPILEDATE);
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
-
- res = ssenddataf(ctx, "Built on %s in %s\n\n", WT_HOSTNAME, WT_ROOT);
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
-
- if (WT_MODIFIED > 0) {
- res = ssenddataf(ctx,
- "Working tree has %d modification%s\n\n",
- WT_MODIFIED, WT_MODIFIED == 1 ? "" : "s");
- } else {
- res = ssenddataf(ctx, "Working tree is not modified.\n");
- }
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
-
- for (modidx = 0; modidx < WT_MODIFIED; ++modidx) {
- res = ssenddataf(ctx,
- " %s %s\n",
- modifications[modidx].modtype,
- modifications[modidx].leaf);
- if (res != NSERROR_OK) {
- goto fetch_about_testament_handler_aborted;
- }
- }
-
- fetch_about_send_finished(ctx);
-
- return true;
-
-fetch_about_testament_handler_aborted:
- return false;
-}
-
-
-/**
- * Handler to generate about scheme logo page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_logo_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
-
- /* content is going to return redirect */
- fetch_set_http_code(ctx->fetchh, 302);
-
- msg.type = FETCH_REDIRECT;
- msg.data.redirect = "resource:netsurf.png";
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-}
-
-
-/**
- * Handler to generate about scheme welcome page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_welcome_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
-
- /* content is going to return redirect */
- fetch_set_http_code(ctx->fetchh, 302);
-
- msg.type = FETCH_REDIRECT;
- msg.data.redirect = "resource:welcome.html";
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-}
-
-
-/**
- * Handler to generate about scheme maps page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_maps_handler(struct fetch_about_context *ctx)
-{
- fetch_msg msg;
-
- /* content is going to return redirect */
- fetch_set_http_code(ctx->fetchh, 302);
-
- msg.type = FETCH_REDIRECT;
- msg.data.redirect = "resource:maps.html";
-
- fetch_about_send_callback(&msg, ctx);
-
- return true;
-}
-
-
-/**
- * generate the description of the login query
- */
-static nserror
-get_authentication_description(struct nsurl *url,
- const char *realm,
- const char *username,
- const char *password,
- char **out_str)
-{
- nserror res;
- char *url_s;
- size_t url_l;
- char *str = NULL;
- int slen;
- const char *key;
-
- res = nsurl_get(url, NSURL_HOST, &url_s, &url_l);
- if (res != NSERROR_OK) {
- return res;
- }
-
- if ((*username == 0) && (*password == 0)) {
- key = "LoginDescription";
- } else {
- key = "LoginAgain";
- }
-
- str = messages_get_buff(key, url_s, realm);
- NSLOG(netsurf, INFO,
- "key:%s url:%s realm:%s str:%s", key, url_s, realm, str);
-
- if ((str != NULL) && (strcmp(key, str) != 0)) {
- *out_str = str;
- } else {
- /* no message so fallback */
- slen = snprintf(str, 0, authentication_description_fallback,
- url_s, realm) + 1;
- str = malloc(slen);
- if (str == NULL) {
- res = NSERROR_NOMEM;
- } else {
- snprintf(str, slen, authentication_description_fallback,
- url_s, realm);
- *out_str = str;
- }
- }
-
- free(url_s);
-
- return res;
-}
-
-
-/**
- * Handler to generate about scheme authentication query page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_query_auth_handler(struct fetch_about_context *ctx)
-{
- nserror res;
- char *url_s;
- size_t url_l;
- const char *realm = "";
- const char *username = "";
- const char *password = "";
- const char *title;
- char *description = NULL;
- struct nsurl *siteurl = NULL;
- const struct fetch_multipart_data *curmd; /* mutipart data iterator */
-
- /* extract parameters from multipart post data */
- curmd = ctx->multipart;
- while (curmd != NULL) {
- if (strcmp(curmd->name, "siteurl") == 0) {
- res = nsurl_create(curmd->value, &siteurl);
- if (res != NSERROR_OK) {
- return fetch_about_srverror(ctx);
- }
- } else if (strcmp(curmd->name, "realm") == 0) {
- realm = curmd->value;
- } else if (strcmp(curmd->name, "username") == 0) {
- username = curmd->value;
- } else if (strcmp(curmd->name, "password") == 0) {
- password = curmd->value;
- }
- curmd = curmd->next;
- }
-
- if (siteurl == NULL) {
- return fetch_about_srverror(ctx);
- }
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
-
- title = messages_get("LoginTitle");
-
- res = ssenddataf(ctx,
- "<html>\n<head>\n"
- "<title>%s</title>\n"
- "<link rel=\"stylesheet\" type=\"text/css\" "
- "href=\"resource:internal.css\">\n"
- "</head>\n"
- "<body id =\"authentication\">\n"
- "<h1>%s</h1>\n",
- title, title);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<form method=\"post\""
- " enctype=\"multipart/form-data\">");
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = get_authentication_description(siteurl,
- realm,
- username,
- password,
- &description);
- if (res == NSERROR_OK) {
- res = ssenddataf(ctx, "<p>%s</p>", description);
- free(description);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
- }
-
- res = ssenddataf(ctx, "<table>");
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<tr>"
- "<th><label for=\"name\">%s:</label></th>"
- "<td><input type=\"text\" id=\"username\" "
- "name=\"username\" value=\"%s\"></td>"
- "</tr>",
- messages_get("Username"), username);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<tr>"
- "<th><label for=\"password\">%s:</label></th>"
- "<td><input type=\"password\" id=\"password\" "
- "name=\"password\" value=\"%s\"></td>"
- "</tr>",
- messages_get("Password"), password);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx, "</table>");
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<div id=\"buttons\">"
- "<input type=\"submit\" id=\"login\" name=\"login\" "
- "value=\"%s\" class=\"default-action\">"
- "<input type=\"submit\" id=\"cancel\" name=\"cancel\" "
- "value=\"%s\">"
- "</div>",
- messages_get("Login"),
- messages_get("Cancel"));
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
- if (res != NSERROR_OK) {
- url_s = strdup("");
- }
- res = ssenddataf(ctx,
- "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
- url_s);
- free(url_s);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<input type=\"hidden\" name=\"realm\" value=\"%s\">",
- realm);
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- res = ssenddataf(ctx, "</form></body>\n</html>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_query_auth_handler_aborted;
- }
-
- fetch_about_send_finished(ctx);
-
- nsurl_unref(siteurl);
-
- return true;
-
-fetch_about_query_auth_handler_aborted:
-
- nsurl_unref(siteurl);
-
- return false;
-}
-
-
-/**
- * generate the description of the privacy query
- */
-static nserror get_privacy_description(struct nsurl *url, char **out_str)
-{
- nserror res;
- char *url_s;
- size_t url_l;
- char *str = NULL;
- const char *key = "PrivacyDescription";
-
- /* get the host in question */
- res = nsurl_get(url, NSURL_HOST, &url_s, &url_l);
- if (res != NSERROR_OK) {
- return res;
- }
-
- /* obtain the description with the url substituted */
- str = messages_get_buff(key, url_s);
- if ((str != NULL) && (strcmp(key, str) == 0)) {
- /* the returned string was simply the key */
- free(str);
- str = NULL;
- }
- if (str == NULL) {
- /* failed to get suitable translated message text so
- * fall back to basic english.
- */
- int slen;
- slen = snprintf(str, 0, privacy_description_fallback, url_s) + 1;
- str = malloc(slen);
- if (str != NULL) {
- snprintf(str, slen, privacy_description_fallback, url_s);
- }
- }
-
- if (str == NULL) {
- res = NSERROR_NOMEM;
- } else {
- *out_str = str;
- }
- free(url_s);
-
- return res;
-}
-
-
-/**
- * Handler to generate about scheme privacy query page
- *
- * \param ctx The fetcher context.
- * \return true if handled false if aborted.
- */
-static bool fetch_about_query_privacy_handler(struct fetch_about_context *ctx)
-{
- nserror res;
- char *url_s;
- size_t url_l;
- const char *reason = "";
- const char *title;
- struct nsurl *siteurl = NULL;
- char *description = NULL;
- const struct fetch_multipart_data *curmd; /* mutipart data iterator */
-
- /* extract parameters from multipart post data */
- curmd = ctx->multipart;
- while (curmd != NULL) {
- if (strcmp(curmd->name, "siteurl") == 0) {
- res = nsurl_create(curmd->value, &siteurl);
- if (res != NSERROR_OK) {
- return fetch_about_srverror(ctx);
- }
- } else if (strcmp(curmd->name, "reason") == 0) {
- reason = curmd->value;
- }
- curmd = curmd->next;
- }
-
- if (siteurl == NULL) {
- return fetch_about_srverror(ctx);
- }
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- title = messages_get("PrivacyTitle");
- res = ssenddataf(ctx,
- "<html>\n<head>\n"
- "<title>%s</title>\n"
- "<link rel=\"stylesheet\" type=\"text/css\" "
- "href=\"resource:internal.css\">\n"
- "</head>\n"
- "<body id =\"privacy\">\n"
- "<h1>%s</h1>\n",
- title, title);
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<form method=\"post\""
- " enctype=\"multipart/form-data\">");
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- res = get_privacy_description(siteurl, &description);
- if (res == NSERROR_OK) {
- res = ssenddataf(ctx, "<div><p>%s</p></div>", description);
- free(description);
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
- }
- res = ssenddataf(ctx, "<div><p>%s</p></div>", reason);
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- res = ssenddataf(ctx,
- "<div id=\"buttons\">"
- "<input type=\"submit\" id=\"back\" name=\"back\" "
- "value=\"%s\" class=\"default-action\">"
- "<input type=\"submit\" id=\"proceed\" name=\"proceed\" "
- "value=\"%s\">"
- "</div>",
- messages_get("Backtosafety"),
- messages_get("Proceed"));
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
- if (res != NSERROR_OK) {
- url_s = strdup("");
- }
- res = ssenddataf(ctx,
- "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
- url_s);
- free(url_s);
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- res = ssenddataf(ctx, "</form></body>\n</html>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_query_ssl_handler_aborted;
- }
-
- fetch_about_send_finished(ctx);
-
- nsurl_unref(siteurl);
-
- return true;
-
-fetch_about_query_ssl_handler_aborted:
- nsurl_unref(siteurl);
-
- return false;
-}
-
-
-/* Forward declaration because this handler requires the handler table. */
-static bool fetch_about_about_handler(struct fetch_about_context *ctx);
-
-/**
- * List of about paths and their handlers
- */
-struct about_handlers about_handler_list[] = {
- {
- "credits",
- SLEN("credits"),
- NULL,
- fetch_about_credits_handler,
- false
- },
- {
- "licence",
- SLEN("licence"),
- NULL,
- fetch_about_licence_handler,
- false
- },
- {
- "license",
- SLEN("license"),
- NULL,
- fetch_about_licence_handler,
- true
- },
- {
- "welcome",
- SLEN("welcome"),
- NULL,
- fetch_about_welcome_handler,
- false
- },
- {
- "maps",
- SLEN("maps"),
- NULL,
- fetch_about_maps_handler,
- false
- },
- {
- "config",
- SLEN("config"),
- NULL,
- fetch_about_config_handler,
- false
- },
- {
- "Choices",
- SLEN("Choices"),
- NULL,
- fetch_about_choices_handler,
- false
- },
- {
- "testament",
- SLEN("testament"),
- NULL,
- fetch_about_testament_handler,
- false
- },
- {
- "about",
- SLEN("about"),
- NULL,
- fetch_about_about_handler,
- true
- },
- {
- "logo",
- SLEN("logo"),
- NULL,
- fetch_about_logo_handler,
- true
- },
- {
- /* details about the image cache */
- "imagecache",
- SLEN("imagecache"),
- NULL,
- fetch_about_imagecache_handler,
- true
- },
- {
- /* The default blank page */
- "blank",
- SLEN("blank"),
- NULL,
- fetch_about_blank_handler,
- true
- },
- {
- "query/auth",
- SLEN("query/auth"),
- NULL,
- fetch_about_query_auth_handler,
- true
- },
- {
- "query/ssl",
- SLEN("query/ssl"),
- NULL,
- fetch_about_query_privacy_handler,
- true
- }
-};
-
-#define about_handler_list_len \
- (sizeof(about_handler_list) / sizeof(struct about_handlers))
-
-/**
- * List all the valid about: paths available
- *
- * \param ctx The fetch context.
- * \return true for sucess or false to generate an error.
- */
-static bool fetch_about_about_handler(struct fetch_about_context *ctx)
-{
- nserror res;
- unsigned int abt_loop = 0;
-
- /* content is going to return ok */
- fetch_set_http_code(ctx->fetchh, 200);
-
- /* content type */
- if (fetch_about_send_header(ctx, "Content-Type: text/html"))
- goto fetch_about_config_handler_aborted;
-
- res = ssenddataf(ctx,
- "<html>\n<head>\n"
- "<title>NetSurf List of About pages</title>\n"
- "<link rel=\"stylesheet\" type=\"text/css\" "
- "href=\"resource:internal.css\">\n"
- "</head>\n"
- "<body id =\"aboutlist\">\n"
- "<p class=\"banner\">"
- "<a href=\"http://www.netsurf-browser.org/\">"
- "<img src=\"resource:netsurf.png\" alt=\"NetSurf\"></a>"
- "</p>\n"
- "<h1>NetSurf List of About pages</h1>\n"
- "<ul>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_config_handler_aborted;
- }
-
- for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
-
- /* Skip over hidden entries */
- if (about_handler_list[abt_loop].hidden)
- continue;
-
- res = ssenddataf(ctx,
- "<li><a href=\"about:%s\">about:%s</a></li>\n",
- about_handler_list[abt_loop].name,
- about_handler_list[abt_loop].name);
- if (res != NSERROR_OK) {
- goto fetch_about_config_handler_aborted;
- }
- }
-
- res = ssenddataf(ctx, "</ul>\n</body>\n</html>\n");
- if (res != NSERROR_OK) {
- goto fetch_about_config_handler_aborted;
- }
-
- fetch_about_send_finished(ctx);
-
- return true;
-
-fetch_about_config_handler_aborted:
- return false;
-}
-
-
-/**
- * callback to initialise the about scheme fetcher.
- */
-static bool fetch_about_initialise(lwc_string *scheme)
-{
- unsigned int abt_loop = 0;
- lwc_error error;
-
- for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
- error = lwc_intern_string(about_handler_list[abt_loop].name,
- about_handler_list[abt_loop].name_len,
- &about_handler_list[abt_loop].lname);
- if (error != lwc_error_ok) {
- while (abt_loop-- != 0) {
- lwc_string_unref(about_handler_list[abt_loop].lname);
- }
- return false;
- }
- }
-
- return true;
-}
-
-
-/**
- * callback to finalise the about scheme fetcher.
- */
-static void fetch_about_finalise(lwc_string *scheme)
-{
- unsigned int abt_loop = 0;
- for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
- lwc_string_unref(about_handler_list[abt_loop].lname);
- }
-}
-
-
-static bool fetch_about_can_fetch(const nsurl *url)
-{
- return true;
-}
-
-
-/**
- * callback to set up a about scheme fetch.
- *
- * \param post_urlenc post data in urlenc format, owned by the llcache object
- * hence valid the entire lifetime of the fetch.
- * \param post_multipart post data in multipart format, owned by the llcache
- * object hence valid the entire lifetime of the fetch.
- */
-static void *
-fetch_about_setup(struct fetch *fetchh,
- nsurl *url,
- bool only_2xx,
- bool downgrade_tls,
- const char *post_urlenc,
- const struct fetch_multipart_data *post_multipart,
- const char **headers)
-{
- struct fetch_about_context *ctx;
- unsigned int handler_loop;
- lwc_string *path;
- bool match;
-
- ctx = calloc(1, sizeof(*ctx));
- if (ctx == NULL)
- return NULL;
-
- path = nsurl_get_component(url, NSURL_PATH);
-
- for (handler_loop = 0;
- handler_loop < about_handler_list_len;
- handler_loop++) {
- ctx->handler = about_handler_list[handler_loop].handler;
- if (lwc_string_isequal(path,
- about_handler_list[handler_loop].lname,
- &match) == lwc_error_ok && match) {
- break;
- }
- }
-
- if (path != NULL)
- lwc_string_unref(path);
-
- ctx->fetchh = fetchh;
- ctx->url = nsurl_ref(url);
- ctx->multipart = post_multipart;
-
- RING_INSERT(ring, ctx);
-
- return ctx;
-}
-
-
-/**
- * callback to free a about scheme fetch
- */
-static void fetch_about_free(void *ctx)
-{
- struct fetch_about_context *c = ctx;
- nsurl_unref(c->url);
- RING_REMOVE(ring, c);
- free(ctx);
-}
-
-
-/**
- * callback to start an about scheme fetch
- */
-static bool fetch_about_start(void *ctx)
-{
- return true;
-}
-
-
-/**
- * callback to abort a about fetch
- */
-static void fetch_about_abort(void *ctx)
-{
- struct fetch_about_context *c = ctx;
-
- /* To avoid the poll loop having to deal with the fetch context
- * disappearing from under it, we simply flag the abort here.
- * The poll loop itself will perform the appropriate cleanup.
- */
- c->aborted = true;
-}
-
-
-/**
- * callback to poll for additional about fetch contents
- */
-static void fetch_about_poll(lwc_string *scheme)
-{
- struct fetch_about_context *c, *next;
-
- if (ring == NULL) return;
-
- /* Iterate over ring, processing each pending fetch */
- c = ring;
- do {
- /* Ignore fetches that have been flagged as locked.
- * This allows safe re-entrant calls to this function.
- * Re-entrancy can occur if, as a result of a callback,
- * the interested party causes fetch_poll() to be called
- * again.
- */
- if (c->locked == true) {
- next = c->r_next;
- continue;
- }
-
- /* Only process non-aborted fetches */
- if (c->aborted == false) {
- /* about fetches can be processed in one go */
- c->handler(c);
- }
-
- /* Compute next fetch item at the last possible moment
- * as processing this item may have added to the ring
- */
- next = c->r_next;
-
- fetch_remove_from_queues(c->fetchh);
- fetch_free(c->fetchh);
-
- /* Advance to next ring entry, exiting if we've reached
- * the start of the ring or the ring has become empty
- */
- } while ( (c = next) != ring && ring != NULL);
-}
-
-
-nserror fetch_about_register(void)
-{
- lwc_string *scheme = lwc_string_ref(corestring_lwc_about);
- const struct fetcher_operation_table fetcher_ops = {
- .initialise = fetch_about_initialise,
- .acceptable = fetch_about_can_fetch,
- .setup = fetch_about_setup,
- .start = fetch_about_start,
- .abort = fetch_about_abort,
- .free = fetch_about_free,
- .poll = fetch_about_poll,
- .finalise = fetch_about_finalise
- };
-
- return fetcher_add(scheme, &fetcher_ops);
-}
diff --git a/content/fetchers/about/Makefile b/content/fetchers/about/Makefile
new file mode 100644
index 000000000..4f12a0626
--- /dev/null
+++ b/content/fetchers/about/Makefile
@@ -0,0 +1,20 @@
+# about fetcher sources
+
+S_FETCHER_ABOUT := \
+ about.c \
+ blank.c \
+ certificate.c \
+ chart.c \
+ choices.c \
+ config.c \
+ imagecache.c \
+ nscolours.c \
+ query.c \
+ query_auth.c \
+ query_fetcherror.c \
+ query_privacy.c \
+ query_timeout.c \
+ testament.c
+
+# The following files depend on the testament
+content/fetchers/about/testament.c: testament $(OBJROOT)/testament.h
diff --git a/content/fetchers/about/about.c b/content/fetchers/about/about.c
new file mode 100644
index 000000000..651894249
--- /dev/null
+++ b/content/fetchers/about/about.c
@@ -0,0 +1,758 @@
+/*
+ * Copyright 2011 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ *
+ * URL handling for the "about" scheme.
+ *
+ * Based on the data fetcher by Rob Kendrick
+ * This fetcher provides a simple scheme for the user to access
+ * information from the browser from a known, fixed URL.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "netsurf/inttypes.h"
+
+#include "utils/errors.h"
+#include "utils/nsurl.h"
+#include "utils/corestrings.h"
+#include "utils/utils.h"
+#include "utils/ring.h"
+
+#include "content/fetch.h"
+#include "content/fetchers.h"
+
+#include "private.h"
+#include "about.h"
+#include "blank.h"
+#include "certificate.h"
+#include "config.h"
+#include "chart.h"
+#include "choices.h"
+#include "imagecache.h"
+#include "nscolours.h"
+#include "query.h"
+#include "query_auth.h"
+#include "query_fetcherror.h"
+#include "query_privacy.h"
+#include "query_timeout.h"
+#include "atestament.h"
+
+typedef bool (*fetch_about_handler)(struct fetch_about_context *);
+
+/**
+ * Context for an about fetch
+ */
+struct fetch_about_context {
+ struct fetch_about_context *r_next, *r_prev;
+
+ struct fetch *fetchh; /**< Handle for this fetch */
+
+ bool aborted; /**< Flag indicating fetch has been aborted */
+ bool locked; /**< Flag indicating entry is already entered */
+
+ nsurl *url; /**< The full url the fetch refers to */
+
+ const struct fetch_multipart_data *multipart; /**< post data */
+
+ fetch_about_handler handler;
+};
+
+static struct fetch_about_context *ring = NULL;
+
+/**
+ * handler info for about scheme
+ */
+struct about_handlers {
+ const char *name; /**< name to match in url */
+ int name_len;
+ lwc_string *lname; /**< Interned name */
+ fetch_about_handler handler; /**< handler for the url */
+ bool hidden; /**< If entry should be hidden in listing */
+};
+
+
+
+/**
+ * issue fetch callbacks with locking
+ */
+static bool
+fetch_about_send_callback(const fetch_msg *msg, struct fetch_about_context *ctx)
+{
+ ctx->locked = true;
+ fetch_send_callback(msg, ctx->fetchh);
+ ctx->locked = false;
+
+ return ctx->aborted;
+}
+
+/* exported interface documented in about/private.h */
+bool
+fetch_about_send_finished(struct fetch_about_context *ctx)
+{
+ fetch_msg msg;
+ msg.type = FETCH_FINISHED;
+ return fetch_about_send_callback(&msg, ctx);
+}
+
+/* exported interface documented in about/private.h */
+bool fetch_about_set_http_code(struct fetch_about_context *ctx, long code)
+{
+ fetch_set_http_code(ctx->fetchh, code);
+
+ return ctx->aborted;
+}
+
+/* exported interface documented in about/private.h */
+bool
+fetch_about_send_header(struct fetch_about_context *ctx, const char *fmt, ...)
+{
+ char header[64];
+ fetch_msg msg;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ vsnprintf(header, sizeof header, fmt, ap);
+
+ va_end(ap);
+
+ msg.type = FETCH_HEADER;
+ msg.data.header_or_data.buf = (const uint8_t *) header;
+ msg.data.header_or_data.len = strlen(header);
+
+ return fetch_about_send_callback(&msg, ctx);
+}
+
+/* exported interface documented in about/private.h */
+nserror
+fetch_about_senddata(struct fetch_about_context *ctx, const uint8_t *data, size_t data_len)
+{
+ fetch_msg msg;
+
+ msg.type = FETCH_DATA;
+ msg.data.header_or_data.buf = data;
+ msg.data.header_or_data.len = data_len;
+
+ if (fetch_about_send_callback(&msg, ctx)) {
+ return NSERROR_INVALID;
+ }
+
+ return NSERROR_OK;
+}
+
+/* exported interface documented in about/private.h */
+nserror
+fetch_about_ssenddataf(struct fetch_about_context *ctx, const char *fmt, ...)
+{
+ char buffer[1024];
+ char *dbuff;
+ fetch_msg msg;
+ va_list ap;
+ int slen;
+
+ va_start(ap, fmt);
+
+ slen = vsnprintf(buffer, sizeof(buffer), fmt, ap);
+
+ va_end(ap);
+
+ if (slen < (int)sizeof(buffer)) {
+ msg.type = FETCH_DATA;
+ msg.data.header_or_data.buf = (const uint8_t *) buffer;
+ msg.data.header_or_data.len = slen;
+
+ if (fetch_about_send_callback(&msg, ctx)) {
+ return NSERROR_INVALID;
+ }
+
+ return NSERROR_OK;
+ }
+
+ dbuff = malloc(slen + 1);
+ if (dbuff == NULL) {
+ return NSERROR_NOSPACE;
+ }
+
+ va_start(ap, fmt);
+
+ slen = vsnprintf(dbuff, slen + 1, fmt, ap);
+
+ va_end(ap);
+
+ msg.type = FETCH_DATA;
+ msg.data.header_or_data.buf = (const uint8_t *)dbuff;
+ msg.data.header_or_data.len = slen;
+
+ if (fetch_about_send_callback(&msg, ctx)) {
+ free(dbuff);
+ return NSERROR_INVALID;
+ }
+
+ free(dbuff);
+ return NSERROR_OK;
+}
+
+
+/* exported interface documented in about/private.h */
+nsurl *fetch_about_get_url(struct fetch_about_context *ctx)
+{
+ return ctx->url;
+}
+
+
+/* exported interface documented in about/private.h */
+const struct fetch_multipart_data *
+fetch_about_get_multipart(struct fetch_about_context *ctx)
+{
+ return ctx->multipart;
+}
+
+
+/* exported interface documented in about/private.h */
+bool fetch_about_srverror(struct fetch_about_context *ctx)
+{
+ nserror res;
+
+ fetch_set_http_code(ctx->fetchh, 500);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
+ return false;
+
+ res = fetch_about_ssenddataf(ctx, "Server error 500");
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+}
+
+
+/**
+ * Handler to generate about scheme credits page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+static bool fetch_about_credits_handler(struct fetch_about_context *ctx)
+{
+ fetch_msg msg;
+
+ /* content is going to return redirect */
+ fetch_set_http_code(ctx->fetchh, 302);
+
+ msg.type = FETCH_REDIRECT;
+ msg.data.redirect = "resource:credits.html";
+
+ fetch_about_send_callback(&msg, ctx);
+
+ return true;
+}
+
+
+/**
+ * Handler to generate about scheme licence page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+static bool fetch_about_licence_handler(struct fetch_about_context *ctx)
+{
+ fetch_msg msg;
+
+ /* content is going to return redirect */
+ fetch_set_http_code(ctx->fetchh, 302);
+
+ msg.type = FETCH_REDIRECT;
+ msg.data.redirect = "resource:licence.html";
+
+ fetch_about_send_callback(&msg, ctx);
+
+ return true;
+}
+
+
+/**
+ * Handler to generate about scheme logo page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+static bool fetch_about_logo_handler(struct fetch_about_context *ctx)
+{
+ fetch_msg msg;
+
+ /* content is going to return redirect */
+ fetch_set_http_code(ctx->fetchh, 302);
+
+ msg.type = FETCH_REDIRECT;
+ msg.data.redirect = "resource:netsurf.png";
+
+ fetch_about_send_callback(&msg, ctx);
+
+ return true;
+}
+
+
+/**
+ * Handler to generate about scheme welcome page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+static bool fetch_about_welcome_handler(struct fetch_about_context *ctx)
+{
+ fetch_msg msg;
+
+ /* content is going to return redirect */
+ fetch_set_http_code(ctx->fetchh, 302);
+
+ msg.type = FETCH_REDIRECT;
+ msg.data.redirect = "resource:welcome.html";
+
+ fetch_about_send_callback(&msg, ctx);
+
+ return true;
+}
+
+
+/* Forward declaration because this handler requires the handler table. */
+static bool fetch_about_about_handler(struct fetch_about_context *ctx);
+
+/**
+ * List of about paths and their handlers
+ */
+struct about_handlers about_handler_list[] = {
+ {
+ "credits",
+ SLEN("credits"),
+ NULL,
+ fetch_about_credits_handler,
+ false
+ },
+ {
+ "licence",
+ SLEN("licence"),
+ NULL,
+ fetch_about_licence_handler,
+ false
+ },
+ {
+ "license",
+ SLEN("license"),
+ NULL,
+ fetch_about_licence_handler,
+ true
+ },
+ {
+ "welcome",
+ SLEN("welcome"),
+ NULL,
+ fetch_about_welcome_handler,
+ false
+ },
+ {
+ "config",
+ SLEN("config"),
+ NULL,
+ fetch_about_config_handler,
+ false
+ },
+ {
+ "Choices",
+ SLEN("Choices"),
+ NULL,
+ fetch_about_choices_handler,
+ false
+ },
+ {
+ "testament",
+ SLEN("testament"),
+ NULL,
+ fetch_about_testament_handler,
+ false
+ },
+ {
+ "about",
+ SLEN("about"),
+ NULL,
+ fetch_about_about_handler,
+ true
+ },
+ {
+ "nscolours.css",
+ SLEN("nscolours.css"),
+ NULL,
+ fetch_about_nscolours_handler,
+ true
+ },
+ {
+ "logo",
+ SLEN("logo"),
+ NULL,
+ fetch_about_logo_handler,
+ true
+ },
+ {
+ /* details about the image cache */
+ "imagecache",
+ SLEN("imagecache"),
+ NULL,
+ fetch_about_imagecache_handler,
+ true
+ },
+ {
+ /* The default blank page */
+ "blank",
+ SLEN("blank"),
+ NULL,
+ fetch_about_blank_handler,
+ true
+ },
+ {
+ /* details about a certificate */
+ "certificate",
+ SLEN("certificate"),
+ NULL,
+ fetch_about_certificate_handler,
+ true
+ },
+ {
+ /* chart generator */
+ "chart",
+ SLEN("chart"),
+ NULL,
+ fetch_about_chart_handler,
+ true
+ },
+ {
+ "query/auth",
+ SLEN("query/auth"),
+ NULL,
+ fetch_about_query_auth_handler,
+ true
+ },
+ {
+ "query/ssl",
+ SLEN("query/ssl"),
+ NULL,
+ fetch_about_query_privacy_handler,
+ true
+ },
+ {
+ "query/timeout",
+ SLEN("query/timeout"),
+ NULL,
+ fetch_about_query_timeout_handler,
+ true
+ },
+ {
+ "query/fetcherror",
+ SLEN("query/fetcherror"),
+ NULL,
+ fetch_about_query_fetcherror_handler,
+ true
+ }
+};
+
+#define about_handler_list_len \
+ (sizeof(about_handler_list) / sizeof(struct about_handlers))
+
+/**
+ * List all the valid about: paths available
+ *
+ * \param ctx The fetch context.
+ * \return true for sucess or false to generate an error.
+ */
+static bool fetch_about_about_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ unsigned int abt_loop = 0;
+
+ /* content is going to return ok */
+ fetch_set_http_code(ctx->fetchh, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html"))
+ goto fetch_about_config_handler_aborted;
+
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>List of NetSurf pages</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body class=\"ns-even-bg ns-even-fg ns-border\">\n"
+ "<h1 class =\"ns-border\">List of NetSurf pages</h1>\n"
+ "<ul>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+
+ for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
+
+ /* Skip over hidden entries */
+ if (about_handler_list[abt_loop].hidden)
+ continue;
+
+ res = fetch_about_ssenddataf(ctx,
+ "<li><a href=\"about:%s\">about:%s</a></li>\n",
+ about_handler_list[abt_loop].name,
+ about_handler_list[abt_loop].name);
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</ul>\n</body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_config_handler_aborted:
+ return false;
+}
+
+static bool
+fetch_about_404_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+
+ /* content is going to return 404 */
+ fetch_set_http_code(ctx->fetchh, 404);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/plain; charset=utf-8")) {
+ return false;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "Unknown page: %s", nsurl_access(ctx->url));
+ if (res != NSERROR_OK) {
+ return false;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+}
+
+/**
+ * callback to initialise the about scheme fetcher.
+ */
+static bool fetch_about_initialise(lwc_string *scheme)
+{
+ unsigned int abt_loop = 0;
+ lwc_error error;
+
+ for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
+ error = lwc_intern_string(about_handler_list[abt_loop].name,
+ about_handler_list[abt_loop].name_len,
+ &about_handler_list[abt_loop].lname);
+ if (error != lwc_error_ok) {
+ while (abt_loop-- != 0) {
+ lwc_string_unref(about_handler_list[abt_loop].lname);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * callback to finalise the about scheme fetcher.
+ */
+static void fetch_about_finalise(lwc_string *scheme)
+{
+ unsigned int abt_loop = 0;
+ for (abt_loop = 0; abt_loop < about_handler_list_len; abt_loop++) {
+ lwc_string_unref(about_handler_list[abt_loop].lname);
+ }
+}
+
+
+static bool fetch_about_can_fetch(const nsurl *url)
+{
+ return true;
+}
+
+
+/**
+ * callback to set up a about scheme fetch.
+ *
+ * \param post_urlenc post data in urlenc format, owned by the llcache object
+ * hence valid the entire lifetime of the fetch.
+ * \param post_multipart post data in multipart format, owned by the llcache
+ * object hence valid the entire lifetime of the fetch.
+ */
+static void *
+fetch_about_setup(struct fetch *fetchh,
+ nsurl *url,
+ bool only_2xx,
+ bool downgrade_tls,
+ const char *post_urlenc,
+ const struct fetch_multipart_data *post_multipart,
+ const char **headers)
+{
+ struct fetch_about_context *ctx;
+ unsigned int handler_loop;
+ lwc_string *path;
+ bool match;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ return NULL;
+
+ path = nsurl_get_component(url, NSURL_PATH);
+
+ for (handler_loop = 0;
+ handler_loop < about_handler_list_len;
+ handler_loop++) {
+ if (lwc_string_isequal(path,
+ about_handler_list[handler_loop].lname,
+ &match) == lwc_error_ok && match) {
+ ctx->handler = about_handler_list[handler_loop].handler;
+ break;
+ }
+ }
+
+ if (path != NULL)
+ lwc_string_unref(path);
+
+ ctx->fetchh = fetchh;
+ ctx->url = nsurl_ref(url);
+ ctx->multipart = post_multipart;
+
+ RING_INSERT(ring, ctx);
+
+ return ctx;
+}
+
+
+/**
+ * callback to free a about scheme fetch
+ */
+static void fetch_about_free(void *ctx)
+{
+ struct fetch_about_context *c = ctx;
+ nsurl_unref(c->url);
+ free(ctx);
+}
+
+
+/**
+ * callback to start an about scheme fetch
+ */
+static bool fetch_about_start(void *ctx)
+{
+ return true;
+}
+
+
+/**
+ * callback to abort a about fetch
+ */
+static void fetch_about_abort(void *ctx)
+{
+ struct fetch_about_context *c = ctx;
+
+ /* To avoid the poll loop having to deal with the fetch context
+ * disappearing from under it, we simply flag the abort here.
+ * The poll loop itself will perform the appropriate cleanup.
+ */
+ c->aborted = true;
+}
+
+
+/**
+ * callback to poll for additional about fetch contents
+ */
+static void fetch_about_poll(lwc_string *scheme)
+{
+ struct fetch_about_context *c, *save_ring = NULL;
+
+ /* Iterate over ring, processing each pending fetch */
+ while (ring != NULL) {
+ /* Take the first entry from the ring */
+ c = ring;
+ RING_REMOVE(ring, c);
+
+ /* Ignore fetches that have been flagged as locked.
+ * This allows safe re-entrant calls to this function.
+ * Re-entrancy can occur if, as a result of a callback,
+ * the interested party causes fetch_poll() to be called
+ * again.
+ */
+ if (c->locked == true) {
+ RING_INSERT(save_ring, c);
+ continue;
+ }
+
+ /* Only process non-aborted fetches */
+ if (c->aborted == false) {
+ /* about fetches can be processed in one go */
+ if (c->handler == NULL) {
+ fetch_about_404_handler(c);
+ } else {
+ c->handler(c);
+ }
+ }
+
+ /* And now finish */
+ fetch_remove_from_queues(c->fetchh);
+ fetch_free(c->fetchh);
+ }
+
+ /* Finally, if we saved any fetches which were locked, put them back
+ * into the ring for next time
+ */
+ ring = save_ring;
+}
+
+
+nserror fetch_about_register(void)
+{
+ lwc_string *scheme = lwc_string_ref(corestring_lwc_about);
+ const struct fetcher_operation_table fetcher_ops = {
+ .initialise = fetch_about_initialise,
+ .acceptable = fetch_about_can_fetch,
+ .setup = fetch_about_setup,
+ .start = fetch_about_start,
+ .abort = fetch_about_abort,
+ .free = fetch_about_free,
+ .poll = fetch_about_poll,
+ .finalise = fetch_about_finalise
+ };
+
+ return fetcher_add(scheme, &fetcher_ops);
+}
diff --git a/content/fetchers/about.h b/content/fetchers/about/about.h
index 944f84a59..bf6379709 100644
--- a/content/fetchers/about.h
+++ b/content/fetchers/about/about.h
@@ -16,12 +16,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-/** \file
- * about: URL method handler
+/**
+ * \file
+ * about scheme URL method handler
*/
-#ifndef NETSURF_CONTENT_FETCHERS_FETCH_ABOUT_H
-#define NETSURF_CONTENT_FETCHERS_FETCH_ABOUT_H
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_ABOUT_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_ABOUT_H
/**
* Register about scheme handler.
diff --git a/content/fetchers/about/atestament.h b/content/fetchers/about/atestament.h
new file mode 100644
index 000000000..1851e8f3c
--- /dev/null
+++ b/content/fetchers/about/atestament.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme testament handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_TESTAMENT_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_TESTAMENT_H
+
+/**
+ * Handler to generate about scheme testament page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_testament_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/blank.c b/content/fetchers/about/blank.c
new file mode 100644
index 000000000..8ad774512
--- /dev/null
+++ b/content/fetchers/about/blank.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme blank page
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "netsurf/types.h"
+#include "utils/errors.h"
+
+#include "private.h"
+#include "blank.h"
+
+/**
+ * Handler to generate about scheme cache page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_blank_handler(struct fetch_about_context *ctx)
+{
+ const char buffer[2] = { ' ', '\0' };
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html"))
+ goto fetch_about_blank_handler_aborted;
+
+ if (fetch_about_senddata(ctx, (const uint8_t *) buffer, strlen(buffer)) != NSERROR_OK)
+ goto fetch_about_blank_handler_aborted;
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_blank_handler_aborted:
+ return false;
+}
diff --git a/content/fetchers/about/blank.h b/content/fetchers/about/blank.h
new file mode 100644
index 000000000..09dcc1f6a
--- /dev/null
+++ b/content/fetchers/about/blank.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme blank handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_BLANK_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_BLANK_H
+
+/**
+ * Handler to generate about scheme blank page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_blank_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/certificate.c b/content/fetchers/about/certificate.c
new file mode 100644
index 000000000..6f634d22a
--- /dev/null
+++ b/content/fetchers/about/certificate.c
@@ -0,0 +1,1291 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme certificate page
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "utils/errors.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "netsurf/inttypes.h"
+#include "netsurf/ssl_certs.h"
+
+#include "private.h"
+#include "certificate.h"
+
+/**
+ * certificate name parameters
+ */
+struct ns_cert_name {
+ char *common_name;
+ char *organisation;
+ char *organisation_unit;
+ char *locality;
+ char *province;
+ char *country;
+};
+
+/**
+ * Certificate public key parameters
+ */
+struct ns_cert_pkey {
+ char *algor;
+ int size;
+ char *modulus;
+ char *exponent;
+ char *curve;
+ char *public;
+};
+
+/**
+ * Certificate subject alternative name
+ */
+struct ns_cert_san {
+ struct ns_cert_san *next;
+ char *name;
+};
+
+/**
+ * certificate information for certificate chain
+ */
+struct ns_cert_info {
+ struct ns_cert_name subject_name; /**< Subject details */
+ struct ns_cert_name issuer_name; /**< Issuer details */
+ struct ns_cert_pkey public_key; /**< public key details */
+ long version; /**< Certificate version */
+ char *not_before; /**< Valid from date */
+ char *not_after; /**< Valid to date */
+ int sig_type; /**< Signature type */
+ char *sig_algor; /**< Signature Algorithm */
+ char *serialnum; /**< Serial number */
+ char *sha1fingerprint; /**< fingerprint shar1 encoded */
+ char *sha256fingerprint; /**< fingerprint shar256 encoded */
+ struct ns_cert_san *san; /**< subject alternative names */
+ ssl_cert_err err; /**< Whatever is wrong with this certificate */
+};
+
+/**
+ * free all resources associated with a certificate information structure
+ */
+static nserror free_ns_cert_info(struct ns_cert_info *cinfo)
+{
+ struct ns_cert_san *san;
+
+ free(cinfo->subject_name.common_name);
+ free(cinfo->subject_name.organisation);
+ free(cinfo->subject_name.organisation_unit);
+ free(cinfo->subject_name.locality);
+ free(cinfo->subject_name.province);
+ free(cinfo->subject_name.country);
+ free(cinfo->issuer_name.common_name);
+ free(cinfo->issuer_name.organisation);
+ free(cinfo->issuer_name.organisation_unit);
+ free(cinfo->issuer_name.locality);
+ free(cinfo->issuer_name.province);
+ free(cinfo->issuer_name.country);
+ free(cinfo->public_key.algor);
+ free(cinfo->public_key.modulus);
+ free(cinfo->public_key.exponent);
+ free(cinfo->public_key.curve);
+ free(cinfo->public_key.public);
+ free(cinfo->not_before);
+ free(cinfo->not_after);
+ free(cinfo->sig_algor);
+ free(cinfo->serialnum);
+
+ /* free san list avoiding use after free */
+ san = cinfo->san;
+ while (san != NULL) {
+ struct ns_cert_san *next;
+ next = san->next;
+ free(san);
+ san = next;
+ }
+
+ free(cinfo);
+
+ return NSERROR_OK;
+}
+
+#ifdef WITH_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+/* OpenSSL 1.1.1 or LibreSSL */
+
+# if defined(LIBRESSL_VERSION_NUMBER)
+ /* LibreSSL */
+# if (LIBRESSL_VERSION_NUMBER < 0x3050000fL)
+ /* LibreSSL <3.5.0 */
+
+# if (LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+ /* LibreSSL <2.7.0 */
+static int ns_X509_get_signature_nid(X509 *cert)
+{
+ return OBJ_obj2nid(cert->cert_info->key->algor->algorithm);
+}
+
+static const unsigned char *ns_ASN1_STRING_get0_data(ASN1_STRING *asn1str)
+{
+ return (const unsigned char *)ASN1_STRING_data(asn1str);
+}
+# else
+# define ns_X509_get_signature_nid X509_get_signature_nid
+# define ns_ASN1_STRING_get0_data ASN1_STRING_get0_data
+# endif
+
+static const BIGNUM *ns_RSA_get0_n(const RSA *d)
+{
+ return d->n;
+}
+
+static const BIGNUM *ns_RSA_get0_e(const RSA *d)
+{
+ return d->e;
+}
+# else
+ /* LibreSSL >= 3.5.0 */
+# define ns_X509_get_signature_nid X509_get_signature_nid
+# define ns_ASN1_STRING_get0_data ASN1_STRING_get0_data
+# define ns_RSA_get0_n RSA_get0_n
+# define ns_RSA_get0_e RSA_get0_e
+# endif
+# else
+ /* OpenSSL 1.1.1 */
+# define ns_X509_get_signature_nid X509_get_signature_nid
+# define ns_ASN1_STRING_get0_data ASN1_STRING_get0_data
+# define ns_RSA_get0_n RSA_get0_n
+# define ns_RSA_get0_e RSA_get0_e
+# endif
+
+static int ns_EVP_PKEY_get_bn_param(const EVP_PKEY *pkey,
+ const char *key_name, BIGNUM **bn) {
+ RSA *rsa;
+ BIGNUM *result = NULL;
+
+ /* Check parameters: only support allocation-form *bn */
+ if (pkey == NULL || key_name == NULL || bn == NULL || *bn != NULL)
+ return 0;
+
+ /* Only support RSA keys */
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
+ return 0;
+
+ rsa = EVP_PKEY_get1_RSA((EVP_PKEY *) pkey);
+ if (rsa == NULL)
+ return 0;
+
+ if (strcmp(key_name, "n") == 0) {
+ const BIGNUM *n = ns_RSA_get0_n(rsa);
+ if (n != NULL)
+ result = BN_dup(n);
+ } else if (strcmp(key_name, "e") == 0) {
+ const BIGNUM *e = ns_RSA_get0_e(rsa);
+ if (e != NULL)
+ result = BN_dup(e);
+ }
+
+ RSA_free(rsa);
+
+ *bn = result;
+
+ return (result != NULL) ? 1 : 0;
+}
+
+static int ns_EVP_PKEY_get_utf8_string_param(const EVP_PKEY *pkey,
+ const char *key_name, char *str, size_t max_len,
+ size_t *out_len)
+{
+ const EC_GROUP *ecgroup;
+ const char *group;
+ EC_KEY *ec;
+ int ret = 0;
+
+ if (pkey == NULL || key_name == NULL)
+ return 0;
+
+ /* Only support EC keys */
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC)
+ return 0;
+
+ /* Only support fetching the group */
+ if (strcmp(key_name, "group") != 0)
+ return 0;
+
+ ec = EVP_PKEY_get1_EC_KEY((EVP_PKEY *) pkey);
+
+ ecgroup = EC_KEY_get0_group(ec);
+ if (ecgroup == NULL) {
+ group = "";
+ } else {
+ group = OBJ_nid2ln(EC_GROUP_get_curve_name(ecgroup));
+ }
+
+ if (str != NULL && max_len > strlen(group)) {
+ strcpy(str, group);
+ str[strlen(group)] = '\0';
+ ret = 1;
+ }
+ if (out_len != NULL)
+ *out_len = strlen(group);
+
+ EC_KEY_free(ec);
+
+ return ret;
+}
+
+static int ns_EVP_PKEY_get_octet_string_param(const EVP_PKEY *pkey,
+ const char *key_name, unsigned char *buf, size_t max_len,
+ size_t *out_len)
+{
+ const EC_GROUP *ecgroup;
+ const EC_POINT *ecpoint;
+ size_t len;
+ BN_CTX *bnctx;
+ EC_KEY *ec;
+ int ret = 0;
+
+ if (pkey == NULL || key_name == NULL)
+ return 0;
+
+ /* Only support EC keys */
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC)
+ return 0;
+
+ if (strcmp(key_name, "encoded-pub-key") != 0)
+ return 0;
+
+ ec = EVP_PKEY_get1_EC_KEY((EVP_PKEY *) pkey);
+ if (ec == NULL)
+ return 0;
+
+ ecgroup = EC_KEY_get0_group(ec);
+ if (ecgroup != NULL) {
+ ecpoint = EC_KEY_get0_public_key(ec);
+ if (ecpoint != NULL) {
+ bnctx = BN_CTX_new();
+ len = EC_POINT_point2oct(ecgroup,
+ ecpoint,
+ POINT_CONVERSION_UNCOMPRESSED,
+ NULL,
+ 0,
+ bnctx);
+ if (len != 0 && len <= max_len) {
+ if (EC_POINT_point2oct(ecgroup,
+ ecpoint,
+ POINT_CONVERSION_UNCOMPRESSED,
+ buf,
+ len,
+ bnctx) == len)
+ ret = 1;
+ }
+ if (out_len != NULL)
+ *out_len = len;
+ BN_CTX_free(bnctx);
+ }
+ }
+
+ EC_KEY_free(ec);
+
+ return ret;
+}
+#else
+/* OpenSSL 3.x and later */
+#define ns_X509_get_signature_nid X509_get_signature_nid
+#define ns_ASN1_STRING_get0_data ASN1_STRING_get0_data
+#define ns_RSA_get0_n RSA_get0_n
+#define ns_RSA_get0_e RSA_get0_e
+#define ns_EVP_PKEY_get_bn_param EVP_PKEY_get_bn_param
+#define ns_EVP_PKEY_get_octet_string_param EVP_PKEY_get_octet_string_param
+#define ns_EVP_PKEY_get_utf8_string_param EVP_PKEY_get_utf8_string_param
+#endif
+
+/**
+ * extract certificate name information
+ *
+ * \param xname The X509 name to convert. The reference is borrowed so is not freeed
+ * \param iname The info structure to recive the extracted parameters.
+ * \return NSERROR_OK on success else error code
+ */
+static nserror
+xname_to_info(X509_NAME *xname, struct ns_cert_name *iname)
+{
+ int entryidx;
+ int entrycnt;
+ X509_NAME_ENTRY *entry; /* current name entry */
+ ASN1_STRING *value;
+ const unsigned char *value_str;
+ ASN1_OBJECT *name;
+ int name_nid;
+ char **field;
+
+ entrycnt = X509_NAME_entry_count(xname);
+
+ for (entryidx = 0; entryidx < entrycnt; entryidx++) {
+ entry = X509_NAME_get_entry(xname, entryidx);
+ name = X509_NAME_ENTRY_get_object(entry);
+ name_nid = OBJ_obj2nid(name);
+ value = X509_NAME_ENTRY_get_data(entry);
+ value_str = ns_ASN1_STRING_get0_data(value);
+ switch (name_nid) {
+ case NID_commonName:
+ field = &iname->common_name;
+ break;
+ case NID_countryName:
+ field = &iname->country;
+ break;
+ case NID_localityName:
+ field = &iname->locality;
+ break;
+ case NID_stateOrProvinceName:
+ field = &iname->province;
+ break;
+ case NID_organizationName:
+ field = &iname->organisation;
+ break;
+ case NID_organizationalUnitName:
+ field = &iname->organisation_unit;
+ break;
+ default :
+ field = NULL;
+ break;
+ }
+ if (field != NULL) {
+ *field = strdup((const char *)value_str);
+ NSLOG(netsurf, DEEPDEBUG,
+ "NID:%d value: %s", name_nid, *field);
+ } else {
+ NSLOG(netsurf, DEEPDEBUG, "NID:%d", name_nid);
+ }
+ }
+
+ /*
+ * ensure the common name is set to something, this being
+ * missing means the certificate is broken but this should be
+ * robust in the face of bad data
+ */
+ if (iname->common_name == NULL) {
+ iname->common_name = strdup("Unknown");
+ }
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * duplicate a hex formatted string inserting the colons
+ *
+ * \todo only uses html entity as separator because netsurfs line breaking
+ * fails otherwise.
+ */
+static char *hexdup(const char *hex)
+{
+ int hexlen;
+ char *dst;
+ char *out;
+ int cn = 0;
+
+ hexlen = strlen(hex);
+ /* allow space fox XXYY to XX&#58;YY&#58; */
+ dst = malloc(((hexlen * 7) + 6) / 2);
+
+ if (dst != NULL) {
+ for (out = dst; *hex != 0; hex++) {
+ if (cn == 2) {
+ cn = 0;
+ *out++ = '&';
+ *out++ = '#';
+ *out++ = '5';
+ *out++ = '8';
+ *out++ = ';';
+ }
+ *out++ = *hex;
+ cn++;
+ }
+ *out = 0;
+ }
+ return dst;
+}
+
+
+/**
+ * create a hex formatted string inserting the colons from binary data
+ *
+ * \todo only uses html entity as separator because netsurfs line breaking
+ * fails otherwise.
+ */
+static char *bindup(unsigned char *bin, unsigned int binlen)
+{
+ char *dst;
+ char *out;
+ unsigned int idx;
+ const char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ /* allow space fox XY to expand to XX&#58;YY&#58; */
+ dst = malloc(binlen * 7);
+
+ if (dst != NULL) {
+ out = dst;
+ for (idx = 0; idx < binlen; idx++) {
+ *out++ = hex[(bin[idx] & 0xf0) >> 4];
+ *out++ = hex[bin[idx] & 0xf];
+
+ *out++ = '&';
+ *out++ = '#';
+ *out++ = '5';
+ *out++ = '8';
+ *out++ = ';';
+ }
+ out -= 5;
+ *out = 0;
+ }
+ return dst;
+}
+
+
+/**
+ * extract RSA key information to info structure
+ *
+ * \param pkey The RSA key to examine.
+ * \param ikey The public key info structure to fill
+ * \rerun NSERROR_OK on success else error code.
+ */
+static nserror
+rsa_to_info(EVP_PKEY *pkey, struct ns_cert_pkey *ikey)
+{
+ BIGNUM *n = NULL, *e = NULL;
+ char *tmp;
+
+ if (ns_EVP_PKEY_get_bn_param(pkey, "n", &n) != 1) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ if (ns_EVP_PKEY_get_bn_param(pkey, "e", &e) != 1) {
+ BN_free(n);
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ ikey->algor = strdup("RSA");
+
+ ikey->size = EVP_PKEY_bits(pkey);
+
+ tmp = BN_bn2hex(n);
+ if (tmp != NULL) {
+ ikey->modulus = hexdup(tmp);
+ OPENSSL_free(tmp);
+ }
+
+ tmp = BN_bn2dec(e);
+ if (tmp != NULL) {
+ ikey->exponent = strdup(tmp);
+ OPENSSL_free(tmp);
+ }
+
+ BN_free(e);
+ BN_free(n);
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * extract DSA key information to info structure
+ *
+ * \param pkey The DSA key to examine.
+ * \param ikey The public key info structure to fill
+ * \rerun NSERROR_OK on success else error code.
+ */
+static nserror
+dsa_to_info(EVP_PKEY *pkey, struct ns_cert_pkey *ikey)
+{
+ ikey->algor = strdup("DSA");
+
+ ikey->size = EVP_PKEY_bits(pkey);
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * extract DH key information to info structure
+ *
+ * \param pkey The DH key to examine.
+ * \param ikey The public key info structure to fill
+ * \rerun NSERROR_OK on success else error code.
+ */
+static nserror
+dh_to_info(EVP_PKEY *pkey, struct ns_cert_pkey *ikey)
+{
+ ikey->algor = strdup("Diffie Hellman");
+
+ ikey->size = EVP_PKEY_bits(pkey);
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * extract EC key information to info structure
+ *
+ * \param pkey The EC key to examine.
+ * \param ikey The public key info structure to fill
+ * \rerun NSERROR_OK on success else error code.
+ */
+static nserror
+ec_to_info(EVP_PKEY *pkey, struct ns_cert_pkey *ikey)
+{
+ size_t len;
+
+ ikey->algor = strdup("Elliptic Curve");
+
+ ikey->size = EVP_PKEY_bits(pkey);
+
+ len = 0;
+ ns_EVP_PKEY_get_utf8_string_param(pkey, "group", NULL, 0, &len);
+ if (len != 0) {
+ ikey->curve = malloc(len + 1);
+ if (ikey->curve != NULL) {
+ if (ns_EVP_PKEY_get_utf8_string_param(pkey, "group",
+ ikey->curve, len + 1, NULL) == 0) {
+ free(ikey->curve);
+ ikey->curve = NULL;
+ }
+ }
+ }
+
+ len = 0;
+ ns_EVP_PKEY_get_octet_string_param(pkey, "encoded-pub-key",
+ NULL, 0, &len);
+ if (len != 0) {
+ unsigned char *point = malloc(len);
+ if (point != NULL) {
+ if (ns_EVP_PKEY_get_octet_string_param(pkey,
+ "encoded-pub-key", point, len,
+ NULL) == 1) {
+ ikey->public = bindup(point, len);
+ }
+ free(point);
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+/**
+ * extract public key information to info structure
+ *
+ * \param pkey the public key to examine. The reference is dropped on return
+ * \param ikey The public key info structure to fill
+ * \rerun NSERROR_OK on success else error code.
+ */
+static nserror
+pkey_to_info(EVP_PKEY *pkey, struct ns_cert_pkey *ikey)
+{
+ nserror res;
+
+ if (pkey == NULL) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ switch (EVP_PKEY_base_id(pkey)) {
+ case EVP_PKEY_RSA:
+ res = rsa_to_info(pkey, ikey);
+ break;
+
+ case EVP_PKEY_DSA:
+ res = dsa_to_info(pkey, ikey);
+ break;
+
+ case EVP_PKEY_DH:
+ res = dh_to_info(pkey, ikey);
+ break;
+
+ case EVP_PKEY_EC:
+ res = ec_to_info(pkey, ikey);
+ break;
+
+ default:
+ res = NSERROR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return res;
+}
+
+static nserror san_to_info(X509 *cert, struct ns_cert_san **prev_next)
+{
+ int idx;
+ int san_names_nb = -1;
+ const GENERAL_NAME *current_name;
+ const unsigned char *dns_name;
+ struct ns_cert_san *isan;
+
+ STACK_OF(GENERAL_NAME) *san_names = NULL;
+
+ san_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (san_names == NULL) {
+ return NSERROR_OK;
+ }
+
+ san_names_nb = sk_GENERAL_NAME_num(san_names);
+
+ /* Check each name within the extension */
+ for (idx = 0; idx < san_names_nb; idx++) {
+ current_name = sk_GENERAL_NAME_value(san_names, idx);
+
+ if (current_name->type == GEN_DNS) {
+ /* extract DNS name into info structure */
+ dns_name = ns_ASN1_STRING_get0_data(current_name->d.dNSName);
+
+ isan = malloc(sizeof(struct ns_cert_san));
+ if (isan != NULL) {
+ isan->name = strdup((const char *)dns_name);
+ isan->next = NULL;
+ *prev_next = isan;
+ prev_next = &isan->next;
+ }
+ }
+ }
+
+ /* AmiSSL can't cope with the "correct" mechanism of freeing
+ * the GENERAL_NAME stack, which is:
+ * sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
+ * So instead we do this open-coded loop which does the same:
+ */
+ for (idx = 0; idx < san_names_nb; idx++) {
+ GENERAL_NAME *entry = sk_GENERAL_NAME_pop(san_names);
+ GENERAL_NAME_free(entry);
+ }
+ sk_GENERAL_NAME_free(san_names);
+
+ return NSERROR_OK;
+}
+
+static nserror
+der_to_certinfo(const uint8_t *der,
+ size_t der_length,
+ struct ns_cert_info *info)
+{
+ BIO *mem;
+ BUF_MEM *buf;
+ const ASN1_INTEGER *asn1_num;
+ BIGNUM *bignum;
+ X509 *cert; /**< Pointer to certificate */
+
+ if (der == NULL) {
+ return NSERROR_OK;
+ }
+
+ cert = d2i_X509(NULL, &der, der_length);
+ if (cert == NULL) {
+ return NSERROR_INVALID;
+ }
+
+ /*
+ * get certificate version
+ *
+ * \note this is defined by standards (X.509 et al) to be one
+ * less than the certificate version.
+ */
+ info->version = X509_get_version(cert) + 1;
+
+ /* not before date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem, X509_get_notBefore(cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ info->not_before = calloc(1, buf->length + 1);
+ if (info->not_before != NULL) {
+ memcpy(info->not_before, buf->data, (unsigned)buf->length);
+ }
+ BUF_MEM_free(buf);
+
+ /* not after date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem,
+ X509_get_notAfter(cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ info->not_after = calloc(1, buf->length + 1);
+ if (info->not_after != NULL) {
+ memcpy(info->not_after, buf->data, (unsigned)buf->length);
+ }
+ BUF_MEM_free(buf);
+
+ /* signature type */
+ info->sig_type = X509_get_signature_type(cert);
+
+ /* signature algorithm */
+ int pkey_nid = ns_X509_get_signature_nid(cert);
+ if (pkey_nid != NID_undef) {
+ const char* sslbuf = OBJ_nid2ln(pkey_nid);
+ if (sslbuf != NULL) {
+ info->sig_algor = strdup(sslbuf);
+ }
+ }
+
+ /* serial number */
+ asn1_num = X509_get_serialNumber(cert);
+ if (asn1_num != NULL) {
+ bignum = ASN1_INTEGER_to_BN(asn1_num, NULL);
+ if (bignum != NULL) {
+ char *tmp = BN_bn2hex(bignum);
+ if (tmp != NULL) {
+ info->serialnum = hexdup(tmp);
+ OPENSSL_free(tmp);
+ }
+ BN_free(bignum);
+ bignum = NULL;
+ }
+ }
+
+ /* fingerprints */
+ const EVP_MD *digest;
+ unsigned int dig_len;
+ unsigned char *buff;
+ int rc;
+
+ digest = EVP_sha1();
+ buff = malloc(EVP_MD_size(digest));
+ if (buff != NULL) {
+ rc = X509_digest(cert, digest, buff, &dig_len);
+ if ((rc == 1) && (dig_len == (unsigned int)EVP_MD_size(digest))) {
+ info->sha1fingerprint = bindup(buff, dig_len);
+ }
+ free(buff);
+ }
+
+ digest = EVP_sha256();
+ buff = malloc(EVP_MD_size(digest));
+ if (buff != NULL) {
+ rc = X509_digest(cert, digest, buff, &dig_len);
+ if ((rc == 1) && (dig_len == (unsigned int)EVP_MD_size(digest))) {
+ info->sha256fingerprint = bindup(buff, dig_len);
+ }
+ free(buff);
+ }
+
+ /* subject alternative names */
+ san_to_info(cert, &info->san);
+
+ /* issuer name */
+ xname_to_info(X509_get_issuer_name(cert), &info->issuer_name);
+
+ /* subject */
+ xname_to_info(X509_get_subject_name(cert), &info->subject_name);
+
+ /* public key */
+ pkey_to_info(X509_get_pubkey(cert), &info->public_key);
+
+ X509_free(cert);
+
+ return NSERROR_OK;
+}
+
+/* copy certificate data */
+static nserror
+convert_chain_to_cert_info(const struct cert_chain *chain,
+ struct ns_cert_info **cert_info_out)
+{
+ struct ns_cert_info *certs;
+ size_t depth;
+ nserror res;
+
+ certs = calloc(chain->depth, sizeof(struct ns_cert_info));
+ if (certs == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ for (depth = 0; depth < chain->depth;depth++) {
+ res = der_to_certinfo(chain->certs[depth].der,
+ chain->certs[depth].der_length,
+ certs + depth);
+ if (res != NSERROR_OK) {
+ free(certs);
+ return res;
+ }
+ certs[depth].err = chain->certs[depth].err;
+ }
+
+ *cert_info_out = certs;
+ return NSERROR_OK;
+}
+
+#else
+static nserror
+convert_chain_to_cert_info(const struct cert_chain *chain,
+ struct ns_cert_info **cert_info_out)
+{
+ return NSERROR_NOT_IMPLEMENTED;
+}
+#endif
+
+
+static nserror
+format_certificate_name(struct fetch_about_context *ctx,
+ struct ns_cert_name *cert_name)
+{
+ nserror res;
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Common Name</th><td>%s</td></tr>\n",
+ cert_name->common_name);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if (cert_name->organisation != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Organisation</th><td>%s</td></tr>\n",
+ cert_name->organisation);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_name->organisation_unit != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Organisation Unit</th><td>%s</td></tr>\n",
+ cert_name->organisation_unit);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_name->locality != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Locality</th><td>%s</td></tr>\n",
+ cert_name->locality);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_name->province != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Privince</th><td>%s</td></tr>\n",
+ cert_name->province);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_name->country != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Country</th><td>%s</td></tr>\n",
+ cert_name->country);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ return res;
+}
+
+/**
+ * output formatted certificate subject alternate names
+ */
+static nserror
+format_certificate_san(struct fetch_about_context *ctx,
+ struct ns_cert_san *san)
+{
+ nserror res;
+
+ if (san == NULL) {
+ return NSERROR_OK;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Alternative Names</th><td><hr></td></tr>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ while (san != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>DNS Name</th><td>%s</td></tr>\n",
+ san->name);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ san = san->next;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</table>\n");
+
+ return res;
+
+}
+
+
+static nserror
+format_certificate_public_key(struct fetch_about_context *ctx,
+ struct ns_cert_pkey *public_key)
+{
+ nserror res;
+
+ if (public_key->algor == NULL) {
+ /* skip the table if no algorithm name */
+ return NSERROR_OK;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Public Key</th><td><hr></td></tr>\n"
+ "<tr><th>Algorithm</th><td>%s</td></tr>\n"
+ "<tr><th>Key Size</th><td>%d</td></tr>\n",
+ public_key->algor,
+ public_key->size);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+
+ if (public_key->exponent != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Exponent</th><td>%s</td></tr>\n",
+ public_key->exponent);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (public_key->modulus != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Modulus</th><td class=\"data\">%s</td></tr>\n",
+ public_key->modulus);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (public_key->curve != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Curve</th><td>%s</td></tr>\n",
+ public_key->curve);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (public_key->public != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Public Value</th><td>%s</td></tr>\n",
+ public_key->public);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</table>\n");
+
+ return res;
+}
+
+static nserror
+format_certificate_fingerprint(struct fetch_about_context *ctx,
+ struct ns_cert_info *cert_info)
+{
+ nserror res;
+
+ if ((cert_info->sha1fingerprint == NULL) &&
+ (cert_info->sha256fingerprint == NULL)) {
+ /* skip the table if no fingerprints */
+ return NSERROR_OK;
+ }
+
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Fingerprints</th><td><hr></td></tr>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if (cert_info->sha256fingerprint != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>SHA-256</th><td class=\"data\">%s</td></tr>\n",
+ cert_info->sha256fingerprint);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_info->sha1fingerprint != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>SHA-1</th><td class=\"data\">%s</td></tr>\n",
+ cert_info->sha1fingerprint);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</table>\n");
+
+ return res;
+}
+
+static nserror
+format_certificate(struct fetch_about_context *ctx,
+ struct ns_cert_info *cert_info,
+ size_t depth)
+{
+ nserror res;
+
+ res = fetch_about_ssenddataf(ctx,
+ "<h2 id=\"%"PRIsizet"\" class=\"ns-border\">%s</h2>\n",
+ depth, cert_info->subject_name.common_name);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if (cert_info->err != SSL_CERT_ERR_OK) {
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr class=\"ns-even-fg-bad\">"
+ "<th>Fault</th>"
+ "<td>%s</td>"
+ "</tr>"
+ "</table>\n",
+ messages_get_sslcode(cert_info->err));
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Issued To</th><td><hr></td></tr>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = format_certificate_name(ctx, &cert_info->subject_name);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "</table>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Issued By</th><td><hr></td></tr>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = format_certificate_name(ctx, &cert_info->issuer_name);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "</table>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Validity</th><td><hr></td></tr>\n"
+ "<tr><th>Valid From</th><td>%s</td></tr>\n"
+ "<tr><th>Valid Until</th><td>%s</td></tr>\n"
+ "</table>\n",
+ cert_info->not_before,
+ cert_info->not_after);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = format_certificate_san(ctx, cert_info->san);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = format_certificate_public_key(ctx, &cert_info->public_key);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<table class=\"info\">\n"
+ "<tr><th>Miscellaneous</th><td><hr></td></tr>\n");
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if (cert_info->serialnum != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Serial Number</th><td>%s</td></tr>\n",
+ cert_info->serialnum);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ if (cert_info->sig_algor != NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Signature Algorithm</th>"
+ "<td>%s</td></tr>\n",
+ cert_info->sig_algor);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<tr><th>Version</th><td>%ld</td></tr>\n"
+ "</table>\n",
+ cert_info->version);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ res = format_certificate_fingerprint(ctx, cert_info);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ return res;
+}
+
+/**
+ * Handler to generate about:certificate page.
+ *
+ * Shows details of a certificate chain
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_certificate_handler(struct fetch_about_context *ctx)
+{
+ int code = 200;
+ nserror res;
+ struct cert_chain *chain = NULL;
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, code);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html"))
+ goto fetch_about_certificate_handler_aborted;
+
+ /* page head */
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>NetSurf Browser Certificate Viewer</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body id=\"certificate\" class=\"ns-even-bg ns-even-fg ns-border\">\n"
+ "<h1 class=\"ns-border\">Certificate</h1>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ res = cert_chain_from_query(fetch_about_get_url(ctx), &chain);
+ if (res != NSERROR_OK) {
+ res = fetch_about_ssenddataf(ctx, "<p>Could not process that</p>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+ } else {
+ struct ns_cert_info *cert_info;
+ res = convert_chain_to_cert_info(chain, &cert_info);
+ if (res == NSERROR_OK) {
+ size_t depth;
+ res = fetch_about_ssenddataf(ctx, "<ul>\n");
+ if (res != NSERROR_OK) {
+ free_ns_cert_info(cert_info);
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ for (depth = 0; depth < chain->depth; depth++) {
+ res = fetch_about_ssenddataf(ctx, "<li><a href=\"#%"PRIsizet"\">%s</a></li>\n",
+ depth, (cert_info + depth)
+ ->subject_name
+ .common_name);
+ if (res != NSERROR_OK) {
+ free_ns_cert_info(cert_info);
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</ul>\n");
+ if (res != NSERROR_OK) {
+ free_ns_cert_info(cert_info);
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ for (depth = 0; depth < chain->depth; depth++) {
+ res = format_certificate(ctx, cert_info + depth,
+ depth);
+ if (res != NSERROR_OK) {
+ free_ns_cert_info(cert_info);
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ }
+ free_ns_cert_info(cert_info);
+
+ } else {
+ res = fetch_about_ssenddataf(ctx,
+ "<p>Invalid certificate data</p>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+ }
+ }
+
+
+ /* page footer */
+ res = fetch_about_ssenddataf(ctx, "</body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_certificate_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ cert_chain_free(chain);
+
+ return true;
+
+fetch_about_certificate_handler_aborted:
+ cert_chain_free(chain);
+ return false;
+}
diff --git a/content/fetchers/about/certificate.h b/content/fetchers/about/certificate.h
new file mode 100644
index 000000000..80ac06853
--- /dev/null
+++ b/content/fetchers/about/certificate.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme certificate handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CERTIFICATE_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_CERTIFICATE_H
+
+/**
+ * Handler to generate about scheme certificate page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_certificate_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/chart.c b/content/fetchers/about/chart.c
new file mode 100644
index 000000000..c030c12b4
--- /dev/null
+++ b/content/fetchers/about/chart.c
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme chart page
+ *
+ * A chart consists of the figure area in which a chart a title and a
+ * key are placed.
+ *
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "utils/config.h"
+#include "netsurf/inttypes.h"
+#include "utils/config.h"
+#include "utils/utils.h"
+#include "utils/errors.h"
+#include "utils/nsurl.h"
+
+#include "private.h"
+#include "chart.h"
+
+/** minimum figure dimension */
+#define FIGURE_MIN_WIDTH 150
+#define FIGURE_MIN_HEIGHT 100
+
+enum chart_type {
+ CHART_TYPE_UNKNOWN,
+ CHART_TYPE_PIE,
+};
+
+/* type of chart key */
+enum key_type {
+ CHART_KEY_UNSET,
+ CHART_KEY_NONE,
+ CHART_KEY_LEFT,
+ CHART_KEY_RIGHT,
+ CHART_KEY_TOP,
+ CHART_KEY_BOT,
+ CHART_KEY_END
+};
+
+
+struct chart_label {
+ char *title; /* label title */
+ unsigned int colour; /* colour */
+};
+
+struct chart_series {
+ unsigned int len; /* number of values in the series */
+ float *value; /* array of values */
+};
+
+#define MAX_SERIES 4
+
+struct chart_data {
+ unsigned int series_len;
+ struct chart_series series[MAX_SERIES];
+
+ unsigned int label_len; /* number of labels */
+ struct chart_label *label;
+
+};
+
+/**
+ * parameters for a chart figure
+ */
+struct chart_param {
+ enum chart_type type;
+ enum key_type key; /* what type of key to use */
+ unsigned int width; /* width of figure */
+ unsigned int height; /* height of figure */
+ char *title; /* title */
+ struct {
+ unsigned int x;
+ unsigned int y;
+ unsigned int width;
+ unsigned int height;
+ } area; /* chart area within figure */
+ struct chart_data data;
+};
+
+#define DEF_COLOUR_NUM 8
+/** default colour series */
+static unsigned int colour_series[DEF_COLOUR_NUM] =
+ {
+ 0x00ff00, /* green */
+ 0x0000ff, /* blue */
+ 0xff0000, /* red */
+ 0xffff00, /* yellow */
+ 0x00ffff, /* cyan */
+ 0xff00ff, /* pink */
+ 0x777777, /* grey */
+ 0x000000, /* black */
+ };
+
+
+/* ensures there are labels present for every value */
+static nserror ensure_label_count(struct chart_param *chart, unsigned int count)
+{
+ unsigned int lidx;
+ int deltac;
+ struct chart_label *nlabels;
+
+ deltac = count - chart->data.label_len;
+ if (deltac <= 0) {
+ /* there are enough labels */
+ return NSERROR_OK;
+ }
+
+ nlabels = realloc(chart->data.label,
+ count * sizeof(struct chart_label));
+ if (nlabels == NULL) {
+ return NSERROR_NOMEM;
+ }
+ chart->data.label = nlabels;
+
+ for (lidx = chart->data.label_len; lidx < count; lidx++) {
+ chart->data.label[lidx].title = calloc(1, 20);
+ snprintf(chart->data.label[lidx].title, 19, "item %d", lidx + 1);
+ chart->data.label[lidx].colour = colour_series[lidx % DEF_COLOUR_NUM];
+ }
+
+ chart->data.label_len = count;
+
+ return NSERROR_OK;
+}
+
+/**
+ * extract values for a series
+ */
+static nserror
+extract_series_values(struct chart_param *chart,
+ unsigned int series_num,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+ unsigned int valcur;
+ size_t valstart;/* value start in valstr */
+ size_t vallen; /* value end in valstr */
+ struct chart_series *series;
+
+ series = chart->data.series + series_num;
+
+ /* ensure we do not leak any data in this series */
+ if (series->value != NULL) {
+ free(series->value);
+ }
+
+ /* count how many values present */
+ for (series->len = 1, valstart=0; valstart < valstrlen; valstart++) {
+ if (valstr[valstart] == ',') {
+ series->len++;
+ }
+ }
+
+ /* allocate storage for values */
+ series->value = calloc(series->len, sizeof(float));
+ if (series->value == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ /* extract values from query string */
+ for (valcur = 0, vallen = 0, valstart = 0;
+ (valstart < valstrlen) && (valcur < series->len);
+ valstart += vallen, valcur++) {
+ /* get query section length */
+ vallen = 0;
+ while (((valstart + vallen) < valstrlen) &&
+ (valstr[valstart + vallen] != ',')) {
+ vallen++;
+ }
+
+ series->value[valcur] = strtof(valstr + valstart, NULL);
+ vallen++; /* account for , separator */
+ }
+
+ res = ensure_label_count(chart, series->len);
+
+ return res;
+}
+
+
+/**
+ * extract values for next series
+ */
+static nserror
+extract_next_series_values(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+
+ if (chart->data.series_len >= MAX_SERIES) {
+ return NSERROR_NOSPACE;
+ }
+
+ res = extract_series_values(chart,
+ chart->data.series_len,
+ valstr,
+ valstrlen);
+ if (res == NSERROR_OK) {
+ chart->data.series_len++;
+ }
+
+ return res;
+}
+
+
+/**
+ * extract label title
+ */
+static nserror
+extract_series_labels(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ nserror res;
+ unsigned int valcount; /* count of values in valstr */
+ unsigned int valcur;
+ size_t valstart;/* value start in valstr */
+ size_t vallen; /* value end in valstr */
+
+ for (valcount = 1, valstart=0; valstart < valstrlen; valstart++) {
+ if (valstr[valstart] == ',') {
+ valcount++;
+ }
+ }
+
+ res = ensure_label_count(chart, valcount);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+
+ for (valcur = 0, vallen = 0, valstart = 0;
+ (valstart < valstrlen) && (valcur < chart->data.label_len);
+ valstart += vallen, valcur++) {
+ /* get query section length */
+ vallen = 0;
+ while (((valstart + vallen) < valstrlen) &&
+ (valstr[valstart + vallen] != ',')) {
+ vallen++;
+ }
+
+ chart->data.label[valcur].title = strndup(valstr + valstart, vallen);
+ vallen++; /* account for , separator */
+ }
+ return NSERROR_OK;
+}
+
+
+/**
+ * extract labels colour
+ */
+static nserror
+extract_series_colours(struct chart_param *chart,
+ const char *valstr,
+ size_t valstrlen)
+{
+ return NSERROR_OK;
+}
+
+/**
+ * process a part of a query
+ */
+static nserror
+process_query_section(const char *str, size_t len, struct chart_param *chart)
+{
+ nserror res = NSERROR_OK;
+
+ if ((len > 6) &&
+ (strncmp(str, "width=", 6) == 0)) {
+ /* figure width */
+ chart->width = strtoul(str + 6, NULL, 10);
+ } else if ((len > 7) &&
+ (strncmp(str, "height=", 7) == 0)) {
+ /* figure height */
+ chart->height = strtoul(str + 7, NULL, 10);
+ } else if ((len > 8) &&
+ (strncmp(str, "cawidth=", 8) == 0)) {
+ /* chart area width */
+ chart->area.width = strtoul(str + 8, NULL, 10);
+ } else if ((len > 9) &&
+ (strncmp(str, "caheight=", 9) == 0)) {
+ /* chart area height */
+ chart->area.height = strtoul(str + 9, NULL, 10);
+ } else if ((len > 4) &&
+ (strncmp(str, "key=", 4) == 0)) {
+ /* figure has key */
+ chart->key = strtoul(str + 4, NULL, 10);
+ } else if ((len > 6) &&
+ (strncmp(str, "title=", 6) == 0)) {
+ chart->title = strndup(str + 6, len - 6);
+ } else if ((len > 5) &&
+ (strncmp(str, "type=", 5) == 0)) {
+ if (strncmp(str + 5, "pie", len - 5) == 0) {
+ chart->type = CHART_TYPE_PIE;
+ } else {
+ chart->type = CHART_TYPE_UNKNOWN;
+ }
+ } else if ((len > 7) &&
+ (strncmp(str, "values=", 7) == 0)) {
+ res = extract_next_series_values(chart, str + 7, len - 7);
+ } else if ((len > 7) &&
+ (strncmp(str, "labels=", 7) == 0)) {
+ res = extract_series_labels(chart, str + 7, len - 7);
+ } else if ((len > 8) &&
+ (strncmp(str, "colours=", 8) == 0)) {
+ res = extract_series_colours(chart, str + 8, len - 8);
+ }
+
+ return res;
+}
+
+
+
+static nserror
+chart_from_query(struct nsurl *url, struct chart_param *chart)
+{
+ nserror res;
+ char *querystr;
+ size_t querylen;
+ size_t kvstart;/* key value start */
+ size_t kvlen; /* key value end */
+
+ res = nsurl_get(url, NSURL_QUERY, &querystr, &querylen);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ for (kvlen = 0, kvstart = 0; kvstart < querylen; kvstart += kvlen) {
+ /* get query section length */
+ kvlen = 0;
+ while (((kvstart + kvlen) < querylen) &&
+ (querystr[kvstart + kvlen] != '&')) {
+ kvlen++;
+ }
+
+ res = process_query_section(querystr + kvstart, kvlen, chart);
+ if (res != NSERROR_OK) {
+ break;
+ }
+ kvlen++; /* account for & separator */
+ }
+ free(querystr);
+
+ /* sanity check dimensions */
+ if (chart->width < FIGURE_MIN_WIDTH) {
+ /* bad width - check height */
+ if (chart->height < FIGURE_MIN_HEIGHT) {
+ /* both bad set to defaults */
+ chart->width = FIGURE_MIN_WIDTH;
+ chart->height = FIGURE_MIN_HEIGHT;
+ } else {
+ /* base width on valid height */
+ chart->width = (chart->height * 3) / 2;
+ }
+ } else {
+ /* good width check height */
+ if (chart->height < FIGURE_MIN_HEIGHT) {
+ /* base height on valid width */
+ chart->height = (chart->width * 2) / 3;
+ }
+ }
+
+ /* ensure legend type correct */
+ if ((chart->key == CHART_KEY_UNSET) ||
+ (chart->key >= CHART_KEY_END )) {
+ /* default to putting key on right */
+ chart->key = CHART_KEY_RIGHT;
+ }
+
+ return NSERROR_OK;
+}
+
+
+static nserror
+output_pie_legend(struct fetch_about_context *ctx, struct chart_param *chart)
+{
+ nserror res;
+ unsigned int lblidx;
+ unsigned int legend_width;
+ unsigned int legend_height;
+ unsigned int vertical_spacing;
+
+ switch (chart->key) {
+
+ case CHART_KEY_NONE:
+ break;
+ case CHART_KEY_RIGHT:
+ legend_width = chart->width - chart->area.width - chart->area.x;
+ legend_width -= 10; /* margin */
+ legend_height = chart->height;
+ vertical_spacing = legend_height / (chart->data.label_len + 1);
+
+ for(lblidx = 0; lblidx < chart->data.label_len ; lblidx++) {
+ res = fetch_about_ssenddataf(ctx,
+ "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"#%06x\" />",
+ chart->width - legend_width,
+ (vertical_spacing * lblidx) + (vertical_spacing/2),
+ vertical_spacing * 2 / 3,
+ vertical_spacing * 2 / 3,
+ chart->data.label[lblidx].colour);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<text x=\"%d\" y=\"%d\" fill=\"#%06x\" >%s</text>",
+ chart->width - legend_width + vertical_spacing,
+ vertical_spacing * (lblidx+1),
+ chart->data.label[lblidx].colour,
+ chart->data.label[lblidx].title);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NSERROR_OK;
+}
+
+static float
+compute_series_total(struct chart_param *chart, unsigned int series)
+{
+ float total;
+ unsigned int curdata;
+
+ for (total = 0, curdata = 0;
+ curdata < chart->data.series[series].len;
+ curdata++) {
+ total += chart->data.series[series].value[curdata];
+ }
+ return total;
+}
+
+/**
+ * render the data as a pie chart svg
+ */
+static bool
+pie_chart(struct fetch_about_context *ctx, struct chart_param *chart)
+{
+ nserror res;
+ float ra; /* pie a radius */
+ float rb; /* pie b radius */
+ float series_total;
+ unsigned int curdata; /* current data point index */
+ float last_x, last_y;
+ float end_x, end_y;
+ float start;
+ float extent;
+ bool large;
+ float circle_centre_x, circle_centre_y;
+
+ /* ensure there is data to render */
+ if ((chart->data.series_len < 1) || (chart->data.series[0].len < 2)) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /* get the first series total value */
+ series_total = compute_series_total(chart, 0);
+ if (series_total == 0) {
+ /* dividing by zero is embarasing */
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /*
+ * need to ensure the chart area is setup correctly
+ *
+ * this is left to each chart type as different charts
+ * have differnt requirements
+ */
+ if ((chart->area.width == 0) || (chart->area.height == 0)) {
+ /*
+ * pie chart defaults to square of smaller of figure
+ * width and height
+ */
+ if (chart->width > chart->height) {
+ chart->area.width = chart->area.height = (chart->height - chart->area.x);
+ } else {
+ chart->area.width = chart->area.height = (chart->width - chart->area.y);
+ }
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx,
+ "Content-Type: image/svg; charset=utf-8")) {
+ goto aborted;
+ }
+
+ /* get the pie charts elipse radii */
+ ra = chart->area.width / 2;
+ rb = chart->area.height / 2;
+
+ /* get the offset to the circle centre */
+ circle_centre_x = chart->area.x + ra;
+ circle_centre_y = chart->area.y + rb;
+
+
+ /* svg header */
+ res = fetch_about_ssenddataf(ctx,
+ "<svg width=\"%u\" height=\"%u\" "
+ "xmlns=\"http://www.w3.org/2000/svg\">\n",
+ chart->width, chart->height);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ /* generate the legend */
+ res = output_pie_legend(ctx, chart);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ /* plot the arcs */
+ start = -M_PI_2;
+ last_x = (ra * cos(start));
+ last_y = (rb * sin(start));
+
+ /* iterate over each data point creating a slice o pie */
+ for (curdata=0; curdata < chart->data.series[0].len; curdata++) {
+ extent = ((chart->data.series[0].value[curdata] / series_total) * 2 * M_PI);
+ end_x = (ra * cos(start + extent));
+ end_y = (rb * sin(start + extent));
+
+ if (extent > M_PI) {
+ large = true;
+ } else {
+ large = false;
+ }
+
+ res = fetch_about_ssenddataf(
+ ctx,
+ "<path d=\"M %g %g\n"
+ "A %g %g 0 %d 1 %g %g\n"
+ "L %g %g Z\" fill=\"#%06x\" />\n",
+ circle_centre_x + last_x,
+ circle_centre_y + last_y,
+ ra, rb, large?1:0,
+ circle_centre_x + end_x,
+ circle_centre_y + end_y,
+ circle_centre_x,
+ circle_centre_y,
+ chart->data.label[curdata].colour);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+ last_x = end_x;
+ last_y = end_y;
+ start +=extent;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</svg>\n");
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+ aborted:
+
+ return false;
+
+}
+
+/**
+ * Handler to generate about scheme chart page.
+ *
+ * generates an svg chart
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_chart_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ struct chart_param chart;
+ memset(&chart, 0, sizeof(struct chart_param));
+
+ res = chart_from_query(fetch_about_get_url(ctx), &chart);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ switch (chart.type) {
+ case CHART_TYPE_PIE:
+ return pie_chart(ctx, &chart);
+
+
+ default:
+ break;
+ }
+
+aborted:
+
+ return false;
+
+}
diff --git a/content/fetchers/about/chart.h b/content/fetchers/about/chart.h
new file mode 100644
index 000000000..4eb7e7898
--- /dev/null
+++ b/content/fetchers/about/chart.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme chart handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H
+
+/**
+ * Handler to generate about scheme chart page.
+ *
+ * generates an svg chart
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_chart_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/choices.c b/content/fetchers/about/choices.c
new file mode 100644
index 000000000..a95502e6e
--- /dev/null
+++ b/content/fetchers/about/choices.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme blank page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "netsurf/types.h"
+#include "utils/errors.h"
+#include "utils/nsoption.h"
+
+#include "private.h"
+#include "choices.h"
+
+/**
+ * Generate the text of a Choices file which represents the current
+ * in use options.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_choices_handler(struct fetch_about_context *ctx)
+{
+ char buffer[1024];
+ int code = 200;
+ int slen;
+ unsigned int opt_loop = 0;
+ int res = 0;
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, code);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
+ goto fetch_about_choices_handler_aborted;
+
+ slen = snprintf(buffer, sizeof buffer,
+ "# Automatically generated current NetSurf browser Choices\n");
+
+ do {
+ res = nsoption_snoptionf(buffer + slen,
+ sizeof buffer - slen,
+ opt_loop,
+ "%k:%v\n");
+ if (res <= 0)
+ break; /* last option */
+
+ if (res >= (int) (sizeof buffer - slen)) {
+ /* last entry would not fit in buffer, submit buffer */
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_choices_handler_aborted;
+ }
+ slen = 0;
+ } else {
+ /* normal addition */
+ slen += res;
+ opt_loop++;
+ }
+ } while (res > 0);
+
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_choices_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_choices_handler_aborted:
+ return false;
+}
diff --git a/content/fetchers/about/choices.h b/content/fetchers/about/choices.h
new file mode 100644
index 000000000..0548f5b9c
--- /dev/null
+++ b/content/fetchers/about/choices.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme choices handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CHOICES_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_CHOICES_H
+
+/**
+ * Handler to generate about scheme choices page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_choices_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/config.c b/content/fetchers/about/config.c
new file mode 100644
index 000000000..a18af956f
--- /dev/null
+++ b/content/fetchers/about/config.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme blank page
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "netsurf/types.h"
+#include "utils/errors.h"
+#include "utils/nsoption.h"
+
+#include "private.h"
+#include "config.h"
+
+/**
+ * Handler to generate about scheme config page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_config_handler(struct fetch_about_context *ctx)
+{
+ char buffer[1024];
+ int slen = 0;
+ unsigned int opt_loop = 0;
+ int elen = 0; /* entry length */
+ nserror res;
+ bool even = false;
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html")) {
+ goto fetch_about_config_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>NetSurf Browser Config</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body "
+ "id =\"configlist\" "
+ "class=\"ns-even-bg ns-even-fg ns-border\" "
+ "style=\"overflow: hidden;\">\n"
+ "<h1 class=\"ns-border\">NetSurf Browser Config</h1>\n"
+ "<table class=\"config\">\n"
+ "<tr><th>Option</th>"
+ "<th>Type</th>"
+ "<th>Provenance</th>"
+ "<th>Setting</th></tr>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+
+
+ do {
+ if (even) {
+ elen = nsoption_snoptionf(buffer + slen,
+ sizeof buffer - slen,
+ opt_loop,
+ "<tr class=\"ns-even-bg\">"
+ "<th class=\"ns-border\">%k</th>"
+ "<td class=\"ns-border\">%t</td>"
+ "<td class=\"ns-border\">%p</td>"
+ "<td class=\"ns-border\">%V</td>"
+ "</tr>\n");
+ } else {
+ elen = nsoption_snoptionf(buffer + slen,
+ sizeof buffer - slen,
+ opt_loop,
+ "<tr class=\"ns-odd-bg\">"
+ "<th class=\"ns-border\">%k</th>"
+ "<td class=\"ns-border\">%t</td>"
+ "<td class=\"ns-border\">%p</td>"
+ "<td class=\"ns-border\">%V</td>"
+ "</tr>\n");
+ }
+ if (elen <= 0)
+ break; /* last option */
+
+ if (elen >= (int) (sizeof buffer - slen)) {
+ /* last entry would not fit in buffer, submit buffer */
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+ slen = 0;
+ } else {
+ /* normal addition */
+ slen += elen;
+ opt_loop++;
+ even = !even;
+ }
+ } while (elen > 0);
+
+ slen += snprintf(buffer + slen, sizeof buffer - slen,
+ "</table>\n</body>\n</html>\n");
+
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_config_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_config_handler_aborted:
+ return false;
+}
diff --git a/content/fetchers/about/config.h b/content/fetchers/about/config.h
new file mode 100644
index 000000000..efef23971
--- /dev/null
+++ b/content/fetchers/about/config.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme config handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CONFIG_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_CONFIG_H
+
+/**
+ * Handler to generate about scheme config page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_config_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/imagecache.c b/content/fetchers/about/imagecache.c
new file mode 100644
index 000000000..23d3ef41c
--- /dev/null
+++ b/content/fetchers/about/imagecache.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme imagecache page
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "netsurf/types.h"
+
+#include "image/image_cache.h"
+
+#include "private.h"
+#include "imagecache.h"
+
+/* exported interface documented in about/imagecache.h */
+bool fetch_about_imagecache_handler(struct fetch_about_context *ctx)
+{
+ char buffer[2048]; /* output buffer */
+ int code = 200;
+ int slen;
+ unsigned int cent_loop = 0;
+ int elen = 0; /* entry length */
+ nserror res;
+ bool even = false;
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, code);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html"))
+ goto fetch_about_imagecache_handler_aborted;
+
+ /* page head */
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>Image Cache Status</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body id =\"cachelist\" class=\"ns-even-bg ns-even-fg ns-border\">\n"
+ "<h1 class=\"ns-border\">Image Cache Status</h1>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ /* image cache summary */
+ slen = image_cache_snsummaryf(buffer, sizeof(buffer),
+ "<p>Configured limit of %a hysteresis of %b</p>\n"
+ "<p>Total bitmap size in use %c (in %d)</p>\n"
+ "<p>Age %es</p>\n"
+ "<p>Peak size %f (in %g)</p>\n"
+ "<p>Peak image count %h (size %i)</p>\n"
+ "<p>Cache total/hit/miss/fail (counts) %j/%k/%l/%m "
+ "(%pj%%/%pk%%/%pl%%/%pm%%)"
+ "<img width=200 height=100 src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%k,%l,%m\" />"
+ "</p>\n");
+ if (slen >= (int) (sizeof(buffer))) {
+ goto fetch_about_imagecache_handler_aborted; /* overflow */
+ }
+
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ /* image cache summary */
+ slen = image_cache_snsummaryf(buffer, sizeof(buffer),
+ "<p>Cache total/hit/miss/fail (size) %n/%o/%q/%r "
+ "(%pn%%/%po%%/%pq%%/%pr%%)"
+ "<img width=200 height=100 src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%o,%q,%r\" /></p>\n"
+ "<p>Total images never rendered: %s "
+ "(includes %t that were converted)</p>\n"
+ "<p>Total number of excessive conversions: %u "
+ "(from %v images converted more than once)"
+ "</p>\n"
+ "<p>Bitmap of size %w had most (%x) conversions</p>\n"
+ "<h2 class=\"ns-border\">Current contents</h2>\n");
+ if (slen >= (int) (sizeof(buffer))) {
+ goto fetch_about_imagecache_handler_aborted; /* overflow */
+ }
+
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ /* image cache entry table */
+ res = fetch_about_ssenddataf(ctx, "<p class=\"imagecachelist\">\n"
+ "<strong>"
+ "<span>Entry</span>"
+ "<span>Content Key</span>"
+ "<span>Redraw Count</span>"
+ "<span>Conversion Count</span>"
+ "<span>Last Redraw</span>"
+ "<span>Bitmap Age</span>"
+ "<span>Bitmap Size</span>"
+ "<span>Source</span>"
+ "</strong>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ slen = 0;
+ do {
+ if (even) {
+ elen = image_cache_snentryf(buffer + slen,
+ sizeof buffer - slen,
+ cent_loop,
+ "<a href=\"%U\">"
+ "<span class=\"ns-border\">%e</span>"
+ "<span class=\"ns-border\">%k</span>"
+ "<span class=\"ns-border\">%r</span>"
+ "<span class=\"ns-border\">%c</span>"
+ "<span class=\"ns-border\">%a</span>"
+ "<span class=\"ns-border\">%g</span>"
+ "<span class=\"ns-border\">%s</span>"
+ "<span class=\"ns-border\">%o</span>"
+ "</a>\n");
+ } else {
+ elen = image_cache_snentryf(buffer + slen,
+ sizeof buffer - slen,
+ cent_loop,
+ "<a class=\"ns-odd-bg\" href=\"%U\">"
+ "<span class=\"ns-border\">%e</span>"
+ "<span class=\"ns-border\">%k</span>"
+ "<span class=\"ns-border\">%r</span>"
+ "<span class=\"ns-border\">%c</span>"
+ "<span class=\"ns-border\">%a</span>"
+ "<span class=\"ns-border\">%g</span>"
+ "<span class=\"ns-border\">%s</span>"
+ "<span class=\"ns-border\">%o</span>"
+ "</a>\n");
+ }
+ if (elen <= 0)
+ break; /* last option */
+
+ if (elen >= (int) (sizeof buffer - slen)) {
+ /* last entry would not fit in buffer, submit buffer */
+ res = fetch_about_senddata(ctx,
+ (const uint8_t *)buffer,
+ slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ slen = 0;
+ } else {
+ /* normal addition */
+ slen += elen;
+ cent_loop++;
+ even = !even;
+ }
+ } while (elen > 0);
+
+ slen += snprintf(buffer + slen, sizeof buffer - slen,
+ "</p>\n</body>\n</html>\n");
+
+ res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen);
+ if (res != NSERROR_OK) {
+ goto fetch_about_imagecache_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_imagecache_handler_aborted:
+ return false;
+}
diff --git a/content/fetchers/about/imagecache.h b/content/fetchers/about/imagecache.h
new file mode 100644
index 000000000..d1419cea7
--- /dev/null
+++ b/content/fetchers/about/imagecache.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme imagecache handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_IMAGECACHE_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_IMAGECACHE_H
+
+/**
+ * Handler to generate about scheme imagecache page.
+ *
+ * Shows details of current image cache.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_imagecache_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/nscolours.c b/content/fetchers/about/nscolours.c
new file mode 100644
index 000000000..bd7a5ed7a
--- /dev/null
+++ b/content/fetchers/about/nscolours.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query privacy page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "netsurf/plot_style.h"
+
+#include "utils/errors.h"
+#include "utils/nscolour.h"
+
+#include "private.h"
+#include "nscolours.h"
+
+/**
+ * Handler to generate the nscolours stylesheet
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_nscolours_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ const char *stylesheet;
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/css; charset=utf-8")) {
+ goto aborted;
+ }
+
+ res = nscolour_get_stylesheet(&stylesheet);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "html {\n"
+ "\tbackground-color: #%06x;\n"
+ "}\n"
+ "%s",
+ colour_rb_swap(nscolours[NSCOLOUR_WIN_ODD_BG]),
+ stylesheet);
+ if (res != NSERROR_OK) {
+ goto aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+aborted:
+
+ return false;
+}
diff --git a/content/fetchers/about/nscolours.h b/content/fetchers/about/nscolours.h
new file mode 100644
index 000000000..a602e4d99
--- /dev/null
+++ b/content/fetchers/about/nscolours.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme nscolours handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_NSCOLOURS_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_NSCOLOURS_H
+
+/**
+ * Handler to generate the nscolours stylesheet
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_nscolours_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/private.h b/content/fetchers/about/private.h
new file mode 100644
index 000000000..fcf9b1987
--- /dev/null
+++ b/content/fetchers/about/private.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * Private interfaces for the about scheme fetcher.
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_PRIVATE_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_PRIVATE_H
+
+struct fetch_about_context;
+struct fetch_multipart_data;
+
+/**
+ * set http response code on about response
+ */
+bool fetch_about_set_http_code(struct fetch_about_context *ctx, long code);
+
+/**
+ * Send a header on the about response
+ *
+ * \param ctx The about fetch context
+ * \param fmt The format specifier of the header
+ * \return true if the fetch has been aborted else false
+ */
+bool fetch_about_send_header(struct fetch_about_context *ctx, const char *fmt, ...);
+
+/**
+ * send data on the about response
+ */
+nserror fetch_about_senddata(struct fetch_about_context *ctx, const uint8_t *data, size_t data_len);
+
+/**
+ * send formatted data on the about response
+ */
+nserror fetch_about_ssenddataf(struct fetch_about_context *ctx, const char *fmt, ...);
+
+/**
+ * complete the about fetch response
+ */
+bool fetch_about_send_finished(struct fetch_about_context *ctx);
+
+/**
+ * Generate a 500 server error respnse
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_srverror(struct fetch_about_context *ctx);
+
+/**
+ * get the fetch url
+ */
+struct nsurl *fetch_about_get_url(struct fetch_about_context *ctx);
+
+/**
+ * get multipart fetch data
+ */
+const struct fetch_multipart_data *fetch_about_get_multipart(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/query.c b/content/fetchers/about/query.c
new file mode 100644
index 000000000..7c4e9f442
--- /dev/null
+++ b/content/fetchers/about/query.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query privacy page
+ */
+
+#include <stdlib.h>
+
+#include "utils/errors.h"
+#include "utils/nsurl.h"
+#include "utils/messages.h"
+
+#include "query.h"
+
+/* exported interface documented in about/query.h */
+nserror
+get_query_description(struct nsurl *url,
+ const char *key,
+ char **out_str)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ char *str = NULL;
+
+ /* get the host in question */
+ res = nsurl_get(url, NSURL_HOST, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ /* obtain the description with the url substituted */
+ str = messages_get_buff(key, url_s);
+ if (str == NULL) {
+ res = NSERROR_NOMEM;
+ } else {
+ *out_str = str;
+ }
+
+ free(url_s);
+
+ return res;
+}
diff --git a/content/fetchers/about/query.h b/content/fetchers/about/query.h
new file mode 100644
index 000000000..889a9ecdd
--- /dev/null
+++ b/content/fetchers/about/query.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme query handlers support interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_H
+
+/**
+ * generate a generic query description
+ *
+ * \param url The site url
+ * \param key message key
+ * \param out_str pointer to buffer with output
+ */
+nserror get_query_description(struct nsurl *url, const char *key, char **out_str);
+
+#endif
diff --git a/content/fetchers/about/query_auth.c b/content/fetchers/about/query_auth.c
new file mode 100644
index 000000000..1ed2e80ee
--- /dev/null
+++ b/content/fetchers/about/query_auth.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query privacy page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "utils/errors.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "content/fetch.h"
+
+#include "private.h"
+#include "query_auth.h"
+
+
+/**
+ * generate the description of the login query
+ */
+static nserror
+get_authentication_description(struct nsurl *url,
+ const char *realm,
+ const char *username,
+ const char *password,
+ char **out_str)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ char *str = NULL;
+ const char *key;
+
+ res = nsurl_get(url, NSURL_HOST, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ return res;
+ }
+
+ if ((*username == 0) && (*password == 0)) {
+ key = "LoginDescription";
+ } else {
+ key = "LoginAgain";
+ }
+
+ str = messages_get_buff(key, url_s, realm);
+ if (str != NULL) {
+ NSLOG(netsurf, INFO,
+ "key:%s url:%s realm:%s str:%s",
+ key, url_s, realm, str);
+ *out_str = str;
+ } else {
+ res = NSERROR_NOMEM;
+ }
+
+ free(url_s);
+
+ return res;
+}
+
+
+/**
+ * Handler to generate about scheme authentication query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_auth_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ const char *realm = "";
+ const char *username = "";
+ const char *password = "";
+ const char *title;
+ char *description = NULL;
+ struct nsurl *siteurl = NULL;
+ const struct fetch_multipart_data *curmd; /* mutipart data iterator */
+
+ /* extract parameters from multipart post data */
+ curmd = fetch_about_get_multipart(ctx);
+ while (curmd != NULL) {
+ if (strcmp(curmd->name, "siteurl") == 0) {
+ res = nsurl_create(curmd->value, &siteurl);
+ if (res != NSERROR_OK) {
+ return fetch_about_srverror(ctx);
+ }
+ } else if (strcmp(curmd->name, "realm") == 0) {
+ realm = curmd->value;
+ } else if (strcmp(curmd->name, "username") == 0) {
+ username = curmd->value;
+ } else if (strcmp(curmd->name, "password") == 0) {
+ password = curmd->value;
+ }
+ curmd = curmd->next;
+ }
+
+ if (siteurl == NULL) {
+ return fetch_about_srverror(ctx);
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ title = messages_get("LoginTitle");
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>%s</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body class=\"ns-even-bg ns-even-fg ns-border\" id =\"authentication\">\n"
+ "<h1 class=\"ns-border\">%s</h1>\n",
+ title, title);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<form method=\"post\""
+ " enctype=\"multipart/form-data\">");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = get_authentication_description(siteurl,
+ realm,
+ username,
+ password,
+ &description);
+ if (res == NSERROR_OK) {
+ res = fetch_about_ssenddataf(ctx, "<p>%s</p>", description);
+ free(description);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+ }
+
+ res = fetch_about_ssenddataf(ctx, "<table>");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<tr>"
+ "<th><label for=\"name\">%s:</label></th>"
+ "<td><input type=\"text\" id=\"username\" "
+ "name=\"username\" value=\"%s\"></td>"
+ "</tr>",
+ messages_get("Username"), username);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<tr>"
+ "<th><label for=\"password\">%s:</label></th>"
+ "<td><input type=\"password\" id=\"password\" "
+ "name=\"password\" value=\"%s\"></td>"
+ "</tr>",
+ messages_get("Password"), password);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</table>");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<div id=\"buttons\">"
+ "<input type=\"submit\" id=\"login\" name=\"login\" "
+ "value=\"%s\" class=\"default-action\">"
+ "<input type=\"submit\" id=\"cancel\" name=\"cancel\" "
+ "value=\"%s\">"
+ "</div>",
+ messages_get("Login"),
+ messages_get("Cancel"));
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ url_s = strdup("");
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
+ url_s);
+ free(url_s);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<input type=\"hidden\" name=\"realm\" value=\"%s\">",
+ realm);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</form></body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_auth_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ nsurl_unref(siteurl);
+
+ return true;
+
+fetch_about_query_auth_handler_aborted:
+
+ nsurl_unref(siteurl);
+
+ return false;
+}
diff --git a/content/fetchers/about/query_auth.h b/content/fetchers/about/query_auth.h
new file mode 100644
index 000000000..1c7f658e1
--- /dev/null
+++ b/content/fetchers/about/query_auth.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme query auth handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_AUTH_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_AUTH_H
+
+/**
+ * Handler to generate about scheme query auth page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_auth_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/query_fetcherror.c b/content/fetchers/about/query_fetcherror.c
new file mode 100644
index 000000000..010a597e4
--- /dev/null
+++ b/content/fetchers/about/query_fetcherror.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query privacy page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "utils/errors.h"
+#include "utils/messages.h"
+#include "content/fetch.h"
+
+#include "private.h"
+#include "query.h"
+#include "query_fetcherror.h"
+
+/**
+ * Handler to generate about scheme fetch error query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_fetcherror_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ const char *reason = "";
+ const char *title;
+ struct nsurl *siteurl = NULL;
+ char *description = NULL;
+ const struct fetch_multipart_data *curmd; /* mutipart data iterator */
+
+ /* extract parameters from multipart post data */
+ curmd = fetch_about_get_multipart(ctx);
+ while (curmd != NULL) {
+ if (strcmp(curmd->name, "siteurl") == 0) {
+ res = nsurl_create(curmd->value, &siteurl);
+ if (res != NSERROR_OK) {
+ return fetch_about_srverror(ctx);
+ }
+ } else if (strcmp(curmd->name, "reason") == 0) {
+ reason = curmd->value;
+ }
+ curmd = curmd->next;
+ }
+
+ if (siteurl == NULL) {
+ return fetch_about_srverror(ctx);
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ title = messages_get("FetchErrorTitle");
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>%s</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body class=\"ns-even-bg ns-even-fg ns-border\" id =\"fetcherror\">\n"
+ "<h1 class=\"ns-border ns-odd-fg-bad\">%s</h1>\n",
+ title, title);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<form method=\"post\""
+ " enctype=\"multipart/form-data\">");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ res = get_query_description(siteurl,
+ "FetchErrorDescription",
+ &description);
+ if (res == NSERROR_OK) {
+ res = fetch_about_ssenddataf(ctx, "<div><p>%s</p></div>", description);
+ free(description);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+ }
+ res = fetch_about_ssenddataf(ctx, "<div><p>%s</p></div>", reason);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<div id=\"buttons\">"
+ "<input type=\"submit\" id=\"back\" name=\"back\" "
+ "value=\"%s\" class=\"default-action\">"
+ "<input type=\"submit\" id=\"retry\" name=\"retry\" "
+ "value=\"%s\">"
+ "</div>",
+ messages_get("Backtoprevious"),
+ messages_get("TryAgain"));
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ url_s = strdup("");
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
+ url_s);
+ free(url_s);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</form></body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_fetcherror_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ nsurl_unref(siteurl);
+
+ return true;
+
+fetch_about_query_fetcherror_handler_aborted:
+ nsurl_unref(siteurl);
+
+ return false;
+}
diff --git a/content/fetchers/about/query_fetcherror.h b/content/fetchers/about/query_fetcherror.h
new file mode 100644
index 000000000..85d972f8e
--- /dev/null
+++ b/content/fetchers/about/query_fetcherror.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme query fetch error handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_FETCHERROR_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_FETCHERROR_H
+
+/**
+ * Handler to generate about scheme fetch error query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_fetcherror_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/query_privacy.c b/content/fetchers/about/query_privacy.c
new file mode 100644
index 000000000..030672bab
--- /dev/null
+++ b/content/fetchers/about/query_privacy.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query privacy page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "utils/errors.h"
+#include "utils/messages.h"
+#include "content/fetch.h"
+
+#include "private.h"
+#include "query.h"
+#include "query_privacy.h"
+
+/**
+ * Handler to generate about scheme privacy query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_privacy_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ const char *reason = "";
+ const char *title;
+ struct nsurl *siteurl = NULL;
+ char *description = NULL;
+ const char *chainurl = NULL;
+ const struct fetch_multipart_data *curmd; /* mutipart data iterator */
+
+ /* extract parameters from multipart post data */
+ curmd = fetch_about_get_multipart(ctx);
+ while (curmd != NULL) {
+ if (strcmp(curmd->name, "siteurl") == 0) {
+ res = nsurl_create(curmd->value, &siteurl);
+ if (res != NSERROR_OK) {
+ return fetch_about_srverror(ctx);
+ }
+ } else if (strcmp(curmd->name, "reason") == 0) {
+ reason = curmd->value;
+ } else if (strcmp(curmd->name, "chainurl") == 0) {
+ chainurl = curmd->value;
+ }
+ curmd = curmd->next;
+ }
+
+ if (siteurl == NULL) {
+ return fetch_about_srverror(ctx);
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ title = messages_get("PrivacyTitle");
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>%s</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body class=\"ns-even-bg ns-even-fg ns-border\" id =\"privacy\">\n"
+ "<h1 class=\"ns-border ns-odd-fg-bad\">%s</h1>\n",
+ title, title);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<form method=\"post\""
+ " enctype=\"multipart/form-data\">");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ res = get_query_description(siteurl,
+ "PrivacyDescription",
+ &description);
+ if (res == NSERROR_OK) {
+ res = fetch_about_ssenddataf(ctx, "<div><p>%s</p></div>", description);
+ free(description);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+ }
+
+ if (chainurl == NULL) {
+ res = fetch_about_ssenddataf(ctx,
+ "<div><p>%s</p></div>"
+ "<div><p>%s</p></div>",
+ reason,
+ messages_get("ViewCertificatesNotPossible"));
+ } else {
+ res = fetch_about_ssenddataf(ctx,
+ "<div><p>%s</p></div>"
+ "<div><p><a href=\"%s\" target=\"_blank\">%s</a></p></div>",
+ reason,
+ chainurl,
+ messages_get("ViewCertificates"));
+ }
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<div id=\"buttons\">"
+ "<input type=\"submit\" id=\"back\" name=\"back\" "
+ "value=\"%s\" class=\"default-action\">"
+ "<input type=\"submit\" id=\"proceed\" name=\"proceed\" "
+ "value=\"%s\">"
+ "</div>",
+ messages_get("Backtosafety"),
+ messages_get("Proceed"));
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ url_s = strdup("");
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
+ url_s);
+ free(url_s);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</form></body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_ssl_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ nsurl_unref(siteurl);
+
+ return true;
+
+fetch_about_query_ssl_handler_aborted:
+ nsurl_unref(siteurl);
+
+ return false;
+}
diff --git a/content/fetchers/about/query_privacy.h b/content/fetchers/about/query_privacy.h
new file mode 100644
index 000000000..38ddbe889
--- /dev/null
+++ b/content/fetchers/about/query_privacy.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme query privacy handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_PRIVACY_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_PRIVACY_H
+
+/**
+ * Handler to generate about scheme query privacy page.
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_privacy_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/query_timeout.c b/content/fetchers/about/query_timeout.c
new file mode 100644
index 000000000..5c014bc9b
--- /dev/null
+++ b/content/fetchers/about/query_timeout.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme query timeout page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "utils/errors.h"
+#include "utils/messages.h"
+#include "content/fetch.h"
+
+#include "private.h"
+#include "query.h"
+#include "query_timeout.h"
+
+/**
+ * Handler to generate about scheme timeout query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_timeout_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ char *url_s;
+ size_t url_l;
+ const char *reason = "";
+ const char *title;
+ struct nsurl *siteurl = NULL;
+ char *description = NULL;
+ const struct fetch_multipart_data *curmd; /* mutipart data iterator */
+
+ /* extract parameters from multipart post data */
+ curmd = fetch_about_get_multipart(ctx);
+ while (curmd != NULL) {
+ if (strcmp(curmd->name, "siteurl") == 0) {
+ res = nsurl_create(curmd->value, &siteurl);
+ if (res != NSERROR_OK) {
+ return fetch_about_srverror(ctx);
+ }
+ } else if (strcmp(curmd->name, "reason") == 0) {
+ reason = curmd->value;
+ }
+ curmd = curmd->next;
+ }
+
+ if (siteurl == NULL) {
+ return fetch_about_srverror(ctx);
+ }
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/html; charset=utf-8")) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ title = messages_get("TimeoutTitle");
+ res = fetch_about_ssenddataf(ctx,
+ "<html>\n<head>\n"
+ "<title>%s</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"resource:internal.css\">\n"
+ "</head>\n"
+ "<body class=\"ns-even-bg ns-even-fg ns-border\" id =\"timeout\">\n"
+ "<h1 class=\"ns-border ns-odd-fg-bad\">%s</h1>\n",
+ title, title);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<form method=\"post\""
+ " enctype=\"multipart/form-data\">");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ res = get_query_description(siteurl,
+ "TimeoutDescription",
+ &description);
+ if (res == NSERROR_OK) {
+ res = fetch_about_ssenddataf(ctx, "<div><p>%s</p></div>", description);
+ free(description);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+ }
+ res = fetch_about_ssenddataf(ctx, "<div><p>%s</p></div>", reason);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "<div id=\"buttons\">"
+ "<input type=\"submit\" id=\"back\" name=\"back\" "
+ "value=\"%s\" class=\"default-action\">"
+ "<input type=\"submit\" id=\"retry\" name=\"retry\" "
+ "value=\"%s\">"
+ "</div>",
+ messages_get("Backtoprevious"),
+ messages_get("TryAgain"));
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ res = nsurl_get(siteurl, NSURL_COMPLETE, &url_s, &url_l);
+ if (res != NSERROR_OK) {
+ url_s = strdup("");
+ }
+ res = fetch_about_ssenddataf(ctx,
+ "<input type=\"hidden\" name=\"siteurl\" value=\"%s\">",
+ url_s);
+ free(url_s);
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "</form></body>\n</html>\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_query_timeout_handler_aborted;
+ }
+
+ fetch_about_send_finished(ctx);
+
+ nsurl_unref(siteurl);
+
+ return true;
+
+fetch_about_query_timeout_handler_aborted:
+ nsurl_unref(siteurl);
+
+ return false;
+}
diff --git a/content/fetchers/about/query_timeout.h b/content/fetchers/about/query_timeout.h
new file mode 100644
index 000000000..a64757f21
--- /dev/null
+++ b/content/fetchers/about/query_timeout.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * about scheme query timeout handler interface
+ */
+
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_TIMEOUT_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_QUERY_TIMEOUT_H
+
+/**
+ * Handler to generate about scheme timeout query page
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_query_timeout_handler(struct fetch_about_context *ctx);
+
+#endif
diff --git a/content/fetchers/about/testament.c b/content/fetchers/about/testament.c
new file mode 100644
index 000000000..ae992ec52
--- /dev/null
+++ b/content/fetchers/about/testament.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2020 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf.
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * content generator for the about scheme testament page
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "utils/errors.h"
+#include "netsurf/inttypes.h"
+#include "testament.h"
+
+#include "private.h"
+#include "atestament.h"
+
+typedef struct {
+ const char *leaf;
+ const char *modtype;
+} modification_t;
+
+/**
+ * Generate the text of an svn testament which represents the current
+ * build-tree status
+ *
+ * \param ctx The fetcher context.
+ * \return true if handled false if aborted.
+ */
+bool fetch_about_testament_handler(struct fetch_about_context *ctx)
+{
+ nserror res;
+ static modification_t modifications[] = WT_MODIFICATIONS;
+ int modidx; /* midification index */
+
+ /* content is going to return ok */
+ fetch_about_set_http_code(ctx, 200);
+
+ /* content type */
+ if (fetch_about_send_header(ctx, "Content-Type: text/plain"))
+ goto fetch_about_testament_handler_aborted;
+
+ res = fetch_about_ssenddataf(ctx,
+ "# Automatically generated by NetSurf build system\n\n");
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+#if defined(WT_BRANCHISTRUNK) || defined(WT_BRANCHISMASTER)
+ "# This is a *DEVELOPMENT* build from the main line.\n\n"
+#elif defined(WT_BRANCHISTAG) && (WT_MODIFIED == 0)
+ "# This is a tagged build of NetSurf\n"
+#ifdef WT_TAGIS
+ "# The tag used was '" WT_TAGIS "'\n\n"
+#else
+ "\n"
+#endif
+#elif defined(WT_NO_SVN) || defined(WT_NO_GIT)
+ "# This NetSurf was built outside of our revision "
+ "control environment.\n"
+ "# This testament is therefore not very useful.\n\n"
+#else
+ "# This NetSurf was built from a branch (" WT_BRANCHPATH ").\n\n"
+#endif
+#if defined(CI_BUILD)
+ "# This build carries the CI build number '" CI_BUILD "'\n\n"
+#endif
+ );
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx,
+ "Built by %s (%s) from %s at revision %s on %s\n\n",
+ GECOS, USERNAME, WT_BRANCHPATH, WT_REVID, WT_COMPILEDATE);
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+
+ res = fetch_about_ssenddataf(ctx, "Built on %s in %s\n\n", WT_HOSTNAME, WT_ROOT);
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+
+ if (WT_MODIFIED > 0) {
+ res = fetch_about_ssenddataf(ctx,
+ "Working tree has %d modification%s\n\n",
+ WT_MODIFIED, WT_MODIFIED == 1 ? "" : "s");
+ } else {
+ res = fetch_about_ssenddataf(ctx, "Working tree is not modified.\n");
+ }
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+
+ for (modidx = 0; modidx < WT_MODIFIED; ++modidx) {
+ res = fetch_about_ssenddataf(ctx,
+ " %s %s\n",
+ modifications[modidx].modtype,
+ modifications[modidx].leaf);
+ if (res != NSERROR_OK) {
+ goto fetch_about_testament_handler_aborted;
+ }
+ }
+
+ fetch_about_send_finished(ctx);
+
+ return true;
+
+fetch_about_testament_handler_aborted:
+ return false;
+}
diff --git a/content/fetchers/curl.c b/content/fetchers/curl.c
index 50c5d6473..680e60456 100644
--- a/content/fetchers/curl.c
+++ b/content/fetchers/curl.c
@@ -38,13 +38,12 @@
#include <strings.h>
#include <time.h>
#include <sys/stat.h>
-#include <openssl/ssl.h>
-#include <openssl/x509v3.h>
#include <libwapcaplet/libwapcaplet.h>
#include <nsutils/time.h>
#include "utils/corestrings.h"
+#include "utils/hashmap.h"
#include "utils/nsoption.h"
#include "utils/log.h"
#include "utils/messages.h"
@@ -62,23 +61,149 @@
#include "content/fetchers/curl.h"
#include "content/urldb.h"
-/** maximum number of progress notifications per second */
+/**
+ * maximum number of progress notifications per second
+ */
#define UPDATES_PER_SECOND 2
-/* the ciphersuites we are willing to use */
+/**
+ * The ciphersuites the browser is prepared to use for TLS1.3
+ */
+#define CIPHER_SUITES \
+ "TLS_AES_256_GCM_SHA384:" \
+ "TLS_CHACHA20_POLY1305_SHA256:" \
+ "TLS_AES_128_GCM_SHA256"
+
+/**
+ * The ciphersuites the browser is prepared to use for TLS<1.3
+ */
#define CIPHER_LIST \
/* disable everything */ \
"-ALL:" \
- /* enable TLSv1.2 PFS suites */ \
- "EECDH+AES+TLSv1.2:EDH+AES+TLSv1.2:" \
- /* enable PFS AES GCM suites */ \
- "EECDH+AESGCM:EDH+AESGCM:" \
- /* Enable PFS AES CBC suites */ \
- "EECDH+AES:EDH+AES:" \
- /* Enable non-PFS fallback suite */ \
- "AES128-SHA:" \
- /* Remove any PFS suites using weak DSA key exchange */ \
- "-DSS"
+ /* enable TLSv1.2 ECDHE AES GCM suites */ \
+ "EECDH+AESGCM+TLSv1.2:" \
+ /* enable ECDHE CHACHA20/POLY1305 suites */ \
+ "EECDH+CHACHA20:" \
+ /* Sort above by strength */ \
+ "@STRENGTH:" \
+ /* enable ECDHE (auth=RSA, mac=SHA1) AES CBC suites */ \
+ "EECDH+aRSA+AES+SHA1"
+
+/**
+ * The legacy cipher suites the browser is prepared to use for TLS<1.3
+ */
+#define CIPHER_LIST_LEGACY \
+ /* as above */ \
+ CIPHER_LIST":" \
+ /* enable (non-PFS) RSA AES GCM suites */ \
+ "RSA+AESGCM:" \
+ /* enable (non-PFS) RSA (mac=SHA1) AES CBC suites */ \
+ "RSA+AES+SHA1"
+
+/* Open SSL compatability for certificate handling */
+#ifdef WITH_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#else /* WITH_OPENSSL */
+
+typedef char X509;
+
+static void X509_free(X509 *cert)
+{
+ free(cert);
+}
+
+#endif /* WITH_OPENSSL */
+
+/* SSL certificate chain cache */
+
+/* We're only interested in the hostname and port */
+static uint32_t
+curl_fetch_ssl_key_hash(void *key)
+{
+ nsurl *url = key;
+ lwc_string *hostname = nsurl_get_component(url, NSURL_HOST);
+ lwc_string *port = nsurl_get_component(url, NSURL_PORT);
+ uint32_t hash;
+
+ if (port == NULL)
+ port = lwc_string_ref(corestring_lwc_443);
+
+ hash = lwc_string_hash_value(hostname) ^ lwc_string_hash_value(port);
+
+ lwc_string_unref(hostname);
+ lwc_string_unref(port);
+
+ return hash;
+}
+
+/* We only compare the hostname and port */
+static bool
+curl_fetch_ssl_key_eq(void *key1, void *key2)
+{
+ nsurl *url1 = key1;
+ nsurl *url2 = key2;
+ lwc_string *hostname1 = nsurl_get_component(url1, NSURL_HOST);
+ lwc_string *hostname2 = nsurl_get_component(url2, NSURL_HOST);
+ lwc_string *port1 = nsurl_get_component(url1, NSURL_PORT);
+ lwc_string *port2 = nsurl_get_component(url2, NSURL_PORT);
+ bool iseq = false;
+
+ if (port1 == NULL)
+ port1 = lwc_string_ref(corestring_lwc_443);
+ if (port2 == NULL)
+ port2 = lwc_string_ref(corestring_lwc_443);
+
+ if (lwc_string_isequal(hostname1, hostname2, &iseq) != lwc_error_ok ||
+ iseq == false)
+ goto out;
+
+ iseq = false;
+ if (lwc_string_isequal(port1, port2, &iseq) != lwc_error_ok)
+ goto out;
+
+out:
+ lwc_string_unref(hostname1);
+ lwc_string_unref(hostname2);
+ lwc_string_unref(port1);
+ lwc_string_unref(port2);
+
+ return iseq;
+}
+
+static void *
+curl_fetch_ssl_value_alloc(void *key)
+{
+ struct cert_chain *out;
+
+ if (cert_chain_alloc(0, &out) != NSERROR_OK) {
+ return NULL;
+ }
+
+ return out;
+}
+
+static void
+curl_fetch_ssl_value_destroy(void *value)
+{
+ struct cert_chain *chain = value;
+ if (cert_chain_free(chain) != NSERROR_OK) {
+ NSLOG(netsurf, WARNING, "Problem freeing SSL certificate chain");
+ }
+}
+
+static hashmap_parameters_t curl_fetch_ssl_hashmap_parameters = {
+ .key_clone = (hashmap_key_clone_t)nsurl_ref,
+ .key_destroy = (hashmap_key_destroy_t)nsurl_unref,
+ .key_eq = curl_fetch_ssl_key_eq,
+ .key_hash = curl_fetch_ssl_key_hash,
+ .value_alloc = curl_fetch_ssl_value_alloc,
+ .value_destroy = curl_fetch_ssl_value_destroy,
+};
+
+static hashmap_t *curl_fetch_ssl_hashmap = NULL;
/** SSL certificate info */
struct cert_info {
@@ -86,15 +211,36 @@ struct cert_info {
long err; /**< OpenSSL error code */
};
+#if LIBCURL_VERSION_NUM >= 0x072000 /* 7.32.0 depricated CURLOPT_PROGRESSFUNCTION*/
+#define NSCURLOPT_PROGRESS_FUNCTION CURLOPT_XFERINFOFUNCTION
+#define NSCURLOPT_PROGRESS_DATA CURLOPT_XFERINFODATA
+#define NSCURL_PROGRESS_T curl_off_t
+#else
+#define NSCURLOPT_PROGRESS_FUNCTION CURLOPT_PROGRESSFUNCTION
+#define NSCURLOPT_PROGRESS_DATA CURLOPT_PROGRESSDATA
+#define NSCURL_PROGRESS_T double
+#endif
+
+#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 depricated curl_formadd */
+#define NSCURL_POSTDATA_T curl_mime
+#define NSCURL_POSTDATA_CURLOPT CURLOPT_MIMEPOST
+#define NSCURL_POSTDATA_FREE(x) curl_mime_free(x)
+#else
+#define NSCURL_POSTDATA_T struct curl_httppost
+#define NSCURL_POSTDATA_CURLOPT CURLOPT_HTTPPOST
+#define NSCURL_POSTDATA_FREE(x) curl_formfree(x)
+#endif
+
/** Information for a single fetch. */
struct curl_fetch_info {
struct fetch *fetch_handle; /**< The fetch handle we're parented by. */
CURL * curl_handle; /**< cURL handle if being fetched, or 0. */
+ bool sent_ssl_chain; /**< Have we tried to send the SSL chain */
bool had_headers; /**< Headers have been processed. */
bool abort; /**< Abort requested. */
bool stopped; /**< Download stopped on purpose. */
bool only_2xx; /**< Only HTTP 2xx responses acceptable. */
- bool downgrade_tls; /**< Downgrade to TLS <= 1.0 */
+ bool downgrade_tls; /**< Downgrade to TLS 1.2 */
nsurl *url; /**< URL of this fetch. */
lwc_string *host; /**< The hostname of this fetch. */
struct curl_slist *headers; /**< List of request headers. */
@@ -102,12 +248,14 @@ struct curl_fetch_info {
unsigned long content_length; /**< Response Content-Length, or 0. */
char *cookie_string; /**< Cookie string for this fetch */
char *realm; /**< HTTP Auth Realm */
- char *post_urlenc; /**< Url encoded POST string, or 0. */
+ struct fetch_postdata *postdata; /**< POST data */
+ NSCURL_POSTDATA_T *curl_postdata; /**< POST data in curl representation */
+
long http_code; /**< HTTP result code from cURL. */
- struct curl_httppost *post_multipart; /**< Multipart post data, or 0. */
+
uint64_t last_progress_update; /**< Time of last progress update */
int cert_depth; /**< deepest certificate in use */
- struct cert_info cert_data[MAX_SSL_CERTS]; /**< HTTPS certificate data */
+ struct cert_info cert_data[MAX_CERT_DEPTH]; /**< HTTPS certificate data */
};
/** curl handle cache entry */
@@ -144,28 +292,6 @@ static char fetch_proxy_userpwd[100];
static bool inside_curl = false;
-/* OpenSSL 1.0.x to 1.1.0 certificate reference counting changed
- * LibreSSL declares its OpenSSL version as 2.1 but only supports the old way
- */
-#if (defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x1010000fL))
-static int ns_X509_up_ref(X509 *cert)
-{
- cert->references++;
- return 1;
-}
-
-static void ns_X509_free(X509 *cert)
-{
- cert->references--;
- if (cert->references == 0) {
- X509_free(cert);
- }
-}
-#else
-#define ns_X509_up_ref X509_up_ref
-#define ns_X509_free X509_free
-#endif
-
/**
* Initialise a cURL fetcher.
*/
@@ -204,6 +330,10 @@ static void fetch_curl_finalise(lwc_string *scheme)
"curl_multi_cleanup failed: ignoring");
curl_global_cleanup();
+
+ NSLOG(netsurf, DEBUG, "Cleaning up SSL cert chain hashmap");
+ hashmap_destroy(curl_fetch_ssl_hashmap);
+ curl_fetch_ssl_hashmap = NULL;
}
/* Free anything remaining in the cached curl handle ring */
@@ -229,85 +359,95 @@ static bool fetch_curl_can_fetch(const nsurl *url)
}
+
/**
- * Convert a list of struct ::fetch_multipart_data to a list of
- * struct curl_httppost for libcurl.
+ * allocate postdata
*/
-static struct curl_httppost *
-fetch_curl_post_convert(const struct fetch_multipart_data *control)
+static struct fetch_postdata *
+fetch_curl_alloc_postdata(const char *post_urlenc,
+ const struct fetch_multipart_data *post_multipart)
{
- struct curl_httppost *post = 0, *last = 0;
- CURLFORMcode code;
- nserror ret;
-
- for (; control; control = control->next) {
- if (control->file) {
- char *leafname = NULL;
- ret = guit->file->basename(control->value, &leafname, NULL);
- if (ret != NSERROR_OK) {
- continue;
+ struct fetch_postdata *postdata;
+ postdata = calloc(1, sizeof(struct fetch_postdata));
+ if (postdata != NULL) {
+
+ if (post_urlenc) {
+ postdata->type = FETCH_POSTDATA_URLENC;
+ postdata->data.urlenc = strdup(post_urlenc);
+ if (postdata->data.urlenc == NULL) {
+ free(postdata);
+ postdata = NULL;
}
-
- /* We have to special case filenames of "", so curl
- * a) actually attempts the fetch and
- * b) doesn't attempt to open the file ""
- */
- if (control->value[0] == '\0') {
- /* dummy buffer - needs to be static so
- * pointer's still valid when we go out
- * of scope (not that libcurl should be
- * attempting to access it, of course).
- */
- static char buf;
-
- code = curl_formadd(&post, &last,
- CURLFORM_COPYNAME, control->name,
- CURLFORM_BUFFER, control->value,
- /* needed, as basename("") == "." */
- CURLFORM_FILENAME, "",
- CURLFORM_BUFFERPTR, &buf,
- CURLFORM_BUFFERLENGTH, 0,
- CURLFORM_CONTENTTYPE,
- "application/octet-stream",
- CURLFORM_END);
- if (code != CURL_FORMADD_OK)
- NSLOG(netsurf, INFO,
- "curl_formadd: %d (%s)", code,
- control->name);
- } else {
- char *mimetype = guit->fetch->mimetype(control->value);
- code = curl_formadd(&post, &last,
- CURLFORM_COPYNAME, control->name,
- CURLFORM_FILE, control->rawfile,
- CURLFORM_FILENAME, leafname,
- CURLFORM_CONTENTTYPE,
- (mimetype != 0 ? mimetype : "text/plain"),
- CURLFORM_END);
- if (code != CURL_FORMADD_OK)
- NSLOG(netsurf, INFO,
- "curl_formadd: %d (%s=%s)",
- code,
- control->name,
- control->value);
- free(mimetype);
+ } else if (post_multipart) {
+ postdata->type = FETCH_POSTDATA_MULTIPART;
+ postdata->data.multipart = fetch_multipart_data_clone(post_multipart);
+ if (postdata->data.multipart == NULL) {
+ free(postdata);
+ postdata = NULL;
}
- free(leafname);
- }
- else {
- code = curl_formadd(&post, &last,
- CURLFORM_COPYNAME, control->name,
- CURLFORM_COPYCONTENTS, control->value,
- CURLFORM_END);
- if (code != CURL_FORMADD_OK)
- NSLOG(netsurf, INFO,
- "curl_formadd: %d (%s=%s)", code,
- control->name, control->value);
+ } else {
+ postdata->type = FETCH_POSTDATA_NONE;
}
}
+ return postdata;
+}
- return post;
+/**
+ * free postdata
+ */
+static void fetch_curl_free_postdata(struct fetch_postdata *postdata)
+{
+ if (postdata != NULL) {
+ switch (postdata->type) {
+ case FETCH_POSTDATA_NONE:
+ break;
+ case FETCH_POSTDATA_URLENC:
+ free(postdata->data.urlenc);
+ break;
+ case FETCH_POSTDATA_MULTIPART:
+ fetch_multipart_data_destroy(postdata->data.multipart);
+ break;
+ }
+
+ free(postdata);
+ }
}
+/**
+ *construct a new fetch structure
+ */
+static struct curl_fetch_info *fetch_alloc(void)
+{
+ struct curl_fetch_info *fetch;
+ fetch = malloc(sizeof (*fetch));
+ if (fetch == NULL)
+ return NULL;
+
+ fetch->curl_handle = NULL;
+ fetch->sent_ssl_chain = false;
+ fetch->had_headers = false;
+ fetch->abort = false;
+ fetch->stopped = false;
+ fetch->only_2xx = false;
+ fetch->downgrade_tls = false;
+ fetch->headers = NULL;
+ fetch->url = NULL;
+ fetch->host = NULL;
+ fetch->location = NULL;
+ fetch->content_length = 0;
+ fetch->http_code = 0;
+ fetch->cookie_string = NULL;
+ fetch->realm = NULL;
+ fetch->last_progress_update = 0;
+ fetch->postdata = NULL;
+ fetch->curl_postdata = NULL;
+
+ /* Clear certificate chain data */
+ memset(fetch->cert_data, 0, sizeof(fetch->cert_data));
+ fetch->cert_depth = -1;
+
+ return fetch;
+}
/**
* Start fetching data for the given URL.
@@ -343,45 +483,22 @@ fetch_curl_setup(struct fetch *parent_fetch,
struct curl_slist *slist;
int i;
- fetch = malloc(sizeof (*fetch));
+ fetch = fetch_alloc();
if (fetch == NULL)
- return 0;
-
- fetch->fetch_handle = parent_fetch;
+ return NULL;
NSLOG(netsurf, INFO, "fetch %p, url '%s'", fetch, nsurl_access(url));
- /* construct a new fetch structure */
- fetch->curl_handle = NULL;
- fetch->had_headers = false;
- fetch->abort = false;
- fetch->stopped = false;
fetch->only_2xx = only_2xx;
fetch->downgrade_tls = downgrade_tls;
- fetch->headers = NULL;
+ fetch->fetch_handle = parent_fetch;
fetch->url = nsurl_ref(url);
fetch->host = nsurl_get_component(url, NSURL_HOST);
- fetch->location = NULL;
- fetch->content_length = 0;
- fetch->http_code = 0;
- fetch->cookie_string = NULL;
- fetch->realm = NULL;
- fetch->post_urlenc = NULL;
- fetch->post_multipart = NULL;
- if (post_urlenc) {
- fetch->post_urlenc = strdup(post_urlenc);
- } else if (post_multipart) {
- fetch->post_multipart = fetch_curl_post_convert(post_multipart);
+ if (fetch->host == NULL) {
+ goto failed;
}
- fetch->last_progress_update = 0;
-
- /* TLS defaults */
- memset(fetch->cert_data, 0, sizeof(fetch->cert_data));
- fetch->cert_depth = -1;
-
- if ((fetch->host == NULL) ||
- (post_multipart != NULL && fetch->post_multipart == NULL) ||
- (post_urlenc != NULL && fetch->post_urlenc == NULL)) {
+ fetch->postdata = fetch_curl_alloc_postdata(post_urlenc, post_multipart);
+ if (fetch->postdata == NULL) {
goto failed;
}
@@ -434,139 +551,67 @@ failed:
lwc_string_unref(fetch->host);
nsurl_unref(fetch->url);
- free(fetch->post_urlenc);
- if (fetch->post_multipart)
- curl_formfree(fetch->post_multipart);
+ fetch_curl_free_postdata(fetch->postdata);
curl_slist_free_all(fetch->headers);
free(fetch);
return NULL;
}
+#ifdef WITH_OPENSSL
+
+/**
+ * Retrieve the ssl cert chain for the fetch, creating a blank one if needed
+ */
+static struct cert_chain *
+fetch_curl_get_cached_chain(struct curl_fetch_info *f)
+{
+ struct cert_chain *chain;
+
+ chain = hashmap_lookup(curl_fetch_ssl_hashmap, f->url);
+ if (chain == NULL) {
+ chain = hashmap_insert(curl_fetch_ssl_hashmap, f->url);
+ }
+
+ return chain;
+}
+
/**
* Report the certificate information in the fetch to the users
*/
static void
-fetch_curl_report_certs_upstream(struct curl_fetch_info *f)
+fetch_curl_store_certs_in_cache(struct curl_fetch_info *f)
{
- int depth;
+ size_t depth;
BIO *mem;
- BUF_MEM *buf;
- const ASN1_INTEGER *asn1_num;
- BIGNUM *bignum;
- struct ssl_cert_info ssl_certs[MAX_SSL_CERTS];
- fetch_msg msg;
- struct cert_info *certs = f->cert_data;
- memset(ssl_certs, 0, sizeof(ssl_certs));
+ BUF_MEM *buf[MAX_CERT_DEPTH];
+ struct cert_chain chain, *cached_chain;
+ struct cert_info *certs;
- for (depth = 0; depth <= f->cert_depth; depth++) {
- assert(certs[depth].cert != NULL);
+ memset(&chain, 0, sizeof(chain));
- /* get certificate version */
- ssl_certs[depth].version = X509_get_version(certs[depth].cert);
+ certs = f->cert_data;
+ chain.depth = f->cert_depth + 1; /* 0 indexed certificate depth */
- /* not before date */
- mem = BIO_new(BIO_s_mem());
- ASN1_TIME_print(mem, X509_get_notBefore(certs[depth].cert));
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[depth].not_before,
- buf->data,
- min(sizeof(ssl_certs[depth].not_before) - 1,
- (unsigned)buf->length));
- ssl_certs[depth].not_before[min(sizeof(ssl_certs[depth].not_before) - 1,
- (unsigned)buf->length)] = 0;
- BUF_MEM_free(buf);
-
- /* not after date */
- mem = BIO_new(BIO_s_mem());
- ASN1_TIME_print(mem,
- X509_get_notAfter(certs[depth].cert));
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[depth].not_after,
- buf->data,
- min(sizeof(ssl_certs[depth].not_after) - 1,
- (unsigned)buf->length));
- ssl_certs[depth].not_after[min(sizeof(ssl_certs[depth].not_after) - 1,
- (unsigned)buf->length)] = 0;
- BUF_MEM_free(buf);
-
- /* signature type */
- ssl_certs[depth].sig_type =
- X509_get_signature_type(certs[depth].cert);
-
- /* serial number */
- asn1_num = X509_get_serialNumber(certs[depth].cert);
- if (asn1_num != NULL) {
- bignum = ASN1_INTEGER_to_BN(asn1_num, NULL);
- if (bignum != NULL) {
- char *tmp = BN_bn2hex(bignum);
- if (tmp != NULL) {
- strncpy(ssl_certs[depth].serialnum,
- tmp,
- sizeof(ssl_certs[depth].serialnum));
- ssl_certs[depth].serialnum[sizeof(ssl_certs[depth].serialnum)-1] = '\0';
- OPENSSL_free(tmp);
- }
- BN_free(bignum);
- bignum = NULL;
- }
+ for (depth = 0; depth < chain.depth; depth++) {
+ if (certs[depth].cert == NULL) {
+ /* This certificate is missing, skip it */
+ chain.certs[depth].err = SSL_CERT_ERR_CERT_MISSING;
+ continue;
}
- /* issuer name */
- mem = BIO_new(BIO_s_mem());
- X509_NAME_print_ex(mem,
- X509_get_issuer_name(certs[depth].cert),
- 0, XN_FLAG_SEP_CPLUS_SPC |
- XN_FLAG_DN_REV | XN_FLAG_FN_NONE);
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[depth].issuer,
- buf->data,
- min(sizeof(ssl_certs[depth].issuer) - 1,
- (unsigned) buf->length));
- ssl_certs[depth].issuer[min(sizeof(ssl_certs[depth].issuer) - 1,
- (unsigned) buf->length)] = 0;
- BUF_MEM_free(buf);
-
- /* subject */
- mem = BIO_new(BIO_s_mem());
- X509_NAME_print_ex(mem,
- X509_get_subject_name(certs[depth].cert),
- 0,
- XN_FLAG_SEP_CPLUS_SPC |
- XN_FLAG_DN_REV |
- XN_FLAG_FN_NONE);
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[depth].subject,
- buf->data,
- min(sizeof(ssl_certs[depth].subject) - 1,
- (unsigned)buf->length));
- ssl_certs[depth].subject[min(sizeof(ssl_certs[depth].subject) - 1,
- (unsigned) buf->length)] = 0;
- BUF_MEM_free(buf);
-
- /* type of certificate */
- ssl_certs[depth].cert_type =
- X509_certificate_type(certs[depth].cert,
- X509_get_pubkey(certs[depth].cert));
-
/* error code (if any) */
switch (certs[depth].err) {
case X509_V_OK:
- ssl_certs[depth].err = SSL_CERT_ERR_OK;
+ chain.certs[depth].err = SSL_CERT_ERR_OK;
break;
+
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
/* fallthrough */
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
- ssl_certs[depth].err = SSL_CERT_ERR_BAD_ISSUER;
+ chain.certs[depth].err = SSL_CERT_ERR_BAD_ISSUER;
break;
+
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
/* fallthrough */
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
@@ -574,44 +619,73 @@ fetch_curl_report_certs_upstream(struct curl_fetch_info *f)
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
/* fallthrough */
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
- ssl_certs[depth].err = SSL_CERT_ERR_BAD_SIG;
+ chain.certs[depth].err = SSL_CERT_ERR_BAD_SIG;
break;
+
case X509_V_ERR_CERT_NOT_YET_VALID:
/* fallthrough */
case X509_V_ERR_CRL_NOT_YET_VALID:
- ssl_certs[depth].err = SSL_CERT_ERR_TOO_YOUNG;
+ chain.certs[depth].err = SSL_CERT_ERR_TOO_YOUNG;
break;
+
case X509_V_ERR_CERT_HAS_EXPIRED:
/* fallthrough */
case X509_V_ERR_CRL_HAS_EXPIRED:
- ssl_certs[depth].err = SSL_CERT_ERR_TOO_OLD;
+ chain.certs[depth].err = SSL_CERT_ERR_TOO_OLD;
break;
+
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- ssl_certs[depth].err = SSL_CERT_ERR_SELF_SIGNED;
+ chain.certs[depth].err = SSL_CERT_ERR_SELF_SIGNED;
break;
+
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
- ssl_certs[depth].err = SSL_CERT_ERR_CHAIN_SELF_SIGNED;
+ chain.certs[depth].err = SSL_CERT_ERR_CHAIN_SELF_SIGNED;
break;
+
case X509_V_ERR_CERT_REVOKED:
- ssl_certs[depth].err = SSL_CERT_ERR_REVOKED;
+ chain.certs[depth].err = SSL_CERT_ERR_REVOKED;
break;
+
case X509_V_ERR_HOSTNAME_MISMATCH:
- ssl_certs[depth].err = SSL_CERT_ERR_HOSTNAME_MISMATCH;
+ chain.certs[depth].err = SSL_CERT_ERR_HOSTNAME_MISMATCH;
break;
+
default:
- ssl_certs[depth].err = SSL_CERT_ERR_UNKNOWN;
+ chain.certs[depth].err = SSL_CERT_ERR_UNKNOWN;
break;
}
+
+ /*
+ * get certificate in Distinguished Encoding Rules (DER) format.
+ */
+ mem = BIO_new(BIO_s_mem());
+ i2d_X509_bio(mem, certs[depth].cert);
+ BIO_get_mem_ptr(mem, &buf[depth]);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+
+ chain.certs[depth].der = (uint8_t *)buf[depth]->data;
+ chain.certs[depth].der_length = buf[depth]->length;
}
- msg.type = FETCH_CERTS;
- msg.data.certs.certs = ssl_certs;
- msg.data.certs.num_certs = depth;
+ /* Now dup that chain into the cache */
+ cached_chain = fetch_curl_get_cached_chain(f);
+ if (cert_chain_dup_into(&chain, cached_chain) != NSERROR_OK) {
+ /* Something went wrong storing the chain, give up */
+ hashmap_remove(curl_fetch_ssl_hashmap, f->url);
+ }
- fetch_send_callback(&msg, f->fetch_handle);
+ /* release the openssl memory buffer */
+ for (depth = 0; depth < chain.depth; depth++) {
+ if (chain.certs[depth].err == SSL_CERT_ERR_CERT_MISSING) {
+ continue;
+ }
+ if (buf[depth] != NULL) {
+ BUF_MEM_free(buf[depth]);
+ }
+ }
}
-
/**
* OpenSSL Certificate verification callback
*
@@ -640,24 +714,24 @@ fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx)
depth = X509_STORE_CTX_get_error_depth(x509_ctx);
fetch = X509_STORE_CTX_get_app_data(x509_ctx);
- /* record the max depth */
- if (depth > fetch->cert_depth) {
- fetch->cert_depth = depth;
- }
-
/* certificate chain is excessively deep so fail verification */
- if (depth >= MAX_SSL_CERTS) {
+ if (depth >= MAX_CERT_DEPTH) {
X509_STORE_CTX_set_error(x509_ctx,
X509_V_ERR_CERT_CHAIN_TOO_LONG);
return 0;
}
+ /* record the max depth */
+ if (depth > fetch->cert_depth) {
+ fetch->cert_depth = depth;
+ }
+
/* save the certificate by incrementing the reference count and
* keeping a pointer.
*/
if (!fetch->cert_data[depth].cert) {
fetch->cert_data[depth].cert = X509_STORE_CTX_get_current_cert(x509_ctx);
- ns_X509_up_ref(fetch->cert_data[depth].cert);
+ X509_up_ref(fetch->cert_data[depth].cert);
fetch->cert_data[depth].err = X509_STORE_CTX_get_error(x509_ctx);
}
@@ -713,7 +787,7 @@ static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm)
ok = X509_verify_cert(x509_ctx);
}
- fetch_curl_report_certs_upstream(f);
+ fetch_curl_store_certs_in_cache(f);
return ok;
}
@@ -732,7 +806,8 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm)
{
struct curl_fetch_info *f = (struct curl_fetch_info *) parm;
SSL_CTX *sslctx = _sslctx;
- long options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ long options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
/* set verify callback for each certificate in chain */
SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, fetch_curl_verify_callback);
@@ -743,19 +818,14 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm)
parm);
if (f->downgrade_tls) {
- /* Disable TLS 1.1/1.2 if the server can't cope with them */
-#ifdef SSL_OP_NO_TLSv1_1
- options |= SSL_OP_NO_TLSv1_1;
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
- options |= SSL_OP_NO_TLSv1_2;
+ /* Disable TLS 1.3 if the server can't cope with it */
+#ifdef SSL_OP_NO_TLSv1_3
+ options |= SSL_OP_NO_TLSv1_3;
#endif
#ifdef SSL_MODE_SEND_FALLBACK_SCSV
/* Ensure server rejects the connection if downgraded too far */
SSL_CTX_set_mode(sslctx, SSL_MODE_SEND_FALLBACK_SCSV);
#endif
- /* Disable TLS1.2 ciphersuites */
- SSL_CTX_set_cipher_list(sslctx, CIPHER_LIST ":-TLSv1.2");
}
SSL_CTX_set_options(sslctx, options);
@@ -768,6 +838,326 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm)
}
+#endif /* WITH_OPENSSL */
+
+
+/**
+ * Report the certificate information in the fetch to the users
+ */
+static void
+fetch_curl_report_certs_upstream(struct curl_fetch_info *f)
+{
+ fetch_msg msg;
+ struct cert_chain *chain;
+
+ chain = hashmap_lookup(curl_fetch_ssl_hashmap, f->url);
+
+ if (chain != NULL) {
+ msg.type = FETCH_CERTS;
+ msg.data.chain = chain;
+
+ fetch_send_callback(&msg, f->fetch_handle);
+ }
+
+ f->sent_ssl_chain = true;
+}
+
+#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 depricated curl_formadd */
+
+/**
+ * curl mime data context
+ */
+struct curl_mime_ctx {
+ char *buffer;
+ curl_off_t size;
+ curl_off_t position;
+};
+
+static size_t mime_data_read_callback(char *buffer, size_t size, size_t nitems, void *arg)
+{
+ struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg;
+ curl_off_t sz = mctx->size - mctx->position;
+
+ nitems *= size;
+ if(sz > (curl_off_t)nitems) {
+ sz = nitems;
+ }
+ if(sz) {
+ memcpy(buffer, mctx->buffer + mctx->position, sz);
+ }
+ mctx->position += sz;
+ return sz;
+}
+
+static int mime_data_seek_callback(void *arg, curl_off_t offset, int origin)
+{
+ struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg;
+
+ switch(origin) {
+ case SEEK_END:
+ offset += mctx->size;
+ break;
+ case SEEK_CUR:
+ offset += mctx->position;
+ break;
+ }
+
+ if(offset < 0) {
+ return CURL_SEEKFUNC_FAIL;
+ }
+ mctx->position = offset;
+ return CURL_SEEKFUNC_OK;
+}
+
+static void mime_data_free_callback(void *arg)
+{
+ struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg;
+ free(mctx);
+}
+
+/**
+ * Convert a POST data list to a libcurl curl_mime.
+ *
+ * \param chandle curl fetch handle.
+ * \param multipart limked list of struct ::fetch_multipart forming post data.
+ */
+static curl_mime *
+fetch_curl_postdata_convert(CURL *chandle,
+ const struct fetch_multipart_data *multipart)
+{
+ curl_mime *cmime;
+ curl_mimepart *part;
+ CURLcode code = CURLE_OK;
+ size_t value_len;
+
+ cmime = curl_mime_init(chandle);
+ if (cmime == NULL) {
+ NSLOG(netsurf, WARNING, "postdata conversion failed to curl mime context");
+ return NULL;
+ }
+
+ /* iterate post data */
+ for (; multipart != NULL; multipart = multipart->next) {
+ part = curl_mime_addpart(cmime);
+ if (part == NULL) {
+ goto convert_failed;
+ }
+
+ code = curl_mime_name(part, multipart->name);
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ value_len = strlen(multipart->value);
+
+ if (multipart->file && value_len==0) {
+ /* file entries with no filename require special handling */
+ code=curl_mime_data(part, multipart->value, value_len);
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ code = curl_mime_filename(part, "");
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ code = curl_mime_type(part, "application/octet-stream");
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ } else if(multipart->file) {
+ /* file entry */
+ nserror ret;
+ char *leafname = NULL;
+ char *mimetype = NULL;
+
+ code = curl_mime_filedata(part, multipart->rawfile);
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ ret = guit->file->basename(multipart->value, &leafname, NULL);
+ if (ret != NSERROR_OK) {
+ goto convert_failed;
+ }
+ code = curl_mime_filename(part, leafname);
+ free(leafname);
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ mimetype = guit->fetch->mimetype(multipart->value);
+ if (mimetype == NULL) {
+ mimetype=strdup("text/plain");
+ }
+ if (mimetype == NULL) {
+ goto convert_failed;
+ }
+ code = curl_mime_type(part, mimetype);
+ free(mimetype);
+ if (code != CURLE_OK) {
+ goto convert_failed;
+ }
+
+ } else {
+ /* make the curl mime reference the existing multipart
+ * data which requires use of a callback and context.
+ */
+ struct curl_mime_ctx *cb_ctx;
+ cb_ctx = malloc(sizeof(struct curl_mime_ctx));
+ if (cb_ctx == NULL) {
+ goto convert_failed;
+ }
+ cb_ctx->buffer = multipart->value;
+ cb_ctx->size = value_len;
+ cb_ctx->position = 0;
+ code = curl_mime_data_cb(part,
+ value_len,
+ mime_data_read_callback,
+ mime_data_seek_callback,
+ mime_data_free_callback,
+ cb_ctx);
+ if (code != CURLE_OK) {
+ free(cb_ctx);
+ goto convert_failed;
+ }
+ }
+ }
+
+ return cmime;
+
+convert_failed:
+ NSLOG(netsurf, WARNING, "postdata conversion failed with curl code: %d", code);
+ curl_mime_free(cmime);
+ return NULL;
+}
+
+#else /* LIBCURL_VERSION_NUM >= 0x073800 */
+
+/**
+ * Convert a list of struct ::fetch_multipart_data to a list of
+ * struct curl_httppost for libcurl.
+ */
+static struct curl_httppost *
+fetch_curl_postdata_convert(CURL *chandle,
+ const struct fetch_multipart_data *control)
+{
+ struct curl_httppost *post = NULL, *last = NULL;
+ CURLFORMcode code;
+ nserror ret;
+
+ for (; control; control = control->next) {
+ if (control->file) {
+ char *leafname = NULL;
+ ret = guit->file->basename(control->value, &leafname, NULL);
+ if (ret != NSERROR_OK) {
+ continue;
+ }
+
+ /* We have to special case filenames of "", so curl
+ * a) actually attempts the fetch and
+ * b) doesn't attempt to open the file ""
+ */
+ if (control->value[0] == '\0') {
+ /* dummy buffer - needs to be static so
+ * pointer's still valid when we go out
+ * of scope (not that libcurl should be
+ * attempting to access it, of course).
+ */
+ static char buf;
+
+ code = curl_formadd(&post, &last,
+ CURLFORM_COPYNAME, control->name,
+ CURLFORM_BUFFER, control->value,
+ /* needed, as basename("") == "." */
+ CURLFORM_FILENAME, "",
+ CURLFORM_BUFFERPTR, &buf,
+ CURLFORM_BUFFERLENGTH, 0,
+ CURLFORM_CONTENTTYPE,
+ "application/octet-stream",
+ CURLFORM_END);
+ if (code != CURL_FORMADD_OK)
+ NSLOG(netsurf, INFO,
+ "curl_formadd: %d (%s)", code,
+ control->name);
+ } else {
+ char *mimetype = guit->fetch->mimetype(control->value);
+ code = curl_formadd(&post, &last,
+ CURLFORM_COPYNAME, control->name,
+ CURLFORM_FILE, control->rawfile,
+ CURLFORM_FILENAME, leafname,
+ CURLFORM_CONTENTTYPE,
+ (mimetype != 0 ? mimetype : "text/plain"),
+ CURLFORM_END);
+ if (code != CURL_FORMADD_OK)
+ NSLOG(netsurf, INFO,
+ "curl_formadd: %d (%s=%s)",
+ code,
+ control->name,
+ control->value);
+ free(mimetype);
+ }
+ free(leafname);
+ } else {
+ code = curl_formadd(&post, &last,
+ CURLFORM_COPYNAME, control->name,
+ CURLFORM_COPYCONTENTS, control->value,
+ CURLFORM_END);
+ if (code != CURL_FORMADD_OK)
+ NSLOG(netsurf, INFO,
+ "curl_formadd: %d (%s=%s)", code,
+ control->name, control->value);
+ }
+ }
+
+ return post;
+}
+
+#endif /* LIBCURL_VERSION_NUM >= 0x073800 */
+
+/**
+ * Setup multipart post data
+ */
+static CURLcode fetch_curl_set_postdata(struct curl_fetch_info *f)
+{
+ CURLcode code = CURLE_OK;
+
+#undef SETOPT
+#define SETOPT(option, value) { \
+ code = curl_easy_setopt(f->curl_handle, option, value); \
+ if (code != CURLE_OK) \
+ return code; \
+ }
+
+ switch (f->postdata->type) {
+ case FETCH_POSTDATA_NONE:
+ SETOPT(CURLOPT_POSTFIELDS, NULL);
+ SETOPT(NSCURL_POSTDATA_CURLOPT, NULL);
+ SETOPT(CURLOPT_HTTPGET, 1L);
+ break;
+
+ case FETCH_POSTDATA_URLENC:
+ SETOPT(NSCURL_POSTDATA_CURLOPT, NULL);
+ SETOPT(CURLOPT_HTTPGET, 0L);
+ SETOPT(CURLOPT_POSTFIELDS, f->postdata->data.urlenc);
+ break;
+
+ case FETCH_POSTDATA_MULTIPART:
+ SETOPT(CURLOPT_POSTFIELDS, NULL);
+ SETOPT(CURLOPT_HTTPGET, 0L);
+ if (f->curl_postdata == NULL) {
+ f->curl_postdata =
+ fetch_curl_postdata_convert(f->curl_handle,
+ f->postdata->data.multipart);
+ }
+ SETOPT(NSCURL_POSTDATA_CURLOPT, f->curl_postdata);
+ break;
+ }
+ return code;
+}
+
/**
* Set options specific for a fetch.
*
@@ -790,21 +1180,11 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f)
SETOPT(CURLOPT_PRIVATE, f);
SETOPT(CURLOPT_WRITEDATA, f);
SETOPT(CURLOPT_WRITEHEADER, f);
- SETOPT(CURLOPT_PROGRESSDATA, f);
- SETOPT(CURLOPT_REFERER, fetch_get_referer_to_send(f->fetch_handle));
+ SETOPT(NSCURLOPT_PROGRESS_DATA, f);
SETOPT(CURLOPT_HTTPHEADER, f->headers);
- if (f->post_urlenc) {
- SETOPT(CURLOPT_HTTPPOST, NULL);
- SETOPT(CURLOPT_HTTPGET, 0L);
- SETOPT(CURLOPT_POSTFIELDS, f->post_urlenc);
- } else if (f->post_multipart) {
- SETOPT(CURLOPT_POSTFIELDS, NULL);
- SETOPT(CURLOPT_HTTPGET, 0L);
- SETOPT(CURLOPT_HTTPPOST, f->post_multipart);
- } else {
- SETOPT(CURLOPT_POSTFIELDS, NULL);
- SETOPT(CURLOPT_HTTPPOST, NULL);
- SETOPT(CURLOPT_HTTPGET, 1L);
+ code = fetch_curl_set_postdata(f);
+ if (code != CURLE_OK) {
+ return code;
}
f->cookie_string = urldb_get_cookie(f->url, true);
@@ -815,7 +1195,7 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f)
}
if ((auth = urldb_get_auth_details(f->url, NULL)) != NULL) {
- SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
SETOPT(CURLOPT_USERPWD, auth);
} else {
SETOPT(CURLOPT_USERPWD, NULL);
@@ -851,6 +1231,12 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f)
SETOPT(CURLOPT_PROXY, NULL);
}
+
+ if (curl_with_openssl) {
+ SETOPT(CURLOPT_SSL_CIPHER_LIST,
+ f->downgrade_tls ? CIPHER_LIST_LEGACY : CIPHER_LIST);
+ }
+
/* Force-enable SSL session ID caching, as some distros are odd. */
SETOPT(CURLOPT_SSL_SESSIONID_CACHE, 1);
@@ -866,10 +1252,12 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f)
/* do verification */
SETOPT(CURLOPT_SSL_VERIFYPEER, 1L);
SETOPT(CURLOPT_SSL_VERIFYHOST, 2L);
+#ifdef WITH_OPENSSL
if (curl_with_openssl) {
SETOPT(CURLOPT_SSL_CTX_FUNCTION, fetch_curl_sslctxfun);
SETOPT(CURLOPT_SSL_CTX_DATA, f);
}
+#endif
}
return CURLE_OK;
@@ -1062,13 +1450,14 @@ static void fetch_curl_free(void *vf)
if (f->headers) {
curl_slist_free_all(f->headers);
}
- free(f->post_urlenc);
- if (f->post_multipart) {
- curl_formfree(f->post_multipart);
- }
+ fetch_curl_free_postdata(f->postdata);
+ NSCURL_POSTDATA_FREE(f->curl_postdata);
- for (i = 0; i < MAX_SSL_CERTS && f->cert_data[i].cert; i++) {
- ns_X509_free(f->cert_data[i].cert);
+ /* free certificate data */
+ for (i = 0; i < MAX_CERT_DEPTH; i++) {
+ if (f->cert_data[i].cert != NULL) {
+ X509_free(f->cert_data[i].cert);
+ }
}
free(f);
@@ -1097,7 +1486,7 @@ static bool fetch_curl_process_headers(struct curl_fetch_info *f)
http_code = f->http_code;
NSLOG(netsurf, INFO, "HTTP status code %li", http_code);
- if (http_code == 304 && !f->post_urlenc && !f->post_multipart) {
+ if ((http_code == 304) && (f->postdata->type==FETCH_POSTDATA_NONE)) {
/* Not Modified && GET request */
msg.type = FETCH_NOTMODIFIED;
fetch_send_callback(&msg, f->fetch_handle);
@@ -1208,6 +1597,10 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
fetch_curl_stop(f);
+ if (f->sent_ssl_chain == false) {
+ fetch_curl_report_certs_upstream(f);
+ }
+
if (abort_fetch) {
; /* fetch was aborted: no callback */
} else if (finished) {
@@ -1298,9 +1691,9 @@ static void fetch_curl_poll(lwc_string *scheme_ignored)
do {
codem = curl_multi_perform(fetch_curl_multi, &running);
if (codem != CURLM_OK && codem != CURLM_CALL_MULTI_PERFORM) {
- NSLOG(netsurf, WARNING, "curl_multi_perform: %i %s",
+ NSLOG(netsurf, WARNING,
+ "curl_multi_perform: %i %s",
codem, curl_multi_strerror(codem));
- guit->misc->warning("MiscError", curl_multi_strerror(codem));
return;
}
} while (codem == CURLM_CALL_MULTI_PERFORM);
@@ -1329,10 +1722,10 @@ static void fetch_curl_poll(lwc_string *scheme_ignored)
*/
static int
fetch_curl_progress(void *clientp,
- double dltotal,
- double dlnow,
- double ultotal,
- double ulnow)
+ NSCURL_PROGRESS_T dltotal,
+ NSCURL_PROGRESS_T dlnow,
+ NSCURL_PROGRESS_T ultotal,
+ NSCURL_PROGRESS_T ulnow)
{
static char fetch_progress_buffer[256]; /**< Progress buffer for cURL */
struct curl_fetch_info *f = (struct curl_fetch_info *) clientp;
@@ -1399,6 +1792,24 @@ fetch_curl_debug(CURL *handle,
}
+static curl_socket_t fetch_curl_socket_open(void *clientp,
+ curlsocktype purpose, struct curl_sockaddr *address)
+{
+ (void) clientp;
+ (void) purpose;
+
+ return (curl_socket_t) guit->fetch->socket_open(
+ address->family, address->socktype,
+ address->protocol);
+}
+
+static int fetch_curl_socket_close(void *clientp, curl_socket_t item)
+{
+ (void) clientp;
+
+ return guit->fetch->socket_close((int) item);
+}
+
/**
* Callback function for cURL.
*/
@@ -1462,6 +1873,10 @@ fetch_curl_header(char *data, size_t size, size_t nmemb, void *_f)
return 0;
}
+ if (f->sent_ssl_chain == false) {
+ fetch_curl_report_certs_upstream(f);
+ }
+
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) data;
msg.data.header_or_data.len = size;
@@ -1565,6 +1980,18 @@ nserror fetch_curl_register(void)
.finalise = fetch_curl_finalise
};
+#if LIBCURL_VERSION_NUM >= 0x073800
+ /* version 7.56.0 can select which SSL backend to use */
+ CURLsslset setres;
+
+ setres = curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL);
+ if (setres == CURLSSLSET_OK) {
+ curl_with_openssl = true;
+ } else {
+ curl_with_openssl = false;
+ }
+#endif
+
NSLOG(netsurf, INFO, "curl_version %s", curl_version());
code = curl_global_init(CURL_GLOBAL_ALL);
@@ -1589,8 +2016,10 @@ nserror fetch_curl_register(void)
#undef SETOPT
#define SETOPT(option, value) \
mcode = curl_multi_setopt(fetch_curl_multi, option, value); \
- if (mcode != CURLM_OK) \
- goto curl_multi_setopt_failed;
+ if (mcode != CURLM_OK) { \
+ NSLOG(netsurf, ERROR, "attempting curl_multi_setopt(%s, ...)", #option); \
+ goto curl_multi_setopt_failed; \
+ }
SETOPT(CURLMOPT_MAXCONNECTS, maxconnects);
SETOPT(CURLMOPT_MAX_TOTAL_CONNECTIONS, maxconnects);
@@ -1610,8 +2039,10 @@ nserror fetch_curl_register(void)
#undef SETOPT
#define SETOPT(option, value) \
code = curl_easy_setopt(fetch_blank_curl, option, value); \
- if (code != CURLE_OK) \
- goto curl_easy_setopt_failed;
+ if (code != CURLE_OK) { \
+ NSLOG(netsurf, ERROR, "attempting curl_easy_setopt(%s, ...)", #option); \
+ goto curl_easy_setopt_failed; \
+ }
SETOPT(CURLOPT_ERRORBUFFER, fetch_error_buffer);
SETOPT(CURLOPT_DEBUGFUNCTION, fetch_curl_debug);
@@ -1626,7 +2057,7 @@ nserror fetch_curl_register(void)
SETOPT(CURLOPT_WRITEFUNCTION, fetch_curl_data);
SETOPT(CURLOPT_HEADERFUNCTION, fetch_curl_header);
- SETOPT(CURLOPT_PROGRESSFUNCTION, fetch_curl_progress);
+ SETOPT(NSCURLOPT_PROGRESS_FUNCTION, fetch_curl_progress);
SETOPT(CURLOPT_NOPROGRESS, 0);
SETOPT(CURLOPT_USERAGENT, user_agent_string());
SETOPT(CURLOPT_ENCODING, "gzip");
@@ -1634,7 +2065,8 @@ nserror fetch_curl_register(void)
SETOPT(CURLOPT_LOW_SPEED_TIME, 180L);
SETOPT(CURLOPT_NOSIGNAL, 1L);
SETOPT(CURLOPT_CONNECTTIMEOUT, nsoption_uint(curl_fetch_timeout));
- SETOPT(CURLOPT_SSL_CIPHER_LIST, CIPHER_LIST);
+ SETOPT(CURLOPT_OPENSOCKETFUNCTION, fetch_curl_socket_open);
+ SETOPT(CURLOPT_CLOSESOCKETFUNCTION, fetch_curl_socket_close);
if (nsoption_charp(ca_bundle) &&
strcmp(nsoption_charp(ca_bundle), "")) {
@@ -1647,12 +2079,32 @@ nserror fetch_curl_register(void)
SETOPT(CURLOPT_CAPATH, nsoption_charp(ca_path));
}
- /* Detect whether the SSL CTX function API works */
- curl_with_openssl = true;
- code = curl_easy_setopt(fetch_blank_curl,
- CURLOPT_SSL_CTX_FUNCTION, NULL);
+#if LIBCURL_VERSION_NUM < 0x073800
+ /*
+ * before 7.56.0 Detect openssl from whether the SSL CTX
+ * function API works
+ */
+ code = curl_easy_setopt(fetch_blank_curl, CURLOPT_SSL_CTX_FUNCTION, NULL);
if (code != CURLE_OK) {
curl_with_openssl = false;
+ } else {
+ curl_with_openssl = true;
+ }
+#endif
+
+ if (curl_with_openssl) {
+ /* only set the cipher list with openssl otherwise the
+ * fetch fails with "Unknown cipher in list"
+ */
+#if LIBCURL_VERSION_NUM >= 0x073d00
+ /* Need libcurl 7.61.0 or later built against OpenSSL with
+ * TLS1.3 support */
+ code = curl_easy_setopt(fetch_blank_curl,
+ CURLOPT_TLS13_CIPHERS, CIPHER_SUITES);
+ if (code != CURLE_OK && code != CURLE_NOT_BUILT_IN)
+ goto curl_easy_setopt_failed;
+#endif
+ SETOPT(CURLOPT_SSL_CIPHER_LIST, CIPHER_LIST);
}
NSLOG(netsurf, INFO, "cURL %slinked against openssl",
@@ -1662,6 +2114,12 @@ nserror fetch_curl_register(void)
data = curl_version_info(CURLVERSION_NOW);
+ curl_fetch_ssl_hashmap = hashmap_create(&curl_fetch_ssl_hashmap_parameters);
+ if (curl_fetch_ssl_hashmap == NULL) {
+ NSLOG(netsurf, CRITICAL, "Unable to initialise SSL certificate hashmap");
+ return NSERROR_NOMEM;
+ }
+
for (i = 0; data->protocols[i]; i++) {
if (strcmp(data->protocols[i], "http") == 0) {
scheme = lwc_string_ref(corestring_lwc_http);
diff --git a/content/fetchers/data.c b/content/fetchers/data.c
index 8c04bcfea..c7de14c3d 100644
--- a/content/fetchers/data.c
+++ b/content/fetchers/data.c
@@ -23,6 +23,7 @@
#include <stdbool.h>
#include <string.h>
+#include <stdarg.h>
#include <stdlib.h>
#include <libwapcaplet/libwapcaplet.h>
#include <nsutils/base64.h>
@@ -74,6 +75,36 @@ static bool fetch_data_can_fetch(const nsurl *url)
return true;
}
+static void fetch_data_send_callback(const fetch_msg *msg,
+ struct fetch_data_context *c)
+{
+ c->locked = true;
+ fetch_send_callback(msg, c->parent_fetch);
+ c->locked = false;
+}
+
+static void fetch_data_send_header(struct fetch_data_context *ctx,
+ const char *fmt, ...)
+{
+ char header[64];
+ fetch_msg msg;
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(header, sizeof(header), fmt, ap);
+ va_end(ap);
+
+ if (len >= (int)sizeof(header) || len < 0) {
+ return;
+ }
+
+ msg.type = FETCH_HEADER;
+ msg.data.header_or_data.len = len;
+ msg.data.header_or_data.buf = (const uint8_t *)header;
+ fetch_data_send_callback(&msg, ctx);
+}
+
static void *fetch_data_setup(struct fetch *parent_fetch, nsurl *url,
bool only_2xx, bool downgrade_tls, const char *post_urlenc,
const struct fetch_multipart_data *post_multipart,
@@ -104,7 +135,6 @@ static void fetch_data_free(void *ctx)
nsurl_unref(c->url);
free(c->data);
free(c->mimetype);
- RING_REMOVE(ring, c);
free(ctx);
}
@@ -119,14 +149,6 @@ static void fetch_data_abort(void *ctx)
c->aborted = true;
}
-static void fetch_data_send_callback(const fetch_msg *msg,
- struct fetch_data_context *c)
-{
- c->locked = true;
- fetch_send_callback(msg, c->parent_fetch);
- c->locked = false;
-}
-
static bool fetch_data_process(struct fetch_data_context *c)
{
nserror res;
@@ -222,13 +244,14 @@ static bool fetch_data_process(struct fetch_data_context *c)
static void fetch_data_poll(lwc_string *scheme)
{
fetch_msg msg;
- struct fetch_data_context *c, *next;
-
- if (ring == NULL) return;
+ struct fetch_data_context *c, *save_ring = NULL;
/* Iterate over ring, processing each pending fetch */
- c = ring;
- do {
+ while (ring != NULL) {
+ /* Take the first entry from the ring */
+ c = ring;
+ RING_REMOVE(ring, c);
+
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
@@ -236,14 +259,12 @@ static void fetch_data_poll(lwc_string *scheme)
* again.
*/
if (c->locked == true) {
- next = c->r_next;
+ RING_INSERT(save_ring, c);
continue;
}
/* Only process non-aborted fetches */
if (c->aborted == false && fetch_data_process(c) == true) {
- char header[64];
-
fetch_set_http_code(c->parent_fetch, 200);
NSLOG(netsurf, INFO,
"setting data: MIME type to %s, length to %"PRIsizet,
@@ -253,22 +274,18 @@ static void fetch_data_poll(lwc_string *scheme)
* Therefore, we _must_ check for this after _every_
* call to fetch_data_send_callback().
*/
- snprintf(header, sizeof header, "Content-Type: %s",
+ fetch_data_send_header(c, "Content-Type: %s",
c->mimetype);
- msg.type = FETCH_HEADER;
- msg.data.header_or_data.buf = (const uint8_t *) header;
- msg.data.header_or_data.len = strlen(header);
- fetch_data_send_callback(&msg, c);
if (c->aborted == false) {
- snprintf(header, sizeof header,
- "Content-Length: %" PRIsizet,
- c->datalen);
- msg.type = FETCH_HEADER;
- msg.data.header_or_data.buf =
- (const uint8_t *) header;
- msg.data.header_or_data.len = strlen(header);
- fetch_data_send_callback(&msg, c);
+ fetch_data_send_header(c, "Content-Length: %"
+ PRIsizet, c->datalen);
+ }
+
+ if (c->aborted == false) {
+ /* Set max-age to 1 year. */
+ fetch_data_send_header(c, "Cache-Control: "
+ "max-age=31536000");
}
if (c->aborted == false) {
@@ -293,18 +310,15 @@ static void fetch_data_poll(lwc_string *scheme)
assert(c->locked == false);
}
- /* Compute next fetch item at the last possible moment as
- * processing this item may have added to the ring.
- */
- next = c->r_next;
-
+ /* And now finish */
fetch_remove_from_queues(c->parent_fetch);
fetch_free(c->parent_fetch);
+ }
- /* Advance to next ring entry, exiting if we've reached
- * the start of the ring or the ring has become empty
- */
- } while ( (c = next) != ring && ring != NULL);
+ /* Finally, if we saved any fetches which were locked, put them back
+ * into the ring for next time
+ */
+ ring = save_ring;
}
nserror fetch_data_register(void)
diff --git a/content/fetchers/file/Makefile b/content/fetchers/file/Makefile
new file mode 100644
index 000000000..c22400a56
--- /dev/null
+++ b/content/fetchers/file/Makefile
@@ -0,0 +1,3 @@
+# File fetcher sources
+
+S_FETCHER_FILE := dirlist.c file.c
diff --git a/content/fetchers/file/dirlist.c b/content/fetchers/file/dirlist.c
new file mode 100644
index 000000000..345dbd8f3
--- /dev/null
+++ b/content/fetchers/file/dirlist.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2010 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Generate HTML content for displaying directory listings (implementation).
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils/nsurl.h"
+#include "utils/messages.h"
+#include "utils/nscolour.h"
+
+#include "netsurf/inttypes.h"
+#include "netsurf/types.h"
+#include "netsurf/plot_style.h"
+
+#include "dirlist.h"
+#include "desktop/system_colour.h"
+
+static int dirlist_filesize_calculate(unsigned long *bytesize);
+static int dirlist_filesize_value(unsigned long bytesize);
+static char* dirlist_filesize_unit(unsigned long bytesize);
+
+
+/**
+ * Generates the top part of an HTML directory listing page
+ *
+ * \return Top of directory listing HTML
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_top(char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "<html>\n"
+ "<head>\n"
+ "<link rel=\"stylesheet\" title=\"Standard\" "
+ "type=\"text/css\" href=\"resource:internal.css\">\n"
+ "<style>\n");
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that can suppress
+ * particular columns
+ *
+ * \param flags flags for which cols to suppress. 0 to suppress none
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_hide_columns(int flags, char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "%s\n%s\n%s\n%s\n%s\n",
+ (flags & DIRLIST_NO_NAME_COLUMN) ?
+ "span.name { display: none; }\n" : "",
+ (flags & DIRLIST_NO_TYPE_COLUMN) ?
+ "span.type { display: none; }\n" : "",
+ (flags & DIRLIST_NO_SIZE_COLUMN) ?
+ "span.size { display: none; }\n" : "",
+ (flags & DIRLIST_NO_DATE_COLUMN) ?
+ "span.date { display: none; }\n" : "",
+ (flags & DIRLIST_NO_TIME_COLUMN) ?
+ "span.time { display: none; }\n" : "");
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that contains the title
+ *
+ * \param title title to use
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_title(const char *title, char *buffer, int buffer_length)
+{
+ const char *stylesheet;
+ nserror err;
+ int error;
+
+ if (title == NULL)
+ title = "";
+
+ err = nscolour_get_stylesheet(&stylesheet);
+ if (err != NSERROR_OK) {
+ return false;
+ }
+
+ error = snprintf(buffer, buffer_length,
+ "</style>\n"
+ "<title>%s</title>\n"
+ "<style>\n"
+ "html {\n"
+ "\tbackground-color: #%06"PRIx32";\n"
+ "}\n"
+ "%s"
+ "</style>\n"
+ "</head>\n"
+ "<body id=\"dirlist\" class=\"ns-even-bg ns-even-fg ns-border\">\n"
+ "<h1 class=\"ns-border\">%s</h1>\n",
+ title,
+ colour_rb_swap(nscolours[NSCOLOUR_WIN_ODD_BG]),
+ stylesheet, title);
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that links to the parent
+ * directory
+ *
+ * \param parent url of parent directory
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_parent_link(const char *parent, char *buffer,
+ int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "<p><a href=\"%s\">%s</a></p>",
+ parent, messages_get("FileParent"));
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that displays the column
+ * headings
+ *
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_headings(char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "<div>\n"
+ "<strong>\n"
+ "\t<span class=\"name\">%s</span>\n"
+ "\t<span class=\"type\">%s</span>\n"
+ "\t<span class=\"size\">%s</span>"
+ "<span class=\"size\"></span>\n"
+ "\t<span class=\"date\">%s</span>\n"
+ "\t<span class=\"time\">%s</span>\n"
+ "</strong>\n",
+ messages_get("FileName"), messages_get("FileType"),
+ messages_get("FileSize"), messages_get("FileDate"),
+ messages_get("FileTime"));
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the part of an HTML directory listing page that displays a row
+ * in the directory contents table
+ *
+ * \param even evenness of row number, for alternate row colouring
+ * \param directory whether this row is for a directory (or a file)
+ * \param url url for row entry
+ * \param name name of row entry
+ * \param mimetype MIME type of row entry
+ * \param size size of row entry. If negative, size is left blank
+ * \param date date row entry was last modified
+ * \param time time row entry was last modified
+ * \param buffer buffer to fill with generated HTML
+ * \param buffer_length maximum size of buffer
+ * \return true iff buffer filled without error
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_row(bool even, bool directory, nsurl *url, char *name,
+ const char *mimetype, long long size, char *date, char *time,
+ char *buffer, int buffer_length)
+{
+ const char *unit;
+ char size_string[100];
+ int error;
+
+ if (size < 0) {
+ unit = "";
+ strncpy(size_string, "", sizeof size_string);
+ } else {
+ unit = messages_get(dirlist_filesize_unit((unsigned long)size));
+ snprintf(size_string, sizeof size_string, "%d",
+ dirlist_filesize_value((unsigned long)size));
+ }
+
+ error = snprintf(buffer, buffer_length,
+ "<a href=\"%s\" class=\"%s %s\">\n"
+ "\t<span class=\"name ns-border\">%s</span>\n"
+ "\t<span class=\"type ns-border\">%s</span>\n"
+ "\t<span class=\"size ns-border\">%s</span>"
+ "<span class=\"size ns-border\">%s</span>\n"
+ "\t<span class=\"date ns-border\">%s</span>\n"
+ "\t<span class=\"time ns-border\">%s</span>\n"
+ "</a>\n", nsurl_access(url),
+ even ? "even ns-even-bg" : "odd ns-odd-bg",
+ directory ? "dir" : "file",
+ name, mimetype, size_string, unit, date, time);
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Generates the bottom part of an HTML directory listing page
+ *
+ * \return Bottom of directory listing HTML
+ *
+ * This is part of a series of functions. To generate a complete page,
+ * call the following functions in order:
+ *
+ * dirlist_generate_top()
+ * dirlist_generate_hide_columns() -- optional
+ * dirlist_generate_title()
+ * dirlist_generate_parent_link() -- optional
+ * dirlist_generate_headings()
+ * dirlist_generate_row() -- call 'n' times for 'n' rows
+ * dirlist_generate_bottom()
+ */
+
+bool dirlist_generate_bottom(char *buffer, int buffer_length)
+{
+ int error = snprintf(buffer, buffer_length,
+ "</div>\n"
+ "</body>\n"
+ "</html>\n");
+ if (error < 0 || error >= buffer_length)
+ /* Error or buffer too small */
+ return false;
+ else
+ /* OK */
+ return true;
+}
+
+
+/**
+ * Obtain display value and units for filesize after conversion to B/kB/MB/GB,
+ * as appropriate.
+ *
+ * \param bytesize file size in bytes, updated to filesize in output units
+ * \return number of times bytesize has been divided by 1024
+ */
+
+int dirlist_filesize_calculate(unsigned long *bytesize)
+{
+ int i = 0;
+ while (*bytesize > 1024 * 4) {
+ *bytesize /= 1024;
+ i++;
+ if (i == 3)
+ break;
+ }
+ return i;
+}
+
+
+/**
+ * Obtain display value for filesize after conversion to B/kB/MB/GB,
+ * as appropriate
+ *
+ * \param bytesize file size in bytes
+ * \return Value to display for file size, in units given by filesize_unit()
+ */
+
+int dirlist_filesize_value(unsigned long bytesize)
+{
+ dirlist_filesize_calculate(&bytesize);
+ return (int)bytesize;
+}
+
+
+/**
+ * Obtain display units for filesize after conversion to B/kB/MB/GB,
+ * as appropriate
+ *
+ * \param bytesize file size in bytes
+ * \return Units to display for file size, for value given by filesize_value()
+ */
+
+char* dirlist_filesize_unit(unsigned long bytesize)
+{
+ const char* units[] = { "Bytes", "kBytes", "MBytes", "GBytes" };
+ return (char*)units[dirlist_filesize_calculate(&bytesize)];
+}
diff --git a/content/fetchers/file/dirlist.h b/content/fetchers/file/dirlist.h
new file mode 100644
index 000000000..3a0d48c1c
--- /dev/null
+++ b/content/fetchers/file/dirlist.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * interface to generate HTML content for displaying directory listings.
+ *
+ * These functions should in general be called via the content interface.
+ */
+
+#ifndef NETSURF_CONTENT_DIRLIST_H_
+#define NETSURF_CONTENT_DIRLIST_H_
+
+#include <stdbool.h>
+
+#define DIRLIST_NO_NAME_COLUMN 1
+#define DIRLIST_NO_TYPE_COLUMN 1 << 1
+#define DIRLIST_NO_SIZE_COLUMN 1 << 2
+#define DIRLIST_NO_DATE_COLUMN 1 << 3
+#define DIRLIST_NO_TIME_COLUMN 1 << 4
+
+struct nsurl;
+
+bool dirlist_generate_top(char *buffer, int buffer_length);
+bool dirlist_generate_hide_columns(int flags, char *buffer, int buffer_length);
+bool dirlist_generate_title(const char *title, char *buffer, int buffer_length);
+bool dirlist_generate_parent_link(const char *parent, char *buffer,
+ int buffer_length);
+bool dirlist_generate_headings(char *buffer, int buffer_length);
+bool dirlist_generate_row(bool even, bool directory, struct nsurl *url,
+ char *name, const char *mimetype, long long size, char *date,
+ char *time, char *buffer, int buffer_length);
+bool dirlist_generate_bottom(char *buffer, int buffer_length);
+
+#endif
diff --git a/content/fetchers/file.c b/content/fetchers/file/file.c
index 4fa1a2138..ff3a1b1f6 100644
--- a/content/fetchers/file.c
+++ b/content/fetchers/file/file.c
@@ -50,15 +50,17 @@
#include "utils/corestrings.h"
#include "utils/messages.h"
#include "utils/utils.h"
+#include "utils/log.h"
+#include "utils/time.h"
#include "utils/ring.h"
#include "utils/file.h"
#include "netsurf/fetch.h"
#include "desktop/gui_internal.h"
-#include "content/dirlist.h"
#include "content/fetch.h"
#include "content/fetchers.h"
-#include "content/fetchers/file.h"
+#include "dirlist.h"
+#include "file.h"
/* Maximum size of read buffer */
#define FETCH_FILE_MAX_BUF_SIZE (1024 * 1024)
@@ -97,19 +99,21 @@ static bool fetch_file_send_header(struct fetch_file_context *ctx,
fetch_msg msg;
char header[64];
va_list ap;
+ int len;
va_start(ap, fmt);
-
- vsnprintf(header, sizeof header, fmt, ap);
-
+ len = vsnprintf(header, sizeof header, fmt, ap);
va_end(ap);
+ if (len >= (int)sizeof(header) || len < 0) {
+ return false;
+ }
+
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
- msg.data.header_or_data.len = strlen(header);
- fetch_file_send_callback(&msg, ctx);
+ msg.data.header_or_data.len = len;
- return ctx->aborted;
+ return fetch_file_send_callback(&msg, ctx);
}
/** callback to initialise the file fetcher. */
@@ -156,18 +160,25 @@ fetch_file_setup(struct fetch *fetchh,
/* Scan request headers looking for If-None-Match */
for (i = 0; headers[i] != NULL; i++) {
- if (strncasecmp(headers[i], "If-None-Match:",
- SLEN("If-None-Match:")) == 0) {
- /* If-None-Match: "12345678" */
- const char *d = headers[i] + SLEN("If-None-Match:");
-
- /* Scan to first digit, if any */
- while (*d != '\0' && (*d < '0' || '9' < *d))
- d++;
-
- /* Convert to time_t */
- if (*d != '\0')
- ctx->file_etag = atoi(d);
+ if (strncasecmp(headers[i], "If-None-Match:",
+ SLEN("If-None-Match:")) != 0) {
+ continue;
+ }
+
+ /* If-None-Match: "12345678" */
+ const char *d = headers[i] + SLEN("If-None-Match:");
+
+ /* Scan to first digit, if any */
+ while (*d != '\0' && (*d < '0' || '9' < *d))
+ d++;
+
+ /* Convert to time_t */
+ if (*d != '\0') {
+ ret = nsc_snptimet(d, strlen(d), &ctx->file_etag);
+ if (ret != NSERROR_OK) {
+ NSLOG(fetch, WARNING,
+ "Bad If-None-Match value");
+ }
}
}
@@ -184,7 +195,6 @@ static void fetch_file_free(void *ctx)
struct fetch_file_context *c = ctx;
nsurl_unref(c->url);
free(c->path);
- RING_REMOVE(ring, c);
free(ctx);
}
@@ -780,13 +790,13 @@ static void fetch_file_process(struct fetch_file_context *ctx)
/** callback to poll for additional file fetch contents */
static void fetch_file_poll(lwc_string *scheme)
{
- struct fetch_file_context *c, *next;
+ struct fetch_file_context *c, *save_ring = NULL;
- if (ring == NULL) return;
+ while (ring != NULL) {
+ /* Take the first entry from the ring */
+ c = ring;
+ RING_REMOVE(ring, c);
- /* Iterate over ring, processing each pending fetch */
- c = ring;
- do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
@@ -794,7 +804,7 @@ static void fetch_file_poll(lwc_string *scheme)
* again.
*/
if (c->locked == true) {
- next = c->r_next;
+ RING_INSERT(save_ring, c);
continue;
}
@@ -804,18 +814,16 @@ static void fetch_file_poll(lwc_string *scheme)
fetch_file_process(c);
}
- /* Compute next fetch item at the last possible moment as
- * processing this item may have added to the ring.
- */
- next = c->r_next;
-
+ /* And now finish */
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
- /* Advance to next ring entry, exiting if we've reached
- * the start of the ring or the ring has become empty
- */
- } while ( (c = next) != ring && ring != NULL);
+ }
+
+ /* Finally, if we saved any fetches which were locked, put them back
+ * into the ring for next time
+ */
+ ring = save_ring;
}
nserror fetch_file_register(void)
diff --git a/content/fetchers/file.h b/content/fetchers/file/file.h
index 5a5cfe89b..5a5cfe89b 100644
--- a/content/fetchers/file.h
+++ b/content/fetchers/file/file.h
diff --git a/content/fetchers/resource.c b/content/fetchers/resource.c
index 78757733e..c8176f34b 100644
--- a/content/fetchers/resource.c
+++ b/content/fetchers/resource.c
@@ -33,6 +33,7 @@
#include "utils/nsurl.h"
#include "utils/corestrings.h"
#include "utils/log.h"
+#include "utils/time.h"
#include "utils/messages.h"
#include "utils/utils.h"
#include "utils/ring.h"
@@ -55,7 +56,6 @@ static const char *fetch_resource_paths[] = {
"credits.html",
"licence.html",
"welcome.html",
- "maps.html",
"favicon.ico",
"default.ico",
"netsurf.png",
@@ -97,7 +97,7 @@ struct fetch_resource_context {
fetch_resource_handler handler;
- int etag;
+ time_t etag;
};
static struct fetch_resource_context *ring = NULL;
@@ -121,19 +121,21 @@ static bool fetch_resource_send_header(struct fetch_resource_context *ctx,
fetch_msg msg;
char header[64];
va_list ap;
+ int len;
va_start(ap, fmt);
-
- vsnprintf(header, sizeof header, fmt, ap);
-
+ len = vsnprintf(header, sizeof header, fmt, ap);
va_end(ap);
+ if (len >= (int)sizeof(header) || len < 0) {
+ return false;
+ }
+
msg.type = FETCH_HEADER;
msg.data.header_or_data.buf = (const uint8_t *) header;
- msg.data.header_or_data.len = strlen(header);
- fetch_resource_send_callback(&msg, ctx);
+ msg.data.header_or_data.len = len;
- return ctx->aborted;
+ return fetch_resource_send_callback(&msg, ctx);
}
@@ -194,6 +196,11 @@ static bool fetch_resource_data_handler(struct fetch_resource_context *ctx)
goto fetch_resource_data_aborted;
}
+ /* create max-age of 1 year */
+ if (fetch_resource_send_header(ctx,
+ "Cache-Control: max-age=31536000")) {
+ goto fetch_resource_data_aborted;
+ }
msg.type = FETCH_DATA;
msg.data.header_or_data.buf = (const uint8_t *) ctx->entry->data;
@@ -328,6 +335,7 @@ fetch_resource_setup(struct fetch *fetchh,
{
struct fetch_resource_context *ctx;
lwc_string *path;
+ nserror ret;
uint32_t i;
ctx = calloc(1, sizeof(*ctx));
@@ -364,17 +372,24 @@ fetch_resource_setup(struct fetch *fetchh,
/* Scan request headers looking for If-None-Match */
for (i = 0; headers[i] != NULL; i++) {
if (strncasecmp(headers[i], "If-None-Match:",
- SLEN("If-None-Match:")) == 0) {
- /* If-None-Match: "12345678" */
- const char *d = headers[i] + SLEN("If-None-Match:");
+ SLEN("If-None-Match:")) != 0) {
+ continue;
+ }
+
+ /* If-None-Match: "12345678" */
+ const char *d = headers[i] + SLEN("If-None-Match:");
- /* Scan to first digit, if any */
- while (*d != '\0' && (*d < '0' || '9' < *d))
- d++;
+ /* Scan to first digit, if any */
+ while (*d != '\0' && (*d < '0' || '9' < *d))
+ d++;
- /* Convert to time_t */
- if (*d != '\0')
- ctx->etag = atoi(d);
+ /* Convert to time_t */
+ if (*d != '\0') {
+ ret = nsc_snptimet(d, strlen(d), &ctx->etag);
+ if (ret != NSERROR_OK) {
+ NSLOG(fetch, WARNING,
+ "Bad If-None-Match value");
+ }
}
}
@@ -391,7 +406,6 @@ static void fetch_resource_free(void *ctx)
struct fetch_resource_context *c = ctx;
if (c->url != NULL)
nsurl_unref(c->url);
- RING_REMOVE(ring, c);
free(ctx);
}
@@ -417,13 +431,13 @@ static void fetch_resource_abort(void *ctx)
/** callback to poll for additional resource fetch contents */
static void fetch_resource_poll(lwc_string *scheme)
{
- struct fetch_resource_context *c, *next;
+ struct fetch_resource_context *c, *save_ring = NULL;
- if (ring == NULL) return;
+ while (ring != NULL) {
+ /* Take the first entry from the ring */
+ c = ring;
+ RING_REMOVE(ring, c);
- /* Iterate over ring, processing each pending fetch */
- c = ring;
- do {
/* Ignore fetches that have been flagged as locked.
* This allows safe re-entrant calls to this function.
* Re-entrancy can occur if, as a result of a callback,
@@ -431,7 +445,7 @@ static void fetch_resource_poll(lwc_string *scheme)
* again.
*/
if (c->locked == true) {
- next = c->r_next;
+ RING_INSERT(save_ring, c);
continue;
}
@@ -441,18 +455,15 @@ static void fetch_resource_poll(lwc_string *scheme)
c->handler(c);
}
- /* Compute next fetch item at the last possible moment
- * as processing this item may have added to the ring
- */
- next = c->r_next;
-
+ /* And now finish */
fetch_remove_from_queues(c->fetchh);
fetch_free(c->fetchh);
+ }
- /* Advance to next ring entry, exiting if we've reached
- * the start of the ring or the ring has become empty
- */
- } while ( (c = next) != ring && ring != NULL);
+ /* Finally, if we saved any fetches which were locked, put them back
+ * into the ring for next time
+ */
+ ring = save_ring;
}
nserror fetch_resource_register(void)