diff --git a/http-internal.h b/http-internal.h index 31002e0d..9647347c 100644 --- a/http-internal.h +++ b/http-internal.h @@ -190,6 +190,7 @@ enum message_read_status evhttp_parse_firstline_(struct evhttp_request *, struct enum message_read_status evhttp_parse_headers_(struct evhttp_request *, struct evbuffer*); void evhttp_start_read_(struct evhttp_connection *); +void evhttp_start_write_(struct evhttp_connection *); /* response sending HTML the data in the buffer */ void evhttp_response_code_(struct evhttp_request *, int, const char *); diff --git a/http.c b/http.c index f7241020..2c69dbc4 100644 --- a/http.c +++ b/http.c @@ -580,6 +580,23 @@ evhttp_make_header_response(struct evhttp_connection *evcon, } } +enum expect { NO, CONTINUE, OTHER }; +static enum expect evhttp_have_expect(struct evhttp_request *req, int input) +{ + const char *expect; + struct evkeyvalq *h = input ? req->input_headers : req->output_headers; + + if (!req->kind == EVHTTP_REQUEST || !REQ_VERSION_ATLEAST(req, 1, 1)) + return NO; + + expect = evhttp_find_header(h, "Expect"); + if (!expect) + return NO; + + return !evutil_ascii_strcasecmp(expect, "100-continue") ? CONTINUE : OTHER; +} + + /** Generate all headers appropriate for sending the http request in req (or * the response, if we're sending a response), and write them to evcon's * bufferevent. Also writes all data from req->output_buffer */ @@ -605,14 +622,12 @@ evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) } evbuffer_add(output, "\r\n", 2); - if (evbuffer_get_length(req->output_buffer) > 0) { + if (evhttp_have_expect(req, 0) != CONTINUE && + evbuffer_get_length(req->output_buffer)) { /* * For a request, we add the POST data, for a reply, this * is the regular data. */ - /* XXX We might want to support waiting (a limited amount of - time) for a continue status line from the server before - sending POST/PUT message bodies. */ evbuffer_add_buffer(output, req->output_buffer); } } @@ -1173,12 +1188,15 @@ evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) { /* This is after writing the request to the server */ struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + struct evbuffer *output = bufferevent_get_output(evcon->bufev); EVUTIL_ASSERT(req != NULL); EVUTIL_ASSERT(evcon->state == EVCON_WRITING); - /* We need to wait until we've written all of our output data before we can continue */ - if (evbuffer_get_length(bufferevent_get_output(evcon->bufev)) > 0) { return; } + /* We need to wait until we've written all of our output data before we can + * continue */ + if (evbuffer_get_length(output) > 0) + return; /* We are done writing our header and are now expecting the response */ req->kind = EVHTTP_RESPONSE; @@ -1649,6 +1667,8 @@ evhttp_parse_response_line(struct evhttp_request *req, char *line) return (-1); } + if (req->response_code_line != NULL) + mm_free(req->response_code_line); if ((req->response_code_line = mm_strdup(readable)) == NULL) { event_warn("%s: strdup", __func__); return (-1); @@ -2165,8 +2185,7 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) req->ntoread = -1; } else { if (evhttp_get_body_length(req) == -1) { - evhttp_connection_fail_(evcon, - EVREQ_HTTP_INVALID_HEADER); + evhttp_connection_fail_(evcon, EVREQ_HTTP_INVALID_HEADER); return; } if (req->kind == EVHTTP_REQUEST && req->ntoread < 1) { @@ -2178,12 +2197,8 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) } /* Should we send a 100 Continue status line? */ - if (req->kind == EVHTTP_REQUEST && REQ_VERSION_ATLEAST(req, 1, 1)) { - const char *expect; - - expect = evhttp_find_header(req->input_headers, "Expect"); - if (expect) { - if (!evutil_ascii_strcasecmp(expect, "100-continue")) { + switch (evhttp_have_expect(req, 1)) { + case CONTINUE: /* XXX It would be nice to do some sanity checking here. Does the resource exist? Should the resource accept post requests? If @@ -2192,19 +2207,19 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) send their message body. */ if (req->ntoread > 0) { /* ntoread is ev_int64_t, max_body_size is ev_uint64_t */ - if ((req->evcon->max_body_size <= EV_INT64_MAX) && (ev_uint64_t)req->ntoread > req->evcon->max_body_size) { + if ((req->evcon->max_body_size <= EV_INT64_MAX) && + (ev_uint64_t)req->ntoread > req->evcon->max_body_size) { evhttp_lingering_fail(evcon, req); return; } } if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev))) evhttp_send_continue(evcon, req); - } else { - evhttp_send_error(req, HTTP_EXPECTATIONFAILED, - NULL); - return; - } - } + break; + case OTHER: + evhttp_send_error(req, HTTP_EXPECTATIONFAILED, NULL); + return; + case NO: break; } evhttp_read_body(evcon, req); @@ -2272,7 +2287,9 @@ evhttp_read_header(struct evhttp_connection *evcon, case EVHTTP_RESPONSE: /* Start over if we got a 100 Continue response. */ if (req->response_code == 100) { - evhttp_start_read_(evcon); + struct evbuffer *output = bufferevent_get_output(evcon->bufev); + evbuffer_add_buffer(output, req->output_buffer); + evhttp_start_write_(evcon); return; } if (!evhttp_response_needs_body(req)) { @@ -2685,6 +2702,16 @@ evhttp_start_read_(struct evhttp_connection *evcon) } } +void +evhttp_start_write_(struct evhttp_connection *evcon) +{ + bufferevent_disable(evcon->bufev, EV_WRITE); + bufferevent_enable(evcon->bufev, EV_READ); + + evcon->state = EVCON_WRITING; + evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); +} + static void evhttp_send_done(struct evhttp_connection *evcon, void *arg) { diff --git a/test/regress_http.c b/test/regress_http.c index f11f8cae..1280a8db 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -3768,7 +3768,6 @@ http_data_length_constraints_test_done(struct evhttp_request *req, void *arg) end: event_base_loopexit(arg, NULL); } - static void http_large_entity_test_done(struct evhttp_request *req, void *arg) { @@ -3777,24 +3776,48 @@ http_large_entity_test_done(struct evhttp_request *req, void *arg) end: event_base_loopexit(arg, NULL); } +static void +http_failed_request_done(struct evhttp_request *req, void *arg) +{ + tt_assert(!req); +end: + event_base_loopexit(arg, NULL); +} +static void +http_expectation_failed_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_EXPECTATIONFAILED); +end: + event_base_loopexit(arg, NULL); +} static void -http_data_length_constraints_test(void *arg) +http_data_length_constraints_test_impl(void *arg, int read_on_write_error) { struct basic_test_data *data = arg; ev_uint16_t port = 0; struct evhttp_connection *evcon = NULL; struct evhttp_request *req = NULL; char *long_str = NULL; - size_t size = (1<<20) * 3; + const size_t continue_size = 1<<20; + const size_t size = (1<<20) * 3; + void (*cb)(struct evhttp_request *, void *); test_ok = 0; + cb = http_failed_request_done; + if (read_on_write_error) + cb = http_data_length_constraints_test_done; http = http_setup(&port, data->base, 0); + tt_assert(continue_size < size); + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); tt_assert(evcon); - tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_READ_ON_WRITE_ERROR)); + + if (read_on_write_error) + tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_READ_ON_WRITE_ERROR)); /* also bind to local host */ evhttp_connection_set_local_address(evcon, "127.0.0.1"); @@ -3830,8 +3853,10 @@ http_data_length_constraints_test(void *arg) } event_base_dispatch(data->base); + if (read_on_write_error) + cb = http_large_entity_test_done; evhttp_set_max_body_size(http, size - 2); - req = evhttp_request_new(http_large_entity_test_done, data->base); + req = evhttp_request_new(cb, data->base); evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { @@ -3848,6 +3873,27 @@ http_data_length_constraints_test(void *arg) } event_base_dispatch(data->base); + req = evhttp_request_new(http_dispatcher_test_done, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); + long_str[continue_size] = '\0'; + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + if (read_on_write_error) + cb = http_expectation_failed_done; + req = evhttp_request_new(cb, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "101-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + test_ok = 1; end: if (evcon) @@ -3857,6 +3903,10 @@ http_data_length_constraints_test(void *arg) if (long_str) free(long_str); } +static void http_data_length_constraints_test(void *arg) +{ http_data_length_constraints_test_impl(arg, 0); } +static void http_read_on_write_error_test(void *arg) +{ http_data_length_constraints_test_impl(arg, 1); } static void http_large_entity_non_lingering_test_done(struct evhttp_request *req, void *arg) @@ -3903,7 +3953,6 @@ http_lingering_close_test_impl(void *arg, int lingering) req = evhttp_request_new(cb, data->base); tt_assert(req); evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); - evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { tt_abort_msg("Couldn't make request"); @@ -4367,6 +4416,7 @@ struct testcase_t http_testcases[] = { TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, HTTP(data_length_constraints), + HTTP(read_on_write_error), HTTP(non_lingering_close), HTTP(lingering_close),