diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 973d13cb..46a3fbf9 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -364,7 +364,11 @@ typedef enum { /** * The WINDOW_UPDATE frame. */ - NGHTTP2_WINDOW_UPDATE = 9 + NGHTTP2_WINDOW_UPDATE = 9, + /** + * The CONTINUATION frame. + */ + NGHTTP2_CONTINUATION = 10 } nghttp2_frame_type; /** diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 125bd88b..12b558d8 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -250,10 +250,6 @@ int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame, const uint8_t *payload, size_t payloadlen) { - /* TODO Return error if header continuation is used for now */ - if((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) { - return NGHTTP2_ERR_PROTO; - } if(frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { assert(payloadlen == 4); frame->pri = nghttp2_get_uint32(payload) & NGHTTP2_PRIORITY_MASK; @@ -409,10 +405,6 @@ int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame, const uint8_t *payload, size_t payloadlen) { - /* TODO Return error if header continuation is used for now */ - if((frame->hd.flags & NGHTTP2_FLAG_END_PUSH_PROMISE) == 0) { - return NGHTTP2_ERR_PROTO; - } frame->promised_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; frame->nva = NULL; diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index a6822dc4..385b319a 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -3398,6 +3398,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, size_t readlen; int rv; int busy = 0; + nghttp2_frame_hd cont_hd; for(;;) { switch(iframe->state) { @@ -3408,6 +3409,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, if(iframe->left) { return in - first; } + nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->buf); iframe->payloadleft = iframe->frame.hd.length; iframe->buflen = 0; @@ -3505,6 +3507,15 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->state = NGHTTP2_IB_READ_NBYTE; iframe->left = 8; break; + case NGHTTP2_CONTINUATION: + rv = nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + if(nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; default: busy = 1; iframe->state = NGHTTP2_IB_IGN_PAYLOAD; @@ -3592,6 +3603,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, readlen, iframe->payloadleft)); rv = inflate_header_block(session, &iframe->frame, &readlen, (uint8_t*)in, readlen, + (iframe->frame.hd.flags & + NGHTTP2_FLAG_END_HEADERS) && iframe->payloadleft == readlen, iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK); if(nghttp2_is_fatal(rv)) { @@ -3620,7 +3633,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, return rv; } } - nghttp2_inbound_frame_reset(session); + if((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) { + iframe->left = NGHTTP2_FRAME_HEAD_LENGTH; + iframe->error_code = 0; + iframe->buflen = 0; + if(iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION; + } else { + iframe->state = NGHTTP2_IB_IGN_CONTINUATION; + } + } else { + nghttp2_inbound_frame_reset(session); + } break; case NGHTTP2_IB_IGN_PAYLOAD: readlen = inbound_frame_payload_readlen(iframe, in, last); @@ -3688,6 +3712,52 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } nghttp2_inbound_frame_reset(session); break; + case NGHTTP2_IB_EXPECT_CONTINUATION: + case NGHTTP2_IB_IGN_CONTINUATION: +#ifdef DEBUGBUILD + if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + fprintf(stderr, "[IB_EXPECT_CONTINUATION]\n"); + } else { + fprintf(stderr, "[IB_IGN_CONTINUATION]\n"); + } +#endif /* DEBUGBUILD */ + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + if(iframe->left) { + return in - first; + } + nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->buf); + iframe->payloadleft = cont_hd.length; + if(cont_hd.type != NGHTTP2_CONTINUATION || + cont_hd.stream_id != iframe->frame.hd.stream_id) { + rv = nghttp2_session_terminate_session(session, + NGHTTP2_PROTOCOL_ERROR); + if(nghttp2_is_fatal(rv)) { + return rv; + } + + rv = session_call_on_end_headers(session, &iframe->frame, + NGHTTP2_PROTOCOL_ERROR); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + /* Mark inflater bad so that we won't perform further decoding */ + session->hd_inflater.ctx.bad = 1; + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + if(cont_hd.flags & NGHTTP2_FLAG_END_HEADERS) { + iframe->frame.hd.flags |= NGHTTP2_FLAG_END_HEADERS; + } + busy = 1; + if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + } else { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + } + break; case NGHTTP2_IB_READ_DATA: DEBUGF(fprintf(stderr, "[IB_READ_DATA]\n")); readlen = inbound_frame_payload_readlen(iframe, in, last); diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index d902958c..675bb5c4 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -81,6 +81,8 @@ typedef enum { NGHTTP2_IB_FRAME_SIZE_ERROR, NGHTTP2_IB_READ_SETTINGS, NGHTTP2_IB_READ_GOAWAY_DEBUG, + NGHTTP2_IB_EXPECT_CONTINUATION, + NGHTTP2_IB_IGN_CONTINUATION, NGHTTP2_IB_READ_DATA, NGHTTP2_IB_IGN_DATA } nghttp2_inbound_state; diff --git a/tests/main.c b/tests/main.c index 6ddd70dc..e7f97334 100644 --- a/tests/main.c +++ b/tests/main.c @@ -83,6 +83,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_eof) || !CU_add_test(pSuite, "session_recv_data", test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_continuation", + test_nghttp2_session_recv_continuation) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 5b55324c..99fa5f93 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -660,6 +660,122 @@ void test_nghttp2_session_recv_data(void) nghttp2_session_del(session); } +void test_nghttp2_session_recv_continuation(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv nv1[] = { + MAKE_NV("method", "GET"), + MAKE_NV("path", "/") + }; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + uint8_t *framedata = NULL; + size_t framedatacap = 0; + size_t framedatalen; + size_t framedataoff; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + uint8_t data[1024]; + size_t datalen; + nghttp2_frame_hd cont_hd; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_header_callback = on_header_callback; + callbacks.on_end_headers_callback = on_end_headers_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST); + + /* Make 1 HEADERS and insert CONTINUATION header */ + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, + 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + framedatalen = nghttp2_frame_pack_headers(&framedata, &framedatacap, + &frame.headers, + &deflater); + nghttp2_frame_headers_free(&frame.headers); + + memcpy(data, framedata, 9); + datalen = 9; + framedataoff = NGHTTP2_FRAME_HEAD_LENGTH + 1; + + nghttp2_put_uint16be(data, 1); + + /* First CONTINUATION, 2 bytes */ + cont_hd.length = 2; + cont_hd.type = NGHTTP2_CONTINUATION; + cont_hd.flags = NGHTTP2_FLAG_NONE; + cont_hd.stream_id = 1; + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HEAD_LENGTH; + + memcpy(data + datalen, framedata + framedataoff, cont_hd.length); + datalen += cont_hd.length; + framedataoff += cont_hd.length; + + /* Second CONTINUATION, rest of the bytes */ + cont_hd.length = framedatalen - framedataoff; + cont_hd.flags = NGHTTP2_FLAG_END_HEADERS; + cont_hd.stream_id = 1; + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HEAD_LENGTH; + + memcpy(data + datalen, framedata + framedataoff, cont_hd.length); + datalen += cont_hd.length; + framedataoff += cont_hd.length; + + assert(framedataoff == framedatalen); + + ud.header_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT(rv == datalen); + CU_ASSERT(2 == ud.header_cb_called); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Expecting CONTINUATION, but get the other frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, NGHTTP2_HD_SIDE_REQUEST); + + /* HEADERS without END_HEADERS flag */ + nvlen = nghttp2_nv_array_copy(&nva, nv1, ARRLEN(nv1)); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, + 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + framedatalen = nghttp2_frame_pack_headers(&framedata, &framedatacap, + &frame.headers, + &deflater); + nghttp2_frame_headers_free(&frame.headers); + memcpy(data, framedata, framedatalen); + datalen = framedatalen; + + /* Followed by PRIORITY */ + nghttp2_frame_priority_init(&frame.priority, 1, 0); + framedatalen = nghttp2_frame_pack_priority(&framedata, &framedatacap, + &frame.priority); + memcpy(data + datalen, framedata, framedatalen); + datalen += framedatalen; + + ud.end_headers_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT(datalen == rv); + + CU_ASSERT(1 == ud.end_headers_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == + OB_CTRL_TYPE(nghttp2_session_get_next_ob_item(session))); + + free(framedata); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index d285f8dc..49f14328 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -30,6 +30,7 @@ void test_nghttp2_session_recv_invalid_stream_id(void); void test_nghttp2_session_recv_invalid_frame(void); void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_data(void); +void test_nghttp2_session_recv_continuation(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void);