diff options
Diffstat (limited to 'modules/http/http_protocol.c')
-rw-r--r-- | modules/http/http_protocol.c | 2439 |
1 files changed, 0 insertions, 2439 deletions
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c deleted file mode 100644 index a4426466be..0000000000 --- a/modules/http/http_protocol.c +++ /dev/null @@ -1,2439 +0,0 @@ -/* ==================================================================== - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2000-2001 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowledgment may appear in the software itself, - * if and wherever such third-party acknowledgments normally appear. - * - * 4. The names "Apache" and "Apache Software Foundation" must - * not be used to endorse or promote products derived from this - * software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache", - * nor may "Apache" appear in their name, without prior written - * permission of the Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * <http://www.apache.org/>. - * - * Portions of this software are based upon public domain software - * originally written at the National Center for Supercomputing Applications, - * University of Illinois, Urbana-Champaign. - */ - -/* - * http_protocol.c --- routines which directly communicate with the client. - * - * Code originally by Rob McCool; much redone by Robert S. Thau - * and the Apache Software Foundation. - */ - -#include "apr.h" -#include "apr_strings.h" -#include "apr_buckets.h" -#include "apr_lib.h" -#include "apr_signal.h" - -#define APR_WANT_STDIO /* for sscanf */ -#define APR_WANT_STRFUNC -#define APR_WANT_MEMFUNC -#include "apr_want.h" - -#define CORE_PRIVATE -#include "util_filter.h" -#include "ap_config.h" -#include "httpd.h" -#include "http_config.h" -#include "http_core.h" -#include "http_protocol.h" -#include "http_main.h" -#include "http_request.h" -#include "http_vhost.h" -#include "http_log.h" /* For errors detected in basic auth common - * support code... */ -#include "util_date.h" /* For parseHTTPdate and BAD_DATE */ -#include "util_charset.h" -#include "util_ebcdic.h" - -#include "mod_core.h" - -#if APR_HAVE_STDARG_H -#include <stdarg.h> -#endif -#if APR_HAVE_UNISTD_H -#include <unistd.h> -#endif - - -AP_DECLARE(int) ap_set_keepalive(request_rec *r) -{ - int ka_sent = 0; - int wimpy = ap_find_token(r->pool, - apr_table_get(r->headers_out, "Connection"), "close"); - const char *conn = apr_table_get(r->headers_in, "Connection"); - - /* The following convoluted conditional determines whether or not - * the current connection should remain persistent after this response - * (a.k.a. HTTP Keep-Alive) and whether or not the output message - * body should use the HTTP/1.1 chunked transfer-coding. In English, - * - * IF we have not marked this connection as errored; - * and the response body has a defined length due to the status code - * being 304 or 204, the request method being HEAD, already - * having defined Content-Length or Transfer-Encoding: chunked, or - * the request version being HTTP/1.1 and thus capable of being set - * as chunked [we know the (r->chunked = 1) side-effect is ugly]; - * and the server configuration enables keep-alive; - * and the server configuration has a reasonable inter-request timeout; - * and there is no maximum # requests or the max hasn't been reached; - * and the response status does not require a close; - * and the response generator has not already indicated close; - * and the client did not request non-persistence (Connection: close); - * and we haven't been configured to ignore the buggy twit - * or they're a buggy twit coming through a HTTP/1.1 proxy - * and the client is requesting an HTTP/1.0-style keep-alive - * or the client claims to be HTTP/1.1 compliant (perhaps a proxy); - * THEN we can be persistent, which requires more headers be output. - * - * Note that the condition evaluation order is extremely important. - */ - if ((r->connection->keepalive != -1) - && ((r->status == HTTP_NOT_MODIFIED) - || (r->status == HTTP_NO_CONTENT) - || r->header_only - || apr_table_get(r->headers_out, "Content-Length") - || ap_find_last_token(r->pool, - apr_table_get(r->headers_out, - "Transfer-Encoding"), - "chunked") - || ((r->proto_num >= HTTP_VERSION(1,1)) - && (r->chunked = 1))) /* THIS CODE IS CORRECT, see comment above. */ - && r->server->keep_alive - && (r->server->keep_alive_timeout > 0) - && ((r->server->keep_alive_max == 0) - || (r->server->keep_alive_max > r->connection->keepalives)) - && !ap_status_drops_connection(r->status) - && !wimpy - && !ap_find_token(r->pool, conn, "close") - && (!apr_table_get(r->subprocess_env, "nokeepalive") - || apr_table_get(r->headers_in, "Via")) - && ((ka_sent = ap_find_token(r->pool, conn, "keep-alive")) - || (r->proto_num >= HTTP_VERSION(1,1)))) { - int left = r->server->keep_alive_max - r->connection->keepalives; - - r->connection->keepalive = 1; - r->connection->keepalives++; - - /* If they sent a Keep-Alive token, send one back */ - if (ka_sent) { - if (r->server->keep_alive_max) - apr_table_setn(r->headers_out, "Keep-Alive", - apr_psprintf(r->pool, "timeout=%d, max=%d", - r->server->keep_alive_timeout, left)); - else - apr_table_setn(r->headers_out, "Keep-Alive", - apr_psprintf(r->pool, "timeout=%d", - r->server->keep_alive_timeout)); - apr_table_mergen(r->headers_out, "Connection", "Keep-Alive"); - } - - return 1; - } - - /* Otherwise, we need to indicate that we will be closing this - * connection immediately after the current response. - * - * We only really need to send "close" to HTTP/1.1 clients, but we - * always send it anyway, because a broken proxy may identify itself - * as HTTP/1.0, but pass our request along with our HTTP/1.1 tag - * to a HTTP/1.1 client. Better safe than sorry. - */ - if (!wimpy) - apr_table_mergen(r->headers_out, "Connection", "close"); - - r->connection->keepalive = 0; - - return 0; -} - -AP_DECLARE(int) ap_meets_conditions(request_rec *r) -{ - const char *etag = apr_table_get(r->headers_out, "ETag"); - const char *if_match, *if_modified_since, *if_unmodified, *if_nonematch; - apr_time_t mtime; - - /* Check for conditional requests --- note that we only want to do - * this if we are successful so far and we are not processing a - * subrequest or an ErrorDocument. - * - * The order of the checks is important, since ETag checks are supposed - * to be more accurate than checks relative to the modification time. - * However, not all documents are guaranteed to *have* ETags, and some - * might have Last-Modified values w/o ETags, so this gets a little - * complicated. - */ - - if (!ap_is_HTTP_SUCCESS(r->status) || r->no_local_copy) { - return OK; - } - - /* XXX: we should define a "time unset" constant */ - mtime = (r->mtime != 0) ? r->mtime : apr_time_now(); - - /* If an If-Match request-header field was given - * AND the field value is not "*" (meaning match anything) - * AND if our strong ETag does not match any entity tag in that field, - * respond with a status of 412 (Precondition Failed). - */ - if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) { - if (if_match[0] != '*' - && (etag == NULL || etag[0] == 'W' - || !ap_find_list_item(r->pool, if_match, etag))) { - return HTTP_PRECONDITION_FAILED; - } - } - else { - /* Else if a valid If-Unmodified-Since request-header field was given - * AND the requested resource has been modified since the time - * specified in this field, then the server MUST - * respond with a status of 412 (Precondition Failed). - */ - if_unmodified = apr_table_get(r->headers_in, "If-Unmodified-Since"); - if (if_unmodified != NULL) { - apr_time_t ius = ap_parseHTTPdate(if_unmodified); - - if ((ius != BAD_DATE) && (mtime > ius)) { - return HTTP_PRECONDITION_FAILED; - } - } - } - - /* If an If-None-Match request-header field was given - * AND the field value is "*" (meaning match anything) - * OR our ETag matches any of the entity tags in that field, fail. - * - * If the request method was GET or HEAD, failure means the server - * SHOULD respond with a 304 (Not Modified) response. - * For all other request methods, failure means the server MUST - * respond with a status of 412 (Precondition Failed). - * - * GET or HEAD allow weak etag comparison, all other methods require - * strong comparison. We can only use weak if it's not a range request. - */ - if_nonematch = apr_table_get(r->headers_in, "If-None-Match"); - if (if_nonematch != NULL) { - if (r->method_number == M_GET) { - if (if_nonematch[0] == '*') { - return HTTP_NOT_MODIFIED; - } - if (etag != NULL) { - if (apr_table_get(r->headers_in, "Range")) { - if (etag[0] != 'W' - && ap_find_list_item(r->pool, if_nonematch, etag)) { - return HTTP_NOT_MODIFIED; - } - } - else if (ap_strstr_c(if_nonematch, etag)) { - return HTTP_NOT_MODIFIED; - } - } - } - else if (if_nonematch[0] == '*' - || (etag != NULL - && ap_find_list_item(r->pool, if_nonematch, etag))) { - return HTTP_PRECONDITION_FAILED; - } - } - /* Else if a valid If-Modified-Since request-header field was given - * AND it is a GET or HEAD request - * AND the requested resource has not been modified since the time - * specified in this field, then the server MUST - * respond with a status of 304 (Not Modified). - * A date later than the server's current request time is invalid. - */ - else if ((r->method_number == M_GET) - && ((if_modified_since = - apr_table_get(r->headers_in, - "If-Modified-Since")) != NULL)) { - apr_time_t ims = ap_parseHTTPdate(if_modified_since); - - if ((ims >= mtime) && (ims <= r->request_time)) { - return HTTP_NOT_MODIFIED; - } - } - return OK; -} - -/* Get the method number associated with the given string, assumed to - * contain an HTTP method. Returns M_INVALID if not recognized. - * - * This is the first step toward placing method names in a configurable - * list. Hopefully it (and other routines) can eventually be moved to - * something like a mod_http_methods.c, complete with config stuff. - */ -AP_DECLARE(int) ap_method_number_of(const char *method) -{ - switch (*method) { - case 'H': - if (strcmp(method, "HEAD") == 0) - return M_GET; /* see header_only in request_rec */ - break; - case 'G': - if (strcmp(method, "GET") == 0) - return M_GET; - break; - case 'P': - if (strcmp(method, "POST") == 0) - return M_POST; - if (strcmp(method, "PUT") == 0) - return M_PUT; - if (strcmp(method, "PATCH") == 0) - return M_PATCH; - if (strcmp(method, "PROPFIND") == 0) - return M_PROPFIND; - if (strcmp(method, "PROPPATCH") == 0) - return M_PROPPATCH; - break; - case 'D': - if (strcmp(method, "DELETE") == 0) - return M_DELETE; - break; - case 'C': - if (strcmp(method, "CONNECT") == 0) - return M_CONNECT; - if (strcmp(method, "COPY") == 0) - return M_COPY; - break; - case 'M': - if (strcmp(method, "MKCOL") == 0) - return M_MKCOL; - if (strcmp(method, "MOVE") == 0) - return M_MOVE; - break; - case 'O': - if (strcmp(method, "OPTIONS") == 0) - return M_OPTIONS; - break; - case 'T': - if (strcmp(method, "TRACE") == 0) - return M_TRACE; - break; - case 'L': - if (strcmp(method, "LOCK") == 0) - return M_LOCK; - break; - case 'U': - if (strcmp(method, "UNLOCK") == 0) - return M_UNLOCK; - break; - } - return M_INVALID; -} - -/* - * Turn a known method number into a name. Doesn't work for - * extension methods, obviously. - */ -AP_DECLARE(const char *) ap_method_name_of(int methnum) -{ - static const char *AP_HTTP_METHODS[METHODS] = { NULL }; - - /* - * This is ugly, but the previous incantation made Windows C - * varf. I'm not even sure it was ANSI C. However, ugly as it - * is, this works, and we only have to do it once. - */ - if (AP_HTTP_METHODS[0] == NULL) { - AP_HTTP_METHODS[M_GET] = "GET"; - AP_HTTP_METHODS[M_PUT] = "PUT"; - AP_HTTP_METHODS[M_POST] = "POST"; - AP_HTTP_METHODS[M_DELETE] = "DELETE"; - AP_HTTP_METHODS[M_CONNECT] = "CONNECT"; - AP_HTTP_METHODS[M_OPTIONS] = "OPTIONS"; - AP_HTTP_METHODS[M_TRACE] = "TRACE"; - AP_HTTP_METHODS[M_PATCH] = "PATCH"; - AP_HTTP_METHODS[M_PROPFIND] = "PROPFIND"; - AP_HTTP_METHODS[M_PROPPATCH] = "PROPPATCH"; - AP_HTTP_METHODS[M_MKCOL] = "MKCOL"; - AP_HTTP_METHODS[M_COPY] = "COPY"; - AP_HTTP_METHODS[M_MOVE] = "MOVE"; - AP_HTTP_METHODS[M_LOCK] = "LOCK"; - AP_HTTP_METHODS[M_UNLOCK] = "UNLOCK"; - AP_HTTP_METHODS[M_INVALID] = NULL; - /* - * Since we're using symbolic names, make sure we only do - * this once by forcing a value into the first slot IFF it's - * still NULL. - */ - if (AP_HTTP_METHODS[0] == NULL) { - AP_HTTP_METHODS[0] = "INVALID"; - } - } - - if ((methnum == M_INVALID) || (methnum >= METHODS)) { - return NULL; - } - return AP_HTTP_METHODS[methnum]; -} - -struct dechunk_ctx { - apr_size_t chunk_size; - apr_size_t bytes_delivered; - enum {WANT_HDR /* must have value zero */, WANT_BODY, WANT_TRL} state; -}; - -static long get_chunk_size(char *); - -apr_status_t ap_dechunk_filter(ap_filter_t *f, apr_bucket_brigade *bb, - ap_input_mode_t mode, apr_size_t *readbytes) -{ - apr_status_t rv; - struct dechunk_ctx *ctx = f->ctx; - apr_bucket *b; - const char *buf; - apr_size_t len; - - if (!ctx) { - f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(struct dechunk_ctx)); - } - - do { - if (ctx->chunk_size == ctx->bytes_delivered) { - /* Time to read another chunk header or trailer... ap_http_filter() is - * the next filter in line and it knows how to return a brigade with - * one line. - */ - char line[30]; - - if ((rv = ap_getline(line, sizeof(line), f->r, 0)) < 0) { - return rv; - } - switch(ctx->state) { - case WANT_HDR: - ctx->chunk_size = get_chunk_size(line); - ctx->bytes_delivered = 0; - if (ctx->chunk_size == 0) { - ctx->state = WANT_TRL; - } - else { - ctx->state = WANT_BODY; - } - break; - case WANT_TRL: - /* XXX sanity check end chunk here */ - if (strlen(line)) { - /* bad trailer */ - } - if (ctx->chunk_size == 0) { /* we just finished the last chunk? */ - /* append eos bucket and get out */ - b = apr_bucket_eos_create(); - APR_BRIGADE_INSERT_TAIL(bb, b); - return APR_SUCCESS; - } - ctx->state = WANT_HDR; - break; - default: - ap_assert(ctx->state == WANT_HDR || ctx->state == WANT_TRL); - } - } - } while (ctx->state != WANT_BODY); - - if (ctx->state == WANT_BODY) { - /* Tell ap_http_filter() how many bytes to deliver. */ - apr_size_t readbytes = ctx->chunk_size - ctx->bytes_delivered; - if ((rv = ap_get_brigade(f->next, bb, mode, &readbytes)) != APR_SUCCESS) { - return rv; - } - /* Walk through the body, accounting for bytes, and removing an eos bucket if - * ap_http_filter() delivered the entire chunk. - */ - b = APR_BRIGADE_FIRST(bb); - while (b != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(b)) { - apr_bucket_read(b, &buf, &len, mode); - AP_DEBUG_ASSERT(len <= ctx->chunk_size - ctx->bytes_delivered); - ctx->bytes_delivered += len; - b = APR_BUCKET_NEXT(b); - } - if (ctx->bytes_delivered == ctx->chunk_size) { - AP_DEBUG_ASSERT(APR_BUCKET_IS_EOS(b)); - apr_bucket_delete(b); - ctx->state = WANT_TRL; - } - } - - return APR_SUCCESS; -} - -typedef struct http_filter_ctx { - apr_bucket_brigade *b; -} http_ctx_t; - -apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_size_t *readbytes) -{ - apr_bucket *e; - char *buff; - apr_size_t len; - char *pos; - http_ctx_t *ctx = f->ctx; - apr_status_t rv; - - if (!ctx) { - f->ctx = ctx = apr_pcalloc(f->c->pool, sizeof(*ctx)); - ctx->b = apr_brigade_create(f->c->pool); - } - - if (mode == AP_MODE_PEEK) { - apr_bucket *e; - const char *str; - apr_size_t length; - - /* The purpose of this loop is to ignore any CRLF (or LF) at the end - * of a request. Many browsers send extra lines at the end of POST - * requests. We use the PEEK method to determine if there is more - * data on the socket, so that we know if we should delay sending the - * end of one request until we have served the second request in a - * pipelined situation. We don't want to actually delay sending a - * response if the server finds a CRLF (or LF), becuause that doesn't - * mean that there is another request, just a blank line. - */ - while (1) { - if (APR_BRIGADE_EMPTY(ctx->b)) { - e = NULL; - } - else { - e = APR_BRIGADE_FIRST(ctx->b); - } - if (!e || apr_bucket_read(e, &str, &length, APR_NONBLOCK_READ) != APR_SUCCESS) { - return APR_EOF; - } - else { - const char *c = str; - while (c - str < length) { - if (*c == APR_ASCII_LF) - c++; - else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF) - c += 2; - else return APR_SUCCESS; - } - apr_bucket_delete(e); - } - } - } - - if (APR_BRIGADE_EMPTY(ctx->b)) { - if ((rv = ap_get_brigade(f->next, ctx->b, mode, readbytes)) != APR_SUCCESS) { - return rv; - } - } - - if (*readbytes) { - while (!APR_BRIGADE_EMPTY(ctx->b)) { - const char *ignore; - - e = APR_BRIGADE_FIRST(ctx->b); - if ((rv = apr_bucket_read(e, &ignore, &len, mode)) != APR_SUCCESS) { - /* probably APR_IS_EAGAIN(rv); socket state isn't correct; - * remove log once we get this squared away */ - ap_log_error(APLOG_MARK, APLOG_ERR, rv, f->c->base_server, - "apr_bucket_read"); - return rv; - } - - if (len) { - /* note: this can sometimes insert empty buckets into the - * brigade, or the data might come in a few characters at - * a time - don't assume that one call to apr_bucket_read() - * will return the full string. - */ - if (*readbytes < len) { - apr_bucket_split(e, *readbytes); - *readbytes = 0; - } - else { - *readbytes -= len; - } - APR_BUCKET_REMOVE(e); - APR_BRIGADE_INSERT_TAIL(b, e); - break; /* once we've gotten some data, deliver it to caller */ - } - apr_bucket_delete(e); - } - if (*readbytes == 0) { - apr_bucket *eos = apr_bucket_eos_create(); - - APR_BRIGADE_INSERT_TAIL(b, eos); - } - return APR_SUCCESS; - } - - while (!APR_BRIGADE_EMPTY(ctx->b)) { - e = APR_BRIGADE_FIRST(ctx->b); - if ((rv = apr_bucket_read(e, (const char **)&buff, &len, mode)) != APR_SUCCESS) { - return rv; - } - - pos = memchr(buff, APR_ASCII_LF, len); - if (pos != NULL) { - apr_bucket_split(e, pos - buff + 1); - APR_BUCKET_REMOVE(e); - APR_BRIGADE_INSERT_TAIL(b, e); - return APR_SUCCESS; - } - APR_BUCKET_REMOVE(e); - APR_BRIGADE_INSERT_TAIL(b, e); - } - return APR_SUCCESS; -} - -/* New Apache routine to map status codes into array indicies - * 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. - */ - -#ifdef UTS21 -/* The second const triggers an assembler bug on UTS 2.1. - * Another workaround is to move some code out of this file into another, - * but this is easier. Dave Dykstra, 3/31/99 - */ -static const char * status_lines[RESPONSE_CODES] = -#else -static const char * const status_lines[RESPONSE_CODES] = -#endif -{ - "100 Continue", - "101 Switching Protocols", - "102 Processing", -#define LEVEL_200 3 - "200 OK", - "201 Created", - "202 Accepted", - "203 Non-Authoritative Information", - "204 No Content", - "205 Reset Content", - "206 Partial Content", - "207 Multi-Status", -#define LEVEL_300 11 - "300 Multiple Choices", - "301 Moved Permanently", - "302 Found", - "303 See Other", - "304 Not Modified", - "305 Use Proxy", - "306 unused", - "307 Temporary Redirect", -#define LEVEL_400 19 - "400 Bad Request", - "401 Authorization Required", - "402 Payment Required", - "403 Forbidden", - "404 Not Found", - "405 Method Not Allowed", - "406 Not Acceptable", - "407 Proxy Authentication Required", - "408 Request Time-out", - "409 Conflict", - "410 Gone", - "411 Length Required", - "412 Precondition Failed", - "413 Request Entity Too Large", - "414 Request-URI Too Large", - "415 Unsupported Media Type", - "416 Requested Range Not Satisfiable", - "417 Expectation Failed", - "418 unused", - "419 unused", - "420 unused", - "421 unused", - "422 Unprocessable Entity", - "423 Locked", - "424 Failed Dependency", -#define LEVEL_500 44 - "500 Internal Server Error", - "501 Method Not Implemented", - "502 Bad Gateway", - "503 Service Temporarily Unavailable", - "504 Gateway Time-out", - "505 HTTP Version Not Supported", - "506 Variant Also Negotiates", - "507 Insufficient Storage", - "508 unused", - "509 unused", - "510 Not Extended" -}; - -/* The index is found by its offset from the x00 code of each level. - * Although this is fast, it will need to be replaced if some nutcase - * decides to define a high-numbered code before the lower numbers. - * If that sad event occurs, replace the code below with a linear search - * from status_lines[shortcut[i]] to status_lines[shortcut[i+1]-1]; - */ -AP_DECLARE(int) ap_index_of_response(int status) -{ - 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; - - for (i = 0; i < 5; i++) { - status -= 100; - if (status < 100) { - pos = (status + shortcut[i]); - if (pos < shortcut[i + 1]) { - return pos; - } - else { - return LEVEL_500; /* status unknown (falls in gap) */ - } - } - } - return LEVEL_500; /* 600 or above is also illegal */ -} - -AP_DECLARE(const char *) ap_get_status_line(int status) -{ - return status_lines[ap_index_of_response(status)]; -} - -typedef struct header_struct { - apr_pool_t *pool; - apr_bucket_brigade *bb; -} header_struct; - -/* Send a single HTTP header field to the client. Note that this function - * is used in calls to table_do(), so their interfaces are co-dependent. - * In other words, don't change this one without checking table_do in alloc.c. - * It returns true unless there was a write error of some kind. - */ -static int form_header_field(header_struct *h, - const char *fieldname, const char *fieldval) -{ - char *headfield; - - headfield = apr_pstrcat(h->pool, fieldname, ": ", fieldval, CRLF, NULL); - ap_xlate_proto_to_ascii(headfield, strlen(headfield)); - apr_brigade_puts(h->bb, NULL, NULL, headfield); - return 1; -} - -/* - * Determine the protocol to use for the response. Potentially downgrade - * to HTTP/1.0 in some situations and/or turn off keepalives. - * - * also prepare r->status_line. - */ -static void basic_http_header_check(request_rec *r, - const char **protocol) -{ - if (r->assbackwards) { - /* no such thing as a response protocol */ - return; - } - - if (!r->status_line) - r->status_line = status_lines[ap_index_of_response(r->status)]; - - /* kluge around broken browsers when indicated by force-response-1.0 - */ - if (r->proto_num == HTTP_VERSION(1,0) - && apr_table_get(r->subprocess_env, "force-response-1.0")) { - - *protocol = "HTTP/1.0"; - r->connection->keepalive = -1; - } - else { - *protocol = AP_SERVER_PROTOCOL; - } -} - -/* fill "bb" with a barebones/initial HTTP response header */ -static void basic_http_header(request_rec *r, apr_bucket_brigade *bb, - const char *protocol) -{ - char *date = NULL; - char *tmp; - header_struct h; - - if (r->assbackwards) { - /* there are no headers to send */ - return; - } - - /* Output the HTTP/1.x Status-Line and the Date and Server fields */ - - tmp = apr_pstrcat(r->pool, protocol, " ", r->status_line, CRLF, NULL); - ap_xlate_proto_to_ascii(tmp, strlen(tmp)); - apr_brigade_puts(bb, NULL, NULL, tmp); - - date = apr_palloc(r->pool, APR_RFC822_DATE_LEN); - apr_rfc822_date(date, r->request_time); - - h.pool = r->pool; - h.bb = bb; - form_header_field(&h, "Date", date); - form_header_field(&h, "Server", ap_get_server_version()); - - apr_table_unset(r->headers_out, "Date"); /* Avoid bogosity */ - apr_table_unset(r->headers_out, "Server"); -} - -AP_DECLARE(void) ap_basic_http_header(request_rec *r, apr_bucket_brigade *bb) -{ - const char *protocol; - - basic_http_header_check(r, &protocol); - basic_http_header(r, bb, protocol); -} - -/* Navigator versions 2.x, 3.x and 4.0 betas up to and including 4.0b2 - * have a header parsing bug. If the terminating \r\n occur starting - * at offset 256, 257 or 258 of output then it will not properly parse - * the headers. Curiously it doesn't exhibit this problem at 512, 513. - * We are guessing that this is because their initial read of a new request - * uses a 256 byte buffer, and subsequent reads use a larger buffer. - * So the problem might exist at different offsets as well. - * - * This should also work on keepalive connections assuming they use the - * same small buffer for the first read of each new request. - * - * At any rate, we check the bytes written so far and, if we are about to - * tickle the bug, we instead insert a bogus padding header. Since the bug - * manifests as a broken image in Navigator, users blame the server. :( - * It is more expensive to check the User-Agent than it is to just add the - * bytes, so we haven't used the BrowserMatch feature here. - */ -static void terminate_header(apr_bucket_brigade *bb) -{ - char tmp[] = "X-Pad: avoid browser bug" CRLF; - char crlf[] = CRLF; - apr_ssize_t len; - - (void) apr_brigade_length(bb, 1, &len); - - if (len >= 255 && len <= 257) { - ap_xlate_proto_to_ascii(tmp, strlen(tmp)); - apr_brigade_puts(bb, NULL, NULL, tmp); - } - ap_xlate_proto_to_ascii(crlf, strlen(crlf)); - apr_brigade_puts(bb, NULL, NULL, crlf); -} - -/* Build the Allow field-value from the request handler method mask. - * Note that we always allow TRACE, since it is handled below. - */ -static char *make_allow(request_rec *r) -{ - char *list; - int mask; - - mask = r->allowed_methods->method_mask; - list = apr_pstrcat(r->pool, - (mask & (1 << M_GET)) ? ", GET, HEAD" : "", - (mask & (1 << M_POST)) ? ", POST" : "", - (mask & (1 << M_PUT)) ? ", PUT" : "", - (mask & (1 << M_DELETE)) ? ", DELETE" : "", - (mask & (1 << M_CONNECT)) ? ", CONNECT" : "", - (mask & (1 << M_OPTIONS)) ? ", OPTIONS" : "", - (mask & (1 << M_PATCH)) ? ", PATCH" : "", - (mask & (1 << M_PROPFIND)) ? ", PROPFIND" : "", - (mask & (1 << M_PROPPATCH)) ? ", PROPPATCH" : "", - (mask & (1 << M_MKCOL)) ? ", MKCOL" : "", - (mask & (1 << M_COPY)) ? ", COPY" : "", - (mask & (1 << M_MOVE)) ? ", MOVE" : "", - (mask & (1 << M_LOCK)) ? ", LOCK" : "", - (mask & (1 << M_UNLOCK)) ? ", UNLOCK" : "", - ", TRACE", - NULL); - if ((mask & (1 << M_INVALID)) - && (r->allowed_methods->method_list != NULL) - && (r->allowed_methods->method_list->nelts != 0)) { - int i; - char **xmethod = (char **) r->allowed_methods->method_list->elts; - - /* - * Append all of the elements of r->allowed_methods->method_list - */ - for (i = 0; i < r->allowed_methods->method_list->nelts; ++i) { - list = apr_pstrcat(r->pool, list, ", ", xmethod[i], NULL); - } - } - /* - * Space past the leading ", ". Wastes two bytes, but that's better - * than futzing around to find the actual length. - */ - return list + 2; -} - -AP_DECLARE(int) ap_send_http_trace(request_rec *r) -{ - int rv; - - /* Get the original request */ - while (r->prev) - r = r->prev; - - if ((rv = ap_setup_client_block(r, REQUEST_NO_BODY))) - return rv; - - r->content_type = "message/http"; - - /* Now we recreate the request, and echo it back */ - - ap_rvputs(r, r->the_request, CRLF, NULL); - - apr_table_do((int (*) (void *, const char *, const char *)) - form_header_field, (void *) r, r->headers_in, NULL); - ap_rputs(CRLF, r); - - return OK; -} - -int ap_send_http_options(request_rec *r) -{ - if (r->assbackwards) - return DECLINED; - - apr_table_setn(r->headers_out, "Allow", make_allow(r)); - - /* the request finalization will send an EOS, which will flush all - the headers out (including the Allow header) */ - - return OK; -} - -/* This routine is called by apr_table_do and merges all instances of - * the passed field values into a single array that will be further - * processed by some later routine. Originally intended to help split - * and recombine multiple Vary fields, though it is generic to any field - * consisting of comma/space-separated tokens. - */ -static int uniq_field_values(void *d, const char *key, const char *val) -{ - apr_array_header_t *values; - char *start; - char *e; - char **strpp; - int i; - - values = (apr_array_header_t *)d; - - e = apr_pstrdup(values->cont, val); - - do { - /* Find a non-empty fieldname */ - - while (*e == ',' || apr_isspace(*e)) { - ++e; - } - if (*e == '\0') { - break; - } - start = e; - while (*e != '\0' && *e != ',' && !apr_isspace(*e)) { - ++e; - } - if (*e != '\0') { - *e++ = '\0'; - } - - /* Now add it to values if it isn't already represented. - * Could be replaced by a ap_array_strcasecmp() if we had one. - */ - for (i = 0, strpp = (char **) values->elts; i < values->nelts; - ++i, ++strpp) { - if (*strpp && strcasecmp(*strpp, start) == 0) { - break; - } - } - if (i == values->nelts) { /* if not found */ - *(char **)apr_array_push(values) = start; - } - } while (*e != '\0'); - - return 1; -} - -/* - * Since some clients choke violently on multiple Vary fields, or - * Vary fields with duplicate tokens, combine any multiples and remove - * any duplicates. - */ -static void fixup_vary(request_rec *r) -{ - apr_array_header_t *varies; - - varies = apr_array_make(r->pool, 5, sizeof(char *)); - - /* Extract all Vary fields from the headers_out, separate each into - * its comma-separated fieldname values, and then add them to varies - * if not already present in the array. - */ - apr_table_do((int (*)(void *, const char *, const char *))uniq_field_values, - (void *) varies, r->headers_out, "Vary", NULL); - - /* If we found any, replace old Vary fields with unique-ified value */ - - if (varies->nelts > 0) { - apr_table_setn(r->headers_out, "Vary", - apr_array_pstrcat(r->pool, varies, ',')); - } -} - -AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter( - ap_filter_t *f, - apr_bucket_brigade *b) -{ - int i; - char *date = NULL; - request_rec *r = f->r; - const char *clheader; - const char *protocol; - apr_bucket *e; - apr_bucket_brigade *b2; - header_struct h; - - AP_DEBUG_ASSERT(!r->main); - - APR_BRIGADE_FOREACH(e, b) { - if (e->type == &ap_bucket_type_error) { - ap_bucket_error *eb = e->data; - - ap_die(eb->status, r); - return AP_FILTER_ERROR; - } - } - - if (r->assbackwards) { - r->bytes_sent = 0; - r->sent_bodyct = 1; - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, b); - } - - /* - * Now that we are ready to send a response, we need to combine the two - * header field tables into a single table. If we don't do this, our - * later attempts to set or unset a given fieldname might be bypassed. - */ - if (!apr_is_empty_table(r->err_headers_out)) - r->headers_out = apr_table_overlay(r->pool, r->err_headers_out, - r->headers_out); - - /* - * Remove the 'Vary' header field if the client can't handle it. - * Since this will have nasty effects on HTTP/1.1 caches, force - * the response into HTTP/1.0 mode. - * - * Note: the force-response-1.0 should come before the call to - * basic_http_header_check() - */ - if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) { - apr_table_unset(r->headers_out, "Vary"); - r->proto_num = HTTP_VERSION(1,0); - apr_table_set(r->subprocess_env, "force-response-1.0", "1"); - } - else { - fixup_vary(r); - } - - /* determine the protocol and whether we should use keepalives. */ - basic_http_header_check(r, &protocol); - ap_set_keepalive(r); - - if (r->chunked) { - apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked"); - apr_table_unset(r->headers_out, "Content-Length"); - - } - - apr_table_setn(r->headers_out, "Content-Type", ap_make_content_type(r, - r->content_type)); - - if (r->content_encoding) { - apr_table_setn(r->headers_out, "Content-Encoding", - r->content_encoding); - } - - if (r->content_languages && r->content_languages->nelts) { - for (i = 0; i < r->content_languages->nelts; ++i) { - apr_table_mergen(r->headers_out, "Content-Language", - ((char **) (r->content_languages->elts))[i]); - } - } - else if (r->content_language) { - apr_table_setn(r->headers_out, "Content-Language", - r->content_language); - } - - /* - * Control cachability for non-cachable responses if not already set by - * some other part of the server configuration. - */ - if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) { - date = apr_palloc(r->pool, APR_RFC822_DATE_LEN); - apr_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 - * if they see a HEAD request. Apache 1.3 handlers were supposed to - * just return in that situation, and the core handled the HEAD. In - * 2.0, if a handler returns, then the core sends an EOS bucket down - * the filter stack, and the content-length filter computes a C-L of - * zero and that gets put in the headers, and we end up sending a - * zero C-L to the client. We can't just remove the C-L filter, - * because well behaved 2.0 handlers will send their data down the stack, - * and we will compute a real C-L for the head request. RBB - */ - if (r->header_only && - (clheader = apr_table_get(r->headers_out, "Content-Length")) && - !strcmp(clheader, "0")) { - apr_table_unset(r->headers_out, "Content-Length"); - } - - b2 = apr_brigade_create(r->pool); - basic_http_header(r, b2, protocol); - - 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", - NULL); - } - else { - apr_table_do((int (*) (void *, const char *, const char *)) form_header_field, - (void *) &h, r->headers_out, NULL); - } - - terminate_header(b2); - - r->sent_bodyct = 1; /* Whatever follows is real body stuff... */ - - ap_pass_brigade(f->next, b2); - - if (r->header_only) { - apr_brigade_destroy(b); - return OK; - } - - if (r->chunked) { - /* We can't add this filter until we have already sent the headers. - * If we add it before this point, then the headers will be chunked - * as well, and that is just wrong. - */ - ap_add_output_filter("CHUNK", NULL, r, r->connection); - } - - /* Don't remove this filter until after we have added the CHUNK filter. - * Otherwise, f->next won't be the CHUNK filter and thus the first - * brigade won't be chunked properly. - */ - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, b); -} - -/* Here we deal with getting the request message body from the client. - * Whether or not the request contains a body is signaled by the presence - * of a non-zero Content-Length or by a Transfer-Encoding: chunked. - * - * Note that this is more complicated than it was in Apache 1.1 and prior - * versions, because chunked support means that the module does less. - * - * The proper procedure is this: - * - * 1. Call setup_client_block() near the beginning of the request - * handler. This will set up all the necessary properties, and will - * return either OK, or an error code. If the latter, the module should - * return that error code. The second parameter selects the policy to - * apply if the request message indicates a body, and how a chunked - * transfer-coding should be interpreted. Choose one of - * - * REQUEST_NO_BODY Send 413 error if message has any body - * REQUEST_CHUNKED_ERROR Send 411 error if body without Content-Length - * REQUEST_CHUNKED_DECHUNK If chunked, remove the chunks for me. - * - * In order to use the last two options, the caller MUST provide a buffer - * large enough to hold a chunk-size line, including any extensions. - * - * 2. When you are ready to read a body (if any), call should_client_block(). - * This will tell the module whether or not to read input. If it is 0, - * the module should assume that there is no message body to read. - * This step also sends a 100 Continue response to HTTP/1.1 clients, - * so should not be called until the module is *definitely* ready to - * read content. (otherwise, the point of the 100 response is defeated). - * Never call this function more than once. - * - * 3. Finally, call get_client_block in a loop. Pass it a buffer and its size. - * It will put data into the buffer (not necessarily a full buffer), and - * return the length of the input block. When it is done reading, it will - * return 0 if EOF, or -1 if there was an error. - * If an error occurs on input, we force an end to keepalive. - */ - -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"); - long max_body; - - r->read_body = read_policy; - r->read_chunked = 0; - r->remaining = 0; - - if (tenc) { - if (strcasecmp(tenc, "chunked")) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Unknown Transfer-Encoding %s", tenc); - return HTTP_NOT_IMPLEMENTED; - } - if (r->read_body == REQUEST_CHUNKED_ERROR) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "chunked Transfer-Encoding forbidden: %s", r->uri); - return (lenp) ? HTTP_BAD_REQUEST : HTTP_LENGTH_REQUIRED; - } - - r->read_chunked = 1; - ap_add_input_filter("DECHUNK", NULL, r, r->connection); - } - else if (lenp) { - const char *pos = lenp; - - while (apr_isdigit(*pos) || apr_isspace(*pos)) { - ++pos; - } - if (*pos != '\0') { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Invalid Content-Length %s", lenp); - return HTTP_BAD_REQUEST; - } - - r->remaining = atol(lenp); - } - - if ((r->read_body == REQUEST_NO_BODY) && - (r->read_chunked || (r->remaining > 0))) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "%s with body is not allowed for %s", r->method, r->uri); - return HTTP_REQUEST_ENTITY_TOO_LARGE; - } - - max_body = ap_get_limit_req_body(r); - if (max_body && (r->remaining > max_body)) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Request content-length of %s is larger than " - "the configured limit of %lu", lenp, max_body); - return HTTP_REQUEST_ENTITY_TOO_LARGE; - } - -#ifdef AP_DEBUG - { - /* Make sure ap_getline() didn't leave any droppings. */ - core_request_config *req_cfg = - (core_request_config *)ap_get_module_config(r->request_config, - &core_module); - AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(req_cfg->bb)); - } -#endif - - return OK; -} - -AP_DECLARE(int) ap_should_client_block(request_rec *r) -{ - /* First check if we have already read the request body */ - - if (r->read_length || (!r->read_chunked && (r->remaining <= 0))) - return 0; - - if (r->expecting_100 && r->proto_num >= HTTP_VERSION(1,1)) { - char *tmp; - apr_bucket *e; - apr_bucket_brigade *bb; - - /* sending 100 Continue interim response */ - tmp = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL, " ", status_lines[0], - CRLF CRLF, NULL); - bb = apr_brigade_create(r->pool); - e = apr_bucket_pool_create(tmp, strlen(tmp), r->pool); - APR_BRIGADE_INSERT_HEAD(bb, e); - e = apr_bucket_flush_create(); - APR_BRIGADE_INSERT_TAIL(bb, e); - - ap_pass_brigade(r->connection->output_filters, bb); - } - - return 1; -} - -static long get_chunk_size(char *b) -{ - long chunksize = 0; - - while (apr_isxdigit(*b)) { - int xvalue = 0; - - if (*b >= '0' && *b <= '9') { - xvalue = *b - '0'; - } - else if (*b >= 'A' && *b <= 'F') { - xvalue = *b - 'A' + 0xa; - } - else if (*b >= 'a' && *b <= 'f') { - xvalue = *b - 'a' + 0xa; - } - - chunksize = (chunksize << 4) | xvalue; - ++b; - } - - return chunksize; -} - -/* get_client_block is called in a loop to get the request message body. - * This is quite simple if the client includes a content-length - * (the normal case), but gets messy if the body is chunked. Note that - * r->remaining is used to maintain state across calls and that - * r->read_length is the total number of bytes given to the caller - * across all invocations. It is messy because we have to be careful not - * to read past the data provided by the client, since these reads block. - * Returns 0 on End-of-body, -1 on error or premature chunk end. - * - * Reading the chunked encoding requires a buffer size large enough to - * hold a chunk-size line, including any extensions. For now, we'll leave - * that to the caller, at least until we can come up with a better solution. - */ -AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, int bufsiz) -{ - apr_size_t len_read, total; - apr_status_t rv; - apr_bucket *b, *old; - const char *tempbuf; - core_request_config *req_cfg = - (core_request_config *)ap_get_module_config(r->request_config, - &core_module); - apr_bucket_brigade *bb = req_cfg->bb; - - do { - if (APR_BRIGADE_EMPTY(bb)) { - if (ap_get_brigade(r->input_filters, bb, AP_MODE_BLOCKING, &r->remaining) != APR_SUCCESS) { - /* if we actually fail here, we want to just return and - * stop trying to read data from the client. - */ - r->connection->keepalive = -1; - apr_brigade_destroy(bb); - return -1; - } - } - b = APR_BRIGADE_FIRST(bb); - } while (APR_BRIGADE_EMPTY(bb)); - - if (APR_BUCKET_IS_EOS(b)) { /* reached eos on previous invocation */ - apr_bucket_delete(b); - return 0; - } - - total = 0; - while (total < bufsiz && b != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(b)) { - if ((rv = apr_bucket_read(b, &tempbuf, &len_read, APR_BLOCK_READ)) != APR_SUCCESS) { - return -1; - } - if (total + len_read > bufsiz) { - apr_bucket_split(b, bufsiz - total); - len_read = bufsiz - total; - } - memcpy(buffer, tempbuf, len_read); - buffer += len_read; - total += len_read; - /* XXX the next two fields shouldn't be mucked with here, as they are in terms - * of bytes in the unfiltered body; gotta see if anybody else actually uses - * these - */ - r->read_length += len_read; /* XXX yank me? */ - r->remaining -= len_read; /* XXX yank me? */ - old = b; - b = APR_BUCKET_NEXT(b); - apr_bucket_delete(old); - } - - return total; -} - -/* 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, - * simply discarding whatever it receives. We need to do this because - * failing to read the request body would cause it to be interpreted - * as the next request on a persistent connection. - * - * Since we return an error status if the request is malformed, this - * routine should be called at the beginning of a no-body handler, e.g., - * - * if ((retval = ap_discard_request_body(r)) != OK) - * return retval; - */ -AP_DECLARE(int) ap_discard_request_body(request_rec *r) -{ - int rv; - - if (r->read_length == 0) { /* if not read already */ - if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) - return rv; - } - - /* In order to avoid sending 100 Continue when we already know the - * final response status, and yet not kill the connection if there is - * no request body to be read, we need to duplicate the test from - * ap_should_client_block() here negated rather than call it directly. - */ - if ((r->read_length == 0) && (r->read_chunked || (r->remaining > 0))) { - char dumpbuf[HUGE_STRING_LEN]; - - if (r->expecting_100) { - r->connection->keepalive = -1; - return OK; - } - - while ((rv = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN)) > 0) - continue; - - if (rv < 0) - return HTTP_BAD_REQUEST; - } - return OK; -} - -static const char *add_optional_notes(request_rec *r, - const char *prefix, - const char *key, - const char *suffix) -{ - const char *notes, *result; - - if ((notes = apr_table_get(r->notes, key)) == NULL) { - result = prefix; - } - else { - result = apr_pstrcat(r->pool, prefix, notes, suffix, NULL); - } - - return result; -} - -static const char *get_canned_error_string(int status, - request_rec *r, - const char *location) - -/* construct and return the default error message for a given - * HTTP defined error code - */ -{ - apr_pool_t *p = r->pool; - const char *error_notes, *h1, *s1; - - switch (status) { - case HTTP_MOVED_PERMANENTLY: - case HTTP_MOVED_TEMPORARILY: - case HTTP_TEMPORARY_REDIRECT: - return(apr_pstrcat(p, - "The document has moved <A HREF=\"", - ap_escape_html(r->pool, location), - "\">here</A>.<P>\n", - NULL)); - case HTTP_SEE_OTHER: - return(apr_pstrcat(p, - "The answer to your request is located <A HREF=\"", - ap_escape_html(r->pool, location), - "\">here</A>.<P>\n", - NULL)); - case HTTP_USE_PROXY: - return(apr_pstrcat(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)); - case HTTP_PROXY_AUTHENTICATION_REQUIRED: - case HTTP_UNAUTHORIZED: - return("This server could not verify that you\n" - "are authorized to access the document\n" - "requested. Either you supplied the wrong\n" - "credentials (e.g., bad password), or your\n" - "browser doesn't understand how to supply\n" - "the credentials required.<P>\n"); - case HTTP_BAD_REQUEST: - return(add_optional_notes(r, - "Your browser sent a request that " - "this server could not understand.<P>\n", - "error-notes", - "<P>\n")); - case HTTP_FORBIDDEN: - return(apr_pstrcat(p, - "You don't have permission to access ", - ap_escape_html(r->pool, r->uri), - "\non this server.<P>\n", - NULL)); - case HTTP_NOT_FOUND: - return(apr_pstrcat(p, - "The requested URL ", - ap_escape_html(r->pool, r->uri), - " was not found on this server.<P>\n", - NULL)); - case HTTP_METHOD_NOT_ALLOWED: - return(apr_pstrcat(p, - "The requested method ", r->method, - " is not allowed for the URL ", - ap_escape_html(r->pool, r->uri), - ".<P>\n", - NULL)); - case HTTP_NOT_ACCEPTABLE: - s1 = apr_pstrcat(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", "")); - case HTTP_MULTIPLE_CHOICES: - return(add_optional_notes(r, "", "variant-list", "")); - case HTTP_LENGTH_REQUIRED: - s1 = apr_pstrcat(p, - "A request of the requested method ", - r->method, - " requires a valid Content-length.<P>\n", - NULL); - return(add_optional_notes(r, s1, "error-notes", "<P>\n")); - case HTTP_PRECONDITION_FAILED: - return(apr_pstrcat(p, - "The precondition on the request for the URL ", - ap_escape_html(r->pool, r->uri), - " evaluated to false.<P>\n", - NULL)); - case HTTP_NOT_IMPLEMENTED: - s1 = apr_pstrcat(p, - ap_escape_html(r->pool, r->method), " to ", - ap_escape_html(r->pool, r->uri), - " not supported.<P>\n", - NULL); - return(add_optional_notes(r, s1, "error-notes", "<P>\n")); - case HTTP_BAD_GATEWAY: - s1 = "The proxy server received an invalid" CRLF - "response from an upstream server.<P>" CRLF; - return(add_optional_notes(r, s1, "error-notes", "<P>\n")); - case HTTP_VARIANT_ALSO_VARIES: - return(apr_pstrcat(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)); - case HTTP_REQUEST_TIME_OUT: - return("I'm tired of waiting for your request.\n"); - case HTTP_GONE: - return(apr_pstrcat(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.\n", - NULL)); - 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 ", - r->method, - " requests, or the amount of data provided in\n" - "the request exceeds the capacity limit.\n", - NULL)); - case HTTP_REQUEST_URI_TOO_LARGE: - s1 = "The requested URL's length exceeds the capacity\n" - "limit for this server.<P>\n"; - return(add_optional_notes(r, s1, "error-notes", "<P>\n")); - case HTTP_UNSUPPORTED_MEDIA_TYPE: - return("The supplied request data is not in a format\n" - "acceptable for processing by this resource.\n"); - case HTTP_RANGE_NOT_SATISFIABLE: - return("None of the range-specifier values in the Range\n" - "request-header field overlap the current extent\n" - "of the selected resource.\n"); - case HTTP_EXPECTATION_FAILED: - return(apr_pstrcat(p, - "The expectation given in the Expect request-header" - "\nfield could not be met by this server.<P>\n" - "The client sent<PRE>\n Expect: ", - apr_table_get(r->headers_in, "Expect"), "\n</PRE>\n" - "but we only allow the 100-continue expectation.\n", - NULL)); - case HTTP_UNPROCESSABLE_ENTITY: - return("The server understands the media type of the\n" - "request entity, but was unable to process the\n" - "contained instructions.\n"); - case HTTP_LOCKED: - return("The requested resource is currently locked.\n" - "The lock must be released or proper identification\n" - "given before the method can be applied.\n"); - case HTTP_FAILED_DEPENDENCY: - return("The method could not be performed on the resource\n" - "because the requested action depended on another\n" - "action and that other action failed.\n"); - case HTTP_INSUFFICIENT_STORAGE: - return("The method could not be performed on the resource\n" - "because the server is unable to store the\n" - "representation needed to successfully complete the\n" - "request. There is insufficient free space left in\n" - "your storage allocation.\n"); - case HTTP_SERVICE_UNAVAILABLE: - return("The server is temporarily unable to service your\n" - "request due to maintenance downtime or capacity\n" - "problems. Please try again later.\n"); - case HTTP_GATEWAY_TIME_OUT: - return("The proxy server did not receive a timely response\n" - "from the upstream server.\n"); - case HTTP_NOT_EXTENDED: - return("A mandatory extension policy in the request is not\n" - "accepted by the server for this resource.\n"); - default: /* HTTP_INTERNAL_SERVER_ERROR */ - /* - * This comparison to expose error-notes could be modified to - * use a configuration directive and export based on that - * directive. For now "*" is used to designate an error-notes - * that is totally safe for any user to see (ie lacks paths, - * database passwords, etc.) - */ - if (((error_notes = apr_table_get(r->notes, "error-notes")) != NULL) - && (h1 = apr_table_get(r->notes, "verbose-error-to")) != NULL - && (strcmp(h1, "*") == 0)) { - return(apr_pstrcat(p, error_notes, "<P>\n", NULL)); - } - else { - return(apr_pstrcat(p, - "The server encountered an internal error or\n" - "misconfiguration and was unable to complete\n" - "your request.<P>\n" - "Please contact the server administrator,\n ", - ap_escape_html(r->pool, r->server->server_admin), - " and inform them of the time the error occurred,\n" - "and anything you might have done that may have\n" - "caused the error.<P>\n" - "More information about this error may be available\n" - "in the server error log.<P>\n", - NULL)); - } - /* - * It would be nice to give the user the information they need to - * fix the problem directly since many users don't have access to - * the error_log (think University sites) even though they can easily - * get this error by misconfiguring an htaccess file. However, the - e error notes tend to include the real file pathname in this case, - * which some people consider to be a breach of privacy. Until we - * can figure out a way to remove the pathname, leave this commented. - * - * if ((error_notes = apr_table_get(r->notes, "error-notes")) != NULL) { - * return(apr_pstrcat(p, error_notes, "<P>\n", NULL); - * } - * else { - * return ""; - * } - */ - } -} - -static void reset_filters(request_rec *r) -{ - ap_filter_t *f = r->output_filters; - - while (f) { - if (!strcasecmp(f->frec->name, "CORE") || - !strcasecmp(f->frec->name, "CONTENT_LENGTH") || - !strcasecmp(f->frec->name, "HTTP_HEADER")) { - f = f->next; - continue; - } - else { - ap_remove_output_filter(f); - f = f->next; - } - } -} - -/* We should have named this send_canned_response, since it is used for any - * response that can be generated by the server from the request record. - * This includes all 204 (no content), 3xx (redirect), 4xx (client error), - * and 5xx (server error) messages that have not been redirected to another - * handler via the ErrorDocument feature. - */ -AP_DECLARE(void) ap_send_error_response(request_rec *r, int recursive_error) -{ - int status = r->status; - int idx = ap_index_of_response(status); - char *custom_response; - const char *location = apr_table_get(r->headers_out, "Location"); - - /* At this point, we are starting the response over, so we have to reset - * this value. - */ - r->eos_sent = 0; - reset_filters(r); - - /* - * It's possible that the Location field might be in r->err_headers_out - * instead of r->headers_out; use the latter if possible, else the - * former. - */ - if (location == NULL) { - location = apr_table_get(r->err_headers_out, "Location"); - } - /* We need to special-case the handling of 204 and 304 responses, - * since they have specific HTTP requirements and do not include a - * message body. Note that being assbackwards here is not an option. - */ - if (status == HTTP_NOT_MODIFIED) { - ap_finalize_request_protocol(r); - return; - } - - if (status == HTTP_NO_CONTENT) { - ap_finalize_request_protocol(r); - return; - } - - if (!r->assbackwards) { - apr_table_t *tmp = r->headers_out; - - /* For all HTTP/1.x responses for which we generate the message, - * we need to avoid inheriting the "normal status" header fields - * that may have been set by the request handler before the - * error or redirect, except for Location on external redirects. - */ - r->headers_out = r->err_headers_out; - r->err_headers_out = tmp; - apr_table_clear(r->err_headers_out); - - if (ap_is_HTTP_REDIRECT(status) || (status == HTTP_CREATED)) { - if ((location != NULL) && *location) { - apr_table_setn(r->headers_out, "Location", location); - } - else { - location = ""; /* avoids coredump when printing, below */ - } - } - - r->content_language = NULL; - r->content_languages = NULL; - r->content_encoding = NULL; - r->clength = 0; - r->content_type = "text/html; charset=iso-8859-1"; - - if ((status == HTTP_METHOD_NOT_ALLOWED) - || (status == HTTP_NOT_IMPLEMENTED)) { - apr_table_setn(r->headers_out, "Allow", make_allow(r)); - } - - if (r->header_only) { - ap_finalize_request_protocol(r); - return; - } - } - - if ((custom_response = ap_response_code_string(r, idx))) { - /* - * We have a custom response output. This should only be - * a text-string to write back. But if the ErrorDocument - * was a local redirect and the requested resource failed - * for any reason, the custom_response will still hold the - * redirect URL. We don't really want to output this URL - * as a text message, so first check the custom response - * string to ensure that it is a text-string (using the - * same test used in ap_die(), i.e. does it start with a "). - * If it doesn't, we've got a recursive error, so find - * the original error and output that as well. - */ - if (custom_response[0] == '\"') { - ap_rputs(custom_response + 1, r); - ap_finalize_request_protocol(r); - return; - } - /* - * Redirect failed, so get back the original error - */ - while (r->prev && (r->prev->status != HTTP_OK)) - r = r->prev; - } - { - const char *title = status_lines[idx]; - const char *h1; - - /* XXX This is a major hack that should be fixed cleanly. The - * problem is that we have the information we need in a previous - * request, but the text of the page must be sent down the last - * request_rec's filter stack. rbb - */ - request_rec *rlast = r; - while (rlast->next) { - rlast = rlast->next; - } - - /* Accept a status_line set by a module, but only if it begins - * with the 3 digit status code - */ - if (r->status_line != NULL - && strlen(r->status_line) > 4 /* long enough */ - && apr_isdigit(r->status_line[0]) - && apr_isdigit(r->status_line[1]) - && apr_isdigit(r->status_line[2]) - && apr_isspace(r->status_line[3]) - && apr_isalnum(r->status_line[4])) { - title = r->status_line; - } - - /* folks decided they didn't want the error code in the H1 text */ - h1 = &title[4]; - - /* can't count on a charset filter being in place here, - * so do ebcdic->ascii translation explicitly (if needed) - */ - - ap_rvputs_proto_in_ascii(rlast, - DOCTYPE_HTML_2_0 - "<HTML><HEAD>\n<TITLE>", title, - "</TITLE>\n</HEAD><BODY>\n<H1>", h1, "</H1>\n", - NULL); - - ap_rvputs_proto_in_ascii(rlast, - get_canned_error_string(status, r, location), - NULL); - - if (recursive_error) { - ap_rvputs_proto_in_ascii(rlast, "<P>Additionally, a ", - status_lines[ap_index_of_response(recursive_error)], - "\nerror was encountered while trying to use an " - "ErrorDocument to handle the request.\n", NULL); - } - ap_rvputs_proto_in_ascii(rlast, ap_psignature("<HR>\n", r), NULL); - ap_rvputs_proto_in_ascii(rlast, "</BODY></HTML>\n", NULL); - } - ap_finalize_request_protocol(r); -} - -/* - * Create a new method list with the specified number of preallocated - * extension slots. - */ -AP_DECLARE(ap_method_list_t *) ap_make_method_list(apr_pool_t *p, int nelts) -{ - ap_method_list_t *ml; - - ml = (ap_method_list_t *) apr_palloc(p, sizeof(ap_method_list_t)); - ml->method_mask = 0; - ml->method_list = apr_array_make(p, sizeof(char *), nelts); - return ml; -} - -/* - * Make a copy of a method list (primarily for subrequests that may - * subsequently change it; don't want them changing the parent's, too!). - */ -AP_DECLARE(void) ap_copy_method_list(ap_method_list_t *dest, - ap_method_list_t *src) -{ - int i; - char **imethods; - char **omethods; - - dest->method_mask = src->method_mask; - imethods = (char **) src->method_list->elts; - for (i = 0; i < src->method_list->nelts; ++i) { - omethods = (char **) apr_array_push(dest->method_list); - *omethods = apr_pstrdup(dest->method_list->cont, imethods[i]); - } -} - -/* - * Invoke a callback routine for each method in the specified list. - */ -AP_DECLARE_NONSTD(void) ap_method_list_do(int (*comp) (void *urec, const char *mname, - int mnum), - void *rec, - const ap_method_list_t *ml, ...) -{ - va_list vp; - va_start(vp, ml); - ap_method_list_vdo(comp, rec, ml, vp); - va_end(vp); -} - -AP_DECLARE(void) ap_method_list_vdo(int (*comp) (void *mrec, - const char *mname, - int mnum), - void *rec, const ap_method_list_t *ml, - va_list vp) -{ - -} - -/* - * Return true if the specified HTTP method is in the provided - * method list. - */ -AP_DECLARE(int) ap_method_in_list(ap_method_list_t *l, const char *method) -{ - int methnum; - int i; - char **methods; - - /* - * If it's one of our known methods, use the shortcut and check the - * bitmask. - */ - methnum = ap_method_number_of(method); - if (methnum != M_INVALID) { - return (l->method_mask & (1 << methnum)); - } - /* - * Otherwise, see if the method name is in the array or string names - */ - if ((l->method_list == NULL) || (l->method_list->nelts == 0)) { - return 0; - } - methods = (char **)l->method_list->elts; - for (i = 0; i < l->method_list->nelts; ++i) { - if (strcmp(method, methods[i]) == 0) { - return 1; - } - } - return 0; -} - -/* - * Add the specified method to a method list (if it isn't already there). - */ -AP_DECLARE(void) ap_method_list_add(ap_method_list_t *l, const char *method) -{ - int methnum; - int i; - const char **xmethod; - char **methods; - - /* - * If it's one of our known methods, use the shortcut and use the - * bitmask. - */ - methnum = ap_method_number_of(method); - l->method_mask |= (1 << methnum); - if (methnum != M_INVALID) { - return; - } - /* - * Otherwise, see if the method name is in the array of string names. - */ - if (l->method_list->nelts != 0) { - methods = (char **)l->method_list->elts; - for (i = 0; i < l->method_list->nelts; ++i) { - if (strcmp(method, methods[i]) == 0) { - return; - } - } - } - xmethod = (const char **) apr_array_push(l->method_list); - *xmethod = method; -} - -/* - * Remove the specified method from a method list. - */ -AP_DECLARE(void) ap_method_list_remove(ap_method_list_t *l, - const char *method) -{ - int methnum; - char **methods; - - /* - * If it's one of our known methods, use the shortcut and use the - * bitmask. - */ - methnum = ap_method_number_of(method); - l->method_mask |= ~(1 << methnum); - if (methnum != M_INVALID) { - return; - } - /* - * Otherwise, see if the method name is in the array of string names. - */ - if (l->method_list->nelts != 0) { - register int i, j, k; - methods = (char **)l->method_list->elts; - for (i = 0; i < l->method_list->nelts; ) { - if (strcmp(method, methods[i]) == 0) { - for (j = i, k = i + 1; k < l->method_list->nelts; ++j, ++k) { - methods[j] = methods[k]; - } - --l->method_list->nelts; - } - else { - ++i; - } - } - } -} - -/* - * Reset a method list to be completely empty. - */ -AP_DECLARE(void) ap_clear_method_list(ap_method_list_t *l) -{ - l->method_mask = 0; - l->method_list->nelts = 0; -} - -/* - * 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. - */ -AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak) -{ - char *etag; - char *weak; - - /* - * Make an ETag header out of various pieces of information. We use - * the last-modified date and, if we have a real file, the - * length and inode number - note that this doesn't have to match - * the content-length (i.e. includes), it just has to be unique - * for the file. - * - * If the request was made within a second of the last-modified date, - * we send a weak tag instead of a strong one, since it could - * be modified again later in the second, and the validation - * would be incorrect. - */ - - weak = ((r->request_time - r->mtime > APR_USEC_PER_SEC) - && !force_weak) ? "" : "W/"; - - if (r->finfo.filetype != 0) { - etag = apr_psprintf(r->pool, - "%s\"%lx-%lx-%lx\"", weak, - (unsigned long) r->finfo.inode, - (unsigned long) r->finfo.size, - (unsigned long) r->mtime); - } - else { - etag = apr_psprintf(r->pool, "%s\"%lx\"", weak, - (unsigned long) r->mtime); - } - - return etag; -} - -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); - } - 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. - */ - - /* 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'); - - variant_etag = ap_make_etag(r, vlv_weak); - - /* 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); - } - - apr_table_setn(r->headers_out, "ETag", etag); -} - -static int parse_byterange(char *range, apr_off_t clength, - apr_off_t *start, apr_off_t *end) -{ - char *dash = strchr(range, '-'); - - if (!dash) - return 0; - - if ((dash == range)) { - /* In the form "-5" */ - *start = clength - atol(dash + 1); - *end = clength - 1; - } - else { - *dash = '\0'; - dash++; - *start = atol(range); - if (*dash) - *end = atol(dash); - else /* "5-" */ - *end = clength - 1; - } - - if (*start < 0) - *start = 0; - - if (*end >= clength) - *end = clength - 1; - - if (*start > *end) - return -1; - - return (*start > 0 || *end < clength); -} - -static int ap_set_byterange(request_rec *r); - -typedef struct byterange_ctx { - apr_bucket_brigade *bb; - int num_ranges; - const char *orig_ct; -} byterange_ctx; - -/* - * Here we try to be compatible with clients that want multipart/x-byteranges - * instead of multipart/byteranges (also see above), as per HTTP/1.1. We - * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication - * that the browser supports an older protocol. We also check User-Agent - * for Microsoft Internet Explorer 3, which needs this as well. - */ -static int use_range_x(request_rec *r) -{ - const char *ua; - return (apr_table_get(r->headers_in, "Request-Range") || - ((ua = apr_table_get(r->headers_in, "User-Agent")) - && ap_strstr_c(ua, "MSIE 3"))); -} - -#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT - -AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter( - ap_filter_t *f, - apr_bucket_brigade *bb) -{ -#define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1) - request_rec *r = f->r; - byterange_ctx *ctx = f->ctx; - apr_bucket *e; - apr_bucket_brigade *bsend; - apr_off_t range_start; - apr_off_t range_end; - char *current; - char *bound_head; - apr_ssize_t bb_length; - apr_off_t clength = 0; - apr_status_t rv; - int found = 0; - - if (!ctx) { - int num_ranges = ap_set_byterange(r); - - if (num_ranges == -1) { - ap_remove_output_filter(f); - bsend = apr_brigade_create(r->pool); - e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, r->pool); - APR_BRIGADE_INSERT_TAIL(bsend, e); - e = apr_bucket_eos_create(); - APR_BRIGADE_INSERT_TAIL(bsend, e); - return ap_pass_brigade(f->next, bsend); - } - if (num_ranges == 0) { - ap_remove_output_filter(f); - return ap_pass_brigade(f->next, bb); - } - - ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx)); - ctx->num_ranges = num_ranges; - - if (num_ranges > 1) { - ctx->orig_ct = r->content_type; - r->content_type = - apr_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/", - "byteranges; boundary=", r->boundary, NULL); - } - - /* create a brigade in case we never call ap_save_brigade() */ - ctx->bb = apr_brigade_create(r->pool); - } - - /* We can't actually deal with byte-ranges until we have the whole brigade - * because the byte-ranges can be in any order, and according to the RFC, - * we SHOULD return the data in the same order it was requested. - */ - if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { - ap_save_brigade(f, &ctx->bb, &bb); - return APR_SUCCESS; - } - - /* compute this once (it is an invariant) */ - bound_head = apr_pstrcat(r->pool, - CRLF "--", r->boundary, - CRLF "Content-type: ", - ap_make_content_type(r, ctx->orig_ct), - CRLF "Content-range: bytes ", - NULL); - ap_xlate_proto_to_ascii(bound_head, strlen(bound_head)); - - /* If we have a saved brigade from a previous run, concat the passed - * brigade with our saved brigade. Otherwise just continue. - */ - if (ctx->bb) { - APR_BRIGADE_CONCAT(ctx->bb, bb); - bb = ctx->bb; - ctx->bb = NULL; /* ### strictly necessary? call brigade_destroy? */ - } - - /* It is possible that we won't have a content length yet, so we have to - * compute the length before we can actually do the byterange work. - */ - (void) apr_brigade_length(bb, 1, &bb_length); - clength = (apr_off_t)bb_length; - - /* this brigade holds what we will be sending */ - bsend = apr_brigade_create(r->pool); - - while ((current = ap_getword(r->pool, &r->range, ',')) && - (rv = parse_byterange(current, clength, &range_start, &range_end))) { - apr_bucket *e2; - apr_bucket *ec; - - if (rv == -1) { - continue; - } - else { - found = 1; - } - - if (ctx->num_ranges > 1) { - char *ts; - - e = apr_bucket_pool_create(bound_head, - strlen(bound_head), r->pool); - APR_BRIGADE_INSERT_TAIL(bsend, e); - - ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, - range_start, range_end, clength); - ap_xlate_proto_to_ascii(ts, strlen(ts)); - e = apr_bucket_pool_create(ts, strlen(ts), r->pool); - APR_BRIGADE_INSERT_TAIL(bsend, e); - } - - e = apr_brigade_partition(bb, range_start); - e2 = apr_brigade_partition(bb, range_end + 1); - - ec = e; - do { - apr_bucket *foo; - const char *str; - apr_size_t len; - - if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) { - apr_bucket_read(ec, &str, &len, APR_BLOCK_READ); - foo = apr_bucket_heap_create(str, len, 0, NULL); - } - APR_BRIGADE_INSERT_TAIL(bsend, foo); - ec = APR_BUCKET_NEXT(ec); - } while (ec != e2); - } - - if (found == 0) { - ap_remove_output_filter(f); - r->status = HTTP_OK; - return HTTP_RANGE_NOT_SATISFIABLE; - } - - if (ctx->num_ranges > 1) { - char *end; - - /* add the final boundary */ - end = apr_pstrcat(r->pool, CRLF "--", r->boundary, "--" CRLF, NULL); - ap_xlate_proto_to_ascii(end, strlen(end)); - e = apr_bucket_pool_create(end, strlen(end), r->pool); - APR_BRIGADE_INSERT_TAIL(bsend, e); - } - - e = apr_bucket_eos_create(); - APR_BRIGADE_INSERT_TAIL(bsend, e); - - /* we're done with the original content */ - apr_brigade_destroy(bb); - - /* send our multipart output */ - return ap_pass_brigade(f->next, bsend); -} - -static int ap_set_byterange(request_rec *r) -{ - const char *range; - const char *if_range; - const char *match; - const char *ct; - apr_off_t range_start; - apr_off_t range_end; - int num_ranges; - - if (r->assbackwards) - return 0; - - /* is content already a single range? */ - if (apr_table_get(r->headers_out, "Content-Range")) { - return 0; - } - - /* is content already a multiple range? */ - if ((ct = apr_table_get(r->headers_out, "Content-Type")) && - (!strncasecmp(ct, "multipart/byteranges", 20) || - !strncasecmp(ct, "multipart/x-byteranges", 22))) { - return 0; - } - - /* Check for Range request-header (HTTP/1.1) or Request-Range for - * backwards-compatibility with second-draft Luotonen/Franks - * byte-ranges (e.g. Netscape Navigator 2-3). - * - * We support this form, with Request-Range, and (farther down) we - * send multipart/x-byteranges instead of multipart/byteranges for - * Request-Range based requests to work around a bug in Netscape - * Navigator 2-3 and MSIE 3. - */ - - if (!(range = apr_table_get(r->headers_in, "Range"))) - range = apr_table_get(r->headers_in, "Request-Range"); - - if (!range || strncasecmp(range, "bytes=", 6)) { - return 0; - } - - /* Check the If-Range header for Etag or Date. - * Note that this check will return false (as required) if either - * of the two etags are weak. - */ - if ((if_range = apr_table_get(r->headers_in, "If-Range"))) { - if (if_range[0] == '"') { - if (!(match = apr_table_get(r->headers_out, "Etag")) || - (strcmp(if_range, match) != 0)) - return 0; - } - else if (!(match = apr_table_get(r->headers_out, "Last-Modified")) || - (strcmp(if_range, match) != 0)) - return 0; - } - - /* would be nice to pick this up from f->ctx */ - ct = ap_make_content_type(r, r->content_type); - - if (!ap_strchr_c(range, ',')) { - int rv; - /* A single range */ - - /* rvarse_byterange() modifies the contents, so make a copy */ - if ((rv = parse_byterange(apr_pstrdup(r->pool, range + 6), r->clength, - &range_start, &range_end)) <= 0) { - return rv; - } - apr_table_setn(r->headers_out, "Content-Range", - apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, - range_start, range_end, r->clength)); - apr_table_setn(r->headers_out, "Content-Type", ct); - - num_ranges = 1; - } - else { - /* a multiple range */ - - num_ranges = 2; - - /* ### it would be nice if r->boundary was in f->ctx */ - r->boundary = apr_psprintf(r->pool, "%qx%lx", - r->request_time, (long) getpid()); - - apr_table_setn(r->headers_out, "Content-Type", - apr_pstrcat(r->pool, - "multipart", use_range_x(r) ? "/x-" : "/", - "byteranges; boundary=", r->boundary, - NULL)); - } - - r->status = HTTP_PARTIAL_CONTENT; - r->range = range + 6; - - return num_ranges; -} - |