auto-response-header: generalisation of content-security-policy
authorSébastien Marie <semarie@users.noreply.github.com>
Fri, 31 Oct 2014 18:46:33 +0000 (19:46 +0100)
committerSébastien Marie <semarie@users.noreply.github.com>
Fri, 31 Oct 2014 18:46:33 +0000 (19:46 +0100)
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
src/arh.c [new file with mode: 0644]
src/arh.h [new file with mode: 0644]
src/main.c
src/main.h
src/setting.c

index 4735abc..94cea53 100644 (file)
@@ -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 (file)
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 (file)
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 */
index ae4009f..e2c5ced 100644 (file)
@@ -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)
index ab0dbf6..fb52fef 100644 (file)
@@ -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 */
index ecb13a3..7190ba2 100644 (file)
@@ -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)) {