| /* |
| * Copyright (c) 2023, Emna Rekik |
| * Copyright (c) 2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net/http/hpack.h> |
| #include <zephyr/net/net_core.h> |
| |
| LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); |
| |
| static inline bool http_hpack_key_is_static(uint32_t key) |
| { |
| return key > HTTP_SERVER_HPACK_INVALID && key <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE; |
| } |
| |
| static inline bool http_hpack_key_is_dynamic(uint32_t key) |
| { |
| return key > HTTP_SERVER_HPACK_WWW_AUTHENTICATE; |
| } |
| |
| struct hpack_table_entry { |
| const char *name; |
| const char *value; |
| }; |
| |
| static const struct hpack_table_entry http_hpack_table_static[] = { |
| [HTTP_SERVER_HPACK_AUTHORITY] = { ":authority", NULL }, |
| [HTTP_SERVER_HPACK_METHOD_GET] = { ":method", "GET" }, |
| [HTTP_SERVER_HPACK_METHOD_POST] = { ":method", "POST" }, |
| [HTTP_SERVER_HPACK_PATH_ROOT] = { ":path", "/" }, |
| [HTTP_SERVER_HPACK_PATH_INDEX] = { ":path", "/index.html" }, |
| [HTTP_SERVER_HPACK_SCHEME_HTTP] = { ":scheme", "http" }, |
| [HTTP_SERVER_HPACK_SCHEME_HTTPS] = { ":scheme", "https" }, |
| [HTTP_SERVER_HPACK_STATUS_200] = { ":status", "200" }, |
| [HTTP_SERVER_HPACK_STATUS_204] = { ":status", "204" }, |
| [HTTP_SERVER_HPACK_STATUS_206] = { ":status", "206" }, |
| [HTTP_SERVER_HPACK_STATUS_304] = { ":status", "304" }, |
| [HTTP_SERVER_HPACK_STATUS_400] = { ":status", "400" }, |
| [HTTP_SERVER_HPACK_STATUS_404] = { ":status", "404" }, |
| [HTTP_SERVER_HPACK_STATUS_500] = { ":status", "500" }, |
| [HTTP_SERVER_HPACK_ACCEPT_CHARSET] = { "accept-charset", NULL }, |
| [HTTP_SERVER_HPACK_ACCEPT_ENCODING] = { "accept-encoding", "gzip, deflate" }, |
| [HTTP_SERVER_HPACK_ACCEPT_LANGUAGE] = { "accept-language", NULL }, |
| [HTTP_SERVER_HPACK_ACCEPT_RANGES] = { "accept-ranges", NULL }, |
| [HTTP_SERVER_HPACK_ACCEPT] = { "accept", NULL }, |
| [HTTP_SERVER_HPACK_ACCESS_CONTROL_ALLOW_ORIGIN] = { "access-control-allow-origin", NULL }, |
| [HTTP_SERVER_HPACK_AGE] = { "age", NULL }, |
| [HTTP_SERVER_HPACK_ALLOW] = { "allow", NULL }, |
| [HTTP_SERVER_HPACK_AUTHORIZATION] = { "authorization", NULL }, |
| [HTTP_SERVER_HPACK_CACHE_CONTROL] = { "cache-control", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_DISPOSITION] = { "content-disposition", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_ENCODING] = { "content-encoding", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_LANGUAGE] = { "content-language", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_LENGTH] = { "content-length", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_LOCATION] = { "content-location", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_RANGE] = { "content-range", NULL }, |
| [HTTP_SERVER_HPACK_CONTENT_TYPE] = { "content-type", NULL }, |
| [HTTP_SERVER_HPACK_COOKIE] = { "cookie", NULL }, |
| [HTTP_SERVER_HPACK_DATE] = { "date", NULL }, |
| [HTTP_SERVER_HPACK_ETAG] = { "etag", NULL }, |
| [HTTP_SERVER_HPACK_EXPECT] = { "expect", NULL }, |
| [HTTP_SERVER_HPACK_EXPIRES] = { "expires", NULL }, |
| [HTTP_SERVER_HPACK_FROM] = { "from", NULL }, |
| [HTTP_SERVER_HPACK_HOST] = { "host", NULL }, |
| [HTTP_SERVER_HPACK_IF_MATCH] = { "if-match", NULL }, |
| [HTTP_SERVER_HPACK_IF_MODIFIED_SINCE] = { "if-modified-since", NULL }, |
| [HTTP_SERVER_HPACK_IF_NONE_MATCH] = { "if-none-match", NULL }, |
| [HTTP_SERVER_HPACK_IF_RANGE] = { "if-range", NULL }, |
| [HTTP_SERVER_HPACK_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", NULL }, |
| [HTTP_SERVER_HPACK_LAST_MODIFIED] = { "last-modified", NULL }, |
| [HTTP_SERVER_HPACK_LINK] = { "link", NULL }, |
| [HTTP_SERVER_HPACK_LOCATION] = { "location", NULL }, |
| [HTTP_SERVER_HPACK_MAX_FORWARDS] = { "max-forwards", NULL }, |
| [HTTP_SERVER_HPACK_PROXY_AUTHENTICATE] = { "proxy-authenticate", NULL }, |
| [HTTP_SERVER_HPACK_PROXY_AUTHORIZATION] = { "proxy-authorization", NULL }, |
| [HTTP_SERVER_HPACK_RANGE] = { "range", NULL }, |
| [HTTP_SERVER_HPACK_REFERER] = { "referer", NULL }, |
| [HTTP_SERVER_HPACK_REFRESH] = { "refresh", NULL }, |
| [HTTP_SERVER_HPACK_RETRY_AFTER] = { "retry-after", NULL }, |
| [HTTP_SERVER_HPACK_SERVER] = { "server", NULL }, |
| [HTTP_SERVER_HPACK_SET_COOKIE] = { "set-cookie", NULL }, |
| [HTTP_SERVER_HPACK_STRICT_TRANSPORT_SECURITY] = { "strict-transport-security", NULL }, |
| [HTTP_SERVER_HPACK_TRANSFER_ENCODING] = { "transfer-encoding", NULL }, |
| [HTTP_SERVER_HPACK_USER_AGENT] = { "user-agent", NULL }, |
| [HTTP_SERVER_HPACK_VARY] = { "vary", NULL }, |
| [HTTP_SERVER_HPACK_VIA] = { "via", NULL }, |
| [HTTP_SERVER_HPACK_WWW_AUTHENTICATE] = { "www-authenticate", NULL }, |
| }; |
| |
| const struct hpack_table_entry *http_hpack_table_get(uint32_t key) |
| { |
| if (!http_hpack_key_is_static(key)) { |
| return NULL; |
| } |
| |
| return &http_hpack_table_static[key]; |
| } |
| |
| static int http_hpack_find_index(struct http_hpack_header_buf *header, |
| bool *name_only) |
| { |
| const struct hpack_table_entry *entry; |
| int candidate = -1; |
| |
| for (int i = HTTP_SERVER_HPACK_AUTHORITY; |
| i <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE; i++) { |
| entry = &http_hpack_table_static[i]; |
| |
| if (entry->name != NULL && |
| strlen(entry->name) == header->name_len && |
| memcmp(entry->name, header->name, header->name_len) == 0) { |
| if (entry->value != NULL && |
| strlen(entry->value) == header->value_len && |
| memcmp(entry->value, header->value, header->value_len) == 0) { |
| /* Got exact match. */ |
| *name_only = false; |
| return i; |
| } |
| |
| if (candidate < 0) { |
| candidate = i; |
| } |
| } |
| } |
| |
| if (candidate > 0) { |
| /* Matched name only. */ |
| *name_only = true; |
| return candidate; |
| } |
| |
| return -ENOENT; |
| } |
| |
| #define HPACK_INTEGER_CONTINUATION_FLAG 0x80 |
| #define HPACK_STRING_HUFFMAN_FLAG 0x80 |
| #define HPACK_STRING_PREFIX_LEN 7 |
| |
| #define HPACK_PREFIX_INDEXED_MASK 0x80 |
| #define HPACK_PREFIX_INDEXED 0x80 |
| #define HPACK_PREFIX_LEN_INDEXED 7 |
| |
| #define HPACK_PREFIX_LITERAL_INDEXING_MASK 0xC0 |
| #define HPACK_PREFIX_LITERAL_INDEXING 0x40 |
| #define HPACK_PREFIX_LEN_LITERAL_INDEXING 6 |
| |
| #define HPACK_PREFIX_LITERAL_NO_INDEXING_MASK 0xF0 |
| #define HPACK_PREFIX_LITERAL_NO_INDEXING 0x00 |
| #define HPACK_PREFIX_LEN_LITERAL_NO_INDEXING 4 |
| |
| #define HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK 0xF0 |
| #define HPACK_PREFIX_LITERAL_NEVER_INDEXED 0x10 |
| #define HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED 4 |
| |
| #define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK 0xE0 |
| #define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE 0x20 |
| #define HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE 5 |
| |
| static int hpack_integer_decode(const uint8_t *buf, size_t datalen, |
| uint8_t n, uint32_t *value) |
| { |
| int len = 0; |
| uint8_t m = 0; |
| uint8_t value_mask = (1 << n) - 1; |
| |
| NET_ASSERT(n < 8); |
| |
| if (datalen == 0) { |
| return -EAGAIN; |
| } |
| |
| /* Based on RFC7541, ch 5.1. */ |
| len++; |
| *value = *buf & value_mask; |
| if (*value < value_mask) { |
| return len; |
| } |
| |
| do { |
| buf++; |
| len++; |
| |
| if (--datalen == 0) { |
| return -EAGAIN; |
| } |
| |
| if (m > sizeof(uint32_t) * 8) { |
| /* Can't handle integer that large. */ |
| return -EBADMSG; |
| } |
| |
| *value += (*buf & ~HPACK_INTEGER_CONTINUATION_FLAG) * (1 << m); |
| m += 7; |
| |
| } while (*buf & HPACK_INTEGER_CONTINUATION_FLAG); |
| |
| return len; |
| } |
| |
| enum hpack_string_type { |
| HPACK_HEADER_NAME, |
| HPACK_HEADER_VALUE, |
| }; |
| |
| static int hpack_huffman_decode(const uint8_t *encoded_buf, size_t encoded_len, |
| enum hpack_string_type type, |
| struct http_hpack_header_buf *header) |
| { |
| uint8_t *buf = header->buf + header->datalen; |
| size_t buflen = sizeof(header->buf) - header->datalen; |
| int ret; |
| |
| NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE); |
| |
| ret = http_hpack_huffman_decode(encoded_buf, encoded_len, buf, buflen); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (type == HPACK_HEADER_NAME) { |
| header->name = buf; |
| header->name_len = ret; |
| } else if (type == HPACK_HEADER_VALUE) { |
| header->value = buf; |
| header->value_len = ret; |
| } |
| |
| header->datalen += ret; |
| |
| return 0; |
| } |
| |
| static int hpack_string_decode(const uint8_t *buf, size_t datalen, |
| enum hpack_string_type type, |
| struct http_hpack_header_buf *header) |
| { |
| uint32_t str_len; |
| bool huffman; |
| int len = 0; |
| int ret; |
| |
| NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE); |
| |
| if (datalen == 0) { |
| return -EAGAIN; |
| } |
| |
| huffman = *buf & HPACK_STRING_HUFFMAN_FLAG; |
| |
| ret = hpack_integer_decode(buf, datalen, HPACK_STRING_PREFIX_LEN, |
| &str_len); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len += ret; |
| datalen -= ret; |
| buf += ret; |
| |
| if (str_len > datalen) { |
| return -EAGAIN; |
| } |
| |
| if (huffman) { |
| ret = hpack_huffman_decode(buf, str_len, type, header); |
| if (ret < 0) { |
| return ret; |
| } |
| } else { |
| if (type == HPACK_HEADER_NAME) { |
| header->name = buf; |
| header->name_len = str_len; |
| } else if (type == HPACK_HEADER_VALUE) { |
| header->value = buf; |
| header->value_len = str_len; |
| } |
| } |
| |
| len += str_len; |
| |
| return len; |
| } |
| |
| static int hpack_handle_indexed(const uint8_t *buf, size_t datalen, |
| struct http_hpack_header_buf *header) |
| { |
| const struct hpack_table_entry *entry; |
| uint32_t index; |
| int ret; |
| |
| ret = hpack_integer_decode(buf, datalen, HPACK_PREFIX_LEN_INDEXED, |
| &index); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (index == 0) { |
| return -EBADMSG; |
| } |
| |
| entry = http_hpack_table_get(index); |
| if (entry == NULL) { |
| return -EBADMSG; |
| } |
| |
| if (entry->name == NULL || entry->value == NULL) { |
| return -EBADMSG; |
| } |
| |
| header->name = entry->name; |
| header->name_len = strlen(entry->name); |
| header->value = entry->value; |
| header->value_len = strlen(entry->value); |
| |
| return ret; |
| } |
| |
| static int hpack_handle_literal(const uint8_t *buf, size_t datalen, |
| struct http_hpack_header_buf *header, |
| uint8_t prefix_len) |
| { |
| uint32_t index; |
| int ret, len; |
| |
| header->datalen = 0; |
| |
| ret = hpack_integer_decode(buf, datalen, prefix_len, &index); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len = ret; |
| buf += ret; |
| datalen -= ret; |
| |
| if (index == 0) { |
| /* Literal name. */ |
| ret = hpack_string_decode(buf, datalen, HPACK_HEADER_NAME, |
| header); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len += ret; |
| buf += ret; |
| datalen -= ret; |
| } else { |
| /* Indexed name. */ |
| const struct hpack_table_entry *entry; |
| |
| entry = http_hpack_table_get(index); |
| if (entry == NULL) { |
| return -EBADMSG; |
| } |
| |
| if (entry->name == NULL) { |
| return -EBADMSG; |
| } |
| |
| header->name = entry->name; |
| header->name_len = strlen(entry->name); |
| } |
| |
| ret = hpack_string_decode(buf, datalen, HPACK_HEADER_VALUE, header); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len += ret; |
| |
| return len; |
| } |
| |
| static int hpack_handle_literal_index(const uint8_t *buf, size_t datalen, |
| struct http_hpack_header_buf *header) |
| { |
| /* TODO Add dynamic table support, if needed. */ |
| |
| return hpack_handle_literal(buf, datalen, header, |
| HPACK_PREFIX_LEN_LITERAL_INDEXING); |
| } |
| |
| static int hpack_handle_literal_no_index(const uint8_t *buf, size_t datalen, |
| struct http_hpack_header_buf *header) |
| { |
| return hpack_handle_literal(buf, datalen, header, |
| HPACK_PREFIX_LEN_LITERAL_NO_INDEXING); |
| } |
| |
| static int hpack_handle_dynamic_size_update(const uint8_t *buf, size_t datalen) |
| { |
| uint32_t max_size; |
| int ret; |
| |
| ret = hpack_integer_decode( |
| buf, datalen, HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE, &max_size); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* TODO Add dynamic table support, if needed. */ |
| |
| return ret; |
| } |
| |
| int http_hpack_decode_header(const uint8_t *buf, size_t datalen, |
| struct http_hpack_header_buf *header) |
| { |
| uint8_t prefix; |
| int ret; |
| |
| if (buf == NULL || header == NULL) { |
| return -EINVAL; |
| } |
| |
| if (datalen == 0) { |
| return -EAGAIN; |
| } |
| |
| prefix = *buf; |
| |
| if ((prefix & HPACK_PREFIX_INDEXED_MASK) == HPACK_PREFIX_INDEXED) { |
| ret = hpack_handle_indexed(buf, datalen, header); |
| } else if ((prefix & HPACK_PREFIX_LITERAL_INDEXING_MASK) == |
| HPACK_PREFIX_LITERAL_INDEXING) { |
| ret = hpack_handle_literal_index(buf, datalen, header); |
| } else if (((prefix & HPACK_PREFIX_LITERAL_NO_INDEXING_MASK) == |
| HPACK_PREFIX_LITERAL_NO_INDEXING) || |
| ((prefix & HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK) == |
| HPACK_PREFIX_LITERAL_NEVER_INDEXED)) { |
| ret = hpack_handle_literal_no_index(buf, datalen, header); |
| } else if ((prefix & HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK) == |
| HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE) { |
| ret = hpack_handle_dynamic_size_update(buf, datalen); |
| } else { |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int hpack_integer_encode(uint8_t *buf, size_t buflen, int value, |
| uint8_t prefix, uint8_t n) |
| { |
| uint8_t limit = (1 << n) - 1; |
| int len = 0; |
| |
| if (buflen == 0) { |
| return -ENOBUFS; |
| } |
| |
| /* Based on RFC7541, ch 5.1. */ |
| if (value < limit) { |
| *buf = prefix | (uint8_t)value; |
| |
| return 1; |
| } |
| |
| *buf++ = prefix | limit; |
| len++; |
| value -= limit; |
| |
| while (value >= 128) { |
| if (len >= buflen) { |
| return -ENOBUFS; |
| } |
| |
| *buf = (uint8_t)((value % 128) + 128); |
| len++; |
| value /= 128; |
| } |
| |
| if (len >= buflen) { |
| return -ENOBUFS; |
| } |
| |
| *buf = (uint8_t)value; |
| len++; |
| |
| return len; |
| } |
| |
| static int hpack_string_encode(uint8_t *buf, size_t buflen, |
| enum hpack_string_type type, |
| struct http_hpack_header_buf *header) |
| { |
| int ret, len = 0; |
| const char *str; |
| size_t str_len; |
| uint8_t prefix = 0; |
| |
| if (type == HPACK_HEADER_NAME) { |
| str = header->name; |
| str_len = header->name_len; |
| } else { |
| str = header->value; |
| str_len = header->value_len; |
| } |
| |
| /* Try to encode string into intermediate buffer. */ |
| ret = http_hpack_huffman_encode(str, str_len, header->buf, |
| sizeof(header->buf)); |
| if (ret > 0 && ret < str_len) { |
| /* Use Huffman encoded string only if smaller than the original. */ |
| str = header->buf; |
| str_len = ret; |
| prefix = HPACK_STRING_HUFFMAN_FLAG; |
| } |
| |
| /* Encode string length. */ |
| ret = hpack_integer_encode(buf, buflen, str_len, prefix, |
| HPACK_STRING_PREFIX_LEN); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| buf += ret; |
| buflen -= ret; |
| len += ret; |
| |
| /* Copy string. */ |
| if (str_len > buflen) { |
| return -ENOBUFS; |
| } |
| |
| memcpy(buf, str, str_len); |
| len += str_len; |
| |
| return len; |
| } |
| |
| static int hpack_encode_literal(uint8_t *buf, size_t buflen, |
| struct http_hpack_header_buf *header) |
| { |
| int ret, len = 0; |
| |
| ret = hpack_integer_encode(buf, buflen, 0, |
| HPACK_PREFIX_LITERAL_NEVER_INDEXED, |
| HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| buf += ret; |
| buflen -= ret; |
| len += ret; |
| |
| ret = hpack_string_encode(buf, buflen, HPACK_HEADER_NAME, header); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| buf += ret; |
| buflen -= ret; |
| len += ret; |
| |
| ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len += ret; |
| |
| return len; |
| } |
| |
| static int hpack_encode_literal_value(uint8_t *buf, size_t buflen, int index, |
| struct http_hpack_header_buf *header) |
| { |
| int ret, len = 0; |
| |
| ret = hpack_integer_encode(buf, buflen, index, |
| HPACK_PREFIX_LITERAL_NEVER_INDEXED, |
| HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| buf += ret; |
| buflen -= ret; |
| len += ret; |
| |
| ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| len += ret; |
| |
| return len; |
| } |
| |
| static int hpack_encode_indexed(uint8_t *buf, size_t buflen, int index) |
| { |
| return hpack_integer_encode(buf, buflen, index, HPACK_PREFIX_INDEXED, |
| HPACK_PREFIX_LEN_INDEXED); |
| } |
| |
| int http_hpack_encode_header(uint8_t *buf, size_t buflen, |
| struct http_hpack_header_buf *header) |
| { |
| int ret, len = 0; |
| bool name_only; |
| |
| if (buf == NULL || header == NULL || |
| header->name == NULL || header->name_len == 0 || |
| header->value == NULL || header->value_len == 0) { |
| return -EINVAL; |
| } |
| |
| if (buflen == 0) { |
| return -ENOBUFS; |
| } |
| |
| ret = http_hpack_find_index(header, &name_only); |
| if (ret < 0) { |
| /* All literal */ |
| len = hpack_encode_literal(buf, buflen, header); |
| } else if (name_only) { |
| /* Literal value */ |
| len = hpack_encode_literal_value(buf, buflen, ret, header); |
| } else { |
| /* Indexed */ |
| len = hpack_encode_indexed(buf, buflen, ret); |
| } |
| |
| return len; |
| } |