From 04b790643b9e45a1ba8919481f3c4beb80a66c31 Mon Sep 17 00:00:00 2001 From: John-Mark Bell Date: Mon, 10 Jun 2019 20:52:15 +0000 Subject: HTTP: add minimal parser for Cache-Control --- utils/http/Makefile | 2 +- utils/http/cache-control.c | 343 +++++++++++++++++++++++++++++++++++++++++++++ utils/http/cache-control.h | 69 +++++++++ 3 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 utils/http/cache-control.c create mode 100644 utils/http/cache-control.h (limited to 'utils/http') diff --git a/utils/http/Makefile b/utils/http/Makefile index f3bb765f0..b60f8f60a 100644 --- a/utils/http/Makefile +++ b/utils/http/Makefile @@ -1,7 +1,7 @@ # http utils sources S_HTTP := challenge.c generics.c primitives.c parameter.c \ - content-disposition.c content-type.c \ + cache-control.c content-disposition.c content-type.c \ strict-transport-security.c www-authenticate.c S_HTTP := $(addprefix utils/http/,$(S_HTTP)) diff --git a/utils/http/cache-control.c b/utils/http/cache-control.c new file mode 100644 index 000000000..f348f1614 --- /dev/null +++ b/utils/http/cache-control.c @@ -0,0 +1,343 @@ +/* + * Copyright 2019 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 Cache-Control + */ +struct http_cache_control { + uint32_t max_age; /**< Max age (delta seconds) */ + bool no_cache; /**< Whether caching is forbidden */ + bool no_store; /**< Whether persistent caching is forbidden */ +}; + +/** + * 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 cache-control.h for documentation */ +nserror http_parse_cache_control(const char *header_value, + http_cache_control **result) +{ + const char *pos = header_value; + http_cache_control *cc; + http_directive *first = NULL; + http_directive *directives = NULL; + lwc_string *value_str = NULL; + uint32_t max_age = 0; + bool no_cache = false; + bool no_store = false; + nserror error; + + /* 1#cache-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; + } + + /* Find max-age */ + error = http_directive_list_find_item(directives, + corestring_lwc_max_age, &value_str); + if (error == NSERROR_OK && value_str != NULL) { + error = parse_max_age(value_str, &max_age); + lwc_string_unref(value_str); + } + + /* Find no-cache */ + error = http_directive_list_find_item(directives, + corestring_lwc_no_cache, &value_str); + if (error == NSERROR_OK) { + no_cache = true; + if (value_str != NULL) { + lwc_string_unref(value_str); + } + } + + /* Find no-store */ + error = http_directive_list_find_item(directives, + corestring_lwc_no_store, &value_str); + if (error == NSERROR_OK) { + no_store = true; + if (value_str != NULL) { + lwc_string_unref(value_str); + } + } + + http_directive_list_destroy(directives); + + cc = malloc(sizeof(*cc)); + if (cc == NULL) { + return NSERROR_NOMEM; + } + + cc->max_age = max_age; + cc->no_cache = no_cache; + cc->no_store = no_store; + + *result = cc; + + return NSERROR_OK; +} + +/* See cache-control.h for documentation */ +void http_cache_control_destroy(http_cache_control *victim) +{ + free(victim); +} + +/* See cache-control.h for documentation */ +uint32_t http_cache_control_max_age(http_cache_control *cc) +{ + return cc->max_age; +} + +/* See cache-control.h for documentation */ +bool http_cache_control_no_cache(http_cache_control *cc) +{ + return cc->no_cache; +} + +/* See cache-control.h for documentation */ +bool http_cache_control_no_store(http_cache_control *cc) +{ + return cc->no_store; +} diff --git a/utils/http/cache-control.h b/utils/http/cache-control.h new file mode 100644 index 000000000..22c5f97ac --- /dev/null +++ b/utils/http/cache-control.h @@ -0,0 +1,69 @@ +/* + * Copyright 2019 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 . + */ + +#ifndef NETSURF_UTILS_HTTP_CACHE_CONTROL_H_ +#define NETSURF_UTILS_HTTP_CACHE_CONTROL_H_ + +#include + +typedef struct http_cache_control http_cache_control; + +/** + * Parse an HTTP Cache-Control 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_cache_control(const char *header_value, + http_cache_control **result); + +/** + * Destroy a cache_control object + * + * \param victim Object to destroy + */ +void http_cache_control_destroy(http_cache_control *victim); + +/** + * Get the value of a cache control's max-age + * + * \param cc Object to inspect + * \return Max age, in delta-seconds + */ +uint32_t http_cache_control_max_age(http_cache_control *cc); + +/** + * Get the value of a cache control's no-cache flag + * + * \param cc Object to inspect + * \return Whether caching is forbidden + */ +bool http_cache_control_no_cache(http_cache_control *cc); + +/** + * Get the value of a cache control's no-store flag + * + * \param cc Object to inspect + * \return Whether persistent caching is forbidden + */ +bool http_cache_control_no_store(http_cache_control *cc); + +#endif -- cgit v1.2.3