/* * Copyright (c) 2023, Emna Rekik * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include 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; } 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; }