diff options
Diffstat (limited to 'modules/http')
-rw-r--r-- | modules/http/byterange_filter.c | 7 | ||||
-rw-r--r-- | modules/http/http_core.c | 16 | ||||
-rw-r--r-- | modules/http/http_etag.c | 349 | ||||
-rw-r--r-- | modules/http/http_filters.c | 290 | ||||
-rw-r--r-- | modules/http/http_protocol.c | 128 | ||||
-rw-r--r-- | modules/http/http_request.c | 33 | ||||
-rw-r--r-- | modules/http/mod_mime.c | 19 |
7 files changed, 532 insertions, 310 deletions
diff --git a/modules/http/byterange_filter.c b/modules/http/byterange_filter.c index de585c5..5ebe853 100644 --- a/modules/http/byterange_filter.c +++ b/modules/http/byterange_filter.c @@ -152,7 +152,6 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength, *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t)); while ((cur = ap_getword(r->pool, &range, ','))) { char *dash; - char *errp; apr_off_t number, start, end; if (!*cur) @@ -169,7 +168,7 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength, if (dash == cur) { /* In the form "-5" */ - if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) { + if (!ap_parse_strict_length(&number, dash+1)) { return 0; } if (number < 1) { @@ -180,12 +179,12 @@ static int ap_set_byterange(request_rec *r, apr_off_t clength, } else { *dash++ = '\0'; - if (apr_strtoff(&number, cur, &errp, 10) || *errp) { + if (!ap_parse_strict_length(&number, cur)) { return 0; } start = number; if (*dash) { - if (apr_strtoff(&number, dash, &errp, 10) || *errp) { + if (!ap_parse_strict_length(&number, dash)) { return 0; } end = number; diff --git a/modules/http/http_core.c b/modules/http/http_core.c index 35869b4..c6cb473 100644 --- a/modules/http/http_core.c +++ b/modules/http/http_core.c @@ -140,16 +140,17 @@ static int ap_process_http_async_connection(conn_rec *c) AP_DEBUG_ASSERT(cs != NULL); AP_DEBUG_ASSERT(cs->state == CONN_STATE_READ_REQUEST_LINE); - while (cs->state == CONN_STATE_READ_REQUEST_LINE) { + if (cs->state == CONN_STATE_READ_REQUEST_LINE) { ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); - + if (ap_extended_status) { + ap_set_conn_count(c->sbh, r, c->keepalives); + } if ((r = ap_read_request(c))) { - - c->keepalive = AP_CONN_UNKNOWN; - /* process the request if it was read without error */ - if (r->status == HTTP_OK) { cs->state = CONN_STATE_HANDLER; + if (ap_extended_status) { + ap_set_conn_count(c->sbh, r, c->keepalives + 1); + } ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r); ap_process_async_request(r); /* After the call to ap_process_request, the @@ -200,9 +201,6 @@ static int ap_process_http_sync_connection(conn_rec *c) keep_alive_timeout = c->base_server->keep_alive_timeout; } - c->keepalive = AP_CONN_UNKNOWN; - /* process the request if it was read without error */ - if (r->status == HTTP_OK) { if (cs) cs->state = CONN_STATE_HANDLER; diff --git a/modules/http/http_etag.c b/modules/http/http_etag.c index 7f3c6d9..af74549 100644 --- a/modules/http/http_etag.c +++ b/modules/http/http_etag.c @@ -16,6 +16,9 @@ #include "apr_strings.h" #include "apr_thread_proc.h" /* for RLIMIT stuff */ +#include "apr_sha1.h" +#include "apr_base64.h" +#include "apr_buckets.h" #define APR_WANT_STRFUNC #include "apr_want.h" @@ -24,9 +27,16 @@ #include "http_config.h" #include "http_connection.h" #include "http_core.h" +#include "http_log.h" #include "http_protocol.h" /* For index_of_response(). Grump. */ #include "http_request.h" +#if APR_HAS_MMAP +#include "apr_mmap.h" +#endif /* APR_HAS_MMAP */ + +#define SHA1_DIGEST_BASE64_LEN 4*(APR_SHA1_DIGESTSIZE/3) + /* Generate the human-readable hex representation of an apr_uint64_t * (basically a faster version of 'sprintf("%llx")') */ @@ -53,19 +63,159 @@ static char *etag_uint64_to_hex(char *next, apr_uint64_t u) #define ETAG_WEAK "W/" #define CHARS_PER_UINT64 (sizeof(apr_uint64_t) * 2) + +static void etag_start(char *etag, const char *weak, char **next) +{ + if (weak) { + while (*weak) { + *etag++ = *weak++; + } + } + *etag++ = '"'; + + *next = etag; +} + +static void etag_end(char *next, const char *vlv, apr_size_t vlv_len) +{ + if (vlv) { + *next++ = ';'; + apr_cpystrn(next, vlv, vlv_len); + } + else { + *next++ = '"'; + *next = '\0'; + } +} + +/* + * Construct a strong ETag by creating a SHA1 hash across the file content. + */ +static char *make_digest_etag(request_rec *r, etag_rec *er, char *vlv, + apr_size_t vlv_len, char *weak, apr_size_t weak_len) +{ + apr_sha1_ctx_t context; + unsigned char digest[APR_SHA1_DIGESTSIZE]; + apr_file_t *fd = NULL; + core_dir_config *cfg; + char *etag, *next; + apr_bucket_brigade *bb; + apr_bucket *e; + + apr_size_t nbytes; + apr_off_t offset = 0, zero = 0, len = 0; + apr_status_t status; + + cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + + if (er->fd) { + fd = er->fd; + } + else if (er->pathname) { + if ((status = apr_file_open(&fd, er->pathname, APR_READ | APR_BINARY, + 0, r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10251) + "Make etag: could not open %s", er->pathname); + return ""; + } + } + if (!fd) { + return ""; + } + + if ((status = apr_file_seek(fd, APR_CUR, &offset)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10252) + "Make etag: could not seek"); + if (er->pathname) { + apr_file_close(fd); + } + return ""; + } + + if ((status = apr_file_seek(fd, APR_END, &len)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10258) + "Make etag: could not seek"); + if (er->pathname) { + apr_file_close(fd); + } + return ""; + } + + if ((status = apr_file_seek(fd, APR_SET, &zero)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10253) + "Make etag: could not seek"); + if (er->pathname) { + apr_file_close(fd); + } + return ""; + } + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + e = apr_brigade_insert_file(bb, fd, 0, len, r->pool); + +#if APR_HAS_MMAP + if (cfg->enable_mmap == ENABLE_MMAP_OFF) { + (void)apr_bucket_file_enable_mmap(e, 0); + } +#endif + + apr_sha1_init(&context); + while (!APR_BRIGADE_EMPTY(bb)) + { + const char *str; + + e = APR_BRIGADE_FIRST(bb); + + if ((status = apr_bucket_read(e, &str, &nbytes, APR_BLOCK_READ)) != APR_SUCCESS) { + apr_brigade_destroy(bb); + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10254) + "Make etag: could not read"); + if (er->pathname) { + apr_file_close(fd); + } + return ""; + } + + apr_sha1_update(&context, str, nbytes); + apr_bucket_delete(e); + } + + if ((status = apr_file_seek(fd, APR_SET, &offset)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(10255) + "Make etag: could not seek"); + if (er->pathname) { + apr_file_close(fd); + } + return ""; + } + apr_sha1_final(digest, &context); + + etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") + + SHA1_DIGEST_BASE64_LEN + vlv_len + 4); + + etag_start(etag, weak, &next); + next += apr_base64_encode_binary(next, digest, APR_SHA1_DIGESTSIZE) - 1; + etag_end(next, vlv, vlv_len); + + if (er->pathname) { + apr_file_close(fd); + } + + return etag; +} + /* * Construct an entity tag (ETag) from resource information. If it's a real * file, build in some of the file characteristics. If the modification time * is newer than (request-time minus 1 second), mark the ETag as weak - it - * could be modified again in as short an interval. We rationalize the - * modification time we're given to keep it from being in the future. + * could be modified again in as short an interval. */ -AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak) +AP_DECLARE(char *) ap_make_etag_ex(request_rec *r, etag_rec *er) { - char *weak; - apr_size_t weak_len; - char *etag; - char *next; + char *weak = NULL; + apr_size_t weak_len = 0, vlv_len = 0; + char *etag, *next, *vlv; core_dir_config *cfg; etag_components_t etag_bits; etag_components_t bits_added; @@ -73,13 +223,62 @@ AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak) cfg = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add; + if (er->force_weak) { + weak = ETAG_WEAK; + weak_len = sizeof(ETAG_WEAK); + } + + if (r->vlist_validator) { + + /* If we have a variant list validator (vlv) due to the + * response being negotiated, then we create a structured + * entity tag which merges the variant etag with the variant + * list validator (vlv). This merging makes revalidation + * somewhat safer, ensures that caches which can deal with + * Vary will (eventually) be updated if the set of variants is + * changed, and is also a protocol requirement for transparent + * content negotiation. + */ + + /* if the variant list validator is weak, we make the whole + * structured etag weak. If we would not, then clients could + * have problems merging range responses if we have different + * variants with the same non-globally-unique strong etag. + */ + + vlv = r->vlist_validator; + if (vlv[0] == 'W') { + vlv += 3; + weak = ETAG_WEAK; + weak_len = sizeof(ETAG_WEAK); + } + else { + vlv++; + } + vlv_len = strlen(vlv); + + } + else { + vlv = NULL; + vlv_len = 0; + } + + /* + * Did a module flag the need for a strong etag, or did the + * configuration tell us to generate a digest? + */ + if (er->finfo->filetype == APR_REG && + (AP_REQUEST_IS_STRONG_ETAG(r) || (etag_bits & ETAG_DIGEST))) { + + return make_digest_etag(r, er, vlv, vlv_len, weak, weak_len); + } + /* * If it's a file (or we wouldn't be here) and no ETags * should be set for files, return an empty string and * note it for the header-sender to ignore. */ if (etag_bits & ETAG_NONE) { - apr_table_setn(r->notes, "no-etag", "omit"); return ""; } @@ -98,123 +297,117 @@ AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak) * be modified again later in the second, and the validation * would be incorrect. */ - if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) && - !force_weak) { - weak = NULL; - weak_len = 0; - } - else { + if ((er->request_time - er->finfo->mtime < (1 * APR_USEC_PER_SEC))) { weak = ETAG_WEAK; weak_len = sizeof(ETAG_WEAK); } - if (r->finfo.filetype != APR_NOFILE) { + if (er->finfo->filetype != APR_NOFILE) { /* * ETag gets set to [W/]"inode-size-mtime", modulo any * FileETag keywords. */ etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") + - 3 * CHARS_PER_UINT64 + 1); - next = etag; - if (weak) { - while (*weak) { - *next++ = *weak++; - } - } - *next++ = '"'; + 3 * CHARS_PER_UINT64 + vlv_len + 2); + + etag_start(etag, weak, &next); + bits_added = 0; if (etag_bits & ETAG_INODE) { - next = etag_uint64_to_hex(next, r->finfo.inode); + next = etag_uint64_to_hex(next, er->finfo->inode); bits_added |= ETAG_INODE; } if (etag_bits & ETAG_SIZE) { if (bits_added != 0) { *next++ = '-'; } - next = etag_uint64_to_hex(next, r->finfo.size); + next = etag_uint64_to_hex(next, er->finfo->size); bits_added |= ETAG_SIZE; } if (etag_bits & ETAG_MTIME) { if (bits_added != 0) { *next++ = '-'; } - next = etag_uint64_to_hex(next, r->mtime); + next = etag_uint64_to_hex(next, er->finfo->mtime); } - *next++ = '"'; - *next = '\0'; + + etag_end(next, vlv, vlv_len); + } else { /* * Not a file document, so just use the mtime: [W/]"mtime" */ etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") + - CHARS_PER_UINT64 + 1); - next = etag; - if (weak) { - while (*weak) { - *next++ = *weak++; - } - } - *next++ = '"'; - next = etag_uint64_to_hex(next, r->mtime); - *next++ = '"'; - *next = '\0'; + CHARS_PER_UINT64 + vlv_len + 2); + + etag_start(etag, weak, &next); + next = etag_uint64_to_hex(next, er->finfo->mtime); + etag_end(next, vlv, vlv_len); + } return etag; } +AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak) +{ + etag_rec er; + + er.vlist_validator = NULL; + er.request_time = r->request_time; + er.finfo = &r->finfo; + er.pathname = r->filename; + er.fd = NULL; + er.force_weak = force_weak; + + return ap_make_etag_ex(r, &er); +} + AP_DECLARE(void) ap_set_etag(request_rec *r) { char *etag; - char *variant_etag, *vlv; - int vlv_weak; - if (!r->vlist_validator) { - etag = ap_make_etag(r, 0); + etag_rec er; - /* If we get a blank etag back, don't set the header. */ - if (!etag[0]) { - return; - } + er.vlist_validator = r->vlist_validator; + er.request_time = r->request_time; + er.finfo = &r->finfo; + er.pathname = r->filename; + er.fd = NULL; + er.force_weak = 0; + + etag = ap_make_etag_ex(r, &er); + + if (etag && etag[0]) { + apr_table_setn(r->headers_out, "ETag", etag); } else { - /* If we have a variant list validator (vlv) due to the - * response being negotiated, then we create a structured - * entity tag which merges the variant etag with the variant - * list validator (vlv). This merging makes revalidation - * somewhat safer, ensures that caches which can deal with - * Vary will (eventually) be updated if the set of variants is - * changed, and is also a protocol requirement for transparent - * content negotiation. - */ + apr_table_setn(r->notes, "no-etag", "omit"); + } - /* if the variant list validator is weak, we make the whole - * structured etag weak. If we would not, then clients could - * have problems merging range responses if we have different - * variants with the same non-globally-unique strong etag. - */ +} - vlv = r->vlist_validator; - vlv_weak = (vlv[0] == 'W'); +AP_DECLARE(void) ap_set_etag_fd(request_rec *r, apr_file_t *fd) +{ + char *etag; - variant_etag = ap_make_etag(r, vlv_weak); + etag_rec er; - /* If we get a blank etag back, don't append vlv and stop now. */ - if (!variant_etag[0]) { - return; - } + er.vlist_validator = r->vlist_validator; + er.request_time = r->request_time; + er.finfo = &r->finfo; + er.pathname = NULL; + er.fd = fd; + er.force_weak = 0; - /* merge variant_etag and vlv into a structured etag */ - variant_etag[strlen(variant_etag) - 1] = '\0'; - if (vlv_weak) { - vlv += 3; - } - else { - vlv++; - } - etag = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL); + etag = ap_make_etag_ex(r, &er); + + if (etag && etag[0]) { + apr_table_setn(r->headers_out, "ETag", etag); + } + else { + apr_table_setn(r->notes, "no-etag", "omit"); } - apr_table_setn(r->headers_out, "ETag", etag); } diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 9828cdf..f20aee7 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -79,7 +79,8 @@ typedef struct http_filter_ctx BODY_CHUNK_END_LF, /* got CR after data, expect LF */ BODY_CHUNK_TRAILER /* trailers */ } state; - unsigned int eos_sent :1; + unsigned int eos_sent :1, + seen_data:1; apr_bucket_brigade *bb; } http_ctx_t; @@ -348,7 +349,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, http_ctx_t *ctx = f->ctx; apr_status_t rv; int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE; - apr_bucket_brigade *bb; int again; /* just get out of the way of things we don't want. */ @@ -361,7 +361,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->state = BODY_NONE; ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); - bb = ctx->bb; /* LimitRequestBody does not apply to proxied responses. * Consider implementing this check in its own filter. @@ -379,8 +378,7 @@ 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") == 0 /* fast path */ - || ap_find_last_token(f->r->pool, tenc, "chunked")) { + if (ap_is_chunked(f->r->pool, tenc)) { ctx->state = BODY_CHUNK; } else if (f->r->proxyreq == PROXYREQ_RESPONSE) { @@ -406,16 +404,13 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, lenp = NULL; } if (lenp) { - char *endstr; - ctx->state = BODY_LENGTH; /* Protects against over/underflow, non-digit chars in the - * string (excluding leading space) (the endstr checks) - * and a negative number. */ - if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10) - || endstr == lenp || *endstr || ctx->remaining < 0) { - + * string, leading plus/minus signs, trailing characters and + * a negative number. + */ + if (!ap_parse_strict_length(&ctx->remaining, lenp)) { ctx->remaining = 0; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587) "Invalid Content-Length"); @@ -452,42 +447,46 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ctx->eos_sent = 1; return APR_SUCCESS; } + } - /* Since we're about to read data, send 100-Continue if needed. - * Only valid on chunked and C-L bodies where the C-L is > 0. */ - if ((ctx->state == BODY_CHUNK || - (ctx->state == BODY_LENGTH && ctx->remaining > 0)) && - f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1) && - !(f->r->eos_sent || f->r->bytes_sent)) { - if (!ap_is_HTTP_SUCCESS(f->r->status)) { - ctx->state = BODY_NONE; - ctx->eos_sent = 1; - } - else { - char *tmp; - int len; - - /* if we send an interim response, we're no longer - * in a state of expecting one. - */ - f->r->expecting_100 = 0; - tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ", - ap_get_status_line(HTTP_CONTINUE), CRLF CRLF, - NULL); - len = strlen(tmp); - ap_xlate_proto_to_ascii(tmp, len); - apr_brigade_cleanup(bb); - e = apr_bucket_pool_create(tmp, len, f->r->pool, - f->c->bucket_alloc); - APR_BRIGADE_INSERT_HEAD(bb, e); - e = apr_bucket_flush_create(f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - - rv = ap_pass_brigade(f->c->output_filters, bb); - if (rv != APR_SUCCESS) { - return AP_FILTER_ERROR; - } - } + /* Since we're about to read data, send 100-Continue if needed. + * Only valid on chunked and C-L bodies where the C-L is > 0. + * + * If the read is to be nonblocking though, the caller may not want to + * handle this just now (e.g. mod_proxy_http), and is prepared to read + * nothing if the client really waits for 100 continue, so we don't + * send it now and wait for later blocking read. + * + * In any case, even if r->expecting remains set at the end of the + * request handling, ap_set_keepalive() will finally do the right + * thing (i.e. "Connection: close" the connection). + */ + if (block == APR_BLOCK_READ + && (ctx->state == BODY_CHUNK + || (ctx->state == BODY_LENGTH && ctx->remaining > 0)) + && f->r->expecting_100 && f->r->proto_num >= HTTP_VERSION(1,1) + && !(ctx->eos_sent || f->r->eos_sent || f->r->bytes_sent)) { + if (!ap_is_HTTP_SUCCESS(f->r->status)) { + ctx->state = BODY_NONE; + ctx->eos_sent = 1; /* send EOS below */ + } + else if (!ctx->seen_data) { + int saved_status = f->r->status; + const char *saved_status_line = f->r->status_line; + f->r->status = HTTP_CONTINUE; + f->r->status_line = NULL; + ap_send_interim_response(f->r, 0); + AP_DEBUG_ASSERT(!f->r->expecting_100); + f->r->status_line = saved_status_line; + f->r->status = saved_status; + } + else { + /* https://tools.ietf.org/html/rfc7231#section-5.1.1 + * A server MAY omit sending a 100 (Continue) response if it + * has already received some or all of the message body for + * the corresponding request [...] + */ + f->r->expecting_100 = 0; } } @@ -538,9 +537,11 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, int parsing = 0; rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ); - if (rv == APR_SUCCESS) { parsing = 1; + if (len > 0) { + ctx->seen_data = 1; + } rv = parse_chunk_size(ctx, buffer, len, f->r->server->limit_req_fieldsize, strict); } @@ -602,6 +603,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, /* How many bytes did we just read? */ apr_brigade_length(b, 0, &totalread); + if (totalread > 0) { + ctx->seen_data = 1; + } /* If this happens, we have a bucket of unknown length. Die because * it means our assumptions have changed. */ @@ -774,6 +778,18 @@ static APR_INLINE int check_headers(request_rec *r) struct check_header_ctx ctx; core_server_config *conf = ap_get_core_module_config(r->server->module_config); + const char *val; + + if ((val = apr_table_get(r->headers_out, "Transfer-Encoding"))) { + if (apr_table_get(r->headers_out, "Content-Length")) { + apr_table_unset(r->headers_out, "Content-Length"); + r->connection->keepalive = AP_CONN_CLOSE; + } + if (!ap_is_chunked(r->pool, val)) { + r->connection->keepalive = AP_CONN_CLOSE; + return 0; + } + } ctx.r = r; ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); @@ -872,7 +888,7 @@ static int uniq_field_values(void *d, const char *key, const char *val) */ for (i = 0, strpp = (char **) values->elts; i < values->nelts; ++i, ++strpp) { - if (*strpp && strcasecmp(*strpp, start) == 0) { + if (*strpp && ap_cstr_casecmp(*strpp, start) == 0) { break; } } @@ -1290,6 +1306,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, request_rec *r = f->r; conn_rec *c = r->connection; const char *clheader; + int header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)); const char *protocol = NULL; apr_bucket *e; apr_bucket_brigade *b2; @@ -1307,7 +1324,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, } else if (ctx->headers_sent) { /* Eat body if response must not have one. */ - if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) { + if (header_only) { /* Still next filters may be waiting for EOS, so pass it (alone) * when encountered and be done with this filter. */ @@ -1348,6 +1365,9 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, */ apr_table_clear(r->headers_out); apr_table_clear(r->err_headers_out); + r->content_type = r->content_encoding = NULL; + r->content_languages = NULL; + r->clength = r->chunked = 0; apr_brigade_cleanup(b); /* Don't recall ap_die() if we come back here (from its own internal @@ -1364,8 +1384,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, APR_BRIGADE_INSERT_TAIL(b, e); e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); - r->content_type = r->content_encoding = NULL; - r->content_languages = NULL; ap_set_content_length(r, 0); recursive_error = 1; } @@ -1392,6 +1410,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, if (!apr_is_empty_table(r->err_headers_out)) { r->headers_out = apr_table_overlay(r->pool, r->err_headers_out, r->headers_out); + apr_table_clear(r->err_headers_out); } /* @@ -1411,6 +1430,17 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, fixup_vary(r); } + + /* + * Control cachability for non-cacheable responses if not already set by + * some other part of the server configuration. + */ + if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) { + char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN); + ap_recent_rfc822_date(date, r->request_time); + apr_table_addn(r->headers_out, "Expires", date); + } + /* * Now remove any ETag response header field if earlier processing * says so (such as a 'FileETag None' directive). @@ -1423,6 +1453,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, basic_http_header_check(r, &protocol); ap_set_keepalive(r); + /* 204/304 responses don't have content related headers */ if (AP_STATUS_IS_HEADER_ONLY(r->status)) { apr_table_unset(r->headers_out, "Transfer-Encoding"); apr_table_unset(r->headers_out, "Content-Length"); @@ -1453,7 +1484,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) { for (i = 0; i < r->content_languages->nelts; ++i) { - if (!strcasecmp(token, languages[i])) + if (!ap_cstr_casecmp(token, languages[i])) break; } if (i == r->content_languages->nelts) { @@ -1465,16 +1496,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, apr_table_setn(r->headers_out, "Content-Language", field); } - /* - * Control cachability for non-cacheable responses if not already set by - * some other part of the server configuration. - */ - if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) { - char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN); - ap_recent_rfc822_date(date, r->request_time); - apr_table_addn(r->headers_out, "Expires", date); - } - /* This is a hack, but I can't find anyway around it. The idea is that * we don't want to send out 0 Content-Lengths if it is a head request. * This happens when modules try to outsmart the server, and return @@ -1499,37 +1520,25 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, h.pool = r->pool; h.bb = b2; - if (r->status == HTTP_NOT_MODIFIED) { - apr_table_do((int (*)(void *, const char *, const char *)) form_header_field, - (void *) &h, r->headers_out, - "Connection", - "Keep-Alive", - "ETag", - "Content-Location", - "Expires", - "Cache-Control", - "Vary", - "Warning", - "WWW-Authenticate", - "Proxy-Authenticate", - "Set-Cookie", - "Set-Cookie2", - NULL); - } - else { - send_all_header_fields(&h, r); - } + send_all_header_fields(&h, r); terminate_header(b2); - rv = ap_pass_brigade(f->next, b2); - if (rv != APR_SUCCESS) { - goto out; + if (header_only) { + e = APR_BRIGADE_LAST(b); + if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(b2, e); + ap_remove_output_filter(f); + } + apr_brigade_cleanup(b); } + + rv = ap_pass_brigade(f->next, b2); + apr_brigade_cleanup(b2); ctx->headers_sent = 1; - if (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status)) { - apr_brigade_cleanup(b); + if (rv != APR_SUCCESS || header_only) { goto out; } @@ -1605,9 +1614,9 @@ AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status) */ AP_DECLARE(int) ap_discard_request_body(request_rec *r) { + int rc = OK; + conn_rec *c = r->connection; apr_bucket_brigade *bb; - 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 @@ -1616,54 +1625,57 @@ AP_DECLARE(int) ap_discard_request_body(request_rec *r) * * This function is also a no-op on a subrequest. */ - if (r->main || r->connection->keepalive == AP_CONN_CLOSE || - ap_status_drops_connection(r->status)) { + if (r->main || c->keepalive == AP_CONN_CLOSE) { + return OK; + } + if (ap_status_drops_connection(r->status)) { + c->keepalive = AP_CONN_CLOSE; return OK; } bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); - seen_eos = 0; - do { - apr_bucket *bucket; + for (;;) { + apr_status_t rv; rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); - if (rv != APR_SUCCESS) { - apr_brigade_destroy(bb); - return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + rc = ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + goto cleanup; } - for (bucket = APR_BRIGADE_FIRST(bb); - bucket != APR_BRIGADE_SENTINEL(bb); - bucket = APR_BUCKET_NEXT(bucket)) - { - const char *data; - apr_size_t len; - - if (APR_BUCKET_IS_EOS(bucket)) { - seen_eos = 1; - break; - } + while (!APR_BRIGADE_EMPTY(bb)) { + apr_bucket *b = APR_BRIGADE_FIRST(bb); - /* These are metadata buckets. */ - if (bucket->length == 0) { - continue; + if (APR_BUCKET_IS_EOS(b)) { + goto cleanup; } - /* We MUST read because in case we have an unknown-length - * bucket or one that morphs, we want to exhaust it. + /* There is no need to read empty or metadata buckets or + * buckets of known length, but we MUST read buckets of + * unknown length in order to exhaust them. */ - rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); - if (rv != APR_SUCCESS) { - apr_brigade_destroy(bb); - return HTTP_BAD_REQUEST; + if (b->length == (apr_size_t)-1) { + apr_size_t len; + const char *data; + + rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + rc = HTTP_BAD_REQUEST; + goto cleanup; + } } + + apr_bucket_delete(b); } - apr_brigade_cleanup(bb); - } while (!seen_eos); + } - return OK; +cleanup: + apr_brigade_cleanup(bb); + if (rc != OK) { + c->keepalive = AP_CONN_CLOSE; + } + return rc; } /* Here we deal with getting the request message body from the client. @@ -1707,13 +1719,14 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) { const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); const char *lenp = apr_table_get(r->headers_in, "Content-Length"); + apr_off_t limit_req_body = ap_get_limit_req_body(r); r->read_body = read_policy; r->read_chunked = 0; r->remaining = 0; if (tenc) { - if (strcasecmp(tenc, "chunked")) { + if (ap_cstr_casecmp(tenc, "chunked")) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01592) "Unknown Transfer-Encoding %s", tenc); return HTTP_NOT_IMPLEMENTED; @@ -1727,13 +1740,10 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) r->read_chunked = 1; } else if (lenp) { - char *endstr; - - if (apr_strtoff(&r->remaining, lenp, &endstr, 10) - || *endstr || r->remaining < 0) { + if (!ap_parse_strict_length(&r->remaining, lenp)) { r->remaining = 0; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01594) - "Invalid Content-Length"); + "Invalid Content-Length '%s'", lenp); return HTTP_BAD_REQUEST; } } @@ -1745,6 +1755,11 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) return HTTP_REQUEST_ENTITY_TOO_LARGE; } + if (limit_req_body > 0 && (r->remaining > limit_req_body)) { + /* will be logged when the body is discarded */ + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + #ifdef AP_DEBUG { /* Make sure ap_getline() didn't leave any droppings. */ @@ -1852,6 +1867,7 @@ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, /* Context struct for ap_http_outerror_filter */ typedef struct { int seen_eoc; + int first_error; } outerror_filter_ctx_t; /* Filter to handle any error buckets on output */ @@ -1880,10 +1896,18 @@ apr_status_t ap_http_outerror_filter(ap_filter_t *f, /* stream aborted and we have not ended it yet */ r->connection->keepalive = AP_CONN_CLOSE; } + /* + * Memorize the status code of the first error bucket for possible + * later use. + */ + if (!ctx->first_error) { + ctx->first_error = ((ap_bucket_error *)(e->data))->status; + } continue; } /* Detect EOC buckets and memorize this in the context. */ if (AP_BUCKET_IS_EOC(e)) { + r->connection->keepalive = AP_CONN_CLOSE; ctx->seen_eoc = 1; } } @@ -1907,6 +1931,18 @@ apr_status_t ap_http_outerror_filter(ap_filter_t *f, * EOS bucket. */ if (ctx->seen_eoc) { + /* + * Set the request status to the status of the first error bucket. + * This should ensure that we log an appropriate status code in + * the access log. + * We need to set r->status on each call after we noticed an EOC as + * data bucket generators like ap_die might have changed the status + * code. But we know better in this case and insist on the status + * code that we have seen in the error bucket. + */ + if (ctx->first_error) { + r->status = ctx->first_error; + } for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index e419eb6..d031f24 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -60,7 +60,7 @@ APLOG_USE_MODULE(http); -/* New Apache routine to map status codes into array indicies +/* New Apache routine to map status codes into array indices * e.g. 100 -> 0, 101 -> 1, 200 -> 2 ... * The number of status lines must equal the value of * RESPONSE_CODES (httpd.h) and must be listed in order. @@ -257,10 +257,9 @@ AP_DECLARE(int) ap_set_keepalive(request_rec *r) && (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status) || apr_table_get(r->headers_out, "Content-Length") - || ap_find_last_token(r->pool, + || ap_is_chunked(r->pool, apr_table_get(r->headers_out, - "Transfer-Encoding"), - "chunked") + "Transfer-Encoding")) || ((r->proto_num >= HTTP_VERSION(1,1)) && (r->chunked = 1))) /* THIS CODE IS CORRECT, see above. */ && r->server->keep_alive @@ -987,14 +986,17 @@ AP_DECLARE(const char *) ap_method_name_of(apr_pool_t *p, int methnum) * from status_lines[shortcut[i]] to status_lines[shortcut[i+1]-1]; * or use NULL to fill the gaps. */ -AP_DECLARE(int) ap_index_of_response(int status) +static int index_of_response(int status) { - static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400, - LEVEL_500, RESPONSE_CODES}; + static int shortcut[6] = {0, LEVEL_200, LEVEL_300, LEVEL_400, LEVEL_500, + RESPONSE_CODES}; int i, pos; - if (status < 100) { /* Below 100 is illegal for HTTP status */ - return LEVEL_500; + if (status < 100) { /* Below 100 is illegal for HTTP status */ + return -1; + } + if (status > 999) { /* Above 999 is also illegal for HTTP status */ + return -1; } for (i = 0; i < 5; i++) { @@ -1005,11 +1007,29 @@ AP_DECLARE(int) ap_index_of_response(int status) return pos; } else { - return LEVEL_500; /* status unknown (falls in gap) */ + break; } } } - return LEVEL_500; /* 600 or above is also illegal */ + return -2; /* Status unknown (falls in gap) or above 600 */ +} + +AP_DECLARE(int) ap_index_of_response(int status) +{ + int index = index_of_response(status); + return (index < 0) ? LEVEL_500 : index; +} + +AP_DECLARE(const char *) ap_get_status_line_ex(apr_pool_t *p, int status) +{ + int index = index_of_response(status); + if (index >= 0) { + return status_lines[index]; + } + else if (index == -2) { + return apr_psprintf(p, "%i Status %i", status, status); + } + return status_lines[LEVEL_500]; } AP_DECLARE(const char *) ap_get_status_line(int status) @@ -1132,13 +1152,10 @@ static const char *get_canned_error_string(int status, "\">here</a>.</p>\n", NULL)); case HTTP_USE_PROXY: - return(apr_pstrcat(p, - "<p>This resource is only accessible " - "through the proxy\n", - ap_escape_html(r->pool, location), - "<br />\nYou will need to configure " - "your client to use that proxy.</p>\n", - NULL)); + return("<p>This resource is only accessible " + "through the proxy\n" + "<br />\nYou will need to configure " + "your client to use that proxy.</p>\n"); case HTTP_PROXY_AUTHENTICATION_REQUIRED: case HTTP_UNAUTHORIZED: return("<p>This server could not verify that you\n" @@ -1154,34 +1171,20 @@ static const char *get_canned_error_string(int status, "error-notes", "</p>\n")); case HTTP_FORBIDDEN: - s1 = apr_pstrcat(p, - "<p>You don't have permission to access ", - ap_escape_html(r->pool, r->uri), - "\non this server.<br />\n", - NULL); - return(add_optional_notes(r, s1, "error-notes", "</p>\n")); + return(add_optional_notes(r, "<p>You don't have permission to access this resource.", "error-notes", "</p>\n")); case HTTP_NOT_FOUND: - return(apr_pstrcat(p, - "<p>The requested URL ", - ap_escape_html(r->pool, r->uri), - " was not found on this server.</p>\n", - NULL)); + return("<p>The requested URL was not found on this server.</p>\n"); case HTTP_METHOD_NOT_ALLOWED: return(apr_pstrcat(p, "<p>The requested method ", ap_escape_html(r->pool, r->method), - " is not allowed for the URL ", - ap_escape_html(r->pool, r->uri), - ".</p>\n", + " is not allowed for this URL.</p>\n", NULL)); case HTTP_NOT_ACCEPTABLE: - s1 = apr_pstrcat(p, - "<p>An appropriate representation of the " - "requested resource ", - ap_escape_html(r->pool, r->uri), - " could not be found on this server.</p>\n", - NULL); - return(add_optional_notes(r, s1, "variant-list", "")); + return(add_optional_notes(r, + "<p>An appropriate representation of the requested resource " + "could not be found on this server.</p>\n", + "variant-list", "")); case HTTP_MULTIPLE_CHOICES: return(add_optional_notes(r, "", "variant-list", "")); case HTTP_LENGTH_REQUIRED: @@ -1192,18 +1195,13 @@ static const char *get_canned_error_string(int status, NULL); return(add_optional_notes(r, s1, "error-notes", "</p>\n")); case HTTP_PRECONDITION_FAILED: - return(apr_pstrcat(p, - "<p>The precondition on the request " - "for the URL ", - ap_escape_html(r->pool, r->uri), - " evaluated to false.</p>\n", - NULL)); + return("<p>The precondition on the request " + "for this URL evaluated to false.</p>\n"); case HTTP_NOT_IMPLEMENTED: s1 = apr_pstrcat(p, "<p>", - ap_escape_html(r->pool, r->method), " to ", - ap_escape_html(r->pool, r->uri), - " not supported.<br />\n", + ap_escape_html(r->pool, r->method), + " not supported for current URL.<br />\n", NULL); return(add_optional_notes(r, s1, "error-notes", "</p>\n")); case HTTP_BAD_GATEWAY: @@ -1211,29 +1209,19 @@ static const char *get_canned_error_string(int status, "response from an upstream server.<br />" CRLF; return(add_optional_notes(r, s1, "error-notes", "</p>\n")); case HTTP_VARIANT_ALSO_VARIES: - return(apr_pstrcat(p, - "<p>A variant for the requested " - "resource\n<pre>\n", - ap_escape_html(r->pool, r->uri), - "\n</pre>\nis itself a negotiable resource. " - "This indicates a configuration error.</p>\n", - NULL)); + return("<p>A variant for the requested " + "resource\n<pre>\n" + "\n</pre>\nis itself a negotiable resource. " + "This indicates a configuration error.</p>\n"); case HTTP_REQUEST_TIME_OUT: return("<p>Server timeout waiting for the HTTP request from the client.</p>\n"); case HTTP_GONE: - return(apr_pstrcat(p, - "<p>The requested resource<br />", - ap_escape_html(r->pool, r->uri), - "<br />\nis no longer available on this server " - "and there is no forwarding address.\n" - "Please remove all references to this " - "resource.</p>\n", - NULL)); + return("<p>The requested resource is no longer available on this server" + " and there is no forwarding address.\n" + "Please remove all references to this resource.</p>\n"); case HTTP_REQUEST_ENTITY_TOO_LARGE: return(apr_pstrcat(p, - "The requested resource<br />", - ap_escape_html(r->pool, r->uri), "<br />\n", - "does not allow request data with ", + "The requested resource does not allow request data with ", ap_escape_html(r->pool, r->method), " requests, or the amount of data provided in\n" "the request exceeds the capacity limit.\n", @@ -1317,11 +1305,9 @@ static const char *get_canned_error_string(int status, "the Server Name Indication (SNI) in use for this\n" "connection.</p>\n"); case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - s1 = apr_pstrcat(p, - "<p>Access to ", ap_escape_html(r->pool, r->uri), - "\nhas been denied for legal reasons.<br />\n", - NULL); - return(add_optional_notes(r, s1, "error-notes", "</p>\n")); + return(add_optional_notes(r, + "<p>Access to this URL has been denied for legal reasons.<br />\n", + "error-notes", "</p>\n")); default: /* HTTP_INTERNAL_SERVER_ERROR */ /* * This comparison to expose error-notes could be modified to diff --git a/modules/http/http_request.c b/modules/http/http_request.c index 9e7c4db..d59cfe2 100644 --- a/modules/http/http_request.c +++ b/modules/http/http_request.c @@ -249,7 +249,7 @@ AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb, apr_brigade_cleanup(bb); rv = ap_get_brigade(c->input_filters, bb, mode, APR_NONBLOCK_READ, len); - if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb) || !max_blank_lines) { + if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) { if (mode == AP_MODE_READBYTES) { /* Unexpected error, stop with this connection */ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02967) @@ -257,23 +257,22 @@ AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb, c->keepalive = AP_CONN_CLOSE; rv = APR_EGENERAL; } - else if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) { - if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { - /* Pipe is dead */ - c->keepalive = AP_CONN_CLOSE; - } - else { - /* Pipe is up and empty */ - rv = APR_EAGAIN; - } + else if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { + /* Pipe is dead */ + c->keepalive = AP_CONN_CLOSE; } else { - apr_off_t n = 0; - /* Single read asked, (non-meta-)data available? */ - rv = apr_brigade_length(bb, 0, &n); - if (rv == APR_SUCCESS && n <= 0) { - rv = APR_EAGAIN; - } + /* Pipe is up and empty */ + rv = APR_EAGAIN; + } + break; + } + if (!max_blank_lines) { + apr_off_t n = 0; + /* Single read asked, (non-meta-)data available? */ + rv = apr_brigade_length(bb, 0, &n); + if (rv == APR_SUCCESS && n <= 0) { + rv = APR_EAGAIN; } break; } @@ -681,7 +680,7 @@ static request_rec *internal_internal_redirect(const char *new_uri, * to do their thing on internal redirects as well. Perhaps this is a * misnamed function. */ - if ((access_status = ap_run_post_read_request(new))) { + if ((access_status = ap_post_read_request(new))) { ap_die(access_status, new); return NULL; } diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c index 28c53be..700f824 100644 --- a/modules/http/mod_mime.c +++ b/modules/http/mod_mime.c @@ -755,7 +755,7 @@ static int find_ct(request_rec *r) mime_dir_config *conf; apr_array_header_t *exception_list; char *ext; - const char *fn, *fntmp, *type, *charset = NULL, *resource_name; + const char *fn, *fntmp, *type, *charset = NULL, *resource_name, *qm; int found_metadata = 0; if (r->finfo.filetype == APR_DIR) { @@ -775,6 +775,19 @@ static int find_ct(request_rec *r) if (conf->use_path_info & 1) { resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL); } + /* + * In the reverse proxy case r->filename might contain a query string if + * the nocanon option was used with ProxyPass. + * If this is the case cut off the query string as the last parameter in + * this query string might end up on an extension we take care about, but + * we only want to match against path components not against query + * parameters. + */ + else if ((r->proxyreq == PROXYREQ_REVERSE) + && (apr_table_get(r->notes, "proxy-nocanon")) + && ((qm = ap_strchr_c(r->filename, '?')) != NULL)) { + resource_name = apr_pstrmemdup(r->pool, r->filename, qm - r->filename); + } else { resource_name = r->filename; } @@ -989,9 +1002,7 @@ static int find_ct(request_rec *r) if (!r->content_languages && conf->default_language) { const char **new; - if (!r->content_languages) { - r->content_languages = apr_array_make(r->pool, 2, sizeof(char *)); - } + r->content_languages = apr_array_make(r->pool, 2, sizeof(char *)); new = (const char **)apr_array_push(r->content_languages); *new = conf->default_language; } |