From c9e154a00c8a6b1e30df859a0a4e90fd42ba0302 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Marie?= Date: Fri, 31 Oct 2014 19:46:33 +0100 Subject: [PATCH] auto-response-header: generalisation of content-security-policy auto-response-header supersede previous content-security-policy setting. This setting is a list of "pattern name=value" entries. When a pattern match a requested uri, the HTTP header "name: value" is added to response. --- doc/vimb.1 | 28 ++++--- src/arh.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/arh.h | 31 +++++++ src/main.c | 13 ++- src/main.h | 2 +- src/setting.c | 25 +++++- 6 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 src/arh.c create mode 100644 src/arh.h diff --git a/doc/vimb.1 b/doc/vimb.1 index 4735abc..94cea53 100644 --- a/doc/vimb.1 +++ b/doc/vimb.1 @@ -650,8 +650,7 @@ being completed. .TP .B RequestQueued Fired before each request (and so, multiple times in one page: one time for -each image, css, scripts, frames...). This is the right event to set -`content-security-policy' setting. +each image, css, scripts, frames...). .TP .B DownloadStart Fired right before a download is started. This is fired for vimb downloads as @@ -1197,22 +1196,27 @@ Header completely from request. .PD .RE .TP -.B content-security-policy (string) -Prepend a `Content-Security-Policy' HTTP-Header to responses received from -server. This setting has to be setted early if managed with `autocmd' (at -RequestQueued), in order to be managed by webkit. +.B auto-response-header (list) +Prepend HTTP-Header to responses received from server, based on pattern +matching. The purpose of this setting is to enforce some security setting in +the client. For example, you could set Content-Security-Policy (see +`http://www.w3.org/TR/CSP/') for implement a whitelist policy, or set +Strict-Transport-Security for server that don't provide this header whereas +they propose https website. -It could be used to implement a whitelist policy for visited uri. +Note that this setting will not remplace existing headers, but add a new one. +If multiple patterns match a request uri, the last matched rule will be +applied. You could also specified differents headers for same pattern. -Note that this setting will not remplace existing headers, but add one. - -Please refer to `http://www.w3.org/TR/CSP/' for syntax. +The format is: `pattern name=value`. For each request matching `pattern`, an +HTTP header "name: value" will be added to the response. .RS .PP Example: .PD 0 -.IP ":set content-security-policy=default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'none'" -Webkit will see the `Content-Security-Policy' header defined with each response. +.IP ":set auto-response-header=* Content-security-policy=default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'none' +.IP ":set auto-response-header+=https://example.com/* Content-security-policy=default-src 'self' https://*.example.com/ +.IP ":set auto-response-header+=https://example.com/* Strict-Transport-Security=max-age=31536000 .PD .RE .TP diff --git a/src/arh.c b/src/arh.c new file mode 100644 index 0000000..ffb895e --- /dev/null +++ b/src/arh.c @@ -0,0 +1,222 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2014 Daniel Carl + * Copyright (C) 2014 Sébastien Marie + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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/. + */ +#include "config.h" +#include "ascii.h" +#include "arh.h" +#include "util.h" + +typedef struct { + char *pattern; /* pattern the uri is matched against */ + char *name; /* header name */ + char *value; /* header value */ +} MatchARH; + +static void marh_free(MatchARH *); +static char *read_pattern(char **parse); +static char *read_name(char **parse); +static char *read_value(char **parse); + + +/** + * parse the data string to a list of MatchARH + * + * pattern name=value[,...] + */ +GSList *arh_parse(const char *data, const char **error) +{ + GSList *parsed = NULL; + char *data_dup = g_strdup(data); + char *parse = data_dup; + + while (*parse) { + char *pattern = read_pattern(&parse); + char *name = read_name(&parse); + char *value = read_value(&parse); + + if (pattern && name && value) { + MatchARH *marh = g_slice_new0(MatchARH); + + marh->pattern = g_strdup(pattern); + marh->name = g_strdup(name); + marh->value = g_strdup(value); + + parsed = g_slist_append(parsed, marh); + + PRINT_DEBUG("append pattern='%s' name='%s' value='%s'", + marh->pattern, marh->name, marh->value); + + } else { + /* one error occurs: free already parsed list */ + arh_free(parsed); + + /* set error if asked */ + if ( error != NULL ) { + *error = "syntax error"; + } + + g_free(data_dup); + return NULL; + } + } + + g_free(data_dup); + return parsed; +} + + +/** + * free the list of MatchARH + */ +void arh_free(GSList *list) +{ + g_slist_free_full(list, (GDestroyNotify)marh_free); +} + + +/** + * append to reponse-header of SoupMessage, + * the header that match the specified uri + */ +void arh_run(GSList *list, const char *uri, SoupMessage *msg) +{ + PRINT_DEBUG("uri: %s", uri); + + /* walk thought the list */ + for (GSList *l=list; l; l = g_slist_next(l)) { + MatchARH *marh = (MatchARH *)l->data; + + if (util_wildmatch(marh->pattern, uri)) { + /* the pattern match, so replace headers + * as side-effect, for one header the last matched will be keeped */ + soup_message_headers_replace(msg->response_headers, + marh->name, marh->value); + + PRINT_DEBUG(" header added: %s: %s", marh->name, marh->value); + } + } +} + + +/** + * free a MatchARH + */ +static void marh_free(MatchARH *marh) +{ + if (marh) { + g_free(marh->pattern); + g_free(marh->name); + g_free(marh->value); + + g_slice_free(MatchARH, marh); + } +} + +/** + * read until ' ' (or any other SPACE) + */ +static char *read_pattern(char **line) +{ + char *pattern; + + if (!*line || !**line) { + return NULL; + } + + /* remember where the pattern starts */ + pattern = *line; + + /* move pointer to the end of the pattern (or end-of-line) */ + while (**line && !VB_IS_SPACE(**line)) { + (*line)++; + } + + if (**line) { + /* end the pattern */ + *(*line)++ = '\0'; + + /* skip trailing whitespace */ + while (VB_IS_SPACE(**line)) { + (*line)++; + } + + return pattern; + + } else { + /* end-of-line was encounter */ + return NULL; + } +} + +/** + * read until '=' + */ +static char *read_name(char **line) +{ + char *name; + + if (!*line || !**line) { + return NULL; + } + + /* remember where the name starts */ + name = *line; + + /* move pointer to the end of the name (or end-of-line) */ + while (**line && **line != '=') { + (*line)++; + } + + if (**line) { + /* end the name */ + *(*line)++ = '\0'; + return name; + + } else { + /* end-of-line was encounter */ + return NULL; + } +} + +/** + * read until ',' or end-of-line + */ +static char *read_value(char **line) +{ + char *value; + + if (!*line || !**line) { + return NULL; + } + + /* remember where the value starts */ + value = *line; + + /* move pointer to the end of the value or of the line */ + while (**line && **line != ',') { + (*line)++; + } + + /* end the value */ + if (**line) { + *(*line)++ = '\0'; + } + + return value; +} diff --git a/src/arh.h b/src/arh.h new file mode 100644 index 0000000..24463d4 --- /dev/null +++ b/src/arh.h @@ -0,0 +1,31 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2014 Daniel Carl + * Copyright (C) 2014 Sébastien Marie + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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/. + */ +#include "config.h" + +#ifndef _ARH_H +#define _ARH_H + +#include "main.h" + +GSList *arh_parse(const char *, const char **); +void arh_free(GSList *); +void arh_run(GSList *, const char *, SoupMessage *); + +#endif /* end of include guard: _ARH_H */ diff --git a/src/main.c b/src/main.c index ae4009f..e2c5ced 100644 --- a/src/main.c +++ b/src/main.c @@ -43,6 +43,7 @@ #include "bookmark.h" #include "js.h" #include "autocmd.h" +#include "arh.h" /* variables */ static char **args; @@ -1495,14 +1496,13 @@ static void read_from_stdin(void) static void session_request_queued_cb(SoupSession *session, SoupMessage *msg, gpointer data) { -#ifdef FEATURE_AUTOCMD SoupURI *suri = soup_message_get_uri(msg); char *uri = soup_uri_to_string(suri, false); +#ifdef FEATURE_AUTOCMD autocmd_run(AU_REQUEST_QUEUED, uri, NULL); - - g_free(uri); #endif + #ifdef DEBUG SoupMessageHeadersIter iter; const char *name, *value; @@ -1514,11 +1514,8 @@ static void session_request_queued_cb(SoupSession *session, SoupMessage *msg, gp } #endif - /* add a fake Content-Security-Policy header in server-response header */ - if (vb.config.contentsecuritypolicy && *vb.config.contentsecuritypolicy != '\0') { - soup_message_headers_append(msg->response_headers, "Content-Security-Policy", - vb.config.contentsecuritypolicy); - } + arh_run(vb.config.autoresponseheader, uri, msg); + g_free(uri); } static gboolean autocmdOptionArgFunc(const gchar *option_name, const gchar *value, gpointer data, GError **error) diff --git a/src/main.h b/src/main.h index ab0dbf6..fb52fef 100644 --- a/src/main.h +++ b/src/main.h @@ -317,7 +317,7 @@ typedef struct { guint timeoutlen; /* timeout for ambiguous mappings */ gboolean strict_focus; GHashTable *headers; /* holds user defined header appended to requests */ - char *contentsecuritypolicy; /* holds user defined Content-Security-Policy */ + GSList *autoresponseheader; /* holds user defined list of auto-response-header */ char *nextpattern; /* regex patter nfor prev link matching */ char *prevpattern; /* regex patter nfor next link matching */ char *file; /* path to the custome config file */ diff --git a/src/setting.c b/src/setting.c index ecb13a3..7190ba2 100644 --- a/src/setting.c +++ b/src/setting.c @@ -29,6 +29,7 @@ #ifdef FEATURE_HSTS #include "hsts.h" #endif +#include "arh.h" typedef enum { TYPE_BOOLEAN, @@ -78,6 +79,7 @@ static int ca_bundle(const char *name, int type, void *value, void *data); static int proxy(const char *name, int type, void *value, void *data); static int user_style(const char *name, int type, void *value, void *data); static int headers(const char *name, int type, void *value, void *data); +static int autoresponseheader(const char *name, int type, void *value, void *data); static int prevnext(const char *name, int type, void *value, void *data); static int fullscreen(const char *name, int type, void *value, void *data); #ifdef FEATURE_HSTS @@ -203,7 +205,7 @@ void setting_init() setting_add("history-max-items", TYPE_INTEGER, &i, internal, 0, &vb.config.history_max); setting_add("editor-command", TYPE_CHAR, &"x-terminal-emulator -e -vi '%s'", NULL, 0, NULL); setting_add("header", TYPE_CHAR, &"", headers, FLAG_LIST|FLAG_NODUP, NULL); - setting_add("content-security-policy", TYPE_CHAR, &"", internal, 0, &vb.config.contentsecuritypolicy); + setting_add("auto-response-header", TYPE_CHAR, &"", autoresponseheader, FLAG_LIST|FLAG_NODUP, NULL); setting_add("nextpattern", TYPE_CHAR, &"/\\bnext\\b/i,/^(>\\|>>\\|»)$/,/^(>\\|>>\\|»)/,/(>\\|>>\\|»)$/,/\\bmore\\b/i", prevnext, FLAG_LIST|FLAG_NODUP, NULL); setting_add("previouspattern", TYPE_CHAR, &"/\\bprev\\|previous\\b/i,/^(<\\|<<\\|«)$/,/^(<\\|<<\\|«)/,/(<\\|<<\\|«)$/", prevnext, FLAG_LIST|FLAG_NODUP, NULL); setting_add("fullscreen", TYPE_BOOLEAN, &off, fullscreen, 0, NULL); @@ -813,6 +815,27 @@ static int headers(const char *name, int type, void *value, void *data) return SETTING_OK; } +static int autoresponseheader(const char *name, int type, void *value, void *data) +{ + const char *error = NULL; + + GSList *new = arh_parse((char *)value, &error); + + if (! error) { + /* remove previous parsed headers */ + arh_free(vb.config.autoresponseheader); + + /* add the new one */ + vb.config.autoresponseheader = new; + + return SETTING_OK; + + } else { + vb_echo(VB_MSG_ERROR, true, "auto-response-header: %s", error); + return SETTING_ERROR | SETTING_USER_NOTIFIED; + } +} + static int prevnext(const char *name, int type, void *value, void *data) { if (validate_js_regexp_list((char*)value)) { -- 2.20.1