diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2016-12-24 07:35:29 -0500 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2017-01-31 14:36:15 -0500 |
commit | 37dac9a23cbe5dd63c4b7bc19b01aa124f4740fd (patch) | |
tree | 989d3c8ae6cd5fb800e824bd37828da09778eccb | |
parent | 82feb705880d8f7c42118224be403dbfe6bf8d1c (diff) | |
download | lighttpd-git-37dac9a23cbe5dd63c4b7bc19b01aa124f4740fd.tar.gz |
[core] support Expect: 100-continue with HTTP/1.1 (fixes #377, #1017, #1953, #2438)
support Expect: 100-continue with HTTP/1.1 requests
Ignore config option server.reject-expect-100-with-417;
server.reject-expect-100-with-417 will be removed in a future release.
x-ref:
"Incorrect handling of the 100 (Continue) Status"
https://redmine.lighttpd.net/issues/377
"'Expect' header gives HTTP error 417"
https://redmine.lighttpd.net/issues/1017
"Improve DAV support to be able to handle git as a client"
https://redmine.lighttpd.net/issues/1953
"Change server.reject-expect-100-with-417 from flag to regular expression matching the URL"
https://redmine.lighttpd.net/issues/2438
-rw-r--r-- | src/connections-glue.c | 61 | ||||
-rw-r--r-- | src/request.c | 22 | ||||
-rwxr-xr-x | tests/request.t | 11 |
3 files changed, 70 insertions, 24 deletions
diff --git a/src/connections-glue.c b/src/connections-glue.c index 75319a64..94709473 100644 --- a/src/connections-glue.c +++ b/src/connections-glue.c @@ -339,6 +339,53 @@ int connection_write_chunkqueue(server *srv, connection *con, chunkqueue *cq, of return ret; } +static int connection_write_100_continue(server *srv, connection *con) { + /* Make best effort to send all or none of "HTTP/1.1 100 Continue" */ + /* (Note: also choosing not to update con->write_request_ts + * which differs from connections.c:connection_handle_write()) */ + static const char http_100_continue[] = "HTTP/1.1 100 Continue\r\n\r\n"; + chunkqueue *cq; + off_t written; + int rc; + + off_t max_bytes = + connection_write_throttle(srv, con, sizeof(http_100_continue)-1); + if (max_bytes < (off_t)sizeof(http_100_continue)-1) { + return 1; /* success; skip sending if throttled to partial */ + } + + cq = con->write_queue; + written = cq->bytes_out; + + chunkqueue_append_mem(cq,http_100_continue,sizeof(http_100_continue)-1); + rc = con->network_write(srv, con, cq, sizeof(http_100_continue)-1); + + written = cq->bytes_out - written; + con->bytes_written += written; + con->bytes_written_cur_second += written; + *(con->conf.global_bytes_per_second_cnt_ptr) += written; + + if (rc < 0) { + connection_set_state(srv, con, CON_STATE_ERROR); + return 0; /* error */ + } + + if (written == sizeof(http_100_continue)-1) { + chunkqueue_remove_finished_chunks(cq); + } else if (0 == written) { + /* skip sending 100 Continue if send would block */ + chunkqueue_mark_written(cq, sizeof(http_100_continue)-1); + con->is_writable = 0; + } + /* else partial write (unlikely), which can cause corrupt + * response if response is later cleared, e.g. sending errdoc. + * However, situation of partial write can occur here only on + * keep-alive request where client has sent pipelined request, + * and more than 0 chars were written, but fewer than 25 chars */ + + return 1; /* success; sent all or none of "HTTP/1.1 100 Continue" */ +} + handler_t connection_handle_read_post_state(server *srv, connection *con) { chunkqueue *cq = con->read_queue; chunkqueue *dst_cq = con->request_content_queue; @@ -362,6 +409,20 @@ handler_t connection_handle_read_post_state(server *srv, connection *con) { chunkqueue_remove_finished_chunks(cq); + /* Check for Expect: 100-continue in request headers + * if no request body received yet */ + if (chunkqueue_is_empty(cq) && 0 == dst_cq->bytes_in + && con->request.http_version != HTTP_VERSION_1_0 + && chunkqueue_is_empty(con->write_queue) && con->is_writable) { + data_string *ds = (data_string *)array_get_element(con->request.headers, "Expect"); + if (NULL != ds && 0 == buffer_caseless_compare(CONST_BUF_LEN(ds->value), CONST_STR_LEN("100-continue"))) { + buffer_reset(ds->value); /* unset value in request headers */ + if (!connection_write_100_continue(srv, con)) { + return HANDLER_ERROR; + } + } + } + if (-1 == con->request.content_length) { /*(Transfer-Encoding: chunked)*/ handler_t rc = connection_handle_read_post_chunked(srv, con, cq, dst_cq); if (HANDLER_GO_ON != rc) return rc; diff --git a/src/request.c b/src/request.c index 4065cd9f..3d7d096e 100644 --- a/src/request.c +++ b/src/request.c @@ -1020,28 +1020,6 @@ int http_request_parse(server *srv, connection *con) { array_insert_unique(con->request.headers, (data_unset *)ds); return 0; } - } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Expect")))) { - /* HTTP 2616 8.2.3 - * Expect: 100-continue - * - * -> (10.1.1) 100 (read content, process request, send final status-code) - * -> (10.4.18) 417 (close) - * - * (not handled at all yet, we always send 417 here) - * - * What has to be added ? - * 1. handling of chunked request body - * 2. out-of-order sending from the HTTP/1.1 100 Continue - * header - * - */ - - if (srv->srvconf.reject_expect_100_with_417 && 0 == buffer_caseless_compare(CONST_BUF_LEN(ds->value), CONST_STR_LEN("100-continue"))) { - con->http_status = 417; - con->keep_alive = 0; - array_insert_unique(con->request.headers, (data_unset *)ds); - return 0; - } } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) { if (reqline_host) { /* ignore all host: headers as we got the host in the request line */ diff --git a/tests/request.t b/tests/request.t index a073a7c1..70f5ee5f 100755 --- a/tests/request.t +++ b/tests/request.t @@ -110,13 +110,20 @@ EOF $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, '-HTTP-Content' => '' } ]; ok($tf->handle_http($t) == 0, 'HEAD request, file-not-found, query-string'); +# (expect 200 OK instead of 100 Continue since request body sent with request) +# (if we waited to send request body, would expect 100 Continue, first) $t->{REQUEST} = ( <<EOF -GET / HTTP/1.1 +POST /get-post-len.pl HTTP/1.1 +Host: www.example.org Connection: close +Content-Type: application/x-www-form-urlencoded +Content-Length: 4 Expect: 100-continue + +123 EOF ); -$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 417 } ]; +$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Continue, Expect'); # note Transfer-Encoding: chunked tests will fail with 411 Length Required if |