/* * lib/hpack.c * * Copyright (C) 2014 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation: * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include "private-libwebsockets.h" /* * Official static header table for HPACK * +-------+-----------------------------+---------------+ | 1 | :authority | | | 2 | :method | GET | | 3 | :method | POST | | 4 | :path | / | | 5 | :path | /index.html | | 6 | :scheme | http | | 7 | :scheme | https | | 8 | :status | 200 | | 9 | :status | 204 | | 10 | :status | 206 | | 11 | :status | 304 | | 12 | :status | 400 | | 13 | :status | 404 | | 14 | :status | 500 | | 15 | accept-charset | | | 16 | accept-encoding | gzip, deflate | | 17 | accept-language | | | 18 | accept-ranges | | | 19 | accept | | | 20 | access-control-allow-origin | | | 21 | age | | | 22 | allow | | | 23 | authorization | | | 24 | cache-control | | | 25 | content-disposition | | | 26 | content-encoding | | | 27 | content-language | | | 28 | content-length | | | 29 | content-location | | | 30 | content-range | | | 31 | content-type | | | 32 | cookie | | | 33 | date | | | 34 | etag | | | 35 | expect | | | 36 | expires | | | 37 | from | | | 38 | host | | | 39 | if-match | | | 40 | if-modified-since | | | 41 | if-none-match | | | 42 | if-range | | | 43 | if-unmodified-since | | | 44 | last-modified | | | 45 | link | | | 46 | location | | | 47 | max-forwards | | | 48 | proxy-authenticate | | | 49 | proxy-authorization | | | 50 | range | | | 51 | referer | | | 52 | refresh | | | 53 | retry-after | | | 54 | server | | | 55 | set-cookie | | | 56 | strict-transport-security | | | 57 | transfer-encoding | | | 58 | user-agent | | | 59 | vary | | | 60 | via | | | 61 | www-authenticate | | +-------+-----------------------------+---------------+ */ static const unsigned char static_token[] = { 0, WSI_TOKEN_HTTP_COLON_AUTHORITY, WSI_TOKEN_HTTP_COLON_METHOD, WSI_TOKEN_HTTP_COLON_METHOD, WSI_TOKEN_HTTP_COLON_PATH, WSI_TOKEN_HTTP_COLON_PATH, WSI_TOKEN_HTTP_COLON_SCHEME, WSI_TOKEN_HTTP_COLON_SCHEME, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_COLON_STATUS, WSI_TOKEN_HTTP_ACCEPT_CHARSET, WSI_TOKEN_HTTP_ACCEPT_ENCODING, WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, WSI_TOKEN_HTTP_ACCEPT_RANGES, WSI_TOKEN_HTTP_ACCEPT, WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN, WSI_TOKEN_HTTP_AGE, WSI_TOKEN_HTTP_ALLOW, WSI_TOKEN_HTTP_AUTHORIZATION, WSI_TOKEN_HTTP_CACHE_CONTROL, WSI_TOKEN_HTTP_CONTENT_DISPOSITION, WSI_TOKEN_HTTP_CONTENT_ENCODING, WSI_TOKEN_HTTP_CONTENT_LANGUAGE, WSI_TOKEN_HTTP_CONTENT_LENGTH, WSI_TOKEN_HTTP_CONTENT_LOCATION, WSI_TOKEN_HTTP_CONTENT_RANGE, WSI_TOKEN_HTTP_CONTENT_TYPE, WSI_TOKEN_HTTP_COOKIE, WSI_TOKEN_HTTP_DATE, WSI_TOKEN_HTTP_ETAG, WSI_TOKEN_HTTP_EXPECT, WSI_TOKEN_HTTP_EXPIRES, WSI_TOKEN_HTTP_FROM, WSI_TOKEN_HOST, WSI_TOKEN_HTTP_IF_MATCH, WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, WSI_TOKEN_HTTP_IF_NONE_MATCH, WSI_TOKEN_HTTP_IF_RANGE, WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE, WSI_TOKEN_HTTP_LAST_MODIFIED, WSI_TOKEN_HTTP_LINK, WSI_TOKEN_HTTP_LOCATION, WSI_TOKEN_HTTP_MAX_FORWARDS, WSI_TOKEN_HTTP_PROXY_AUTHENTICATE, WSI_TOKEN_HTTP_PROXY_AUTHORIZATION, WSI_TOKEN_HTTP_RANGE, WSI_TOKEN_HTTP_REFERER, WSI_TOKEN_HTTP_REFRESH, WSI_TOKEN_HTTP_RETRY_AFTER, WSI_TOKEN_HTTP_SERVER, WSI_TOKEN_HTTP_SET_COOKIE, WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY, WSI_TOKEN_HTTP_TRANSFER_ENCODING, WSI_TOKEN_HTTP_USER_AGENT, WSI_TOKEN_HTTP_VARY, WSI_TOKEN_HTTP_VIA, WSI_TOKEN_HTTP_WWW_AUTHENTICATE, }; /* some of the entries imply values as well as header names */ static const char * const http2_canned[] = { "", "", "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", "", "gzip, deflate" }; /* see minihuf.c */ #include "huftable.h" static int huftable_decode(int pos, char c) { int q = pos + !!c; if (lextable_terms[q >> 3] & (1 << (q & 7))) /* terminal */ return lextable[q] | 0x8000; return pos + (lextable[q] << 1); } static int lws_hpack_update_table_size(struct lws *wsi, int idx) { lwsl_info("hpack set table size %d\n", idx); return 0; } static int lws_frag_start(struct lws *wsi, int hdr_token_idx) { struct allocated_headers * ah = wsi->u.http2.http.ah; if (!hdr_token_idx) { lwsl_err("%s: zero hdr_token_idx\n", __func__); return 1; } if (ah->nfrag >= ARRAY_SIZE(ah->frag_index)) { lwsl_err("%s: frag index %d too big\n", __func__, ah->nfrag); return 1; } ah->frags[ah->nfrag].offset = ah->pos; ah->frags[ah->nfrag].len = 0; ah->frags[ah->nfrag].nfrag = 0; ah->frag_index[hdr_token_idx] = ah->nfrag; return 0; } static int lws_frag_append(struct lws *wsi, unsigned char c) { struct allocated_headers * ah = wsi->u.http2.http.ah; ah->data[ah->pos++] = c; ah->frags[ah->nfrag].len++; return ah->pos >= wsi->context->max_http_header_data; } static int lws_frag_end(struct lws *wsi) { if (lws_frag_append(wsi, 0)) return 1; wsi->u.http2.http.ah->nfrag++; return 0; } static void lws_dump_header(struct lws *wsi, int hdr) { char s[200]; int len = lws_hdr_copy(wsi, s, sizeof(s) - 1, hdr); s[len] = '\0'; lwsl_info(" hdr tok %d (%s) = '%s'\n", hdr, lws_token_to_string(hdr), s); } static int lws_token_from_index(struct lws *wsi, int index, char **arg, int *len) { struct hpack_dynamic_table *dyn; /* dynamic table only belongs to network wsi */ wsi = lws_http2_get_network_wsi(wsi); dyn = wsi->u.http2.hpack_dyn_table; if (index < ARRAY_SIZE(static_token)) return static_token[index]; if (!dyn) return 0; index -= ARRAY_SIZE(static_token); if (index >= dyn->num_entries) return 0; if (arg && len) { *arg = dyn->args + dyn->entries[index].arg_offset; *len = dyn->entries[index].arg_len; } return dyn->entries[index].token; } static int lws_hpack_add_dynamic_header(struct lws *wsi, int token, char *arg, int len) { struct hpack_dynamic_table *dyn; int ret = 1; wsi = lws_http2_get_network_wsi(wsi); dyn = wsi->u.http2.hpack_dyn_table; if (!dyn) { dyn = lws_zalloc(sizeof(*dyn)); if (!dyn) return 1; wsi->u.http2.hpack_dyn_table = dyn; dyn->args = lws_malloc(1024); if (!dyn->args) goto bail1; dyn->args_length = 1024; dyn->entries = lws_malloc(sizeof(dyn->entries[0]) * 20); if (!dyn->entries) goto bail2; dyn->num_entries = 20; } if (dyn->next == dyn->num_entries) return 1; if (dyn->args_length - dyn->pos < len) return 1; dyn->entries[dyn->next].token = token; dyn->entries[dyn->next].arg_offset = dyn->pos; if (len) memcpy(dyn->args + dyn->pos, arg, len); dyn->entries[dyn->next].arg_len = len; lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n", __func__, dyn->next, token, lws_token_to_string(token), len); dyn->pos += len; dyn->next++; return 0; bail2: lws_free(dyn->args); bail1: lws_free(dyn); wsi->u.http2.hpack_dyn_table = NULL; return ret; } static int lws_write_indexed_hdr(struct lws *wsi, int idx) { const char *p; int tok = lws_token_from_index(wsi, idx, NULL, 0); lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok, lws_token_to_string(tok)); if (lws_frag_start(wsi, tok)) return 1; if (idx < ARRAY_SIZE(http2_canned)) { p = http2_canned[idx]; while (*p) if (lws_frag_append(wsi, *p++)) return 1; } if (lws_frag_end(wsi)) return 1; lws_dump_header(wsi, tok); return 0; } int lws_hpack_interpret(struct lws *wsi, unsigned char c) { unsigned int prev; unsigned char c1; int n; lwsl_debug(" state %d\n", wsi->u.http2.hpack); switch (wsi->u.http2.hpack) { case HPKS_OPT_PADDING: wsi->u.http2.padding = c; lwsl_info("padding %d\n", c); if (wsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) { wsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY; wsi->u.http2.hpack_m = 4; } else wsi->u.http2.hpack = HPKS_TYPE; break; case HKPS_OPT_E_DEPENDENCY: wsi->u.http2.hpack_e_dep <<= 8; wsi->u.http2.hpack_e_dep |= c; if (! --wsi->u.http2.hpack_m) { lwsl_info("hpack_e_dep = 0x%x\n", wsi->u.http2.hpack_e_dep); wsi->u.http2.hpack = HKPS_OPT_WEIGHT; } break; case HKPS_OPT_WEIGHT: /* weight */ wsi->u.http2.hpack = HPKS_TYPE; break; case HPKS_TYPE: if (wsi->u.http2.count > (wsi->u.http2.length - wsi->u.http2.padding)) { lwsl_info("padding eat\n"); break; } if (c & 0x80) { /* indexed header field only */ /* just a possibly-extended integer */ wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_7; lwsl_debug("HKPS_TYPE setting header_index %d\n", c & 0x7f); wsi->u.http2.header_index = c & 0x7f; if ((c & 0x7f) == 0x7f) { wsi->u.http2.hpack_len = c & 0x7f; wsi->u.http2.hpack_m = 0; wsi->u.http2.hpack = HPKS_IDX_EXT; break; } lwsl_debug("HKPS_TYPE: %d\n", c & 0x7f); if (lws_write_indexed_hdr(wsi, c & 0x7f)) return 1; /* stay at same state */ break; } if (c & 0x40) { /* literal header incr idx */ /* * [possibly-extended hdr idx (6) | new literal hdr name] * H + possibly-extended value length * literal value */ lwsl_debug("HKPS_TYPE 2 setting header_index %d\n", 0); wsi->u.http2.header_index = 0; if (c == 0x40) { /* literal name */ wsi->u.http2.hpack_type = HPKT_LITERAL_HDR_VALUE_INCR; wsi->u.http2.value = 0; wsi->u.http2.hpack = HPKS_HLEN; break; } /* indexed name */ wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_6_VALUE_INCR; if ((c & 0x3f) == 0x3f) { wsi->u.http2.hpack_len = c & 0x3f; wsi->u.http2.hpack_m = 0; wsi->u.http2.hpack = HPKS_IDX_EXT; break; } lwsl_debug("HKPS_TYPE 3 setting header_index %d\n", c & 0x3f); wsi->u.http2.header_index = c & 0x3f; wsi->u.http2.value = 1; wsi->u.http2.hpack = HPKS_HLEN; break; } switch(c & 0xf0) { case 0x10: /* literal header never index */ case 0: /* literal header without indexing */ /* * follows 0x40 except 4-bit hdr idx * and don't add to index */ if (c == 0) { /* literal name */ wsi->u.http2.hpack_type = HPKT_LITERAL_HDR_VALUE; wsi->u.http2.hpack = HPKS_HLEN; wsi->u.http2.value = 0; break; } //lwsl_debug("indexed\n"); /* indexed name */ wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_4_VALUE; wsi->u.http2.header_index = 0; if ((c & 0xf) == 0xf) { wsi->u.http2.hpack_len = c & 0xf; wsi->u.http2.hpack_m = 0; wsi->u.http2.hpack = HPKS_IDX_EXT; break; } //lwsl_err("HKPS_TYPE 5 setting header_index %d\n", c & 0xf); wsi->u.http2.header_index = c & 0xf; wsi->u.http2.value = 1; wsi->u.http2.hpack = HPKS_HLEN; break; case 0x20: case 0x30: /* header table size update */ /* possibly-extended size value (5) */ wsi->u.http2.hpack_type = HPKT_SIZE_5; if ((c & 0x1f) == 0x1f) { wsi->u.http2.hpack_len = c & 0x1f; wsi->u.http2.hpack_m = 0; wsi->u.http2.hpack = HPKS_IDX_EXT; break; } lws_hpack_update_table_size(wsi, c & 0x1f); /* stay at HPKS_TYPE state */ break; } break; case HPKS_IDX_EXT: wsi->u.http2.hpack_len += (c & 0x7f) << wsi->u.http2.hpack_m; wsi->u.http2.hpack_m += 7; if (!(c & 0x80)) { switch (wsi->u.http2.hpack_type) { case HPKT_INDEXED_HDR_7: //lwsl_err("HKPS_IDX_EXT hdr idx %d\n", wsi->u.http2.hpack_len); if (lws_write_indexed_hdr(wsi, wsi->u.http2.hpack_len)) return 1; wsi->u.http2.hpack = HPKS_TYPE; break; default: // lwsl_err("HKPS_IDX_EXT setting header_index %d\n", // wsi->u.http2.hpack_len); wsi->u.http2.header_index = wsi->u.http2.hpack_len; wsi->u.http2.value = 1; wsi->u.http2.hpack = HPKS_HLEN; break; } } break; case HPKS_HLEN: /* [ H | 7+ ] */ wsi->u.http2.huff = !!(c & 0x80); wsi->u.http2.hpack_pos = 0; wsi->u.http2.hpack_len = c & 0x7f; if (wsi->u.http2.hpack_len < 0x7f) { pre_data: if (wsi->u.http2.value) { if (wsi->u.http2.header_index) if (lws_frag_start(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL))) { // lwsl_notice("%s: hlen failed\n", __func__); return 1; } } else wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.http2.hpack = HPKS_DATA; break; } wsi->u.http2.hpack_m = 0; wsi->u.http2.hpack = HPKS_HLEN_EXT; break; case HPKS_HLEN_EXT: wsi->u.http2.hpack_len += (c & 0x7f) << wsi->u.http2.hpack_m; wsi->u.http2.hpack_m += 7; if (!(c & 0x80)) goto pre_data; break; case HPKS_DATA: for (n = 0; n < 8; n++) { if (wsi->u.http2.huff) { prev = wsi->u.http2.hpack_pos; wsi->u.http2.hpack_pos = huftable_decode( wsi->u.http2.hpack_pos, (c >> 7) & 1); c <<= 1; if (wsi->u.http2.hpack_pos == 0xffff) return 1; if (!(wsi->u.http2.hpack_pos & 0x8000)) continue; c1 = wsi->u.http2.hpack_pos & 0x7fff; wsi->u.http2.hpack_pos = 0; if (!c1 && prev == HUFTABLE_0x100_PREV) ; /* EOT */ } else { n = 8; c1 = c; } if (wsi->u.http2.value) { /* value */ if (wsi->u.http2.header_index) if (lws_frag_append(wsi, c1)) return 1; } else { /* name */ if (lws_parse(wsi, c1)) return 1; } } if (--wsi->u.http2.hpack_len == 0) { switch (wsi->u.http2.hpack_type) { case HPKT_LITERAL_HDR_VALUE_INCR: case HPKT_INDEXED_HDR_6_VALUE_INCR: // !!! if (lws_hpack_add_dynamic_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL), NULL, 0)) return 1; break; default: break; } n = 8; if (wsi->u.http2.value) { if (lws_frag_end(wsi)) return 1; // lwsl_err("data\n"); lws_dump_header(wsi, lws_token_from_index( wsi, wsi->u.http2.header_index, NULL, NULL)); if (wsi->u.http2.count + wsi->u.http2.padding == wsi->u.http2.length) wsi->u.http2.hpack = HKPS_OPT_DISCARD_PADDING; else wsi->u.http2.hpack = HPKS_TYPE; } else { /* name */ //if (wsi->u.hdr.parser_state < WSI_TOKEN_COUNT) wsi->u.http2.value = 1; wsi->u.http2.hpack = HPKS_HLEN; } } break; case HKPS_OPT_DISCARD_PADDING: lwsl_info("eating padding %x\n", c); if (! --wsi->u.http2.padding) wsi->u.http2.hpack = HPKS_TYPE; break; } return 0; } static int lws_http2_num(int starting_bits, unsigned long num, unsigned char **p, unsigned char *end) { int mask = (1 << starting_bits) - 1; if (num < mask) { *((*p)++) |= num; return *p >= end; } *((*p)++) |= mask; if (*p >= end) return 1; num -= mask; while (num >= 128) { *((*p)++) = 0x80 | (num & 0x7f); if (*p >= end) return 1; num >>= 7; } return 0; } int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name, const unsigned char *value, int length, unsigned char **p, unsigned char *end) { int len; lwsl_info("%s: %p %s:%s\n", __func__, *p, name, value); len = strlen((char *)name); if (len) if (name[len - 1] == ':') len--; if (end - *p < len + length + 8) return 1; *((*p)++) = 0; /* not indexed, literal name */ **p = 0; /* non-HUF */ if (lws_http2_num(7, len, p, end)) return 1; memcpy(*p, name, len); *p += len; *(*p) = 0; /* non-HUF */ if (lws_http2_num(7, length, p, end)) return 1; memcpy(*p, value, length); *p += length; return 0; } int lws_add_http2_header_by_token(struct lws *wsi, enum lws_token_indexes token, const unsigned char *value, int length, unsigned char **p, unsigned char *end) { const unsigned char *name; name = lws_token_to_string(token); if (!name) return 1; return lws_add_http2_header_by_name(wsi, name, value, length, p, end); } int lws_add_http2_header_status(struct lws *wsi, unsigned int code, unsigned char **p, unsigned char *end) { unsigned char status[10]; int n; wsi->u.http2.send_END_STREAM = !!(code >= 400); n = sprintf((char *)status, "%u", code); if (lws_add_http2_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_STATUS, status, n, p, end)) return 1; return 0; }