[core] support Transfer-Encoding: chunked req body (fixes #2156)
support Transfer-Encoding: chunked request body in conjunction with server.stream-request-body = 0 dynamic handlers will still return 411 Length Required if server.stream-request-body = 1 or 2 (!= 0) since CGI-like env requires CONTENT_LENGTH be set (and mod_proxy currently sends HTTP/1.0 requests to backends, and Content-Length recommended for robust interaction with backend) x-ref: "request: support Chunked Transfer Coding for HTTP PUT" https://redmine.lighttpd.net/issues/2156personal/stbuehler/mod-csrf
parent
f792d84cf9
commit
4d7f5737f1
|
@ -170,7 +170,8 @@ typedef struct {
|
|||
array *headers;
|
||||
|
||||
/* CONTENT */
|
||||
size_t content_length; /* returned by strtoul() */
|
||||
off_t content_length; /* returned by strtoll() */
|
||||
off_t te_chunked;
|
||||
|
||||
/* internal representation */
|
||||
int accept_encoding;
|
||||
|
|
|
@ -315,6 +315,221 @@ int connection_handle_read(server *srv, connection *con) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int connection_handle_read_post_cq_compact(chunkqueue *cq) {
|
||||
/* combine first mem chunk with next non-empty mem chunk
|
||||
* (loop if next chunk is empty) */
|
||||
chunk *c;
|
||||
while (NULL != (c = cq->first) && NULL != c->next) {
|
||||
buffer *mem = c->next->mem;
|
||||
off_t offset = c->next->offset;
|
||||
size_t blen = buffer_string_length(mem) - (size_t)offset;
|
||||
force_assert(c->type == MEM_CHUNK);
|
||||
force_assert(c->next->type == MEM_CHUNK);
|
||||
buffer_append_string_len(c->mem, mem->ptr+offset, blen);
|
||||
c->next->offset = c->offset;
|
||||
c->next->mem = c->mem;
|
||||
c->mem = mem;
|
||||
c->offset = offset + (off_t)blen;
|
||||
chunkqueue_remove_finished_chunks(cq);
|
||||
if (0 != blen) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int connection_handle_read_post_chunked_crlf(chunkqueue *cq) {
|
||||
/* caller might check chunkqueue_length(cq) >= 2 before calling here
|
||||
* to limit return value to either 1 for good or -1 for error */
|
||||
chunk *c;
|
||||
buffer *b;
|
||||
char *p;
|
||||
size_t len;
|
||||
|
||||
/* caller must have called chunkqueue_remove_finished_chunks(cq), so if
|
||||
* chunkqueue is not empty, it contains chunk with at least one char */
|
||||
if (chunkqueue_is_empty(cq)) return 0;
|
||||
|
||||
c = cq->first;
|
||||
b = c->mem;
|
||||
p = b->ptr+c->offset;
|
||||
if (p[0] != '\r') return -1; /* error */
|
||||
if (p[1] == '\n') return 1;
|
||||
len = buffer_string_length(b) - (size_t)c->offset;
|
||||
if (1 != len) return -1; /* error */
|
||||
|
||||
while (NULL != (c = c->next)) {
|
||||
b = c->mem;
|
||||
len = buffer_string_length(b) - (size_t)c->offset;
|
||||
if (0 == len) continue;
|
||||
p = b->ptr+c->offset;
|
||||
return (p[0] == '\n') ? 1 : -1; /* error if not '\n' */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
handler_t connection_handle_read_post_error(server *srv, connection *con, int http_status) {
|
||||
UNUSED(srv);
|
||||
|
||||
con->keep_alive = 0;
|
||||
|
||||
/*(do not change status if response headers already set and possibly sent)*/
|
||||
if (0 != con->bytes_header) return HANDLER_ERROR;
|
||||
|
||||
con->http_status = http_status;
|
||||
con->mode = DIRECT;
|
||||
chunkqueue_reset(con->write_queue);
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
static handler_t connection_handle_read_post_chunked(server *srv, connection *con, chunkqueue *cq, chunkqueue *dst_cq) {
|
||||
|
||||
/* con->conf.max_request_size is in kBytes */
|
||||
const off_t max_request_size = (off_t)con->conf.max_request_size << 10;
|
||||
off_t te_chunked = con->request.te_chunked;
|
||||
do {
|
||||
off_t len = cq->bytes_in - cq->bytes_out;
|
||||
|
||||
while (0 == te_chunked) {
|
||||
char *p;
|
||||
chunk *c = cq->first;
|
||||
force_assert(c->type == MEM_CHUNK);
|
||||
p = strchr(c->mem->ptr+c->offset, '\n');
|
||||
if (NULL != p) { /* found HTTP chunked header line */
|
||||
off_t hsz = p + 1 - (c->mem->ptr+c->offset);
|
||||
unsigned char *s = (unsigned char *)c->mem->ptr+c->offset;
|
||||
for (unsigned char u;(u=(unsigned char)hex2int(*s))!=0xFF;++s) {
|
||||
if (te_chunked > (~((off_t)-1) >> 4)) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"chunked data size too large -> 400");
|
||||
/* 400 Bad Request */
|
||||
return connection_handle_read_post_error(srv, con, 400);
|
||||
}
|
||||
te_chunked <<= 4;
|
||||
te_chunked |= u;
|
||||
}
|
||||
while (*s == ' ' || *s == '\t') ++s;
|
||||
if (*s != '\r' && *s != ';') {
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"chunked header invalid chars -> 400");
|
||||
/* 400 Bad Request */
|
||||
return connection_handle_read_post_error(srv, con, 400);
|
||||
}
|
||||
|
||||
if (hsz >= 1024) {
|
||||
/* prevent theoretical integer overflow
|
||||
* casting to (size_t) and adding 2 (for "\r\n") */
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"chunked header line too long -> 400");
|
||||
/* 400 Bad Request */
|
||||
return connection_handle_read_post_error(srv, con, 400);
|
||||
}
|
||||
|
||||
if (0 == te_chunked) {
|
||||
/* do not consume final chunked header until
|
||||
* (optional) trailers received along with
|
||||
* request-ending blank line "\r\n" */
|
||||
if (p[0] == '\r' && p[1] == '\n') {
|
||||
/*(common case with no trailers; final \r\n received)*/
|
||||
hsz += 2;
|
||||
}
|
||||
else {
|
||||
/* trailers or final CRLF crosses into next cq chunk */
|
||||
hsz -= 2;
|
||||
do {
|
||||
c = cq->first;
|
||||
p = strstr(c->mem->ptr+c->offset+hsz, "\r\n\r\n");
|
||||
} while (NULL == p
|
||||
&& connection_handle_read_post_cq_compact(cq));
|
||||
if (NULL == p) {
|
||||
/*(effectively doubles max request field size
|
||||
* potentially received by backend, if in the future
|
||||
* these trailers are added to request headers)*/
|
||||
if ((off_t)buffer_string_length(c->mem) - c->offset
|
||||
< srv->srvconf.max_request_field_size) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
/* ignore excessively long trailers;
|
||||
* disable keep-alive on connection */
|
||||
con->keep_alive = 0;
|
||||
}
|
||||
}
|
||||
hsz = p + 4 - (c->mem->ptr+c->offset);
|
||||
/* trailers currently ignored, but could be processed
|
||||
* here if 0 == con->conf.stream_request_body, taking
|
||||
* care to reject any fields forbidden in trailers,
|
||||
* making trailers available to CGI and other backends*/
|
||||
}
|
||||
chunkqueue_mark_written(cq, (size_t)hsz);
|
||||
con->request.content_length = dst_cq->bytes_in;
|
||||
break; /* done reading HTTP chunked request body */
|
||||
}
|
||||
|
||||
/* consume HTTP chunked header */
|
||||
chunkqueue_mark_written(cq, (size_t)hsz);
|
||||
len = cq->bytes_in - cq->bytes_out;
|
||||
|
||||
if (0 !=max_request_size
|
||||
&& (max_request_size < te_chunked
|
||||
|| max_request_size - te_chunked < dst_cq->bytes_in)) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "sos",
|
||||
"request-size too long:",
|
||||
dst_cq->bytes_in + te_chunked, "-> 413");
|
||||
/* 413 Payload Too Large */
|
||||
return connection_handle_read_post_error(srv, con, 413);
|
||||
}
|
||||
|
||||
te_chunked += 2; /*(for trailing "\r\n" after chunked data)*/
|
||||
|
||||
break; /* read HTTP chunked header */
|
||||
}
|
||||
|
||||
/*(likely better ways to handle chunked header crossing chunkqueue
|
||||
* chunks, but this situation is not expected to occur frequently)*/
|
||||
if ((off_t)buffer_string_length(c->mem) - c->offset >= 1024) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"chunked header line too long -> 400");
|
||||
/* 400 Bad Request */
|
||||
return connection_handle_read_post_error(srv, con, 400);
|
||||
}
|
||||
else if (!connection_handle_read_post_cq_compact(cq)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (0 == te_chunked) break;
|
||||
|
||||
if (te_chunked > 2) {
|
||||
if (len > te_chunked-2) len = te_chunked-2;
|
||||
if (dst_cq->bytes_in + te_chunked <= 64*1024) {
|
||||
/* avoid buffering request bodies <= 64k on disk */
|
||||
chunkqueue_steal(dst_cq, cq, len);
|
||||
}
|
||||
else if (0 != chunkqueue_steal_with_tempfiles(srv,dst_cq,cq,len)) {
|
||||
/* 500 Internal Server Error */
|
||||
return connection_handle_read_post_error(srv, con, 500);
|
||||
}
|
||||
te_chunked -= len;
|
||||
len = cq->bytes_in - cq->bytes_out;
|
||||
}
|
||||
|
||||
if (len < te_chunked) break;
|
||||
|
||||
if (2 == te_chunked) {
|
||||
if (-1 == connection_handle_read_post_chunked_crlf(cq)) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"chunked data missing end CRLF -> 400");
|
||||
/* 400 Bad Request */
|
||||
return connection_handle_read_post_error(srv, con, 400);
|
||||
}
|
||||
chunkqueue_mark_written(cq, 2);/*consume \r\n at end of chunk data*/
|
||||
te_chunked -= 2;
|
||||
}
|
||||
|
||||
} while (!chunkqueue_is_empty(cq));
|
||||
|
||||
con->request.te_chunked = te_chunked;
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
handler_t connection_handle_read_post_state(server *srv, connection *con) {
|
||||
chunkqueue *cq = con->read_queue;
|
||||
chunkqueue *dst_cq = con->request_content_queue;
|
||||
|
@ -337,18 +552,17 @@ handler_t connection_handle_read_post_state(server *srv, connection *con) {
|
|||
|
||||
chunkqueue_remove_finished_chunks(cq);
|
||||
|
||||
if (con->request.content_length <= 64*1024) {
|
||||
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;
|
||||
}
|
||||
else if (con->request.content_length <= 64*1024) {
|
||||
/* don't buffer request bodies <= 64k on disk */
|
||||
chunkqueue_steal(dst_cq, cq, (off_t)con->request.content_length - dst_cq->bytes_in);
|
||||
}
|
||||
else if (0 != chunkqueue_steal_with_tempfiles(srv, dst_cq, cq, (off_t)con->request.content_length - dst_cq->bytes_in)) {
|
||||
/* writing to temp file failed */
|
||||
con->http_status = 500; /* Internal Server Error */
|
||||
con->keep_alive = 0;
|
||||
con->mode = DIRECT;
|
||||
chunkqueue_reset(con->write_queue);
|
||||
|
||||
return HANDLER_FINISHED;
|
||||
return connection_handle_read_post_error(srv, con, 500); /* Internal Server Error */
|
||||
}
|
||||
|
||||
chunkqueue_remove_finished_chunks(cq);
|
||||
|
@ -362,12 +576,7 @@ handler_t connection_handle_read_post_state(server *srv, connection *con) {
|
|||
return HANDLER_GO_ON;
|
||||
} else if (is_closed) {
|
||||
#if 0
|
||||
con->http_status = 400; /* Bad Request */
|
||||
con->keep_alive = 0;
|
||||
con->mode = DIRECT;
|
||||
chunkqueue_reset(con->write_queue);
|
||||
|
||||
return HANDLER_FINISHED;
|
||||
return connection_handle_read_post_error(srv, con, 400); /* Bad Request */
|
||||
#endif
|
||||
return HANDLER_ERROR;
|
||||
} else {
|
||||
|
|
|
@ -298,8 +298,7 @@ static void connection_handle_response_end_state(server *srv, connection *con) {
|
|||
|
||||
if (con->state != CON_STATE_ERROR) srv->con_written++;
|
||||
|
||||
if ((con->request.content_length
|
||||
&& (off_t)con->request.content_length > con->request_content_queue->bytes_in)
|
||||
if (con->request.content_length != con->request_content_queue->bytes_in
|
||||
|| con->state == CON_STATE_ERROR) {
|
||||
/* request body is present and has not been read completely */
|
||||
con->keep_alive = 0;
|
||||
|
@ -766,6 +765,7 @@ int connection_reset(server *srv, connection *con) {
|
|||
CLEAN(http_content_type);
|
||||
#undef CLEAN
|
||||
con->request.content_length = 0;
|
||||
con->request.te_chunked = 0;
|
||||
|
||||
array_reset(con->request.headers);
|
||||
array_reset(con->environment);
|
||||
|
@ -1203,7 +1203,7 @@ int connection_state_machine(server *srv, connection *con) {
|
|||
plugins_call_connection_reset(srv, con);
|
||||
|
||||
if (con->request.content_length) {
|
||||
if ((off_t)con->request.content_length != chunkqueue_length(con->request_content_queue)) {
|
||||
if (con->request.content_length != con->request_content_queue->bytes_in) {
|
||||
con->keep_alive = 0;
|
||||
}
|
||||
con->request.content_length = 0;
|
||||
|
|
|
@ -18,6 +18,7 @@ const char * connection_get_short_state(connection_state_t state);
|
|||
int connection_state_machine(server *srv, connection *con);
|
||||
int connection_handle_read(server *srv, connection *con);
|
||||
handler_t connection_handle_read_post_state(server *srv, connection *con);
|
||||
handler_t connection_handle_read_post_error(server *srv, connection *con, int http_status);
|
||||
void connection_response_reset(server *srv, connection *con);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -94,6 +94,7 @@ typedef struct {
|
|||
|
||||
buffer *response;
|
||||
buffer *response_header;
|
||||
buffer *cgi_handler; /* dumb pointer */
|
||||
plugin_config conf;
|
||||
} handler_ctx;
|
||||
|
||||
|
@ -542,7 +543,7 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
|
|||
buffer_copy_buffer(con->request.uri, ds->value);
|
||||
|
||||
if (con->request.content_length) {
|
||||
if ((off_t)con->request.content_length != chunkqueue_length(con->request_content_queue)) {
|
||||
if (con->request.content_length != con->request_content_queue->bytes_in) {
|
||||
con->keep_alive = 0;
|
||||
}
|
||||
con->request.content_length = 0;
|
||||
|
@ -1055,7 +1056,7 @@ static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) {
|
|||
}
|
||||
} else {
|
||||
off_t cqlen = cq->bytes_in - cq->bytes_out;
|
||||
if (cq->bytes_in < (off_t)con->request.content_length && cqlen < 65536 - 16384) {
|
||||
if (cq->bytes_in != con->request.content_length && cqlen < 65536 - 16384) {
|
||||
/*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
|
||||
if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) {
|
||||
con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN;
|
||||
|
@ -1330,6 +1331,7 @@ URIHANDLER_FUNC(cgi_is_handled) {
|
|||
stat_cache_entry *sce = NULL;
|
||||
struct stat stbuf;
|
||||
struct stat *st;
|
||||
buffer *cgi_handler;
|
||||
|
||||
if (con->mode != DIRECT) return HANDLER_GO_ON;
|
||||
|
||||
|
@ -1349,10 +1351,11 @@ URIHANDLER_FUNC(cgi_is_handled) {
|
|||
if (!S_ISREG(st->st_mode)) return HANDLER_GO_ON;
|
||||
if (p->conf.execute_x_only == 1 && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return HANDLER_GO_ON;
|
||||
|
||||
if (NULL != cgi_get_handler(p->conf.cgi, fn)) {
|
||||
if (NULL != (cgi_handler = cgi_get_handler(p->conf.cgi, fn))) {
|
||||
handler_ctx *hctx = cgi_handler_ctx_init();
|
||||
hctx->remote_conn = con;
|
||||
hctx->plugin_data = p;
|
||||
hctx->cgi_handler = cgi_handler;
|
||||
memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
|
||||
con->plugin_ctx[p->id] = hctx;
|
||||
con->mode = p->id;
|
||||
|
@ -1457,13 +1460,19 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
|
|||
}
|
||||
}
|
||||
if (r != HANDLER_GO_ON) return r;
|
||||
|
||||
/* CGI environment requires that Content-Length be set.
|
||||
* Send 411 Length Required if Content-Length missing.
|
||||
* (occurs here if client sends Transfer-Encoding: chunked
|
||||
* and module is flagged to stream request body to backend) */
|
||||
if (-1 == con->request.content_length) {
|
||||
return connection_handle_read_post_error(srv, con, 411);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == hctx->fd) {
|
||||
buffer *handler = cgi_get_handler(hctx->conf.cgi, con->physical.path);
|
||||
if (!handler) return HANDLER_GO_ON; /*(should not happen; checked in cgi_is_handled())*/
|
||||
if (cgi_create_env(srv, con, p, hctx, handler)) {
|
||||
if (cgi_create_env(srv, con, p, hctx, hctx->cgi_handler)) {
|
||||
con->http_status = 500;
|
||||
con->mode = DIRECT;
|
||||
|
||||
|
|
|
@ -3034,6 +3034,14 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) {
|
|||
}
|
||||
}
|
||||
if (r != HANDLER_GO_ON) return r;
|
||||
|
||||
/* CGI environment requires that Content-Length be set.
|
||||
* Send 411 Length Required if Content-Length missing.
|
||||
* (occurs here if client sends Transfer-Encoding: chunked
|
||||
* and module is flagged to stream request body to backend) */
|
||||
if (-1 == con->request.content_length) {
|
||||
return connection_handle_read_post_error(srv, con, 411);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1169,6 +1169,15 @@ SUBREQUEST_FUNC(mod_proxy_handle_subrequest) {
|
|||
}
|
||||
}
|
||||
if (r != HANDLER_GO_ON) return r;
|
||||
|
||||
/* mod_proxy sends HTTP/1.0 request and ideally should send
|
||||
* Content-Length with request if request body is present, so
|
||||
* send 411 Length Required if Content-Length missing.
|
||||
* (occurs here if client sends Transfer-Encoding: chunked
|
||||
* and module is flagged to stream request body to backend) */
|
||||
if (-1 == con->request.content_length) {
|
||||
return connection_handle_read_post_error(srv, con, 411);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2468,6 +2468,14 @@ SUBREQUEST_FUNC(mod_scgi_handle_subrequest) {
|
|||
}
|
||||
}
|
||||
if (r != HANDLER_GO_ON) return r;
|
||||
|
||||
/* SCGI requires that Content-Length be set.
|
||||
* Send 411 Length Required if Content-Length missing.
|
||||
* (occurs here if client sends Transfer-Encoding: chunked
|
||||
* and module is flagged to stream request body to backend) */
|
||||
if (-1 == con->request.content_length) {
|
||||
return connection_handle_read_post_error(srv, con, 411);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2736,18 +2744,6 @@ static handler_t scgi_check_extension(server *srv, connection *con, void *p_d, i
|
|||
/* a note about no handler is not sent yet */
|
||||
extension->note_is_sent = 0;
|
||||
|
||||
/* SCGI requires that Content-Length be set.
|
||||
* Send 411 Length Required if Content-Length missing.
|
||||
* (Alternatively, collect full request body before proceeding
|
||||
* in mod_scgi_handle_subrequest()) */
|
||||
if (0 == con->request.content_length
|
||||
&& array_get_element(con->request.headers, "Transfer-Encoding")) {
|
||||
con->keep_alive = 0;
|
||||
con->http_status = 411; /* Length Required */
|
||||
con->mode = DIRECT;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
/*
|
||||
* if check-local is disabled, use the uri.path handler
|
||||
*
|
||||
|
|
|
@ -954,7 +954,7 @@ int http_request_parse(server *srv, connection *con) {
|
|||
|
||||
} else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) {
|
||||
char *err;
|
||||
unsigned long int r;
|
||||
off_t r;
|
||||
size_t j, jlen;
|
||||
|
||||
if (con_length_set) {
|
||||
|
@ -987,9 +987,9 @@ int http_request_parse(server *srv, connection *con) {
|
|||
}
|
||||
}
|
||||
|
||||
r = strtoul(ds->value->ptr, &err, 10);
|
||||
r = strtoll(ds->value->ptr, &err, 10);
|
||||
|
||||
if (*err == '\0') {
|
||||
if (*err == '\0' && r >= 0) {
|
||||
con_length_set = 1;
|
||||
con->request.content_length = r;
|
||||
} else {
|
||||
|
@ -1236,6 +1236,38 @@ int http_request_parse(server *srv, connection *con) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
data_string *ds = (data_string *)array_get_element(con->request.headers, "Transfer-Encoding");
|
||||
if (NULL != ds) {
|
||||
if (con->request.http_version == HTTP_VERSION_1_0) {
|
||||
log_error_write(srv, __FILE__, __LINE__, "s",
|
||||
"HTTP/1.0 with Transfer-Encoding (bad HTTP/1.0 proxy?) -> 400");
|
||||
con->keep_alive = 0;
|
||||
con->http_status = 400; /* Bad Request */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (0 != strcasecmp(ds->value->ptr, "chunked")) {
|
||||
/* Transfer-Encoding might contain additional encodings,
|
||||
* which are not currently supported by lighttpd */
|
||||
con->keep_alive = 0;
|
||||
con->http_status = 501; /* Not Implemented */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* reset value for Transfer-Encoding, a hop-by-hop header,
|
||||
* which must not be blindly forwarded to backends */
|
||||
buffer_reset(ds->value); /* headers with empty values are ignored */
|
||||
|
||||
con_length_set = 1;
|
||||
con->request.content_length = -1;
|
||||
|
||||
/*(note: ignore whether or not Content-Length was provided)*/
|
||||
ds = (data_string *)array_get_element(con->request.headers, "Content-Length");
|
||||
if (NULL != ds) buffer_reset(ds->value); /* headers with empty values are ignored */
|
||||
}
|
||||
}
|
||||
|
||||
switch(con->request.http_method) {
|
||||
case HTTP_METHOD_GET:
|
||||
case HTTP_METHOD_HEAD:
|
||||
|
@ -1264,31 +1296,12 @@ int http_request_parse(server *srv, connection *con) {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
/* require Content-Length if request contains request body */
|
||||
if (array_get_element(con->request.headers, "Transfer-Encoding")) {
|
||||
/* presence of Transfer-Encoding in request headers requires "chunked"
|
||||
* be final encoding in HTTP/1.1. Return 411 Length Required as
|
||||
* lighttpd does not support request input transfer-encodings */
|
||||
con->keep_alive = 0;
|
||||
con->http_status = 411; /* 411 Length Required */
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* check if we have read post data */
|
||||
if (con_length_set) {
|
||||
/* don't handle more the SSIZE_MAX bytes in content-length */
|
||||
if (con->request.content_length > SSIZE_MAX) {
|
||||
con->http_status = 413;
|
||||
con->keep_alive = 0;
|
||||
|
||||
log_error_write(srv, __FILE__, __LINE__, "sos",
|
||||
"request-size too long:", (off_t) con->request.content_length, "-> 413");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we have content */
|
||||
if (con->request.content_length != 0) {
|
||||
return 1;
|
||||
|
|
117
tests/request.t
117
tests/request.t
|
@ -8,7 +8,7 @@ BEGIN {
|
|||
|
||||
use strict;
|
||||
use IO::Socket;
|
||||
use Test::More tests => 52;
|
||||
use Test::More tests => 59;
|
||||
use LightyTest;
|
||||
|
||||
my $tf = LightyTest->new();
|
||||
|
@ -119,6 +119,121 @@ EOF
|
|||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 417 } ];
|
||||
ok($tf->handle_http($t) == 0, 'Continue, Expect');
|
||||
|
||||
# note Transfer-Encoding: chunked tests will fail with 411 Length Required if
|
||||
# server.stream-request-body != 0 in lighttpd.conf
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
a
|
||||
0123456789
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, lc hex');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
A
|
||||
0123456789
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, uc hex');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
a
|
||||
0123456789
|
||||
0
|
||||
Test-Trailer: testing
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, with trailer');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
a; comment
|
||||
0123456789
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, chunked header comment');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
az
|
||||
0123456789
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; bad chunked header');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
a
|
||||
0123456789xxxxxxxx
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; mismatch chunked header size and chunked data size');
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
POST /get-post-len.pl HTTP/1.1
|
||||
Host: www.example.org
|
||||
Connection: close
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
a ; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
0123456789
|
||||
0
|
||||
|
||||
EOF
|
||||
);
|
||||
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ];
|
||||
ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; chunked header too long');
|
||||
|
||||
## ranges
|
||||
|
||||
$t->{REQUEST} = ( <<EOF
|
||||
|
|
Loading…
Reference in New Issue