From 1c05280b5cd23c94ed10c2a9415a500393100285 Mon Sep 17 00:00:00 2001 From: John-Mark Bell Date: Sun, 22 Apr 2018 01:24:45 +0000 Subject: HSTS: add parser for Strict-Transport-Security --- utils/http/strict-transport-security.c | 341 +++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 utils/http/strict-transport-security.c (limited to 'utils/http/strict-transport-security.c') diff --git a/utils/http/strict-transport-security.c b/utils/http/strict-transport-security.c new file mode 100644 index 000000000..9de610c73 --- /dev/null +++ b/utils/http/strict-transport-security.c @@ -0,0 +1,341 @@ +/* + * Copyright 2018 John-Mark Bell + * + * 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 . + */ + +#include +#include + +#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; +} + -- cgit v1.2.3