From 0b46b39e95ad77951176f09782138305ba34edf3 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Fri, 11 Mar 2016 13:08:28 +0300 Subject: [PATCH] http: fix "Expect: 100-continue" client side Instead of sending data always at the beginning of the request wait until the server will respond with "HTTP/1.1 100 Continue". Before this patch server do send "HTTP/1.1 100 Continue" but client always send post data even without waiting server response. P.S. this patch also touches some not 100% related tab-align issues. Covered-by: http/data_length_constraints Covered-by: http/read_on_write_error --- http-internal.h | 1 + http.c | 69 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 22 deletions(-) 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..21d080b8 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; @@ -2165,8 +2183,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 +2195,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 +2205,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 +2285,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 +2700,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) { -- 2.50.1