diff --git a/lib/hpack.c b/lib/hpack.c index bd4f4624..4b2a1a85 100644 --- a/lib/hpack.c +++ b/lib/hpack.c @@ -238,25 +238,96 @@ static void lws_dump_header(struct libwebsocket *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'\n", hdr, s); + lwsl_info(" hdr tok %d (%s) = '%s'\n", hdr, lws_token_to_string(hdr), s); } -static int lws_token_from_index(struct libwebsocket *wsi, int index) +static int lws_token_from_index(struct libwebsocket *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; - // dynamic indexes + index -= ARRAY_SIZE(static_token); + if (index >= dyn->num_entries) + return 0; - 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_add_indexed_hdr(struct libwebsocket *wsi, int idx) +static int lws_hpack_add_dynamic_header(struct libwebsocket *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 = malloc(sizeof(*dyn)); + if (!dyn) + return 1; + memset(dyn, 0, sizeof(*dyn)); + wsi->u.http2.hpack_dyn_table = dyn; + + dyn->args = malloc(1024); + if (!dyn->args) + goto bail1; + dyn->args_length = 1024; + dyn->entries = 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: + free(dyn->args); +bail1: + free(dyn); + wsi->u.http2.hpack_dyn_table = NULL; + + return ret; +} + +static int lws_write_indexed_hdr(struct libwebsocket *wsi, int idx) { const char *p; - int tok = lws_token_from_index(wsi, idx); + int tok = lws_token_from_index(wsi, idx, NULL, 0); - lwsl_info("adding indexed hdr %d (tok %d)\n", idx, tok); + 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; @@ -283,6 +354,28 @@ int lws_hpack_interpret(struct libwebsocket_context *context, int n; 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 (c & 0x80) { /* indexed header field only */ /* just a possibly-extended integer */ @@ -294,7 +387,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context, wsi->u.http2.hpack = HPKS_IDX_EXT; break; } - if (lws_add_indexed_hdr(wsi, c & 0x7f)) + if (lws_write_indexed_hdr(wsi, c & 0x7f)) return 1; /* stay at same state */ break; @@ -374,7 +467,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context, if (!(c & 0x80)) { switch (wsi->u.http2.hpack_type) { case HPKT_INDEXED_HDR_7: - if (lws_add_indexed_hdr(wsi, 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; @@ -396,7 +489,7 @@ pre_data: if (wsi->u.http2.value) { if (lws_frag_start(wsi, lws_token_from_index(wsi, - wsi->u.http2.header_index))) + wsi->u.http2.header_index, NULL, NULL))) return 1; } else wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; @@ -448,14 +541,27 @@ pre_data: } } 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; - lws_dump_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index)); - - wsi->u.http2.hpack = HPKS_TYPE; + 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) @@ -464,6 +570,11 @@ pre_data: } } 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; diff --git a/lib/http2.c b/lib/http2.c index 08466518..ae962378 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -128,14 +128,19 @@ lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned c return 0; } +struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi) +{ + while (wsi->u.http2.parent_wsi) + wsi = wsi->u.http2.parent_wsi; + + return wsi; +} + int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf) { - struct libwebsocket *wsi_eff = wsi; + struct libwebsocket *wsi_eff = lws_http2_get_network_wsi(wsi); unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH]; int n; - - while (wsi_eff->u.http2.parent_wsi) - wsi_eff = wsi_eff->u.http2.parent_wsi; *p++ = len >> 16; *p++ = len >> 8; @@ -174,6 +179,7 @@ int lws_http2_parser(struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned char c) { + struct libwebsocket *swsi; int n; //dstruct libwebsocket *wsi_new; @@ -200,6 +206,8 @@ lws_http2_parser(struct libwebsocket_context *context, case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS: case WSI_STATE_HTTP2_ESTABLISHED: if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { // payload + wsi->u.http2.count++; + wsi->u.http2.stream_wsi->u.http2.count = wsi->u.http2.count; /* applies to wsi->u.http2.stream_wsi which may be wsi*/ switch(wsi->u.http2.type) { case LWS_HTTP2_FRAME_TYPE_SETTINGS: @@ -217,7 +225,6 @@ lws_http2_parser(struct libwebsocket_context *context, return 1; break; } - wsi->u.http2.count++; if (wsi->u.http2.count != wsi->u.http2.length) break; @@ -294,15 +301,34 @@ lws_http2_parser(struct libwebsocket_context *context, wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id); if (!wsi->u.http2.stream_wsi) wsi->u.http2.stream_wsi = lws_create_server_child_wsi(context, wsi, wsi->u.http2.stream_id); - - if (!wsi->u.http2.stream_wsi) - return 1; - + /* END_STREAM means after servicing this, close the stream */ wsi->u.http2.END_STREAM = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_STREAM); + lwsl_info("%s: headers END_STREAM = %d\n",__func__, wsi->u.http2.END_STREAM); update_end_headers: /* no END_HEADERS means CONTINUATION must come */ wsi->u.http2.END_HEADERS = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_HEADERS); + + swsi = wsi->u.http2.stream_wsi; + if (!swsi) + return 1; + + + /* prepare the hpack parser at the right start */ + + swsi->u.http2.flags = wsi->u.http2.flags; + swsi->u.http2.length = wsi->u.http2.length; + swsi->u.http2.END_STREAM = wsi->u.http2.END_STREAM; + + if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PADDED) + swsi->u.http2.hpack = HPKS_OPT_PADDING; + else + if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) { + swsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY; + swsi->u.http2.hpack_m = 4; + } else + swsi->u.http2.hpack = HPKS_TYPE; + lwsl_info("initial hpack state %d\n", swsi->u.http2.hpack); break; } if (wsi->u.http2.length == 0) diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 444d7160..0ed5592d 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -265,6 +265,9 @@ enum libwebsocket_write_protocol { LWS_WRITE_PING, LWS_WRITE_PONG, + /* Same as write_http but we know this write ends the transaction */ + LWS_WRITE_HTTP_FINAL, + /* HTTP2 */ LWS_WRITE_HTTP_HEADERS, @@ -1082,6 +1085,11 @@ lws_add_http_header_by_token(struct libwebsocket_context *context, int length, unsigned char **p, unsigned char *end); +LWS_VISIBLE LWS_EXTERN int lws_add_http_header_content_length(struct libwebsocket_context *context, + struct libwebsocket *wsi, + unsigned long content_length, + unsigned char **p, + unsigned char *end); LWS_VISIBLE LWS_EXTERN int lws_add_http_header_status(struct libwebsocket_context *context, struct libwebsocket *wsi, diff --git a/lib/output.c b/lib/output.c index 7c5cb537..aebec9cc 100644 --- a/lib/output.c +++ b/lib/output.c @@ -264,7 +264,9 @@ LWS_VISIBLE int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, return 0; } - if (protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_HEADERS) + if (protocol == LWS_WRITE_HTTP || + protocol == LWS_WRITE_HTTP_FINAL || + protocol == LWS_WRITE_HTTP_HEADERS) goto send_raw; /* websocket protocol, either binary or text */ @@ -431,6 +433,7 @@ send_raw: case LWS_WRITE_CLOSE: /* lwsl_hexdump(&buf[-pre], len + post); */ case LWS_WRITE_HTTP: + case LWS_WRITE_HTTP_FINAL: case LWS_WRITE_HTTP_HEADERS: case LWS_WRITE_PONG: case LWS_WRITE_PING: @@ -443,6 +446,21 @@ send_raw: n = LWS_HTTP2_FRAME_TYPE_HEADERS; flags = LWS_HTTP2_FLAG_END_HEADERS; } + + if ((protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_FINAL) && wsi->u.http.content_length) { + wsi->u.http.content_remain -= len; + lwsl_info("%s: content_remain = %lu\n", __func__, wsi->u.http.content_remain); + if (!wsi->u.http.content_remain) { + lwsl_info("%s: selecting final write mode\n", __func__); + protocol = LWS_WRITE_HTTP_FINAL; + } + } + + if (protocol == LWS_WRITE_HTTP_FINAL && wsi->u.http2.END_STREAM) { + lwsl_info("%s: setting END_STREAM\n", __func__); + flags |= LWS_HTTP2_FLAG_END_STREAM; + } + return lws_http2_frame_write(wsi, n, flags, wsi->u.http2.my_stream_id, len, buf); } #endif @@ -519,12 +537,12 @@ LWS_VISIBLE int libwebsockets_serve_http_file_fragment( if (n < 0) return -1; /* caller will close */ if (n) { + wsi->u.http.filepos += n; m = libwebsocket_write(wsi, context->service_buffer, n, - LWS_WRITE_HTTP); + wsi->u.http.filepos == wsi->u.http.filelen ? LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP); if (m < 0) return -1; - wsi->u.http.filepos += m; if (m != n) /* adjust for what was not sent */ compatible_file_seek_cur(wsi->u.http.fd, m - n); diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index e0a66800..4631bfed 100755 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -626,6 +626,13 @@ struct http2_settings { }; enum http2_hpack_state { + + /* optional before first header block */ + HPKS_OPT_PADDING, + HKPS_OPT_E_DEPENDENCY, + HKPS_OPT_WEIGHT, + + /* header block */ HPKS_TYPE, HPKS_IDX_EXT, @@ -634,6 +641,9 @@ enum http2_hpack_state { HPKS_HLEN_EXT, HPKS_DATA, + + /* optional after last header block */ + HKPS_OPT_DISCARD_PADDING, }; enum http2_hpack_type { @@ -645,6 +655,21 @@ enum http2_hpack_type { HPKT_SIZE_5 }; +struct hpack_dt_entry { + int token; /* additions that don't map to a token are ignored */ + int arg_offset; + int arg_len; +}; + +struct hpack_dynamic_table { + struct hpack_dt_entry *entries; + char *args; + int pos; + int next; + int num_entries; + int args_length; +}; + struct _lws_http2_related { /* * having this first lets us also re-use all HTTP union code @@ -659,6 +684,8 @@ struct _lws_http2_related { struct libwebsocket *parent_wsi; struct libwebsocket *next_child_wsi; + struct hpack_dynamic_table *hpack_dyn_table; + unsigned int count; /* frame */ @@ -668,6 +695,7 @@ struct _lws_http2_related { unsigned char type; unsigned char flags; unsigned char frame_state; + unsigned char padding; unsigned int END_STREAM:1; unsigned int END_HEADERS:1; @@ -679,6 +707,7 @@ struct _lws_http2_related { unsigned int hpack_len; unsigned short hpack_pos; unsigned char hpack_m; + unsigned int hpack_e_dep; unsigned int huff:1; unsigned int value:1; @@ -923,6 +952,7 @@ user_callback_handle_rxflow(callback_function, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len); #ifdef LWS_USE_HTTP2 +LWS_EXTERN struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi); LWS_EXTERN int lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len); LWS_EXTERN void lws_http2_init(struct http2_settings *settings); diff --git a/lib/server.c b/lib/server.c index 6b9b84a0..f02e5dee 100644 --- a/lib/server.c +++ b/lib/server.c @@ -943,6 +943,24 @@ int lws_add_http_header_by_token(struct libwebsocket_context *context, return lws_add_http_header_by_name(context, wsi, name, value, length, p, end); } +int lws_add_http_header_content_length(struct libwebsocket_context *context, + struct libwebsocket *wsi, + unsigned long content_length, + unsigned char **p, + unsigned char *end) +{ + char b[24]; + int n; + + n = sprintf(b, "%lu", content_length); + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end)) + return 1; + wsi->u.http.content_length = content_length; + wsi->u.http.content_remain = content_length; + + return 0; +} + static const char *err400[] = { "Bad Request", "Unauthorized", @@ -1069,9 +1087,7 @@ LWS_VISIBLE int libwebsockets_serve_http_file( unsigned char *p = response; unsigned char *end = p + sizeof(context->service_buffer) - LWS_SEND_BUFFER_PRE_PADDING; - unsigned char clen[10]; int ret = 0; - int n; wsi->u.http.fd = lws_plat_open_file(file, &wsi->u.http.filelen); @@ -1088,8 +1104,7 @@ LWS_VISIBLE int libwebsockets_serve_http_file( return -1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end)) return -1; - n = sprintf((char *)clen, "%lu", (unsigned long)wsi->u.http.filelen); - if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, clen, n, &p, end)) + if (lws_add_http_header_content_length(context, wsi, wsi->u.http.filelen, &p, end)) return -1; if (other_headers) { diff --git a/test-server/test-server.c b/test-server/test-server.c index 421eca4e..04f0b34e 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -234,8 +234,7 @@ static int callback_http(struct libwebsocket_context *context, return 1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; - n = sprintf(b64, "%u", (unsigned int)stat_buf.st_size); - if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b64, n, &p, end)) + if (lws_add_http_header_content_length(context, wsi,stat_buf.st_size, &p, end)) return 1; if (lws_finalize_http_header(context, wsi, &p, end)) return 1; @@ -356,8 +355,10 @@ static int callback_http(struct libwebsocket_context *context, /* sent it all, close conn */ if (n == 0) goto flush_bail; + /* * To support HTTP2, must take care about preamble space + * and identify when we send the last frame */ m = libwebsocket_write(wsi, buffer + LWS_SEND_BUFFER_PRE_PADDING, @@ -365,6 +366,9 @@ static int callback_http(struct libwebsocket_context *context, if (m < 0) /* write failed, close conn */ goto bail; + /* + * http2 won't do this + */ if (m != n) /* partial write, adjust */ lseek(pss->fd, m - n, SEEK_CUR); @@ -378,6 +382,7 @@ static int callback_http(struct libwebsocket_context *context, break; } while (!lws_send_pipe_choked(wsi)); + libwebsocket_callback_on_writable(context, wsi); break; flush_bail: