Gitweb links:

...log 
http://git.netsurf-browser.org/netsurf.git/shortlog/103c015784a73e86b120e2e26b3906c6787209ef
...commit 
http://git.netsurf-browser.org/netsurf.git/commit/103c015784a73e86b120e2e26b3906c6787209ef
...tree 
http://git.netsurf-browser.org/netsurf.git/tree/103c015784a73e86b120e2e26b3906c6787209ef

The branch, master has been updated
       via  103c015784a73e86b120e2e26b3906c6787209ef (commit)
       via  ec1936cc93f2f2f40d26a2913e31952e6ed24d77 (commit)
       via  83f5332708d7b8cbb6a2efb76eef4bdeec318b5a (commit)
       via  f2121d1c0f36ef3951e0570552e70f9450f0fab5 (commit)
       via  a6014cae157107ec868816fcccf8689a5d6cc766 (commit)
       via  1c05280b5cd23c94ed10c2a9415a500393100285 (commit)
       via  badc0d437adff453f046cd8f0eb46c4fa5afe2bf (commit)
      from  9c164e591f7101ccafb6656cc9c8b286e2c76ca1 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=103c015784a73e86b120e2e26b3906c6787209ef
commit 103c015784a73e86b120e2e26b3906c6787209ef
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    HSTS: make llcache update policy on 3xx responses

diff --git a/content/llcache.c b/content/llcache.c
index bb61236..29b42a1 100644
--- a/content/llcache.c
+++ b/content/llcache.c
@@ -1981,6 +1981,8 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        /* And mark it complete */
        object->fetch.state = LLCACHE_FETCH_COMPLETE;
 
+       (void) llcache_hsts_update_policy(object);
+
        /* Forcibly stop redirecting if we've followed too many redirects */
 #define REDIRECT_LIMIT 10
        if (object->fetch.redirect_count > REDIRECT_LIMIT) {


commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=ec1936cc93f2f2f40d26a2913e31952e6ed24d77
commit ec1936cc93f2f2f40d26a2913e31952e6ed24d77
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    HSTS: prevent llcache being nice
    
    If the server has defined a HSTS policy, then the user no longer
    gets to click-through a garbage certificate. Additionally, if
    the server has provided a HSTS policy, it should do TLS properly,
    so don't permit client-driven TLS version downgrades in that case,
    either.

diff --git a/content/llcache.c b/content/llcache.c
index 30cb10c..bb61236 100644
--- a/content/llcache.c
+++ b/content/llcache.c
@@ -109,6 +109,8 @@ typedef struct {
 
        uint32_t retries_remaining;     /**< Number of times to retry on 
timeout */
 
+       bool hsts_in_use;               /**< Whether HSTS applies to this fetch 
*/
+
        bool tried_with_auth;           /**< Whether we've tried with auth */
 
        bool tried_with_tls_downgrade;  /**< Whether we've tried TLS <= 1.0 */
@@ -904,11 +906,12 @@ static nserror llcache_object_refetch(llcache_object 
*object)
  * \param referer        Referring URL, or NULL for none
  * \param post           POST data, or NULL for GET
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \return NSERROR_OK on success, appropriate error otherwise
  */
 static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
                nsurl *referer, const llcache_post_data *post,
-               uint32_t redirect_count)
+               uint32_t redirect_count, bool hsts_in_use)
 {
        nserror error;
        nsurl *referer_clone = NULL;
@@ -930,6 +933,7 @@ static nserror llcache_object_fetch(llcache_object *object, 
uint32_t flags,
        object->fetch.post = post_clone;
        object->fetch.redirect_count = redirect_count;
        object->fetch.retries_remaining = llcache->fetch_attempts;
+       object->fetch.hsts_in_use = hsts_in_use;
 
        return llcache_object_refetch(object);
 }
@@ -1566,6 +1570,7 @@ llcache_object_fetch_persistent(llcache_object *object,
  * \param referer        Referring URL, or NULL if none
  * \param post           POST data, or NULL for a GET request
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \param result         Pointer to location to receive retrieved object
  * \return NSERROR_OK on success, appropriate error otherwise
  */
@@ -1575,6 +1580,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
                                   nsurl *referer,
                                   const llcache_post_data *post,
                                   uint32_t redirect_count,
+                                  bool hsts_in_use,
                                   llcache_object **result)
 {
        nserror error;
@@ -1683,7 +1689,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
 
                        /* Attempt to kick-off fetch */
                        error = llcache_object_fetch(obj, flags, referer, post,
-                                                    redirect_count);
+                                                    redirect_count, 
hsts_in_use);
                        if (error != NSERROR_OK) {
                                newest->candidate_count--;
                                llcache_object_destroy(obj);
@@ -1715,7 +1721,8 @@ llcache_object_retrieve_from_cache(nsurl *url,
        }
 
        /* Attempt to kick-off fetch */
-       error = llcache_object_fetch(obj, flags, referer, post, redirect_count);
+       error = llcache_object_fetch(obj, flags, referer, post,
+                       redirect_count, hsts_in_use);
        if (error != NSERROR_OK) {
                llcache_object_destroy(obj);
                return error;
@@ -1737,6 +1744,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
  * \param referer        Referring URL, or NULL if none
  * \param post           POST data, or NULL for a GET request
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \param result         Pointer to location to receive retrieved object
  * \return NSERROR_OK on success, appropriate error otherwise
  */
@@ -1746,6 +1754,7 @@ llcache_object_retrieve(nsurl *url,
                        nsurl *referer,
                        const llcache_post_data *post,
                        uint32_t redirect_count,
+                       bool hsts_in_use,
                        llcache_object **result)
 {
        nserror error;
@@ -1800,7 +1809,7 @@ llcache_object_retrieve(nsurl *url,
 
                /* Attempt to kick-off fetch */
                error = llcache_object_fetch(obj, flags, referer, post,
-                               redirect_count);
+                               redirect_count, hsts_in_use);
                if (error != NSERROR_OK) {
                        llcache_object_destroy(obj);
                        nsurl_unref(defragmented_url);
@@ -1811,7 +1820,8 @@ llcache_object_retrieve(nsurl *url,
                llcache_object_add_to_list(obj, &llcache->uncached_objects);
        } else {
                error = llcache_object_retrieve_from_cache(defragmented_url,
-                               flags, referer, post, redirect_count, &obj);
+                               flags, referer, post, redirect_count,
+                               hsts_in_use, &obj);
                if (error != NSERROR_OK) {
                        nsurl_unref(defragmented_url);
                        return error;
@@ -2054,7 +2064,8 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        /* Attempt to fetch target URL */
        error = llcache_object_retrieve(hsts_url, object->fetch.flags,
                        object->fetch.referer, post,
-                       object->fetch.redirect_count + 1, &dest);
+                       object->fetch.redirect_count + 1,
+                       hsts_in_use, &dest);
 
        /* No longer require url */
        nsurl_unref(hsts_url);
@@ -2348,7 +2359,8 @@ static nserror llcache_fetch_cert_error(llcache_object 
*object,
        /* Consider the TLS transport tainted */
        object->fetch.tainted_tls = true;
 
-       if (llcache->query_cb != NULL) {
+       /* Only give the user a chance if HSTS isn't in use for this fetch */
+       if (object->fetch.hsts_in_use == false && llcache->query_cb != NULL) {
                llcache_query query;
 
                /* Emit query for TLS */
@@ -2401,7 +2413,10 @@ static nserror llcache_fetch_ssl_error(llcache_object 
*object)
        /* Consider the TLS transport tainted */
        object->fetch.tainted_tls = true;
 
-       if (object->fetch.tried_with_tls_downgrade == true) {
+       /* Make no attempt to downgrade if HSTS is in use
+        * (i.e. assume server does TLS properly) */
+       if (object->fetch.hsts_in_use ||
+                       object->fetch.tried_with_tls_downgrade) {
                /* Have already tried to downgrade, so give up */
                llcache_event event;
 
@@ -3607,7 +3622,8 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t 
flags,
 
        /* Retrieve a suitable object from the cache,
         * creating a new one if needed. */
-       error = llcache_object_retrieve(hsts_url, flags, referer, post, 0, 
&object);
+       error = llcache_object_retrieve(hsts_url, flags, referer, post, 0,
+                       hsts_in_use, &object);
        if (error != NSERROR_OK) {
                llcache_object_user_destroy(user);
                nsurl_unref(hsts_url);


commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=83f5332708d7b8cbb6a2efb76eef4bdeec318b5a
commit 83f5332708d7b8cbb6a2efb76eef4bdeec318b5a
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    HSTS: teach llcache to update and enforce policy.

diff --git a/content/llcache.c b/content/llcache.c
index 58803ea..30cb10c 100644
--- a/content/llcache.c
+++ b/content/llcache.c
@@ -114,6 +114,8 @@ typedef struct {
        bool tried_with_tls_downgrade;  /**< Whether we've tried TLS <= 1.0 */
 
        bool outstanding_query;         /**< Waiting for a query response */
+
+       bool tainted_tls;               /**< Whether the TLS transport is 
tainted */
 } llcache_fetch_ctx;
 
 /**
@@ -1857,6 +1859,86 @@ static nserror llcache_object_add_user(llcache_object 
*object,
 }
 
 /**
+ * Transform a request-URI based on HSTS policy
+ *
+ * \param url URL to transform
+ * \param result Pointer to location to receive transformed URL
+ * \param hsts_in_use Pointer to location to receive HSTS in-use flag
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+static nserror llcache_hsts_transform_url(nsurl *url, nsurl **result,
+               bool *hsts_in_use)
+{
+       lwc_string *scheme = NULL;
+       bool match;
+       nserror error = NSERROR_OK;
+
+       scheme = nsurl_get_component(url, NSURL_SCHEME);
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_http,
+                       &match) != lwc_error_ok || match == false) {
+               /* Non-HTTP fetch: ignore */
+               lwc_string_unref(scheme);
+               *result = nsurl_ref(url);
+               *hsts_in_use = false;
+               return error;
+       }
+       lwc_string_unref(scheme);
+
+       if (urldb_get_hsts_enabled(url)) {
+               /* Only need to force HTTPS. If original port was explicitly
+                * specified as 80, nsurl_create/join will remove it (as
+                * it's redundant) */
+               error = nsurl_replace_scheme(url, corestring_lwc_https,
+                               result);
+               *hsts_in_use = (error == NSERROR_OK);
+       } else {
+               *result = nsurl_ref(url);
+               *hsts_in_use = false;
+       }
+
+       return error;
+}
+
+/**
+ * Update HSTS policy for target domain.
+ *
+ * \param object Newly-fetched cache object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+static nserror llcache_hsts_update_policy(llcache_object *object)
+{
+       size_t i;
+       lwc_string *scheme = NULL;
+       bool match = false;
+
+       scheme = nsurl_get_component(object->url, NSURL_SCHEME);
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_https,
+                       &match) != lwc_error_ok || match == false) {
+               /* Non-HTTPS fetch: ignore */
+               lwc_string_unref(scheme);
+               return NSERROR_OK;
+       }
+       lwc_string_unref(scheme);
+
+       if (object->fetch.tainted_tls) {
+               /* Transport is tainted: ignore */
+               return NSERROR_OK;
+       }
+
+       for (i = 0; i < object->num_headers; i++) {
+               if (strcasecmp("Strict-Transport-Security",
+                               object->headers[i].name) == 0) {
+                       urldb_set_hsts_policy(object->url,
+                                       object->headers[i].value);
+                       /* Only process the first one we find */
+                       break;
+               }
+       }
+
+       return NSERROR_OK;
+}
+
+/**
  * Handle FETCH_REDIRECT event
  *
  * \param object       Object being redirected
@@ -1871,10 +1953,10 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        llcache_object *dest;
        llcache_object_user *user, *next;
        const llcache_post_data *post = object->fetch.post;
-       nsurl *url;
+       nsurl *url, *hsts_url;
        lwc_string *scheme;
        lwc_string *object_scheme;
-       bool match;
+       bool match, hsts_in_use;
        /* Extract HTTP response code from the fetch object */
        long http_code = fetch_http_code(object->fetch.fetch);
        llcache_event event;
@@ -1906,15 +1988,23 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        if (error != NSERROR_OK)
                return error;
 
+       /* Perform HSTS transform */
+       error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use);
+       if (error != NSERROR_OK) {
+               nsurl_unref(url);
+               return error;
+       }
+       nsurl_unref(url);
+
        /* Inform users of redirect */
        event.type = LLCACHE_EVENT_REDIRECT;
        event.data.redirect.from = object->url;
-       event.data.redirect.to = url;
+       event.data.redirect.to = hsts_url;
 
        error = llcache_send_event_to_users(object, &event);
 
        if (error != NSERROR_OK) {
-               nsurl_unref(url);
+               nsurl_unref(hsts_url);
                return error;
        }
 
@@ -1922,7 +2012,7 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
         * A "validated" scheme is one over which we have some guarantee that
         * the source is trustworthy. */
        object_scheme = nsurl_get_component(object->url, NSURL_SCHEME);
-       scheme = nsurl_get_component(url, NSURL_SCHEME);
+       scheme = nsurl_get_component(hsts_url, NSURL_SCHEME);
 
        /* resource: and about: are allowed to redirect anywhere */
        if ((lwc_string_isequal(object_scheme, corestring_lwc_resource,
@@ -1938,7 +2028,7 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
                                &match) == lwc_error_ok && match == true)) {
                        lwc_string_unref(object_scheme);
                        lwc_string_unref(scheme);
-                       nsurl_unref(url);
+                       nsurl_unref(hsts_url);
                        return NSERROR_OK;
                }
        }
@@ -1947,8 +2037,8 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        lwc_string_unref(object_scheme);
 
        /* Bail out if we've no way of handling this URL */
-       if (fetch_can_fetch(url) == false) {
-               nsurl_unref(url);
+       if (fetch_can_fetch(hsts_url) == false) {
+               nsurl_unref(hsts_url);
                return NSERROR_OK;
        }
 
@@ -1957,17 +2047,17 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
                post = NULL;
        } else if (http_code != 307 || post != NULL) {
                /** \todo 300, 305, 307 with POST */
-               nsurl_unref(url);
+               nsurl_unref(hsts_url);
                return NSERROR_OK;
        }
 
        /* Attempt to fetch target URL */
-       error = llcache_object_retrieve(url, object->fetch.flags,
+       error = llcache_object_retrieve(hsts_url, object->fetch.flags,
                        object->fetch.referer, post,
                        object->fetch.redirect_count + 1, &dest);
 
        /* No longer require url */
-       nsurl_unref(url);
+       nsurl_unref(hsts_url);
 
        if (error != NSERROR_OK)
                return error;
@@ -2059,6 +2149,8 @@ static nserror llcache_fetch_notmodified(llcache_object 
*object,
        /* Mark it complete */
        object->fetch.state = LLCACHE_FETCH_COMPLETE;
 
+       (void) llcache_hsts_update_policy(object);
+
        /* Old object will be flushed from the cache on the next poll */
 
        return NSERROR_OK;
@@ -2253,6 +2345,9 @@ static nserror llcache_fetch_cert_error(llcache_object 
*object,
        /* Invalidate cache-control data */
        llcache_invalidate_cache_control_data(object);
 
+       /* Consider the TLS transport tainted */
+       object->fetch.tainted_tls = true;
+
        if (llcache->query_cb != NULL) {
                llcache_query query;
 
@@ -2303,6 +2398,9 @@ static nserror llcache_fetch_ssl_error(llcache_object 
*object)
        /* Invalidate cache-control data */
        llcache_invalidate_cache_control_data(object);
 
+       /* Consider the TLS transport tainted */
+       object->fetch.tainted_tls = true;
+
        if (object->fetch.tried_with_tls_downgrade == true) {
                /* Have already tried to downgrade, so give up */
                llcache_event event;
@@ -2684,6 +2782,8 @@ static void llcache_fetch_callback(const fetch_msg *msg, 
void *p)
                /* record when the fetch finished */
                object->cache.fin_time = time(NULL);
 
+               (void) llcache_hsts_update_policy(object);
+
                guit->misc->schedule(5000, llcache_persist, NULL);
        }
                break;
@@ -3483,23 +3583,34 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t 
flags,
        nserror error;
        llcache_object_user *user;
        llcache_object *object;
+       nsurl *hsts_url;
+       bool hsts_in_use;
+
+       /* Perform HSTS transform */
+       error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use);
+       if (error != NSERROR_OK) {
+               return error;
+       }
 
        /* Can we fetch this URL at all? */
-       if (fetch_can_fetch(url) == false) {
+       if (fetch_can_fetch(hsts_url) == false) {
+               nsurl_unref(hsts_url);
                return NSERROR_NO_FETCH_HANDLER;
        }
 
        /* Create a new object user */
        error = llcache_object_user_new(cb, pw, &user);
        if (error != NSERROR_OK) {
+               nsurl_unref(hsts_url);
                return error;
        }
 
        /* Retrieve a suitable object from the cache,
         * creating a new one if needed. */
-       error = llcache_object_retrieve(url, flags, referer, post, 0, &object);
+       error = llcache_object_retrieve(hsts_url, flags, referer, post, 0, 
&object);
        if (error != NSERROR_OK) {
                llcache_object_user_destroy(user);
+               nsurl_unref(hsts_url);
                return error;
        }
 
@@ -3511,6 +3622,8 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t 
flags,
        /* Users exist which are now not caught up! */
        llcache_users_not_caught_up();
 
+       nsurl_unref(hsts_url);
+
        return NSERROR_OK;
 }
 


commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=f2121d1c0f36ef3951e0570552e70f9450f0fab5
commit f2121d1c0f36ef3951e0570552e70f9450f0fab5
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    NSURL: add ability to create replacement scheme

diff --git a/utils/corestringlist.h b/utils/corestringlist.h
index 8d15eeb..90dd796 100644
--- a/utils/corestringlist.h
+++ b/utils/corestringlist.h
@@ -70,6 +70,7 @@ CORESTRING_LWC_STRING(filename);
 CORESTRING_LWC_STRING(font);
 CORESTRING_LWC_STRING(frame);
 CORESTRING_LWC_STRING(frameset);
+CORESTRING_LWC_STRING(ftp);
 CORESTRING_LWC_STRING(h1);
 CORESTRING_LWC_STRING(h2);
 CORESTRING_LWC_STRING(h3);
diff --git a/utils/nsurl.h b/utils/nsurl.h
index f97562b..054baf2 100644
--- a/utils/nsurl.h
+++ b/utils/nsurl.h
@@ -301,6 +301,25 @@ nserror nsurl_replace_query(const nsurl *url, const char 
*query,
 
 
 /**
+ * Create a NetSurf URL object, with scheme replaced
+ *
+ * \param url    NetSurf URL to create new NetSurf URL from
+ * \param scheme  Scheme to use
+ * \param new_url Returns new NetSurf URL with scheme provided
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * If return value != NSERROR_OK, nothing will be returned in new_url.
+ *
+ * It is up to the client to call nsurl_unref when they are finished with
+ * the created object.
+ *
+ * Any scheme component in url is replaced with scheme in new_url.
+ */
+nserror nsurl_replace_scheme(const nsurl *url, lwc_string *scheme,
+               nsurl **new_url);
+
+
+/**
  * Attempt to find a nice filename for a URL.
  *
  * \param url          A NetSurf URL object to create a filename from
diff --git a/utils/nsurl/nsurl.c b/utils/nsurl/nsurl.c
index 3b0af93..8c769cf 100644
--- a/utils/nsurl/nsurl.c
+++ b/utils/nsurl/nsurl.c
@@ -648,6 +648,93 @@ nserror nsurl_replace_query(const nsurl *url, const char 
*query,
 }
 
 
+/* exported interface, documented in nsurl.h */
+nserror nsurl_replace_scheme(const nsurl *url, lwc_string *scheme,
+               nsurl **new_url)
+{
+       int scheme_len;
+       int base_len;
+       char *pos;
+       size_t len;
+       bool match;
+
+       assert(url != NULL);
+       assert(scheme != NULL);
+
+       /* Get the length of the new scheme */
+       scheme_len = lwc_string_length(scheme);
+
+       /* Find the change in length from url to new_url */
+       base_len = url->length;
+       if (url->components.scheme != NULL) {
+               base_len -= lwc_string_length(url->components.scheme);
+       }
+
+       /* Set new_url's length */
+       len = base_len + scheme_len;
+
+       /* Create NetSurf URL object */
+       *new_url = malloc(sizeof(nsurl) + len + 1); /* Add 1 for \0 */
+       if (*new_url == NULL) {
+               return NSERROR_NOMEM;
+       }
+
+       (*new_url)->length = len;
+
+       /* Set string */
+       pos = (*new_url)->string;
+       memcpy(pos, lwc_string_data(scheme), scheme_len);
+       memcpy(pos + scheme_len,
+                       url->string + url->length - base_len, base_len);
+       pos[len] = '\0';
+
+       /* Copy components */
+       (*new_url)->components.scheme = lwc_string_ref(scheme);
+       (*new_url)->components.username =
+                       nsurl__component_copy(url->components.username);
+       (*new_url)->components.password =
+                       nsurl__component_copy(url->components.password);
+       (*new_url)->components.host =
+                       nsurl__component_copy(url->components.host);
+       (*new_url)->components.port =
+                       nsurl__component_copy(url->components.port);
+       (*new_url)->components.path =
+                       nsurl__component_copy(url->components.path);
+       (*new_url)->components.query =
+                       nsurl__component_copy(url->components.query);
+       (*new_url)->components.fragment =
+                       nsurl__component_copy(url->components.fragment);
+
+       /* Compute new scheme type */
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_http,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_HTTP;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_https,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_HTTPS;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_file,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_FILE;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_ftp,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_FTP;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_mailto,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_MAILTO;
+       } else {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_OTHER;
+       }
+
+       /* Get the nsurl's hash */
+       nsurl__calc_hash(*new_url);
+
+       /* Give the URL a reference */
+       (*new_url)->count = 1;
+
+       return NSERROR_OK;
+}
+
+
 /* exported interface documented in utils/nsurl.h */
 nserror nsurl_nice(const nsurl *url, char **result, bool remove_extensions)
 {


commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=a6014cae157107ec868816fcccf8689a5d6cc766
commit a6014cae157107ec868816fcccf8689a5d6cc766
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    HSTS: support policy in urldb

diff --git a/content/urldb.c b/content/urldb.c
index cacc475..242a29c 100644
--- a/content/urldb.c
+++ b/content/urldb.c
@@ -108,6 +108,7 @@
 #include "utils/time.h"
 #include "utils/nsurl.h"
 #include "utils/ascii.h"
+#include "utils/http.h"
 #include "netsurf/bitmap.h"
 #include "desktop/cookie_manager.h"
 #include "desktop/gui_internal.h"
@@ -222,6 +223,11 @@ struct path_data {
        struct path_data *last; /**< Last child */
 };
 
+struct hsts_data {
+       time_t expires; /**< Expiry time */
+       bool include_sub_domains; /**< Whether to include subdomains */
+};
+
 struct host_part {
        /**
         * Known paths on this host. This _must_ be first so that
@@ -233,6 +239,8 @@ struct host_part {
         * without verifying certificate authenticity
         */
        bool permit_invalid_certs;
+       /* HSTS data */
+       struct hsts_data hsts;
 
        /**
         * Part of host string
@@ -290,7 +298,7 @@ static int loaded_cookie_file_version;
 /** Minimum URL database file version */
 #define MIN_URL_FILE_VERSION 106
 /** Current URL database file version */
-#define URL_FILE_VERSION 106
+#define URL_FILE_VERSION 107
 
 /**
  * filter for url presence in database
@@ -511,7 +519,8 @@ static void urldb_save_search_tree(struct search_node 
*parent, FILE *fp)
        unsigned int path_count = 0;
        char *path, *p, *end;
        int path_alloc = 64, path_used = 1;
-       time_t expiry;
+       time_t expiry, hsts_expiry = 0;
+       int hsts_include_subdomains = 0;
 
        expiry = time(NULL) - ((60 * 60 * 24) * nsoption_int(expire_url));
 
@@ -537,13 +546,25 @@ static void urldb_save_search_tree(struct search_node 
*parent, FILE *fp)
                p += written;
        }
 
+       h = parent->data;
+       if (h && h->hsts.expires > expiry) {
+               hsts_expiry = h->hsts.expires;
+               hsts_include_subdomains = h->hsts.include_sub_domains;
+       }
+
        urldb_count_urls(&parent->data->paths, expiry, &path_count);
 
        if (path_count > 0) {
-               fprintf(fp, "%s\n%i\n", host, path_count);
+               fprintf(fp, "%s %i ", host, hsts_include_subdomains);
+               urldb_write_timet(fp, hsts_expiry);
+               fprintf(fp, "%i\n", path_count);
 
                urldb_write_paths(&parent->data->paths, host, fp,
                                  &path, &path_alloc, &path_used, expiry);
+       } else if (hsts_expiry) {
+               fprintf(fp, "%s %i ", host, hsts_include_subdomains);
+               urldb_write_timet(fp, hsts_expiry);
+               fprintf(fp, "0\n");
        }
 
        free(path);
@@ -2894,6 +2915,9 @@ nserror urldb_load(const char *filename)
        }
 
        while (fgets(host, sizeof host, fp)) {
+               time_t hsts_expiry = 0;
+               int hsts_include_sub_domains = 0;
+
                /* get the hostname */
                length = strlen(host) - 1;
                host[length] = '\0';
@@ -2911,6 +2935,25 @@ nserror urldb_load(const char *filename)
                        continue;
                }
 
+               if (version >= 107) {
+                       char *p = host;
+                       while (*p && *p != ' ') p++;
+                       while (*p && *p == ' ') { *p = '\0'; p++; }
+                       hsts_include_sub_domains = (*p == '1');
+                       while (*p && *p != ' ') p++;
+                       while (*p && *p == ' ') p++;
+                       nsc_snptimet(p, strlen(p), &hsts_expiry);
+               }
+
+               h = urldb_add_host(host);
+               if (!h) {
+                       NSLOG(netsurf, INFO, "Failed adding host: '%s'", host);
+                       fclose(fp);
+                       return NSERROR_NOMEM;
+               }
+               h->hsts.expires = hsts_expiry;
+               h->hsts.include_sub_domains = hsts_include_sub_domains;
+
                /* read number of URLs */
                if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
                        break;
@@ -2922,13 +2965,6 @@ nserror urldb_load(const char *filename)
                        continue;
                }
 
-               h = urldb_add_host(host);
-               if (!h) {
-                       NSLOG(netsurf, INFO, "Failed adding host: '%s'", host);
-                       fclose(fp);
-                       return NSERROR_NOMEM;
-               }
-
                /* load the non-corrupt data */
                for (i = 0; i < urls; i++) {
                        struct path_data *p = NULL;
@@ -3460,6 +3496,138 @@ bool urldb_get_cert_permissions(nsurl *url)
 }
 
 
+/* exported interface documented in content/urldb.h */
+bool urldb_set_hsts_policy(struct nsurl *url, const char *header)
+{
+       struct path_data *p;
+       struct host_part *h;
+       lwc_string *host;
+       time_t now = time(NULL);
+       http_strict_transport_security *sts;
+       uint32_t max_age = 0;
+       nserror error;
+
+       assert(url);
+
+       host = nsurl_get_component(url, NSURL_HOST);
+       if (host != NULL) {
+               if (urldb__host_is_ip_address(lwc_string_data(host))) {
+                       /* Host is IP: ignore */
+                       lwc_string_unref(host);
+                       return true;
+               } else if (lwc_string_length(host) == 0) {
+                       /* Host is blank: ignore */
+                       lwc_string_unref(host);
+                       return true;
+               }
+
+               lwc_string_unref(host);
+       } else {
+               /* No host part: ignore */
+               return true;
+       }
+
+       /* add url, in case it's missing */
+       urldb_add_url(url);
+
+       p = urldb_find_url(url);
+       if (!p)
+               return false;
+
+       for (; p && p->parent; p = p->parent)
+               /* do nothing */;
+       assert(p);
+
+       h = (struct host_part *)p;
+       if (h->permit_invalid_certs) {
+               /* Transport is tainted: ignore */
+               return true;
+       }
+
+       error = http_parse_strict_transport_security(header, &sts);
+       if (error != NSERROR_OK) {
+               /* Parse failed: ignore */
+               return true;
+       }
+
+       h->hsts.include_sub_domains =
+               http_strict_transport_security_include_subdomains(sts);
+
+       max_age = http_strict_transport_security_max_age(sts);
+       if (max_age == 0) {
+               h->hsts.expires = 0;
+               h->hsts.include_sub_domains = false;
+       } else if (now + max_age > h->hsts.expires) {
+               h->hsts.expires = now + max_age;
+       }
+
+       http_strict_transport_security_destroy(sts);
+
+       return true;
+}
+
+
+/* exported interface documented in content/urldb.h */
+bool urldb_get_hsts_enabled(struct nsurl *url)
+{
+       struct path_data *p;
+       const struct host_part *h;
+       lwc_string *host;
+       time_t now = time(NULL);
+
+       assert(url);
+
+       host = nsurl_get_component(url, NSURL_HOST);
+       if (host != NULL) {
+               if (urldb__host_is_ip_address(lwc_string_data(host))) {
+                       /* Host is IP: not enabled */
+                       lwc_string_unref(host);
+                       return false;
+               } else if (lwc_string_length(host) == 0) {
+                       /* Host is blank: not enabled */
+                       lwc_string_unref(host);
+                       return false;
+               }
+
+               lwc_string_unref(host);
+       } else {
+               /* No host part: not enabled */
+               return false;
+       }
+
+       /* The URL must exist in the db in order to find HSTS policy, since
+        * we search up the tree from the URL node, and policy from further
+        * up may also apply. */
+       urldb_add_url(url);
+
+       p = urldb_find_url(url);
+       if (!p)
+               return false;
+
+       for (; p && p->parent; p = p->parent)
+               /* do nothing */;
+       assert(p);
+
+       h = (const struct host_part *)p;
+
+       /* Consult record for this host */
+       if (h->hsts.expires > now) {
+               /* Not expired */
+               return true;
+       }
+
+       /* Consult parent domains */
+       for (h = h->parent; h && h != &db_root; h = h->parent) {
+               if (h->hsts.expires > now && h->hsts.include_sub_domains) {
+                       /* Not expired and subdomains included */
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
 /* exported interface documented in netsurf/url_db.h */
 void
 urldb_iterate_partial(const char *prefix,
diff --git a/content/urldb.h b/content/urldb.h
index 4aa5487..0ad6426 100644
--- a/content/urldb.h
+++ b/content/urldb.h
@@ -131,4 +131,22 @@ bool urldb_set_cookie(const char *header, struct nsurl 
*url, struct nsurl *refer
 char *urldb_get_cookie(struct nsurl *url, bool include_http_only);
 
 
+/**
+ * Set HSTS policy for an URL
+ *
+ * \param url URL being fetched
+ * \param header Strict-Transport-Security header value
+ * \return true on success, false otherwise
+ */
+bool urldb_set_hsts_policy(struct nsurl *url, const char *header);
+
+
+/**
+ * Determine if HSTS policy is enabled for an URL
+ *
+ * \param url URL being fetched
+ * \return true if HSTS policy is enabled, false otherwise
+ */
+bool urldb_get_hsts_enabled(struct nsurl *url);
+
 #endif


commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=1c05280b5cd23c94ed10c2a9415a500393100285
commit 1c05280b5cd23c94ed10c2a9415a500393100285
Author: John-Mark Bell <[email protected]>
Commit: John-Mark Bell <[email protected]>

    HSTS: add parser for Strict-Transport-Security

diff --git a/utils/corestringlist.h b/utils/corestringlist.h
index def5a73..8d15eeb 100644
--- a/utils/corestringlist.h
+++ b/utils/corestringlist.h
@@ -86,6 +86,7 @@ CORESTRING_LWC_STRING(icon);
 CORESTRING_LWC_STRING(iframe);
 CORESTRING_LWC_STRING(image);
 CORESTRING_LWC_STRING(img);
+CORESTRING_LWC_STRING(includesubdomains);
 CORESTRING_LWC_STRING(input);
 CORESTRING_LWC_STRING(javascript);
 CORESTRING_LWC_STRING(justify);
@@ -141,6 +142,7 @@ CORESTRING_LWC_STRING(_top);
 /* unusual lwc strings */
 CORESTRING_LWC_VALUE(shortcut_icon, "shortcut icon");
 CORESTRING_LWC_VALUE(slash_, "/");
+CORESTRING_LWC_VALUE(max_age, "max-age");
 
 /* mime types */
 CORESTRING_LWC_VALUE(multipart_form_data, "multipart/form-data");
diff --git a/utils/http.h b/utils/http.h
index 173604f..00caf89 100644
--- a/utils/http.h
+++ b/utils/http.h
@@ -29,6 +29,7 @@
 
 #include "utils/http/content-disposition.h"
 #include "utils/http/content-type.h"
+#include "utils/http/strict-transport-security.h"
 #include "utils/http/www-authenticate.h"
 
 #endif
diff --git a/utils/http/Makefile b/utils/http/Makefile
index 198588b..f3bb765 100644
--- a/utils/http/Makefile
+++ b/utils/http/Makefile
@@ -1,6 +1,7 @@
 # http utils sources
 
 S_HTTP := challenge.c generics.c primitives.c parameter.c              \
-       content-disposition.c content-type.c www-authenticate.c
+       content-disposition.c content-type.c \
+       strict-transport-security.c www-authenticate.c
 
-S_HTTP := $(addprefix utils/http/,$(S_HTTP))
\ No newline at end of file
+S_HTTP := $(addprefix utils/http/,$(S_HTTP))
diff --git a/utils/http/challenge.c b/utils/http/challenge.c
index 578532e..9b85fcc 100644
--- a/utils/http/challenge.c
+++ b/utils/http/challenge.c
@@ -92,7 +92,7 @@ nserror http__parse_challenge(const char **input, 
http_challenge **challenge)
        http__skip_LWS(&pos);
 
        if (*pos == ',') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, first, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(scheme);
diff --git a/utils/http/content-disposition.c b/utils/http/content-disposition.c
index 5d5e94c..03bd12b 100644
--- a/utils/http/content-disposition.c
+++ b/utils/http/content-disposition.c
@@ -45,7 +45,7 @@ nserror http_parse_content_disposition(const char 
*header_value,
        http__skip_LWS(&pos);
 
        if (*pos == ';') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, NULL, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(mtype);
diff --git a/utils/http/content-type.c b/utils/http/content-type.c
index f84da8c..d4279f5 100644
--- a/utils/http/content-type.c
+++ b/utils/http/content-type.c
@@ -68,7 +68,7 @@ nserror http_parse_content_type(const char *header_value,
        http__skip_LWS(&pos);
 
        if (*pos == ';') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, NULL, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(subtype);
diff --git a/utils/http/generics.h b/utils/http/generics.h
index 8c391c4..a5af734 100644
--- a/utils/http/generics.h
+++ b/utils/http/generics.h
@@ -19,6 +19,8 @@
 #ifndef NETSURF_UTILS_HTTP_GENERICS_H_
 #define NETSURF_UTILS_HTTP_GENERICS_H_
 
+#include <stdbool.h>
+
 #include "utils/errors.h"
 
 /**
diff --git a/utils/http/strict-transport-security.c 
b/utils/http/strict-transport-security.c
new file mode 100644
index 0000000..9de610c
--- /dev/null
+++ b/utils/http/strict-transport-security.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2018 John-Mark Bell <[email protected]>
+ *
+ * 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/>.
+ */
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include "utils/corestrings.h"
+#include "utils/http.h"
+
+#include "utils/http/generics.h"
+#include "utils/http/primitives.h"
+
+/**
+ * Representation of a Strict-Transport-Security
+ */
+struct http_strict_transport_security {
+       uint32_t max_age;               /**< Max age (delta seconds) */
+       bool include_sub_domains;       /**< Whether subdomains are included */
+};
+
+/**
+ * Representation of a directive
+ */
+typedef struct http_directive {
+       http__item base;
+
+       lwc_string *name;               /**< Parameter name */
+       lwc_string *value;              /**< Parameter value (optional) */
+} http_directive;
+
+
+static void http_destroy_directive(http_directive *self)
+{
+       lwc_string_unref(self->name);
+       if (self->value != NULL) {
+               lwc_string_unref(self->value);
+       }
+       free(self);
+}
+
+static nserror http__parse_directive(const char **input,
+               http_directive **result)
+{
+       const char *pos = *input;
+       lwc_string *name;
+       lwc_string *value = NULL;
+       http_directive *directive;
+       nserror error;
+
+       /* token [ "=" ( token | quoted-string ) ] */
+
+       error = http__parse_token(&pos, &name);
+       if (error != NSERROR_OK)
+               return error;
+
+       http__skip_LWS(&pos);
+
+       if (*pos == '=') {
+               pos++;
+
+               http__skip_LWS(&pos);
+
+               if (*pos == '"')
+                       error = http__parse_quoted_string(&pos, &value);
+               else
+                       error = http__parse_token(&pos, &value);
+
+               if (error != NSERROR_OK) {
+                       lwc_string_unref(name);
+                       return error;
+               }
+       }
+
+       directive = malloc(sizeof(*directive));
+       if (directive == NULL) {
+               if (value != NULL) {
+                       lwc_string_unref(value);
+               }
+               lwc_string_unref(name);
+               return NSERROR_NOMEM;
+       }
+
+       HTTP__ITEM_INIT(directive, NULL, http_destroy_directive);
+       directive->name = name;
+       directive->value = value;
+
+       *result = directive;
+       *input = pos;
+
+       return NSERROR_OK;
+}
+
+static void http_directive_list_destroy(http_directive *list)
+{
+       http__item_list_destroy(list);
+}
+
+static nserror http_directive_list_find_item(const http_directive *list,
+               lwc_string *name, lwc_string **value)
+{
+       bool match;
+
+       while (list != NULL) {
+               if (lwc_string_caseless_isequal(name, list->name,
+                               &match) == lwc_error_ok && match)
+                       break;
+
+               list = (http_directive *) list->base.next;
+       }
+
+       if (list == NULL)
+               return NSERROR_NOT_FOUND;
+
+       if (list->value != NULL) {
+               *value = lwc_string_ref(list->value);
+       } else {
+               *value = NULL;
+       }
+
+       return NSERROR_OK;
+}
+
+static const http_directive *http_directive_list_iterate(
+               const http_directive *cur,
+               lwc_string **name, lwc_string **value)
+{
+       if (cur == NULL)
+               return NULL;
+
+       *name = lwc_string_ref(cur->name);
+       if (cur->value != NULL) {
+               *value = lwc_string_ref(cur->value);
+       } else {
+               *value = NULL;
+       }
+
+       return (http_directive *) cur->base.next;
+}
+
+static uint32_t count(const http_directive *list, lwc_string *key)
+{
+       uint32_t count = 0;
+       bool match;
+
+       while (list != NULL) {
+               if (lwc_string_caseless_isequal(key, list->name,
+                               &match) == lwc_error_ok && match) {
+                       count++;
+               }
+
+               list = (http_directive *) list->base.next;
+       }
+
+       return count;
+}
+
+static bool check_duplicates(const http_directive *directives)
+{
+       bool result = true;
+       const http_directive *key = directives;
+
+       if (key == NULL) {
+               /* No directives, so there can't be any duplicates */
+               return true;
+       }
+
+       do {
+               lwc_string *name = NULL, *value = NULL;
+
+               key = http_directive_list_iterate(key, &name, &value);
+
+               result &= (count(directives, name) == 1);
+
+               lwc_string_unref(name);
+               if (value != NULL) {
+                       lwc_string_unref(value);
+               }
+       } while (key != NULL);
+
+       return result;
+}
+
+static nserror parse_max_age(lwc_string *value, uint32_t *result)
+{
+       const char *pos = lwc_string_data(value);
+       const char *end = pos + lwc_string_length(value);
+       uint32_t val = 0;
+
+       /* 1*DIGIT */
+
+       if (pos == end) {
+               /* Blank value */
+               return NSERROR_NOT_FOUND;
+       }
+
+       while (pos < end) {
+               if ('0' <= *pos && *pos <= '9') {
+                       uint32_t nv = val * 10 + (*pos - '0');
+                       if (nv < val) {
+                               val = UINT_MAX;
+                       } else {
+                               val = nv;
+                       }
+               } else {
+                       /* Non-digit */
+                       return NSERROR_NOT_FOUND;
+               }
+
+               pos++;
+       }
+
+       *result = val;
+
+       return NSERROR_OK;
+}
+
+/* See strict-transport-security.h for documentation */
+nserror http_parse_strict_transport_security(const char *header_value,
+               http_strict_transport_security **result)
+{
+       const char *pos = header_value;
+       http_strict_transport_security *sts;
+       http_directive *first = NULL;
+       http_directive *directives = NULL;
+       lwc_string *max_age_str = NULL, *isd_str = NULL;
+       uint32_t max_age;
+       bool include_sub_domains = false;
+       nserror error;
+
+       /* directive *( ";" directive ) */
+
+       http__skip_LWS(&pos);
+
+       error = http__parse_directive(&pos, &first);
+       if (error != NSERROR_OK) {
+               return error;
+       }
+
+       http__skip_LWS(&pos);
+
+       if (*pos == ';') {
+               error = http__item_list_parse(&pos,
+                               http__parse_directive, first, &directives);
+               if (error != NSERROR_OK) {
+                       if (directives != NULL) {
+                               http_directive_list_destroy(directives);
+                       }
+                       return error;
+               }
+       } else {
+               directives = first;
+       }
+
+       /* Each directive must only appear once */
+       if (check_duplicates(directives) == false) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+
+       /* max-age is required */
+       error = http_directive_list_find_item(directives,
+                       corestring_lwc_max_age, &max_age_str);
+       if (error != NSERROR_OK || max_age_str == NULL) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+
+       error = parse_max_age(max_age_str, &max_age);
+       if (error != NSERROR_OK) {
+               lwc_string_unref(max_age_str);
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+       lwc_string_unref(max_age_str);
+
+       /* includeSubDomains is optional and valueless */
+       error = http_directive_list_find_item(directives,
+                       corestring_lwc_includesubdomains, &isd_str);
+       if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       } else if (error == NSERROR_OK) {
+               if (isd_str != NULL) {
+                       /* Present, but not valueless: invalid */
+                       lwc_string_unref(isd_str);
+                       http_directive_list_destroy(directives);
+                       return NSERROR_NOT_FOUND;
+               }
+               include_sub_domains = true;
+       }
+       http_directive_list_destroy(directives);
+
+       sts = malloc(sizeof(*sts));
+       if (sts == NULL) {
+               return NSERROR_NOMEM;
+       }
+
+       sts->max_age = max_age;
+       sts->include_sub_domains = include_sub_domains;
+
+       *result = sts;
+
+       return NSERROR_OK;
+}
+
+/* See strict-transport-security.h for documentation */
+void http_strict_transport_security_destroy(
+               http_strict_transport_security *victim)
+{
+       free(victim);
+}
+
+/* See strict-transport-security.h for documentation */
+uint32_t http_strict_transport_security_max_age(
+               http_strict_transport_security *sts)
+{
+       return sts->max_age;
+}
+
+/* See strict-transport-security.h for documentation */
+bool http_strict_transport_security_include_subdomains(
+               http_strict_transport_security *sts)
+{
+       return sts->include_sub_domains;
+}
+
diff --git a/utils/http/strict-transport-security.h 
b/utils/http/strict-transport-security.h
new file mode 100644
index 0000000..4e52419
--- /dev/null
+++ b/utils/http/strict-transport-security.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 John-Mark Bell <[email protected]>
+ *
+ * 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/>.
+ */
+
+#ifndef NETSURF_UTILS_HTTP_STRICT_TRANSPORT_SECURITY_H_
+#define NETSURF_UTILS_HTTP_STRICT_TRANSPORT_SECURITY_H_
+
+#include <libwapcaplet/libwapcaplet.h>
+
+typedef struct http_strict_transport_security http_strict_transport_security;
+
+/**
+ * Parse an HTTP Strict-Transport-Security header value
+ *
+ * \param header_value  Header value to parse
+ * \param result        Pointer to location to receive result
+ * \return NSERROR_OK on success,
+ *         NSERROR_NOMEM on memory exhaustion,
+ *         appropriate error otherwise
+ */
+nserror http_parse_strict_transport_security(const char *header_value,
+               http_strict_transport_security **result);
+
+/**
+ * Destroy a strict transport security object
+ *
+ * \param victim  Object to destroy
+ */
+void http_strict_transport_security_destroy(
+               http_strict_transport_security *victim);
+
+/**
+ * Get the value of a strict transport security's max-age
+ *
+ * \param sts Object to inspect
+ * \return Max age, in delta-seconds
+ */
+uint32_t http_strict_transport_security_max_age(
+               http_strict_transport_security *sts);
+
+/**
+ * Get the value of a strict transport security's includeSubDomains flag
+ *
+ * \param sts Object to inspect
+ * \return Whether subdomains should be included
+ */
+bool http_strict_transport_security_include_subdomains(
+               http_strict_transport_security *sts);
+
+#endif


-----------------------------------------------------------------------

Summary of changes:
 content/llcache.c                      |  215 ++++++++++++++++----
 content/urldb.c                        |  188 +++++++++++++++++-
 content/urldb.h                        |   18 ++
 utils/corestringlist.h                 |    3 +
 utils/http.h                           |    1 +
 utils/http/Makefile                    |    5 +-
 utils/http/challenge.c                 |    2 +-
 utils/http/content-disposition.c       |    2 +-
 utils/http/content-type.c              |    2 +-
 utils/http/generics.h                  |    2 +
 utils/http/strict-transport-security.c |  341 ++++++++++++++++++++++++++++++++
 utils/http/strict-transport-security.h |   64 ++++++
 utils/nsurl.h                          |   19 ++
 utils/nsurl/nsurl.c                    |   87 ++++++++
 14 files changed, 892 insertions(+), 57 deletions(-)
 create mode 100644 utils/http/strict-transport-security.c
 create mode 100644 utils/http/strict-transport-security.h

diff --git a/content/llcache.c b/content/llcache.c
index 0c6f3a9..29b42a1 100644
--- a/content/llcache.c
+++ b/content/llcache.c
@@ -86,7 +86,7 @@ struct llcache_handle {
 typedef struct llcache_object_user {
        llcache_handle *handle;         /**< Handle data for client */
 
-       bool iterator_target;           /**< This is the an iterator target */
+       bool iterator_target;           /**< This is the iterator target */
        bool queued_for_delete;         /**< This user is queued for deletion */
 
        struct llcache_object_user *prev;       /**< Previous in list */
@@ -109,11 +109,15 @@ typedef struct {
 
        uint32_t retries_remaining;     /**< Number of times to retry on 
timeout */
 
+       bool hsts_in_use;               /**< Whether HSTS applies to this fetch 
*/
+
        bool tried_with_auth;           /**< Whether we've tried with auth */
 
        bool tried_with_tls_downgrade;  /**< Whether we've tried TLS <= 1.0 */
 
        bool outstanding_query;         /**< Waiting for a query response */
+
+       bool tainted_tls;               /**< Whether the TLS transport is 
tainted */
 } llcache_fetch_ctx;
 
 /**
@@ -126,7 +130,7 @@ typedef enum {
 } llcache_validate;
 
 /**
- * cache control value for invalid age.
+ * Cache control value for invalid age.
  */
 #define INVALID_AGE -1
 
@@ -150,7 +154,7 @@ typedef struct {
        char *value;            /**< Header value */
 } llcache_header;
 
-/** Current status of objects data */
+/** Current status of an object's data */
 typedef enum {
        LLCACHE_STATE_RAM = 0, /**< source data is stored in RAM only */
        LLCACHE_STATE_DISC, /**< source data is stored on disc */
@@ -191,7 +195,7 @@ struct llcache_object {
 
        /* Instrumentation. These elements are strictly for information
         * to improve the cache performance and to provide performance
-        * metrics. The values are non-authorative and must not be used to
+        * metrics. The values are non-authoritative and must not be used to
         * determine object lifetime etc.
         */
        time_t last_used; /**< time the last user was removed from the object */
@@ -791,7 +795,7 @@ static nserror llcache_fetch_process_header(llcache_object 
*object,
 /**
  * (Re)fetch an object
  *
- * sets up headers and attempts to start an actual fetch from the
+ * Sets up headers and attempts to start an actual fetch from the
  * fetchers system updating the llcache object with the new fetch on
  * successful start.
  *
@@ -902,11 +906,12 @@ static nserror llcache_object_refetch(llcache_object 
*object)
  * \param referer        Referring URL, or NULL for none
  * \param post           POST data, or NULL for GET
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \return NSERROR_OK on success, appropriate error otherwise
  */
 static nserror llcache_object_fetch(llcache_object *object, uint32_t flags,
                nsurl *referer, const llcache_post_data *post,
-               uint32_t redirect_count)
+               uint32_t redirect_count, bool hsts_in_use)
 {
        nserror error;
        nsurl *referer_clone = NULL;
@@ -928,6 +933,7 @@ static nserror llcache_object_fetch(llcache_object *object, 
uint32_t flags,
        object->fetch.post = post_clone;
        object->fetch.redirect_count = redirect_count;
        object->fetch.retries_remaining = llcache->fetch_attempts;
+       object->fetch.hsts_in_use = hsts_in_use;
 
        return llcache_object_refetch(object);
 }
@@ -1165,14 +1171,14 @@ llcache_object_remove_from_list(llcache_object *object, 
llcache_object **list)
 /**
  * Retrieve source data for an object from persistent store if necessary.
  *
- * If an objects source data has been placed in the persistent store
- * and the in memory copy released this will attempt to retrieve the
- * source data.
+ * If an object's source data has been placed in the persistent store
+ * and there is no in-memory copy, then attempt to retrieve the source
+ * data.
  *
  * \param object the object to operate on.
  * \return appropriate error code.
  */
-static nserror llcache_persist_retrieve(llcache_object *object)
+static nserror llcache_retrieve_persisted_data(llcache_object *object)
 {
        /* ensure the source data is present if necessary */
        if ((object->source_data != NULL) ||
@@ -1191,7 +1197,7 @@ static nserror llcache_persist_retrieve(llcache_object 
*object)
 }
 
 /**
- * Generate a serialised version of an objects metadata
+ * Generate a serialised version of an object's metadata
  *
  * The metadata includes object headers.
  *
@@ -1335,7 +1341,7 @@ operror:
 }
 
 /**
- * Deserialisation of an objects metadata.
+ * Deserialisation of an object's metadata.
  *
  * Attempt to retrieve and deserialise the metadata for an object from
  * the backing store.
@@ -1361,7 +1367,7 @@ llcache_process_metadata(llcache_object *object)
 
        size_t source_length;
        time_t request_time;
-       time_t reponse_time;
+       time_t response_time;
        time_t completion_time;
        size_t num_headers;
        size_t hloop;
@@ -1412,7 +1418,7 @@ llcache_process_metadata(llcache_object *object)
        nsurl_unref(metadataurl);
 
 
-       /* metadata line 2 is the objects length */
+       /* metadata line 2 is the object's length */
        line = 2;
        ln += lnsize + 1;
        lnsize = strlen(ln);
@@ -1438,7 +1444,7 @@ llcache_process_metadata(llcache_object *object)
        ln += lnsize + 1;
        lnsize = strlen(ln);
 
-       res = nsc_snptimet(ln, lnsize, &reponse_time);
+       res = nsc_snptimet(ln, lnsize, &response_time);
        if (res != NSERROR_OK)
                goto format_error;
 
@@ -1485,7 +1491,7 @@ llcache_process_metadata(llcache_object *object)
        object->source_alloc = metadatalen;
 
        object->cache.req_time = request_time;
-       object->cache.res_time = reponse_time;
+       object->cache.res_time = response_time;
        object->cache.fin_time = completion_time;
 
        /* object stored in backing store */
@@ -1514,7 +1520,7 @@ format_error:
  *         cache else appropriate error code.
  */
 static nserror
-llcache_object_fetch_persistant(llcache_object *object,
+llcache_object_fetch_persistent(llcache_object *object,
                                uint32_t flags,
                                nsurl *referer,
                                const llcache_post_data *post,
@@ -1564,6 +1570,7 @@ llcache_object_fetch_persistant(llcache_object *object,
  * \param referer        Referring URL, or NULL if none
  * \param post           POST data, or NULL for a GET request
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \param result         Pointer to location to receive retrieved object
  * \return NSERROR_OK on success, appropriate error otherwise
  */
@@ -1573,6 +1580,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
                                   nsurl *referer,
                                   const llcache_post_data *post,
                                   uint32_t redirect_count,
+                                  bool hsts_in_use,
                                   llcache_object **result)
 {
        nserror error;
@@ -1604,7 +1612,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
                        return error;
 
                /* attempt to retrieve object from persistent store */
-               error = llcache_object_fetch_persistant(obj, flags, referer, 
post, redirect_count);
+               error = llcache_object_fetch_persistent(obj, flags, referer, 
post, redirect_count);
                if (error == NSERROR_OK) {
                        NSLOG(llcache, DEBUG, "retrieved object from persistent 
store");
 
@@ -1631,7 +1639,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
                 */
 
                /* ensure the source data is present */
-               error = llcache_persist_retrieve(newest);
+               error = llcache_retrieve_persisted_data(newest);
                if (error == NSERROR_OK) {
                        /* source data was successfully retrieved from
                         * persistent store
@@ -1658,7 +1666,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
                /* Found a candidate object but it needs freshness validation */
 
                /* ensure the source data is present */
-               error = llcache_persist_retrieve(newest);
+               error = llcache_retrieve_persisted_data(newest);
                if (error == NSERROR_OK) {
 
                        /* Create a new object */
@@ -1681,7 +1689,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
 
                        /* Attempt to kick-off fetch */
                        error = llcache_object_fetch(obj, flags, referer, post,
-                                                    redirect_count);
+                                                    redirect_count, 
hsts_in_use);
                        if (error != NSERROR_OK) {
                                newest->candidate_count--;
                                llcache_object_destroy(obj);
@@ -1713,7 +1721,8 @@ llcache_object_retrieve_from_cache(nsurl *url,
        }
 
        /* Attempt to kick-off fetch */
-       error = llcache_object_fetch(obj, flags, referer, post, redirect_count);
+       error = llcache_object_fetch(obj, flags, referer, post,
+                       redirect_count, hsts_in_use);
        if (error != NSERROR_OK) {
                llcache_object_destroy(obj);
                return error;
@@ -1735,6 +1744,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
  * \param referer        Referring URL, or NULL if none
  * \param post           POST data, or NULL for a GET request
  * \param redirect_count  Number of redirects followed so far
+ * \param hsts_in_use     Whether HSTS applies to this fetch
  * \param result         Pointer to location to receive retrieved object
  * \return NSERROR_OK on success, appropriate error otherwise
  */
@@ -1744,6 +1754,7 @@ llcache_object_retrieve(nsurl *url,
                        nsurl *referer,
                        const llcache_post_data *post,
                        uint32_t redirect_count,
+                       bool hsts_in_use,
                        llcache_object **result)
 {
        nserror error;
@@ -1798,7 +1809,7 @@ llcache_object_retrieve(nsurl *url,
 
                /* Attempt to kick-off fetch */
                error = llcache_object_fetch(obj, flags, referer, post,
-                               redirect_count);
+                               redirect_count, hsts_in_use);
                if (error != NSERROR_OK) {
                        llcache_object_destroy(obj);
                        nsurl_unref(defragmented_url);
@@ -1809,7 +1820,8 @@ llcache_object_retrieve(nsurl *url,
                llcache_object_add_to_list(obj, &llcache->uncached_objects);
        } else {
                error = llcache_object_retrieve_from_cache(defragmented_url,
-                               flags, referer, post, redirect_count, &obj);
+                               flags, referer, post, redirect_count,
+                               hsts_in_use, &obj);
                if (error != NSERROR_OK) {
                        nsurl_unref(defragmented_url);
                        return error;
@@ -1857,6 +1869,86 @@ static nserror llcache_object_add_user(llcache_object 
*object,
 }
 
 /**
+ * Transform a request-URI based on HSTS policy
+ *
+ * \param url URL to transform
+ * \param result Pointer to location to receive transformed URL
+ * \param hsts_in_use Pointer to location to receive HSTS in-use flag
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+static nserror llcache_hsts_transform_url(nsurl *url, nsurl **result,
+               bool *hsts_in_use)
+{
+       lwc_string *scheme = NULL;
+       bool match;
+       nserror error = NSERROR_OK;
+
+       scheme = nsurl_get_component(url, NSURL_SCHEME);
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_http,
+                       &match) != lwc_error_ok || match == false) {
+               /* Non-HTTP fetch: ignore */
+               lwc_string_unref(scheme);
+               *result = nsurl_ref(url);
+               *hsts_in_use = false;
+               return error;
+       }
+       lwc_string_unref(scheme);
+
+       if (urldb_get_hsts_enabled(url)) {
+               /* Only need to force HTTPS. If original port was explicitly
+                * specified as 80, nsurl_create/join will remove it (as
+                * it's redundant) */
+               error = nsurl_replace_scheme(url, corestring_lwc_https,
+                               result);
+               *hsts_in_use = (error == NSERROR_OK);
+       } else {
+               *result = nsurl_ref(url);
+               *hsts_in_use = false;
+       }
+
+       return error;
+}
+
+/**
+ * Update HSTS policy for target domain.
+ *
+ * \param object Newly-fetched cache object
+ * \return NSERROR_OK on success, appropriate error otherwise
+ */
+static nserror llcache_hsts_update_policy(llcache_object *object)
+{
+       size_t i;
+       lwc_string *scheme = NULL;
+       bool match = false;
+
+       scheme = nsurl_get_component(object->url, NSURL_SCHEME);
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_https,
+                       &match) != lwc_error_ok || match == false) {
+               /* Non-HTTPS fetch: ignore */
+               lwc_string_unref(scheme);
+               return NSERROR_OK;
+       }
+       lwc_string_unref(scheme);
+
+       if (object->fetch.tainted_tls) {
+               /* Transport is tainted: ignore */
+               return NSERROR_OK;
+       }
+
+       for (i = 0; i < object->num_headers; i++) {
+               if (strcasecmp("Strict-Transport-Security",
+                               object->headers[i].name) == 0) {
+                       urldb_set_hsts_policy(object->url,
+                                       object->headers[i].value);
+                       /* Only process the first one we find */
+                       break;
+               }
+       }
+
+       return NSERROR_OK;
+}
+
+/**
  * Handle FETCH_REDIRECT event
  *
  * \param object       Object being redirected
@@ -1871,10 +1963,10 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        llcache_object *dest;
        llcache_object_user *user, *next;
        const llcache_post_data *post = object->fetch.post;
-       nsurl *url;
+       nsurl *url, *hsts_url;
        lwc_string *scheme;
        lwc_string *object_scheme;
-       bool match;
+       bool match, hsts_in_use;
        /* Extract HTTP response code from the fetch object */
        long http_code = fetch_http_code(object->fetch.fetch);
        llcache_event event;
@@ -1889,6 +1981,8 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        /* And mark it complete */
        object->fetch.state = LLCACHE_FETCH_COMPLETE;
 
+       (void) llcache_hsts_update_policy(object);
+
        /* Forcibly stop redirecting if we've followed too many redirects */
 #define REDIRECT_LIMIT 10
        if (object->fetch.redirect_count > REDIRECT_LIMIT) {
@@ -1906,15 +2000,23 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        if (error != NSERROR_OK)
                return error;
 
+       /* Perform HSTS transform */
+       error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use);
+       if (error != NSERROR_OK) {
+               nsurl_unref(url);
+               return error;
+       }
+       nsurl_unref(url);
+
        /* Inform users of redirect */
        event.type = LLCACHE_EVENT_REDIRECT;
        event.data.redirect.from = object->url;
-       event.data.redirect.to = url;
+       event.data.redirect.to = hsts_url;
 
        error = llcache_send_event_to_users(object, &event);
 
        if (error != NSERROR_OK) {
-               nsurl_unref(url);
+               nsurl_unref(hsts_url);
                return error;
        }
 
@@ -1922,7 +2024,7 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
         * A "validated" scheme is one over which we have some guarantee that
         * the source is trustworthy. */
        object_scheme = nsurl_get_component(object->url, NSURL_SCHEME);
-       scheme = nsurl_get_component(url, NSURL_SCHEME);
+       scheme = nsurl_get_component(hsts_url, NSURL_SCHEME);
 
        /* resource: and about: are allowed to redirect anywhere */
        if ((lwc_string_isequal(object_scheme, corestring_lwc_resource,
@@ -1938,7 +2040,7 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
                                &match) == lwc_error_ok && match == true)) {
                        lwc_string_unref(object_scheme);
                        lwc_string_unref(scheme);
-                       nsurl_unref(url);
+                       nsurl_unref(hsts_url);
                        return NSERROR_OK;
                }
        }
@@ -1947,8 +2049,8 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
        lwc_string_unref(object_scheme);
 
        /* Bail out if we've no way of handling this URL */
-       if (fetch_can_fetch(url) == false) {
-               nsurl_unref(url);
+       if (fetch_can_fetch(hsts_url) == false) {
+               nsurl_unref(hsts_url);
                return NSERROR_OK;
        }
 
@@ -1957,17 +2059,18 @@ static nserror llcache_fetch_redirect(llcache_object 
*object,
                post = NULL;
        } else if (http_code != 307 || post != NULL) {
                /** \todo 300, 305, 307 with POST */
-               nsurl_unref(url);
+               nsurl_unref(hsts_url);
                return NSERROR_OK;
        }
 
        /* Attempt to fetch target URL */
-       error = llcache_object_retrieve(url, object->fetch.flags,
+       error = llcache_object_retrieve(hsts_url, object->fetch.flags,
                        object->fetch.referer, post,
-                       object->fetch.redirect_count + 1, &dest);
+                       object->fetch.redirect_count + 1,
+                       hsts_in_use, &dest);
 
        /* No longer require url */
-       nsurl_unref(url);
+       nsurl_unref(hsts_url);
 
        if (error != NSERROR_OK)
                return error;
@@ -2059,6 +2162,8 @@ static nserror llcache_fetch_notmodified(llcache_object 
*object,
        /* Mark it complete */
        object->fetch.state = LLCACHE_FETCH_COMPLETE;
 
+       (void) llcache_hsts_update_policy(object);
+
        /* Old object will be flushed from the cache on the next poll */
 
        return NSERROR_OK;
@@ -2253,7 +2358,11 @@ static nserror llcache_fetch_cert_error(llcache_object 
*object,
        /* Invalidate cache-control data */
        llcache_invalidate_cache_control_data(object);
 
-       if (llcache->query_cb != NULL) {
+       /* Consider the TLS transport tainted */
+       object->fetch.tainted_tls = true;
+
+       /* Only give the user a chance if HSTS isn't in use for this fetch */
+       if (object->fetch.hsts_in_use == false && llcache->query_cb != NULL) {
                llcache_query query;
 
                /* Emit query for TLS */
@@ -2303,7 +2412,13 @@ static nserror llcache_fetch_ssl_error(llcache_object 
*object)
        /* Invalidate cache-control data */
        llcache_invalidate_cache_control_data(object);
 
-       if (object->fetch.tried_with_tls_downgrade == true) {
+       /* Consider the TLS transport tainted */
+       object->fetch.tainted_tls = true;
+
+       /* Make no attempt to downgrade if HSTS is in use
+        * (i.e. assume server does TLS properly) */
+       if (object->fetch.hsts_in_use ||
+                       object->fetch.tried_with_tls_downgrade) {
                /* Have already tried to downgrade, so give up */
                llcache_event event;
 
@@ -2684,6 +2799,8 @@ static void llcache_fetch_callback(const fetch_msg *msg, 
void *p)
                /* record when the fetch finished */
                object->cache.fin_time = time(NULL);
 
+               (void) llcache_hsts_update_policy(object);
+
                guit->misc->schedule(5000, llcache_persist, NULL);
        }
                break;
@@ -3444,8 +3561,8 @@ static void llcache_catch_up_all_users(void *ignored)
        llcache_object *object;
 
        /* Assume after this we'll be all caught up.  If any user of a handle
-        * defers then we'll end up set not caught up and we'll
-        * reschedule at that point via llcache_users_not_caught_up()
+        * defers then we'll invalidate all_caught_up and reschedule via
+        * llcache_users_not_caught_up()
         */
        llcache->all_caught_up = true;
 
@@ -3483,23 +3600,35 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t 
flags,
        nserror error;
        llcache_object_user *user;
        llcache_object *object;
+       nsurl *hsts_url;
+       bool hsts_in_use;
+
+       /* Perform HSTS transform */
+       error = llcache_hsts_transform_url(url, &hsts_url, &hsts_in_use);
+       if (error != NSERROR_OK) {
+               return error;
+       }
 
        /* Can we fetch this URL at all? */
-       if (fetch_can_fetch(url) == false) {
+       if (fetch_can_fetch(hsts_url) == false) {
+               nsurl_unref(hsts_url);
                return NSERROR_NO_FETCH_HANDLER;
        }
 
        /* Create a new object user */
        error = llcache_object_user_new(cb, pw, &user);
        if (error != NSERROR_OK) {
+               nsurl_unref(hsts_url);
                return error;
        }
 
        /* Retrieve a suitable object from the cache,
         * creating a new one if needed. */
-       error = llcache_object_retrieve(url, flags, referer, post, 0, &object);
+       error = llcache_object_retrieve(hsts_url, flags, referer, post, 0,
+                       hsts_in_use, &object);
        if (error != NSERROR_OK) {
                llcache_object_user_destroy(user);
+               nsurl_unref(hsts_url);
                return error;
        }
 
@@ -3511,6 +3640,8 @@ nserror llcache_handle_retrieve(nsurl *url, uint32_t 
flags,
        /* Users exist which are now not caught up! */
        llcache_users_not_caught_up();
 
+       nsurl_unref(hsts_url);
+
        return NSERROR_OK;
 }
 
diff --git a/content/urldb.c b/content/urldb.c
index cacc475..242a29c 100644
--- a/content/urldb.c
+++ b/content/urldb.c
@@ -108,6 +108,7 @@
 #include "utils/time.h"
 #include "utils/nsurl.h"
 #include "utils/ascii.h"
+#include "utils/http.h"
 #include "netsurf/bitmap.h"
 #include "desktop/cookie_manager.h"
 #include "desktop/gui_internal.h"
@@ -222,6 +223,11 @@ struct path_data {
        struct path_data *last; /**< Last child */
 };
 
+struct hsts_data {
+       time_t expires; /**< Expiry time */
+       bool include_sub_domains; /**< Whether to include subdomains */
+};
+
 struct host_part {
        /**
         * Known paths on this host. This _must_ be first so that
@@ -233,6 +239,8 @@ struct host_part {
         * without verifying certificate authenticity
         */
        bool permit_invalid_certs;
+       /* HSTS data */
+       struct hsts_data hsts;
 
        /**
         * Part of host string
@@ -290,7 +298,7 @@ static int loaded_cookie_file_version;
 /** Minimum URL database file version */
 #define MIN_URL_FILE_VERSION 106
 /** Current URL database file version */
-#define URL_FILE_VERSION 106
+#define URL_FILE_VERSION 107
 
 /**
  * filter for url presence in database
@@ -511,7 +519,8 @@ static void urldb_save_search_tree(struct search_node 
*parent, FILE *fp)
        unsigned int path_count = 0;
        char *path, *p, *end;
        int path_alloc = 64, path_used = 1;
-       time_t expiry;
+       time_t expiry, hsts_expiry = 0;
+       int hsts_include_subdomains = 0;
 
        expiry = time(NULL) - ((60 * 60 * 24) * nsoption_int(expire_url));
 
@@ -537,13 +546,25 @@ static void urldb_save_search_tree(struct search_node 
*parent, FILE *fp)
                p += written;
        }
 
+       h = parent->data;
+       if (h && h->hsts.expires > expiry) {
+               hsts_expiry = h->hsts.expires;
+               hsts_include_subdomains = h->hsts.include_sub_domains;
+       }
+
        urldb_count_urls(&parent->data->paths, expiry, &path_count);
 
        if (path_count > 0) {
-               fprintf(fp, "%s\n%i\n", host, path_count);
+               fprintf(fp, "%s %i ", host, hsts_include_subdomains);
+               urldb_write_timet(fp, hsts_expiry);
+               fprintf(fp, "%i\n", path_count);
 
                urldb_write_paths(&parent->data->paths, host, fp,
                                  &path, &path_alloc, &path_used, expiry);
+       } else if (hsts_expiry) {
+               fprintf(fp, "%s %i ", host, hsts_include_subdomains);
+               urldb_write_timet(fp, hsts_expiry);
+               fprintf(fp, "0\n");
        }
 
        free(path);
@@ -2894,6 +2915,9 @@ nserror urldb_load(const char *filename)
        }
 
        while (fgets(host, sizeof host, fp)) {
+               time_t hsts_expiry = 0;
+               int hsts_include_sub_domains = 0;
+
                /* get the hostname */
                length = strlen(host) - 1;
                host[length] = '\0';
@@ -2911,6 +2935,25 @@ nserror urldb_load(const char *filename)
                        continue;
                }
 
+               if (version >= 107) {
+                       char *p = host;
+                       while (*p && *p != ' ') p++;
+                       while (*p && *p == ' ') { *p = '\0'; p++; }
+                       hsts_include_sub_domains = (*p == '1');
+                       while (*p && *p != ' ') p++;
+                       while (*p && *p == ' ') p++;
+                       nsc_snptimet(p, strlen(p), &hsts_expiry);
+               }
+
+               h = urldb_add_host(host);
+               if (!h) {
+                       NSLOG(netsurf, INFO, "Failed adding host: '%s'", host);
+                       fclose(fp);
+                       return NSERROR_NOMEM;
+               }
+               h->hsts.expires = hsts_expiry;
+               h->hsts.include_sub_domains = hsts_include_sub_domains;
+
                /* read number of URLs */
                if (!fgets(s, MAXIMUM_URL_LENGTH, fp))
                        break;
@@ -2922,13 +2965,6 @@ nserror urldb_load(const char *filename)
                        continue;
                }
 
-               h = urldb_add_host(host);
-               if (!h) {
-                       NSLOG(netsurf, INFO, "Failed adding host: '%s'", host);
-                       fclose(fp);
-                       return NSERROR_NOMEM;
-               }
-
                /* load the non-corrupt data */
                for (i = 0; i < urls; i++) {
                        struct path_data *p = NULL;
@@ -3460,6 +3496,138 @@ bool urldb_get_cert_permissions(nsurl *url)
 }
 
 
+/* exported interface documented in content/urldb.h */
+bool urldb_set_hsts_policy(struct nsurl *url, const char *header)
+{
+       struct path_data *p;
+       struct host_part *h;
+       lwc_string *host;
+       time_t now = time(NULL);
+       http_strict_transport_security *sts;
+       uint32_t max_age = 0;
+       nserror error;
+
+       assert(url);
+
+       host = nsurl_get_component(url, NSURL_HOST);
+       if (host != NULL) {
+               if (urldb__host_is_ip_address(lwc_string_data(host))) {
+                       /* Host is IP: ignore */
+                       lwc_string_unref(host);
+                       return true;
+               } else if (lwc_string_length(host) == 0) {
+                       /* Host is blank: ignore */
+                       lwc_string_unref(host);
+                       return true;
+               }
+
+               lwc_string_unref(host);
+       } else {
+               /* No host part: ignore */
+               return true;
+       }
+
+       /* add url, in case it's missing */
+       urldb_add_url(url);
+
+       p = urldb_find_url(url);
+       if (!p)
+               return false;
+
+       for (; p && p->parent; p = p->parent)
+               /* do nothing */;
+       assert(p);
+
+       h = (struct host_part *)p;
+       if (h->permit_invalid_certs) {
+               /* Transport is tainted: ignore */
+               return true;
+       }
+
+       error = http_parse_strict_transport_security(header, &sts);
+       if (error != NSERROR_OK) {
+               /* Parse failed: ignore */
+               return true;
+       }
+
+       h->hsts.include_sub_domains =
+               http_strict_transport_security_include_subdomains(sts);
+
+       max_age = http_strict_transport_security_max_age(sts);
+       if (max_age == 0) {
+               h->hsts.expires = 0;
+               h->hsts.include_sub_domains = false;
+       } else if (now + max_age > h->hsts.expires) {
+               h->hsts.expires = now + max_age;
+       }
+
+       http_strict_transport_security_destroy(sts);
+
+       return true;
+}
+
+
+/* exported interface documented in content/urldb.h */
+bool urldb_get_hsts_enabled(struct nsurl *url)
+{
+       struct path_data *p;
+       const struct host_part *h;
+       lwc_string *host;
+       time_t now = time(NULL);
+
+       assert(url);
+
+       host = nsurl_get_component(url, NSURL_HOST);
+       if (host != NULL) {
+               if (urldb__host_is_ip_address(lwc_string_data(host))) {
+                       /* Host is IP: not enabled */
+                       lwc_string_unref(host);
+                       return false;
+               } else if (lwc_string_length(host) == 0) {
+                       /* Host is blank: not enabled */
+                       lwc_string_unref(host);
+                       return false;
+               }
+
+               lwc_string_unref(host);
+       } else {
+               /* No host part: not enabled */
+               return false;
+       }
+
+       /* The URL must exist in the db in order to find HSTS policy, since
+        * we search up the tree from the URL node, and policy from further
+        * up may also apply. */
+       urldb_add_url(url);
+
+       p = urldb_find_url(url);
+       if (!p)
+               return false;
+
+       for (; p && p->parent; p = p->parent)
+               /* do nothing */;
+       assert(p);
+
+       h = (const struct host_part *)p;
+
+       /* Consult record for this host */
+       if (h->hsts.expires > now) {
+               /* Not expired */
+               return true;
+       }
+
+       /* Consult parent domains */
+       for (h = h->parent; h && h != &db_root; h = h->parent) {
+               if (h->hsts.expires > now && h->hsts.include_sub_domains) {
+                       /* Not expired and subdomains included */
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
 /* exported interface documented in netsurf/url_db.h */
 void
 urldb_iterate_partial(const char *prefix,
diff --git a/content/urldb.h b/content/urldb.h
index 4aa5487..0ad6426 100644
--- a/content/urldb.h
+++ b/content/urldb.h
@@ -131,4 +131,22 @@ bool urldb_set_cookie(const char *header, struct nsurl 
*url, struct nsurl *refer
 char *urldb_get_cookie(struct nsurl *url, bool include_http_only);
 
 
+/**
+ * Set HSTS policy for an URL
+ *
+ * \param url URL being fetched
+ * \param header Strict-Transport-Security header value
+ * \return true on success, false otherwise
+ */
+bool urldb_set_hsts_policy(struct nsurl *url, const char *header);
+
+
+/**
+ * Determine if HSTS policy is enabled for an URL
+ *
+ * \param url URL being fetched
+ * \return true if HSTS policy is enabled, false otherwise
+ */
+bool urldb_get_hsts_enabled(struct nsurl *url);
+
 #endif
diff --git a/utils/corestringlist.h b/utils/corestringlist.h
index def5a73..90dd796 100644
--- a/utils/corestringlist.h
+++ b/utils/corestringlist.h
@@ -70,6 +70,7 @@ CORESTRING_LWC_STRING(filename);
 CORESTRING_LWC_STRING(font);
 CORESTRING_LWC_STRING(frame);
 CORESTRING_LWC_STRING(frameset);
+CORESTRING_LWC_STRING(ftp);
 CORESTRING_LWC_STRING(h1);
 CORESTRING_LWC_STRING(h2);
 CORESTRING_LWC_STRING(h3);
@@ -86,6 +87,7 @@ CORESTRING_LWC_STRING(icon);
 CORESTRING_LWC_STRING(iframe);
 CORESTRING_LWC_STRING(image);
 CORESTRING_LWC_STRING(img);
+CORESTRING_LWC_STRING(includesubdomains);
 CORESTRING_LWC_STRING(input);
 CORESTRING_LWC_STRING(javascript);
 CORESTRING_LWC_STRING(justify);
@@ -141,6 +143,7 @@ CORESTRING_LWC_STRING(_top);
 /* unusual lwc strings */
 CORESTRING_LWC_VALUE(shortcut_icon, "shortcut icon");
 CORESTRING_LWC_VALUE(slash_, "/");
+CORESTRING_LWC_VALUE(max_age, "max-age");
 
 /* mime types */
 CORESTRING_LWC_VALUE(multipart_form_data, "multipart/form-data");
diff --git a/utils/http.h b/utils/http.h
index 173604f..00caf89 100644
--- a/utils/http.h
+++ b/utils/http.h
@@ -29,6 +29,7 @@
 
 #include "utils/http/content-disposition.h"
 #include "utils/http/content-type.h"
+#include "utils/http/strict-transport-security.h"
 #include "utils/http/www-authenticate.h"
 
 #endif
diff --git a/utils/http/Makefile b/utils/http/Makefile
index 198588b..f3bb765 100644
--- a/utils/http/Makefile
+++ b/utils/http/Makefile
@@ -1,6 +1,7 @@
 # http utils sources
 
 S_HTTP := challenge.c generics.c primitives.c parameter.c              \
-       content-disposition.c content-type.c www-authenticate.c
+       content-disposition.c content-type.c \
+       strict-transport-security.c www-authenticate.c
 
-S_HTTP := $(addprefix utils/http/,$(S_HTTP))
\ No newline at end of file
+S_HTTP := $(addprefix utils/http/,$(S_HTTP))
diff --git a/utils/http/challenge.c b/utils/http/challenge.c
index 578532e..9b85fcc 100644
--- a/utils/http/challenge.c
+++ b/utils/http/challenge.c
@@ -92,7 +92,7 @@ nserror http__parse_challenge(const char **input, 
http_challenge **challenge)
        http__skip_LWS(&pos);
 
        if (*pos == ',') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, first, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(scheme);
diff --git a/utils/http/content-disposition.c b/utils/http/content-disposition.c
index 5d5e94c..03bd12b 100644
--- a/utils/http/content-disposition.c
+++ b/utils/http/content-disposition.c
@@ -45,7 +45,7 @@ nserror http_parse_content_disposition(const char 
*header_value,
        http__skip_LWS(&pos);
 
        if (*pos == ';') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, NULL, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(mtype);
diff --git a/utils/http/content-type.c b/utils/http/content-type.c
index f84da8c..d4279f5 100644
--- a/utils/http/content-type.c
+++ b/utils/http/content-type.c
@@ -68,7 +68,7 @@ nserror http_parse_content_type(const char *header_value,
        http__skip_LWS(&pos);
 
        if (*pos == ';') {
-               error = http__item_list_parse(&pos, 
+               error = http__item_list_parse(&pos,
                                http__parse_parameter, NULL, &params);
                if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
                        lwc_string_unref(subtype);
diff --git a/utils/http/generics.h b/utils/http/generics.h
index 8c391c4..a5af734 100644
--- a/utils/http/generics.h
+++ b/utils/http/generics.h
@@ -19,6 +19,8 @@
 #ifndef NETSURF_UTILS_HTTP_GENERICS_H_
 #define NETSURF_UTILS_HTTP_GENERICS_H_
 
+#include <stdbool.h>
+
 #include "utils/errors.h"
 
 /**
diff --git a/utils/http/strict-transport-security.c 
b/utils/http/strict-transport-security.c
new file mode 100644
index 0000000..9de610c
--- /dev/null
+++ b/utils/http/strict-transport-security.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2018 John-Mark Bell <[email protected]>
+ *
+ * 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/>.
+ */
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include "utils/corestrings.h"
+#include "utils/http.h"
+
+#include "utils/http/generics.h"
+#include "utils/http/primitives.h"
+
+/**
+ * Representation of a Strict-Transport-Security
+ */
+struct http_strict_transport_security {
+       uint32_t max_age;               /**< Max age (delta seconds) */
+       bool include_sub_domains;       /**< Whether subdomains are included */
+};
+
+/**
+ * Representation of a directive
+ */
+typedef struct http_directive {
+       http__item base;
+
+       lwc_string *name;               /**< Parameter name */
+       lwc_string *value;              /**< Parameter value (optional) */
+} http_directive;
+
+
+static void http_destroy_directive(http_directive *self)
+{
+       lwc_string_unref(self->name);
+       if (self->value != NULL) {
+               lwc_string_unref(self->value);
+       }
+       free(self);
+}
+
+static nserror http__parse_directive(const char **input,
+               http_directive **result)
+{
+       const char *pos = *input;
+       lwc_string *name;
+       lwc_string *value = NULL;
+       http_directive *directive;
+       nserror error;
+
+       /* token [ "=" ( token | quoted-string ) ] */
+
+       error = http__parse_token(&pos, &name);
+       if (error != NSERROR_OK)
+               return error;
+
+       http__skip_LWS(&pos);
+
+       if (*pos == '=') {
+               pos++;
+
+               http__skip_LWS(&pos);
+
+               if (*pos == '"')
+                       error = http__parse_quoted_string(&pos, &value);
+               else
+                       error = http__parse_token(&pos, &value);
+
+               if (error != NSERROR_OK) {
+                       lwc_string_unref(name);
+                       return error;
+               }
+       }
+
+       directive = malloc(sizeof(*directive));
+       if (directive == NULL) {
+               if (value != NULL) {
+                       lwc_string_unref(value);
+               }
+               lwc_string_unref(name);
+               return NSERROR_NOMEM;
+       }
+
+       HTTP__ITEM_INIT(directive, NULL, http_destroy_directive);
+       directive->name = name;
+       directive->value = value;
+
+       *result = directive;
+       *input = pos;
+
+       return NSERROR_OK;
+}
+
+static void http_directive_list_destroy(http_directive *list)
+{
+       http__item_list_destroy(list);
+}
+
+static nserror http_directive_list_find_item(const http_directive *list,
+               lwc_string *name, lwc_string **value)
+{
+       bool match;
+
+       while (list != NULL) {
+               if (lwc_string_caseless_isequal(name, list->name,
+                               &match) == lwc_error_ok && match)
+                       break;
+
+               list = (http_directive *) list->base.next;
+       }
+
+       if (list == NULL)
+               return NSERROR_NOT_FOUND;
+
+       if (list->value != NULL) {
+               *value = lwc_string_ref(list->value);
+       } else {
+               *value = NULL;
+       }
+
+       return NSERROR_OK;
+}
+
+static const http_directive *http_directive_list_iterate(
+               const http_directive *cur,
+               lwc_string **name, lwc_string **value)
+{
+       if (cur == NULL)
+               return NULL;
+
+       *name = lwc_string_ref(cur->name);
+       if (cur->value != NULL) {
+               *value = lwc_string_ref(cur->value);
+       } else {
+               *value = NULL;
+       }
+
+       return (http_directive *) cur->base.next;
+}
+
+static uint32_t count(const http_directive *list, lwc_string *key)
+{
+       uint32_t count = 0;
+       bool match;
+
+       while (list != NULL) {
+               if (lwc_string_caseless_isequal(key, list->name,
+                               &match) == lwc_error_ok && match) {
+                       count++;
+               }
+
+               list = (http_directive *) list->base.next;
+       }
+
+       return count;
+}
+
+static bool check_duplicates(const http_directive *directives)
+{
+       bool result = true;
+       const http_directive *key = directives;
+
+       if (key == NULL) {
+               /* No directives, so there can't be any duplicates */
+               return true;
+       }
+
+       do {
+               lwc_string *name = NULL, *value = NULL;
+
+               key = http_directive_list_iterate(key, &name, &value);
+
+               result &= (count(directives, name) == 1);
+
+               lwc_string_unref(name);
+               if (value != NULL) {
+                       lwc_string_unref(value);
+               }
+       } while (key != NULL);
+
+       return result;
+}
+
+static nserror parse_max_age(lwc_string *value, uint32_t *result)
+{
+       const char *pos = lwc_string_data(value);
+       const char *end = pos + lwc_string_length(value);
+       uint32_t val = 0;
+
+       /* 1*DIGIT */
+
+       if (pos == end) {
+               /* Blank value */
+               return NSERROR_NOT_FOUND;
+       }
+
+       while (pos < end) {
+               if ('0' <= *pos && *pos <= '9') {
+                       uint32_t nv = val * 10 + (*pos - '0');
+                       if (nv < val) {
+                               val = UINT_MAX;
+                       } else {
+                               val = nv;
+                       }
+               } else {
+                       /* Non-digit */
+                       return NSERROR_NOT_FOUND;
+               }
+
+               pos++;
+       }
+
+       *result = val;
+
+       return NSERROR_OK;
+}
+
+/* See strict-transport-security.h for documentation */
+nserror http_parse_strict_transport_security(const char *header_value,
+               http_strict_transport_security **result)
+{
+       const char *pos = header_value;
+       http_strict_transport_security *sts;
+       http_directive *first = NULL;
+       http_directive *directives = NULL;
+       lwc_string *max_age_str = NULL, *isd_str = NULL;
+       uint32_t max_age;
+       bool include_sub_domains = false;
+       nserror error;
+
+       /* directive *( ";" directive ) */
+
+       http__skip_LWS(&pos);
+
+       error = http__parse_directive(&pos, &first);
+       if (error != NSERROR_OK) {
+               return error;
+       }
+
+       http__skip_LWS(&pos);
+
+       if (*pos == ';') {
+               error = http__item_list_parse(&pos,
+                               http__parse_directive, first, &directives);
+               if (error != NSERROR_OK) {
+                       if (directives != NULL) {
+                               http_directive_list_destroy(directives);
+                       }
+                       return error;
+               }
+       } else {
+               directives = first;
+       }
+
+       /* Each directive must only appear once */
+       if (check_duplicates(directives) == false) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+
+       /* max-age is required */
+       error = http_directive_list_find_item(directives,
+                       corestring_lwc_max_age, &max_age_str);
+       if (error != NSERROR_OK || max_age_str == NULL) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+
+       error = parse_max_age(max_age_str, &max_age);
+       if (error != NSERROR_OK) {
+               lwc_string_unref(max_age_str);
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       }
+       lwc_string_unref(max_age_str);
+
+       /* includeSubDomains is optional and valueless */
+       error = http_directive_list_find_item(directives,
+                       corestring_lwc_includesubdomains, &isd_str);
+       if (error != NSERROR_OK && error != NSERROR_NOT_FOUND) {
+               http_directive_list_destroy(directives);
+               return NSERROR_NOT_FOUND;
+       } else if (error == NSERROR_OK) {
+               if (isd_str != NULL) {
+                       /* Present, but not valueless: invalid */
+                       lwc_string_unref(isd_str);
+                       http_directive_list_destroy(directives);
+                       return NSERROR_NOT_FOUND;
+               }
+               include_sub_domains = true;
+       }
+       http_directive_list_destroy(directives);
+
+       sts = malloc(sizeof(*sts));
+       if (sts == NULL) {
+               return NSERROR_NOMEM;
+       }
+
+       sts->max_age = max_age;
+       sts->include_sub_domains = include_sub_domains;
+
+       *result = sts;
+
+       return NSERROR_OK;
+}
+
+/* See strict-transport-security.h for documentation */
+void http_strict_transport_security_destroy(
+               http_strict_transport_security *victim)
+{
+       free(victim);
+}
+
+/* See strict-transport-security.h for documentation */
+uint32_t http_strict_transport_security_max_age(
+               http_strict_transport_security *sts)
+{
+       return sts->max_age;
+}
+
+/* See strict-transport-security.h for documentation */
+bool http_strict_transport_security_include_subdomains(
+               http_strict_transport_security *sts)
+{
+       return sts->include_sub_domains;
+}
+
diff --git a/utils/http/strict-transport-security.h 
b/utils/http/strict-transport-security.h
new file mode 100644
index 0000000..4e52419
--- /dev/null
+++ b/utils/http/strict-transport-security.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 John-Mark Bell <[email protected]>
+ *
+ * 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/>.
+ */
+
+#ifndef NETSURF_UTILS_HTTP_STRICT_TRANSPORT_SECURITY_H_
+#define NETSURF_UTILS_HTTP_STRICT_TRANSPORT_SECURITY_H_
+
+#include <libwapcaplet/libwapcaplet.h>
+
+typedef struct http_strict_transport_security http_strict_transport_security;
+
+/**
+ * Parse an HTTP Strict-Transport-Security header value
+ *
+ * \param header_value  Header value to parse
+ * \param result        Pointer to location to receive result
+ * \return NSERROR_OK on success,
+ *         NSERROR_NOMEM on memory exhaustion,
+ *         appropriate error otherwise
+ */
+nserror http_parse_strict_transport_security(const char *header_value,
+               http_strict_transport_security **result);
+
+/**
+ * Destroy a strict transport security object
+ *
+ * \param victim  Object to destroy
+ */
+void http_strict_transport_security_destroy(
+               http_strict_transport_security *victim);
+
+/**
+ * Get the value of a strict transport security's max-age
+ *
+ * \param sts Object to inspect
+ * \return Max age, in delta-seconds
+ */
+uint32_t http_strict_transport_security_max_age(
+               http_strict_transport_security *sts);
+
+/**
+ * Get the value of a strict transport security's includeSubDomains flag
+ *
+ * \param sts Object to inspect
+ * \return Whether subdomains should be included
+ */
+bool http_strict_transport_security_include_subdomains(
+               http_strict_transport_security *sts);
+
+#endif
diff --git a/utils/nsurl.h b/utils/nsurl.h
index f97562b..054baf2 100644
--- a/utils/nsurl.h
+++ b/utils/nsurl.h
@@ -301,6 +301,25 @@ nserror nsurl_replace_query(const nsurl *url, const char 
*query,
 
 
 /**
+ * Create a NetSurf URL object, with scheme replaced
+ *
+ * \param url    NetSurf URL to create new NetSurf URL from
+ * \param scheme  Scheme to use
+ * \param new_url Returns new NetSurf URL with scheme provided
+ * \return NSERROR_OK on success, appropriate error otherwise
+ *
+ * If return value != NSERROR_OK, nothing will be returned in new_url.
+ *
+ * It is up to the client to call nsurl_unref when they are finished with
+ * the created object.
+ *
+ * Any scheme component in url is replaced with scheme in new_url.
+ */
+nserror nsurl_replace_scheme(const nsurl *url, lwc_string *scheme,
+               nsurl **new_url);
+
+
+/**
  * Attempt to find a nice filename for a URL.
  *
  * \param url          A NetSurf URL object to create a filename from
diff --git a/utils/nsurl/nsurl.c b/utils/nsurl/nsurl.c
index 3b0af93..8c769cf 100644
--- a/utils/nsurl/nsurl.c
+++ b/utils/nsurl/nsurl.c
@@ -648,6 +648,93 @@ nserror nsurl_replace_query(const nsurl *url, const char 
*query,
 }
 
 
+/* exported interface, documented in nsurl.h */
+nserror nsurl_replace_scheme(const nsurl *url, lwc_string *scheme,
+               nsurl **new_url)
+{
+       int scheme_len;
+       int base_len;
+       char *pos;
+       size_t len;
+       bool match;
+
+       assert(url != NULL);
+       assert(scheme != NULL);
+
+       /* Get the length of the new scheme */
+       scheme_len = lwc_string_length(scheme);
+
+       /* Find the change in length from url to new_url */
+       base_len = url->length;
+       if (url->components.scheme != NULL) {
+               base_len -= lwc_string_length(url->components.scheme);
+       }
+
+       /* Set new_url's length */
+       len = base_len + scheme_len;
+
+       /* Create NetSurf URL object */
+       *new_url = malloc(sizeof(nsurl) + len + 1); /* Add 1 for \0 */
+       if (*new_url == NULL) {
+               return NSERROR_NOMEM;
+       }
+
+       (*new_url)->length = len;
+
+       /* Set string */
+       pos = (*new_url)->string;
+       memcpy(pos, lwc_string_data(scheme), scheme_len);
+       memcpy(pos + scheme_len,
+                       url->string + url->length - base_len, base_len);
+       pos[len] = '\0';
+
+       /* Copy components */
+       (*new_url)->components.scheme = lwc_string_ref(scheme);
+       (*new_url)->components.username =
+                       nsurl__component_copy(url->components.username);
+       (*new_url)->components.password =
+                       nsurl__component_copy(url->components.password);
+       (*new_url)->components.host =
+                       nsurl__component_copy(url->components.host);
+       (*new_url)->components.port =
+                       nsurl__component_copy(url->components.port);
+       (*new_url)->components.path =
+                       nsurl__component_copy(url->components.path);
+       (*new_url)->components.query =
+                       nsurl__component_copy(url->components.query);
+       (*new_url)->components.fragment =
+                       nsurl__component_copy(url->components.fragment);
+
+       /* Compute new scheme type */
+       if (lwc_string_caseless_isequal(scheme, corestring_lwc_http,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_HTTP;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_https,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_HTTPS;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_file,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_FILE;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_ftp,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_FTP;
+       } else if (lwc_string_caseless_isequal(scheme, corestring_lwc_mailto,
+                       &match) == lwc_error_ok && match == true) {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_MAILTO;
+       } else {
+               (*new_url)->components.scheme_type = NSURL_SCHEME_OTHER;
+       }
+
+       /* Get the nsurl's hash */
+       nsurl__calc_hash(*new_url);
+
+       /* Give the URL a reference */
+       (*new_url)->count = 1;
+
+       return NSERROR_OK;
+}
+
+
 /* exported interface documented in utils/nsurl.h */
 nserror nsurl_nice(const nsurl *url, char **result, bool remove_extensions)
 {


-- 
NetSurf Browser

_______________________________________________
netsurf-commits mailing list
[email protected]
http://listmaster.pepperfish.net/cgi-bin/mailman/listinfo/netsurf-commits-netsurf-browser.org

Reply via email to