summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2016-12-24 07:35:29 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2017-01-31 14:36:15 -0500
commit37dac9a23cbe5dd63c4b7bc19b01aa124f4740fd (patch)
tree989d3c8ae6cd5fb800e824bd37828da09778eccb
parent82feb705880d8f7c42118224be403dbfe6bf8d1c (diff)
downloadlighttpd-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.c61
-rw-r--r--src/request.c22
-rwxr-xr-xtests/request.t11
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