[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
personal/stbuehler/mod-csrf
Glenn Strauss 2016-12-24 07:35:29 -05:00
parent 82feb70588
commit 37dac9a23c
3 changed files with 70 additions and 24 deletions

View File

@ -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;

View File

@ -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 */

View File

@ -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