diff --git a/include/http_protocol.h b/include/http_protocol.h index 415270b..67fa02f 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -502,6 +502,23 @@ AP_DECLARE(int) ap_should_client_block(request_rec *r); */ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz); +/* + * Map specific APR codes returned by the filter stack to HTTP error + * codes, or the default status code provided. Use it as follows: + * + * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + * + * If the filter has already handled the error, AP_FILTER_ERROR will + * be returned, which is cleanly passed through. + * + * These mappings imply that the filter stack is reading from the + * downstream client, the proxy will map these codes differently. + * @param rv APR status code + * @param status Default HTTP code should the APR code not be recognised + * @return Mapped HTTP status code + */ +AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status); + /** * In HTTP/1.1, any method can have a body. However, most GET handlers * wouldn't know what to do with a request body if they received one. diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 1dde402..ed8749f 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -57,24 +57,29 @@ APLOG_USE_MODULE(http); -#define INVALID_CHAR -2 - -static long get_chunk_size(char *); - -typedef struct http_filter_ctx { +typedef struct http_filter_ctx +{ apr_off_t remaining; apr_off_t limit; apr_off_t limit_used; - enum { - BODY_NONE, - BODY_LENGTH, - BODY_CHUNK, - BODY_CHUNK_PART + apr_int32_t chunk_used; + apr_int32_t chunk_bws; + apr_int32_t chunkbits; + enum + { + BODY_NONE, /* streamed data */ + BODY_LENGTH, /* data constrained by content length */ + BODY_CHUNK, /* chunk expected */ + BODY_CHUNK_PART, /* chunk digits */ + BODY_CHUNK_EXT, /* chunk extension */ + BODY_CHUNK_CR, /* got space(s) after digits, expect [CR]LF or ext */ + BODY_CHUNK_LF, /* got CR after digits or ext, expect LF */ + BODY_CHUNK_DATA, /* data constrained by chunked encoding */ + BODY_CHUNK_END, /* chunked data terminating CRLF */ + BODY_CHUNK_END_LF, /* got CR after data, expect LF */ + BODY_CHUNK_TRAILER /* trailers */ } state; - int eos_sent; - char chunk_ln[32]; - char *pos; - apr_off_t linesize; + unsigned int eos_sent :1; apr_bucket_brigade *bb; } http_ctx_t; @@ -87,6 +92,23 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, apr_bucket_brigade *bb = ctx->bb; apr_brigade_cleanup(bb); + + if (f->r->proxyreq == PROXYREQ_RESPONSE) { + switch (http_error) { + case HTTP_REQUEST_ENTITY_TOO_LARGE: + return APR_ENOSPC; + + case HTTP_REQUEST_TIME_OUT: + return APR_INCOMPLETE; + + case HTTP_NOT_IMPLEMENTED: + return APR_ENOTIMPL; + + default: + return APR_EGENERAL; + } + } + e = ap_bucket_error_create(http_error, NULL, f->r->pool, f->c->bucket_alloc); @@ -102,117 +124,154 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, return ap_pass_brigade(f->r->output_filters, bb); } -static apr_status_t get_remaining_chunk_line(http_ctx_t *ctx, - apr_bucket_brigade *b, - int linelimit) +/** + * Parse a chunk line with optional extension, detect overflow. + * There are two error cases: + * 1) If the conversion would require too many bits, APR_EGENERAL is returned. + * 2) If the conversion used the correct number of bits, but an overflow + * caused only the sign bit to flip, then APR_ENOSPC is returned. + * In general, any negative number can be considered an overflow error. + */ +static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, + apr_size_t len, int linelimit) { - apr_status_t rv; - apr_off_t brigade_length; - apr_bucket *e; - const char *lineend; - apr_size_t len = 0; + apr_size_t i = 0; - /* - * As the brigade b should have been requested in mode AP_MODE_GETLINE - * all buckets in this brigade are already some type of memory - * buckets (due to the needed scanning for LF in mode AP_MODE_GETLINE) - * or META buckets. - */ - rv = apr_brigade_length(b, 0, &brigade_length); - if (rv != APR_SUCCESS) { - return rv; - } - /* Sanity check. Should never happen. See above. */ - if (brigade_length == -1) { - return APR_EGENERAL; - } - if (!brigade_length) { - return APR_EAGAIN; - } - ctx->linesize += brigade_length; - if (ctx->linesize > linelimit) { - return APR_ENOSPC; - } - /* - * As all buckets are already some type of memory buckets or META buckets - * (see above), we only need to check the last byte in the last data bucket. - */ - for (e = APR_BRIGADE_LAST(b); - e != APR_BRIGADE_SENTINEL(b); - e = APR_BUCKET_PREV(e)) { + while (i < len) { + char c = buffer[i]; + + ap_xlate_proto_from_ascii(&c, 1); - if (APR_BUCKET_IS_METADATA(e)) { + /* handle CRLF after the chunk */ + if (ctx->state == BODY_CHUNK_END + || ctx->state == BODY_CHUNK_END_LF) { + if (c == LF) { + ctx->state = BODY_CHUNK; + } + else if (c == CR && ctx->state == BODY_CHUNK_END) { + ctx->state = BODY_CHUNK_END_LF; + } + else { + /* + * LF expected. + */ + return APR_EINVAL; + } + i++; continue; } - rv = apr_bucket_read(e, &lineend, &len, APR_BLOCK_READ); - if (rv != APR_SUCCESS) { - return rv; + + /* handle start of the chunk */ + if (ctx->state == BODY_CHUNK) { + if (!apr_isxdigit(c)) { + /* + * Detect invalid character at beginning. This also works for + * empty chunk size lines. + */ + return APR_EINVAL; + } + else { + ctx->state = BODY_CHUNK_PART; + } + ctx->remaining = 0; + ctx->chunkbits = sizeof(apr_off_t) * 8; + ctx->chunk_used = 0; + ctx->chunk_bws = 0; } - if (len > 0) { - break; /* we got the data we want */ + + if (c == LF) { + if (ctx->remaining) { + ctx->state = BODY_CHUNK_DATA; + } + else { + ctx->state = BODY_CHUNK_TRAILER; + } } - /* If we got a zero-length data bucket, we try the next one */ - } - /* We had no data in this brigade */ - if (!len || e == APR_BRIGADE_SENTINEL(b)) { - return APR_EAGAIN; - } - if (lineend[len - 1] != APR_ASCII_LF) { - return APR_EAGAIN; - } - /* Line is complete. So reset ctx for next round. */ - ctx->linesize = 0; - ctx->pos = ctx->chunk_ln; - return APR_SUCCESS; -} + else if (ctx->state == BODY_CHUNK_LF) { + /* + * LF expected. + */ + return APR_EINVAL; + } + else if (c == CR) { + ctx->state = BODY_CHUNK_LF; + } + else if (c == ';') { + ctx->state = BODY_CHUNK_EXT; + } + else if (ctx->state == BODY_CHUNK_EXT) { + /* + * Control chars (but tabs) are invalid. + */ + if (c != '\t' && apr_iscntrl(c)) { + return APR_EINVAL; + } + } + else if (c == ' ' || c == '\t') { + /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3). + */ + ctx->state = BODY_CHUNK_CR; + if (++ctx->chunk_bws > 10) { + return APR_EINVAL; + } + } + else if (ctx->state == BODY_CHUNK_CR) { + /* + * ';', CR or LF expected. + */ + return APR_EINVAL; + } + else if (ctx->state == BODY_CHUNK_PART) { + int xvalue; -static apr_status_t get_chunk_line(http_ctx_t *ctx, apr_bucket_brigade *b, - int linelimit) -{ - apr_size_t len; - int tmp_len; - apr_status_t rv; + /* ignore leading zeros */ + if (!ctx->remaining && c == '0') { + i++; + continue; + } - tmp_len = sizeof(ctx->chunk_ln) - (ctx->pos - ctx->chunk_ln) - 1; - /* Saveguard ourselves against underflows */ - if (tmp_len < 0) { - len = 0; - } - else { - len = (apr_size_t) tmp_len; - } - /* - * Check if there is space left in ctx->chunk_ln. If not, then either - * the chunk size is insane or we have chunk-extensions. Ignore both - * by discarding the remaining part of the line via - * get_remaining_chunk_line. Only bail out if the line is too long. - */ - if (len > 0) { - rv = apr_brigade_flatten(b, ctx->pos, &len); - if (rv != APR_SUCCESS) { - return rv; + ctx->chunkbits -= 4; + if (ctx->chunkbits < 0) { + /* overflow */ + return APR_ENOSPC; + } + + if (c >= '0' && c <= '9') { + xvalue = c - '0'; + } + else if (c >= 'A' && c <= 'F') { + xvalue = c - 'A' + 0xa; + } + else if (c >= 'a' && c <= 'f') { + xvalue = c - 'a' + 0xa; + } + else { + /* bogus character */ + return APR_EINVAL; + } + + ctx->remaining = (ctx->remaining << 4) | xvalue; + if (ctx->remaining < 0) { + /* overflow */ + return APR_ENOSPC; + } } - ctx->pos += len; - ctx->linesize += len; - *(ctx->pos) = '\0'; - /* - * Check if we really got a full line. If yes the - * last char in the just read buffer must be LF. - * If not advance the buffer and return APR_EAGAIN. - * We do not start processing until we have the - * full line. - */ - if (ctx->pos[-1] != APR_ASCII_LF) { - /* Check if the remaining data in the brigade has the LF */ - return get_remaining_chunk_line(ctx, b, linelimit); + else { + /* Should not happen */ + return APR_EGENERAL; } - /* Line is complete. So reset ctx->pos for next round. */ - ctx->pos = ctx->chunk_ln; - return APR_SUCCESS; + + i++; } - return get_remaining_chunk_line(ctx, b, linelimit); -} + /* sanity check */ + ctx->chunk_used += len; + if (ctx->chunk_used < 0 || ctx->chunk_used > linelimit) { + return APR_ENOSPC; + } + + return APR_SUCCESS; +} static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *b, int merge) @@ -226,7 +285,6 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, r->status = HTTP_OK; r->headers_in = r->trailers_in; apr_table_clear(r->headers_in); - ctx->state = BODY_NONE; ap_get_mime_headers(r); if(r->status == HTTP_OK) { @@ -239,7 +297,7 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, else { const char *error_notes = apr_table_get(r->notes, "error-notes"); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02656) "Error while reading HTTP trailer: %i%s%s", r->status, error_notes ? ": " : "", error_notes ? error_notes : ""); @@ -270,9 +328,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, apr_bucket *e; http_ctx_t *ctx = f->ctx; apr_status_t rv; - apr_off_t totalread; int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE; apr_bucket_brigade *bb; + int again; conf = (core_server_config *) ap_get_module_config(f->r->server->module_config, &core_module); @@ -286,7 +344,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, const char *tenc, *lenp; f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->state = BODY_NONE; - ctx->pos = ctx->chunk_ln; ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); bb = ctx->bb; @@ -306,25 +363,33 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, lenp = apr_table_get(f->r->headers_in, "Content-Length"); if (tenc) { - if (!strcasecmp(tenc, "chunked")) { + if (strcasecmp(tenc, "chunked") == 0 /* fast path */ + || ap_find_last_token(f->r->pool, tenc, "chunked")) { ctx->state = BODY_CHUNK; } - /* test lenp, because it gives another case we can handle */ - else if (!lenp) { - /* Something that isn't in HTTP, unless some future + else if (f->r->proxyreq == PROXYREQ_RESPONSE) { + /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 + * Section 3.3.3.3: "If a Transfer-Encoding header field is + * present in a response and the chunked transfer coding is not + * the final encoding, the message body length is determined by + * reading the connection until it is closed by the server." + */ + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02555) + "Unknown Transfer-Encoding: %s; " + "using read-until-close", tenc); + tenc = NULL; + } + else { + /* Something that isn't a HTTP request, unless some future * edition defines new transfer encodings, is unsupported. */ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01585) "Unknown Transfer-Encoding: %s", tenc); - return bail_out_on_error(ctx, f, HTTP_NOT_IMPLEMENTED); - } - else { - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01586) - "Unknown Transfer-Encoding: %s; using Content-Length", tenc); - tenc = NULL; + return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); } + lenp = NULL; } - if (lenp && !tenc) { + if (lenp) { char *endstr; ctx->state = BODY_LENGTH; @@ -339,7 +404,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587) "Invalid Content-Length"); - return bail_out_on_error(ctx, f, HTTP_REQUEST_ENTITY_TOO_LARGE); + return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); } /* If we have a limit in effect and we know the C-L ahead of @@ -381,7 +446,8 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, if (!ap_is_HTTP_SUCCESS(f->r->status)) { ctx->state = BODY_NONE; ctx->eos_sent = 1; - } else { + } + else { char *tmp; int len; @@ -389,7 +455,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, * in a state of expecting one. */ f->r->expecting_100 = 0; - tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL, " ", + tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ", ap_get_status_line(HTTP_CONTINUE), CRLF CRLF, NULL); len = strlen(tmp); @@ -401,279 +467,205 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, e = apr_bucket_flush_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); - ap_pass_brigade(f->c->output_filters, bb); - } - } - - /* We can't read the chunk until after sending 100 if required. */ - if (ctx->state == BODY_CHUNK) { - apr_brigade_cleanup(bb); - - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, - block, 0); - - /* for timeout */ - if (block == APR_NONBLOCK_READ && - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || - (APR_STATUS_IS_EAGAIN(rv)) )) { - ctx->state = BODY_CHUNK_PART; - return APR_EAGAIN; - } - - if (rv == APR_SUCCESS) { - rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); - if (APR_STATUS_IS_EAGAIN(rv)) { - apr_brigade_cleanup(bb); - ctx->state = BODY_CHUNK_PART; - return rv; - } - if (rv == APR_SUCCESS) { - ctx->remaining = get_chunk_size(ctx->chunk_ln); - if (ctx->remaining == INVALID_CHAR) { - rv = APR_EGENERAL; - http_error = HTTP_BAD_REQUEST; - } + rv = ap_pass_brigade(f->c->output_filters, bb); + if (rv != APR_SUCCESS) { + return AP_FILTER_ERROR; } } - apr_brigade_cleanup(bb); - - /* Detect chunksize error (such as overflow) */ - if (rv != APR_SUCCESS || ctx->remaining < 0) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01589) "Error reading first chunk %s ", - (ctx->remaining < 0) ? "(overflow)" : ""); - ctx->remaining = 0; /* Reset it in case we have to - * come back here later */ - if (APR_STATUS_IS_TIMEUP(rv)) { - http_error = HTTP_REQUEST_TIME_OUT; - } - return bail_out_on_error(ctx, f, http_error); - } - - if (!ctx->remaining) { - return read_chunked_trailers(ctx, f, b, - conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); - } } } - else { - bb = ctx->bb; - } + /* sanity check in case we're read twice */ if (ctx->eos_sent) { e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); return APR_SUCCESS; } - if (!ctx->remaining) { + do { + apr_brigade_cleanup(b); + again = 0; /* until further notice */ + + /* read and handle the brigade */ switch (ctx->state) { - case BODY_NONE: - break; - case BODY_LENGTH: - e = apr_bucket_eos_create(f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(b, e); - ctx->eos_sent = 1; - return APR_SUCCESS; case BODY_CHUNK: case BODY_CHUNK_PART: - { - apr_brigade_cleanup(bb); + case BODY_CHUNK_EXT: + case BODY_CHUNK_CR: + case BODY_CHUNK_LF: + case BODY_CHUNK_END: + case BODY_CHUNK_END_LF: { - /* We need to read the CRLF after the chunk. */ - if (ctx->state == BODY_CHUNK) { - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, - block, 0); - if (block == APR_NONBLOCK_READ && - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || - (APR_STATUS_IS_EAGAIN(rv)) )) { - return APR_EAGAIN; - } - /* If we get an error, then leave */ - if (rv != APR_SUCCESS) { - return rv; - } - /* - * We really don't care whats on this line. If it is RFC - * compliant it should be only \r\n. If there is more - * before we just ignore it as long as we do not get over - * the limit for request lines. - */ - rv = get_remaining_chunk_line(ctx, bb, - f->r->server->limit_req_line); - apr_brigade_cleanup(bb); - if (APR_STATUS_IS_EAGAIN(rv)) { - return rv; - } - } else { - rv = APR_SUCCESS; - } + rv = ap_get_brigade(f->next, b, AP_MODE_GETLINE, block, 0); + + /* for timeout */ + if (block == APR_NONBLOCK_READ + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) + || (APR_STATUS_IS_EAGAIN(rv)))) { + return APR_EAGAIN; + } + + if (rv == APR_EOF) { + return APR_INCOMPLETE; + } + + if (rv != APR_SUCCESS) { + return rv; + } + + e = APR_BRIGADE_FIRST(b); + while (e != APR_BRIGADE_SENTINEL(b)) { + const char *buffer; + apr_size_t len; + + if (!APR_BUCKET_IS_METADATA(e)) { + int parsing = 0; + + rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ); - if (rv == APR_SUCCESS) { - /* Read the real chunk line. */ - rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, - block, 0); - /* Test timeout */ - if (block == APR_NONBLOCK_READ && - ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || - (APR_STATUS_IS_EAGAIN(rv)) )) { - ctx->state = BODY_CHUNK_PART; - return APR_EAGAIN; - } - ctx->state = BODY_CHUNK; if (rv == APR_SUCCESS) { - rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); - if (APR_STATUS_IS_EAGAIN(rv)) { - ctx->state = BODY_CHUNK_PART; - apr_brigade_cleanup(bb); - return rv; - } - if (rv == APR_SUCCESS) { - ctx->remaining = get_chunk_size(ctx->chunk_ln); - if (ctx->remaining == INVALID_CHAR) { - rv = APR_EGENERAL; + parsing = 1; + rv = parse_chunk_size(ctx, buffer, len, + f->r->server->limit_req_fieldsize); + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) + "Error reading/parsing chunk %s ", + (APR_ENOSPC == rv) ? "(overflow)" : ""); + if (parsing) { + if (rv != APR_ENOSPC) { http_error = HTTP_BAD_REQUEST; } + return bail_out_on_error(ctx, f, http_error); } + return rv; } - apr_brigade_cleanup(bb); } - /* Detect chunksize error (such as overflow) */ - if (rv != APR_SUCCESS || ctx->remaining < 0) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) "Error reading chunk %s ", - (ctx->remaining < 0) ? "(overflow)" : ""); - ctx->remaining = 0; /* Reset it in case we have to - * come back here later */ - if (APR_STATUS_IS_TIMEUP(rv)) { - http_error = HTTP_REQUEST_TIME_OUT; - } - return bail_out_on_error(ctx, f, http_error); - } + apr_bucket_delete(e); + e = APR_BRIGADE_FIRST(b); + } + again = 1; /* come around again */ - if (!ctx->remaining) { - return read_chunked_trailers(ctx, f, b, + if (ctx->state == BODY_CHUNK_TRAILER) { + /* Treat UNSET as DISABLE - trailers aren't merged by default */ + return read_chunked_trailers(ctx, f, b, conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); - } } + break; } - } + case BODY_NONE: + case BODY_LENGTH: + case BODY_CHUNK_DATA: { - /* Ensure that the caller can not go over our boundary point. */ - if (ctx->state == BODY_LENGTH || ctx->state == BODY_CHUNK) { - if (ctx->remaining < readbytes) { - readbytes = ctx->remaining; - } - AP_DEBUG_ASSERT(readbytes > 0); - } + /* Ensure that the caller can not go over our boundary point. */ + if (ctx->state != BODY_NONE && ctx->remaining < readbytes) { + readbytes = ctx->remaining; + } + if (readbytes > 0) { + apr_off_t totalread; - rv = ap_get_brigade(f->next, b, mode, block, readbytes); + rv = ap_get_brigade(f->next, b, mode, block, readbytes); - if (rv != APR_SUCCESS) { - return rv; - } + /* for timeout */ + if (block == APR_NONBLOCK_READ + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) + || (APR_STATUS_IS_EAGAIN(rv)))) { + return APR_EAGAIN; + } - /* How many bytes did we just read? */ - apr_brigade_length(b, 0, &totalread); + if (rv == APR_EOF && ctx->state != BODY_NONE + && ctx->remaining > 0) { + return APR_INCOMPLETE; + } - /* If this happens, we have a bucket of unknown length. Die because - * it means our assumptions have changed. */ - AP_DEBUG_ASSERT(totalread >= 0); + if (rv != APR_SUCCESS) { + return rv; + } - if (ctx->state != BODY_NONE) { - ctx->remaining -= totalread; - if (ctx->remaining > 0) { - e = APR_BRIGADE_LAST(b); - if (APR_BUCKET_IS_EOS(e)) - return APR_EOF; - } - } + /* How many bytes did we just read? */ + apr_brigade_length(b, 0, &totalread); - /* If we have no more bytes remaining on a C-L request, - * save the callter a roundtrip to discover EOS. - */ - if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { - e = apr_bucket_eos_create(f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(b, e); - } + /* If this happens, we have a bucket of unknown length. Die because + * it means our assumptions have changed. */ + AP_DEBUG_ASSERT(totalread >= 0); - /* We have a limit in effect. */ - if (ctx->limit) { - /* FIXME: Note that we might get slightly confused on chunked inputs - * as we'd need to compensate for the chunk lengths which may not - * really count. This seems to be up for interpretation. */ - ctx->limit_used += totalread; - if (ctx->limit < ctx->limit_used) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01591) - "Read content-length of %" APR_OFF_T_FMT - " is larger than the configured limit" - " of %" APR_OFF_T_FMT, ctx->limit_used, ctx->limit); - apr_brigade_cleanup(bb); - e = ap_bucket_error_create(HTTP_REQUEST_ENTITY_TOO_LARGE, NULL, - f->r->pool, - f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - e = apr_bucket_eos_create(f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - ctx->eos_sent = 1; - return ap_pass_brigade(f->r->output_filters, bb); - } - } + if (ctx->state != BODY_NONE) { + ctx->remaining -= totalread; + if (ctx->remaining > 0) { + e = APR_BRIGADE_LAST(b); + if (APR_BUCKET_IS_EOS(e)) { + apr_bucket_delete(e); + return APR_INCOMPLETE; + } + } + else if (ctx->state == BODY_CHUNK_DATA) { + /* next chunk please */ + ctx->state = BODY_CHUNK_END; + ctx->chunk_used = 0; + } + } - return APR_SUCCESS; -} + /* We have a limit in effect. */ + if (ctx->limit) { + /* FIXME: Note that we might get slightly confused on + * chunked inputs as we'd need to compensate for the chunk + * lengths which may not really count. This seems to be up + * for interpretation. + */ + ctx->limit_used += totalread; + if (ctx->limit < ctx->limit_used) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, + APLOGNO(01591) "Read content length of " + "%" APR_OFF_T_FMT " is larger than the " + "configured limit of %" APR_OFF_T_FMT, + ctx->limit_used, ctx->limit); + return bail_out_on_error(ctx, f, + HTTP_REQUEST_ENTITY_TOO_LARGE); + } + } + } -/** - * Parse a chunk extension, detect overflow. - * There are two error cases: - * 1) If the conversion would require too many bits, a -1 is returned. - * 2) If the conversion used the correct number of bits, but an overflow - * caused only the sign bit to flip, then that negative number is - * returned. - * In general, any negative number can be considered an overflow error. - */ -static long get_chunk_size(char *b) -{ - long chunksize = 0; - size_t chunkbits = sizeof(long) * 8; + /* If we have no more bytes remaining on a C-L request, + * save the caller a round trip to discover EOS. + */ + if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + ctx->eos_sent = 1; + } - ap_xlate_proto_from_ascii(b, strlen(b)); + break; + } + case BODY_CHUNK_TRAILER: { - if (!apr_isxdigit(*b)) { - /* - * Detect invalid character at beginning. This also works for empty - * chunk size lines. - */ - return INVALID_CHAR; - } - /* Skip leading zeros */ - while (*b == '0') { - ++b; - } + rv = ap_get_brigade(f->next, b, mode, block, readbytes); - while (apr_isxdigit(*b) && (chunkbits > 0)) { - int xvalue = 0; + /* for timeout */ + if (block == APR_NONBLOCK_READ + && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) + || (APR_STATUS_IS_EAGAIN(rv)))) { + return APR_EAGAIN; + } + + if (rv != APR_SUCCESS) { + return rv; + } - if (*b >= '0' && *b <= '9') { - xvalue = *b - '0'; + break; } - else if (*b >= 'A' && *b <= 'F') { - xvalue = *b - 'A' + 0xa; + default: { + /* Should not happen */ + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02901) + "Unexpected body state (%i)", (int)ctx->state); + return APR_EGENERAL; } - else if (*b >= 'a' && *b <= 'f') { - xvalue = *b - 'a' + 0xa; } - chunksize = (chunksize << 4) | xvalue; - chunkbits -= 4; - ++b; - } - if (apr_isxdigit(*b)) { - /* overflow */ - return -1; - } + } while (again); - return chunksize; + return APR_SUCCESS; } typedef struct header_struct { @@ -1385,6 +1377,39 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, return ap_pass_brigade(f->next, b); } +/* + * Map specific APR codes returned by the filter stack to HTTP error + * codes, or the default status code provided. Use it as follows: + * + * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + * + * If the filter has already handled the error, AP_FILTER_ERROR will + * be returned, which is cleanly passed through. + * + * These mappings imply that the filter stack is reading from the + * downstream client, the proxy will map these codes differently. + */ +AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status) +{ + switch (rv) { + case AP_FILTER_ERROR: { + return AP_FILTER_ERROR; + } + case APR_ENOSPC: { + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + case APR_ENOTIMPL: { + return HTTP_NOT_IMPLEMENTED; + } + case APR_ETIMEDOUT: { + return HTTP_REQUEST_TIME_OUT; + } + default: { + return status; + } + } +} + /* In HTTP/1.1, any method can have a body. However, most GET handlers * wouldn't know what to do with a request body if they received one. * This helper routine tests for and reads any message body in the request, @@ -1402,7 +1427,8 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, AP_DECLARE(int) ap_discard_request_body(request_rec *r) { apr_bucket_brigade *bb; - int rv, seen_eos; + int seen_eos; + apr_status_t rv; /* Sometimes we'll get in a state where the input handling has * detected an error where we want to drop the connection, so if @@ -1425,21 +1451,8 @@ AP_DECLARE(int) ap_discard_request_body(request_rec *r) APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { - /* FIXME: If we ever have a mapping from filters (apr_status_t) - * to HTTP error codes, this would be a good place for them. - * - * If we received the special case AP_FILTER_ERROR, it means - * that the filters have already handled this error. - * Otherwise, we should assume we have a bad request. - */ - if (rv == AP_FILTER_ERROR) { - apr_brigade_destroy(bb); - return rv; - } - else { - apr_brigade_destroy(bb); - return HTTP_BAD_REQUEST; - } + apr_brigade_destroy(bb); + return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); } for (bucket = APR_BRIGADE_FIRST(bb); @@ -1608,6 +1621,13 @@ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, /* We lose the failure code here. This is why ap_get_client_block should * not be used. */ + if (rv == AP_FILTER_ERROR) { + /* AP_FILTER_ERROR means a filter has responded already, + * we are DONE. + */ + apr_brigade_destroy(bb); + return -1; + } if (rv != APR_SUCCESS) { /* if we actually fail here, we want to just return and * stop trying to read data from the client.