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