summaryrefslogtreecommitdiff
path: root/APACHE_1_3_42/src/main/http_protocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'APACHE_1_3_42/src/main/http_protocol.c')
-rw-r--r--APACHE_1_3_42/src/main/http_protocol.c3233
1 files changed, 3233 insertions, 0 deletions
diff --git a/APACHE_1_3_42/src/main/http_protocol.c b/APACHE_1_3_42/src/main/http_protocol.c
new file mode 100644
index 0000000000..cd370fcd8b
--- /dev/null
+++ b/APACHE_1_3_42/src/main/http_protocol.c
@@ -0,0 +1,3233 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_protocol.c --- routines which directly communicate with the client.
+ *
+ * Code originally by Rob McCool; much redone by Robert S. Thau
+ * and the Apache Group.
+ */
+
+#define CORE_PRIVATE
+#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 <stdarg.h>
+#include "http_conf_globals.h"
+#include "util_md5.h" /* For digestAuth */
+
+#define SET_BYTES_SENT(r) \
+ do { if (r->sent_bodyct) \
+ ap_bgetopt (r->connection->client, BO_BYTECT, &r->bytes_sent); \
+ } while (0)
+
+#ifdef CHARSET_EBCDIC
+/* Save & Restore the current conversion settings
+ * "input" means: ASCII -> EBCDIC (when reading MIME Headers and PUT/POST data)
+ * "output" means: EBCDIC -> ASCII (when sending MIME Headers and Chunks)
+ */
+
+#define PUSH_EBCDIC_INPUTCONVERSION_STATE(_buff, _onoff) \
+ int _convert_in = ap_bgetflag(_buff, B_ASCII2EBCDIC); \
+ ap_bsetflag(_buff, B_ASCII2EBCDIC, _onoff);
+
+#define POP_EBCDIC_INPUTCONVERSION_STATE(_buff) \
+ ap_bsetflag(_buff, B_ASCII2EBCDIC, _convert_in);
+
+#define PUSH_EBCDIC_INPUTCONVERSION_STATE_r(_req, _onoff) \
+ ap_bsetflag(_req->connection->client, B_ASCII2EBCDIC, _onoff);
+
+#define POP_EBCDIC_INPUTCONVERSION_STATE_r(_req) \
+ ap_bsetflag(_req->connection->client, B_ASCII2EBCDIC, _req->ebcdic.conv_in);
+
+#define PUSH_EBCDIC_OUTPUTCONVERSION_STATE_r(_req, _onoff) \
+ ap_bsetflag(_req->connection->client, B_EBCDIC2ASCII, _onoff);
+
+#define POP_EBCDIC_OUTPUTCONVERSION_STATE_r(_req) \
+ ap_bsetflag(_req->connection->client, B_EBCDIC2ASCII, _req->ebcdic.conv_out);
+
+#endif /*CHARSET_EBCDIC*/
+
+/*
+ * Builds the content-type that should be sent to the client from the
+ * content-type specified. The following rules are followed:
+ * - if type is NULL, type is set to ap_default_type(r)
+ * - if charset adding is disabled, stop processing and return type.
+ * - then, if there are no parameters on type, add the default charset
+ * - return type
+ */
+static const char *make_content_type(request_rec *r, const char *type) {
+ char *needcset[] = {
+ "text/plain",
+ "text/html",
+ NULL };
+ char **pcset;
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+ if (!type) {
+ type = ap_default_type(r);
+ }
+ if (conf->add_default_charset != ADD_DEFAULT_CHARSET_ON) {
+ return type;
+ }
+
+ if (ap_strcasestr(type, "charset=") != NULL) {
+ /* already has parameter, do nothing */
+ /* XXX we don't check the validity */
+ ;
+ }
+ else {
+ /* see if it makes sense to add the charset. At present,
+ * we only add it if the Content-type is one of needcset[]
+ */
+ for (pcset = needcset; *pcset ; pcset++) {
+ if (ap_strcasestr(type, *pcset) != NULL) {
+ type = ap_pstrcat(r->pool, type, "; charset=",
+ conf->add_default_charset_name, NULL);
+ break;
+ }
+ }
+ }
+ return type;
+}
+
+enum byterange_token {
+ BYTERANGE_OK,
+ BYTERANGE_EMPTY,
+ BYTERANGE_BADSYNTAX,
+ BYTERANGE_UNSATISFIABLE
+};
+
+static enum byterange_token
+ parse_byterange(request_rec *r, long *start, long *end)
+{
+ /* parsing first, semantics later */
+
+ while (ap_isspace(*r->range))
+ ++r->range;
+
+ /* check for an empty range, which is OK */
+ if (*r->range == '\0') {
+ return BYTERANGE_EMPTY;
+ }
+ else if (*r->range == ',') {
+ ++r->range;
+ return BYTERANGE_EMPTY;
+ }
+
+ if (ap_isdigit(*r->range))
+ *start = ap_strtol(r->range, (char **)&r->range, 10);
+ else
+ *start = -1;
+
+ while (ap_isspace(*r->range))
+ ++r->range;
+
+ if (*r->range != '-')
+ return BYTERANGE_BADSYNTAX;
+ ++r->range;
+
+ while (ap_isspace(*r->range))
+ ++r->range;
+
+ if (ap_isdigit(*r->range))
+ *end = ap_strtol(r->range, (char **)&r->range, 10);
+ else
+ *end = -1;
+
+ while (ap_isspace(*r->range))
+ ++r->range;
+
+ /* check the end of the range */
+ if (*r->range == ',') {
+ ++r->range;
+ }
+ else if (*r->range != '\0') {
+ return BYTERANGE_BADSYNTAX;
+ }
+
+ /* parsing done; now check the numbers */
+
+ if (*start < 0) { /* suffix-byte-range-spec */
+ if (*end < 0) /* no numbers */
+ return BYTERANGE_BADSYNTAX;
+ *start = r->clength - *end;
+ if (*start < 0)
+ *start = 0;
+ *end = r->clength - 1;
+ }
+ else {
+ if (*end >= 0 && *start > *end) /* out-of-order range */
+ return BYTERANGE_BADSYNTAX;
+ if (*end < 0 || *end >= r->clength)
+ *end = r->clength - 1;
+ }
+ /* RFC 2616 is somewhat unclear about what we should do if the end
+ * is missing and the start is after the clength. The robustness
+ * principle says we should accept it as an unsatisfiable range.
+ * We accept suffix-byte-range-specs like -0 for the same reason.
+ */
+ if (*start >= r->clength)
+ return BYTERANGE_UNSATISFIABLE;
+
+ return BYTERANGE_OK;
+}
+
+/* If this function is called with output=1, it will spit out the
+ * correct headers for a byterange chunk. If output=0 it will not
+ * output anything but just return the number of bytes it would have
+ * output. If start or end are less than 0 then it will do a byterange
+ * chunk trailer instead of a header.
+ */
+static int byterange_boundary(request_rec *r, long start, long end, int output)
+{
+ int length = 0;
+
+#ifdef CHARSET_EBCDIC
+ /* determine current setting of conversion flag,
+ * set to ON (protocol strings MUST be converted)
+ * and reset to original setting before returning
+ */
+ PUSH_EBCDIC_OUTPUTCONVERSION_STATE_r(r, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ if (start < 0 || end < 0) {
+ if (output)
+ ap_rvputs(r, CRLF "--", r->boundary, "--" CRLF, NULL);
+ else
+ length = 4 + strlen(r->boundary) + 4;
+ }
+ else {
+ const char *ct = make_content_type(r, r->content_type);
+ char ts[MAX_STRING_LEN];
+
+ ap_snprintf(ts, sizeof(ts), "%ld-%ld/%ld", start, end, r->clength);
+ if (output)
+ ap_rvputs(r, CRLF "--", r->boundary, CRLF "Content-type: ",
+ ct, CRLF "Content-range: bytes ", ts, CRLF CRLF,
+ NULL);
+ else
+ length = 4 + strlen(r->boundary) + 16
+ + strlen(ct) + 23 + strlen(ts) + 4;
+ }
+
+#ifdef CHARSET_EBCDIC
+ POP_EBCDIC_OUTPUTCONVERSION_STATE_r(r);
+#endif /*CHARSET_EBCDIC*/
+
+ return length;
+}
+
+API_EXPORT(int) ap_set_byterange(request_rec *r)
+{
+ const char *range, *if_range, *match;
+ long length, start, end, one_start = 0, one_end = 0;
+ int ranges, empty;
+
+ if (!r->clength || r->assbackwards)
+ 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 = ap_table_get(r->headers_in, "Range")))
+ range = ap_table_get(r->headers_in, "Request-Range");
+
+ if (!range || strncasecmp(range, "bytes=", 6) || (r->status != HTTP_OK)) {
+ return 0;
+ }
+ range += 6;
+
+ /* 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 = ap_table_get(r->headers_in, "If-Range"))) {
+ if (if_range[0] == '"') {
+ if (!(match = ap_table_get(r->headers_out, "Etag")) ||
+ (strcmp(if_range, match) != 0))
+ return 0;
+ }
+ else if (!(match = ap_table_get(r->headers_out, "Last-Modified")) ||
+ (strcmp(if_range, match) != 0))
+ return 0;
+ }
+
+ /*
+ * Parse the byteranges, counting how many of them there are and
+ * the total number of bytes we will send to the client. This is a
+ * dummy run for the while(ap_each_byterange()) loop that the
+ * caller will perform if we return 1.
+ */
+ r->range = range;
+ r->boundary = ap_psprintf(r->pool, "%lx%lx",
+ r->request_time, (long) getpid());
+ length = 0;
+ ranges = 0;
+ empty = 1;
+ do {
+ switch (parse_byterange(r, &start, &end)) {
+ case BYTERANGE_UNSATISFIABLE:
+ empty = 0;
+ break;
+ default:
+ /* be more defensive here? */
+ case BYTERANGE_BADSYNTAX:
+ r->boundary = NULL;
+ r->range = NULL;
+ return 0;
+ case BYTERANGE_EMPTY:
+ break;
+ case BYTERANGE_OK:
+ ++ranges;
+ length += byterange_boundary(r, start, end, 0)
+ + end - start + 1;
+ /* save in case of unsatisfiable ranges */
+ one_start = start;
+ one_end = end;
+ break;
+ }
+ } while (*r->range != '\0');
+
+ if (ranges == 0) {
+ /* no ranges or only unsatisfiable ranges */
+ if (empty || if_range) {
+ r->boundary = NULL;
+ r->range = NULL;
+ return 0;
+ }
+ else {
+ ap_table_setn(r->headers_out, "Content-Range",
+ ap_psprintf(r->pool, "bytes */%ld", r->clength));
+ ap_set_content_length(r, 0);
+ r->boundary = NULL;
+ r->range = range;
+ r->header_only = 1;
+ r->status = HTTP_RANGE_NOT_SATISFIABLE;
+ return 1;
+ }
+ }
+ else if (ranges == 1) {
+ /* simple handling of a single range -- no boundaries */
+ ap_table_setn(r->headers_out, "Content-Range",
+ ap_psprintf(r->pool, "bytes %ld-%ld/%ld",
+ one_start, one_end, r->clength));
+ ap_table_setn(r->headers_out, "Content-Length",
+ ap_psprintf(r->pool, "%ld", one_end - one_start + 1));
+ r->boundary = NULL;
+ r->byterange = 1;
+ r->range = range;
+ r->status = PARTIAL_CONTENT;
+ return 1;
+ }
+ else {
+ /* multiple ranges */
+ length += byterange_boundary(r, -1, -1, 0);
+ ap_table_setn(r->headers_out, "Content-Length",
+ ap_psprintf(r->pool, "%ld", length));
+ r->byterange = 2;
+ r->range = range;
+ r->status = PARTIAL_CONTENT;
+ return 1;
+ }
+}
+
+API_EXPORT(int) ap_each_byterange(request_rec *r, long *offset, long *length)
+{
+ long start, end;
+
+ do {
+ if (parse_byterange(r, &start, &end) == BYTERANGE_OK) {
+ if (r->byterange > 1)
+ byterange_boundary(r, start, end, 1);
+ *offset = start;
+ *length = end - start + 1;
+ return 1;
+ }
+ } while (*r->range != '\0');
+ if (r->byterange > 1)
+ byterange_boundary(r, -1, -1, 1);
+ return 0;
+}
+
+API_EXPORT(int) ap_set_content_length(request_rec *r, long clength)
+{
+ r->clength = clength;
+ ap_table_setn(r->headers_out, "Content-Length", ap_psprintf(r->pool, "%ld", clength));
+ return 0;
+}
+
+API_EXPORT(int) ap_set_keepalive(request_rec *r)
+{
+ int ka_sent = 0;
+ int wimpy = ap_find_token(r->pool,
+ ap_table_get(r->headers_out, "Connection"), "close");
+ const char *conn = ap_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 ||
+ ap_table_get(r->headers_out, "Content-Length") ||
+ ap_find_last_token(r->pool,
+ ap_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") &&
+ (!ap_table_get(r->subprocess_env, "nokeepalive") ||
+ ap_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;
+
+ /*
+ * ap_set_keepalive could be called multiple times (eg: in
+ * ap_die() followed by ap_send_http_header()) during this
+ * one single request. To ensure that we don't incorrectly
+ * increment the keepalives counter for each call, we
+ * assume that only here do we set keepalive. So if keepalive
+ * is already set to 1, we must have already been here and
+ * we should not increment the keepalives counter since we
+ * already done so for this request.
+ */
+ if (r->connection->keepalive != 1) {
+ 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)
+ ap_table_setn(r->headers_out, "Keep-Alive",
+ ap_psprintf(r->pool, "timeout=%d, max=%d",
+ r->server->keep_alive_timeout, left));
+ else
+ ap_table_setn(r->headers_out, "Keep-Alive",
+ ap_psprintf(r->pool, "timeout=%d",
+ r->server->keep_alive_timeout));
+ ap_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)
+ ap_table_mergen(r->headers_out, "Connection", "close");
+
+ r->connection->keepalive = 0;
+
+ return 0;
+}
+
+/*
+ * Return the latest rational time from a request/mtime (modification time)
+ * pair. We return the mtime unless it's in the future, in which case we
+ * return the current time. We use the request time as a reference in order
+ * to limit the number of calls to time(). We don't check for futurosity
+ * unless the mtime is at least as new as the reference.
+ */
+API_EXPORT(time_t) ap_rationalize_mtime(request_rec *r, time_t mtime)
+{
+ time_t now;
+
+ /* For all static responses, it's almost certain that the file was
+ * last modified before the beginning of the request. So there's
+ * no reason to call time(NULL) again. But if the response has been
+ * created on demand, then it might be newer than the time the request
+ * started. In this event we really have to call time(NULL) again
+ * so that we can give the clients the most accurate Last-Modified. If we
+ * were given a time in the future, we return the current time - the
+ * Last-Modified can't be in the future.
+ */
+ now = (mtime < r->request_time) ? r->request_time : time(NULL);
+ return (mtime > now) ? now : mtime;
+}
+
+API_EXPORT(int) ap_meets_conditions(request_rec *r)
+{
+ const char *etag = ap_table_get(r->headers_out, "ETag");
+ const char *if_match, *if_modified_since, *if_unmodified, *if_nonematch;
+ 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;
+ }
+
+ mtime = (r->mtime != 0) ? r->mtime : time(NULL);
+
+ /* 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 = ap_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 = ap_table_get(r->headers_in, "If-Unmodified-Since");
+ if (if_unmodified != NULL) {
+ 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 = ap_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 (ap_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 (strstr(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 =
+ ap_table_get(r->headers_in, "If-Modified-Since")) != NULL)) {
+ time_t ims = ap_parseHTTPdate(if_modified_since);
+
+ if ((ims >= mtime) && (ims <= r->request_time)) {
+ return HTTP_NOT_MODIFIED;
+ }
+ }
+ return OK;
+}
+
+/*
+ * 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.
+ */
+API_EXPORT(char *) ap_make_etag(request_rec *r, int force_weak)
+{
+ char *etag;
+ char *weak;
+ core_dir_config *cfg;
+ etag_components_t etag_bits;
+
+ cfg = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+ etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
+ if (etag_bits == ETAG_UNSET) {
+ etag_bits = ETAG_BACKWARD;
+ }
+ /*
+ * 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 > 1) && !force_weak) ? "" : "W/";
+
+ if (r->finfo.st_mode != 0) {
+ char **ent;
+ array_header *components;
+ int i;
+
+ /*
+ * 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 ap_send_header_field() to ignore.
+ */
+ if (etag_bits & ETAG_NONE) {
+ ap_table_setn(r->notes, "no-etag", "omit");
+ return "";
+ }
+
+ components = ap_make_array(r->pool, 4, sizeof(char *));
+ if (etag_bits & ETAG_INODE) {
+ ent = (char **) ap_push_array(components);
+ *ent = ap_psprintf(r->pool, "%lx",
+ (unsigned long) r->finfo.st_ino);
+ }
+ if (etag_bits & ETAG_SIZE) {
+ ent = (char **) ap_push_array(components);
+ *ent = ap_psprintf(r->pool, "%lx",
+ (unsigned long) r->finfo.st_size);
+ }
+ if (etag_bits & ETAG_MTIME) {
+ ent = (char **) ap_push_array(components);
+ *ent = ap_psprintf(r->pool, "%lx", (unsigned long) r->mtime);
+ }
+ ent = (char **) components->elts;
+ etag = ap_pstrcat(r->pool, weak, "\"", NULL);
+ for (i = 0; i < components->nelts; ++i) {
+ etag = ap_psprintf(r->pool, "%s%s%s", etag,
+ (i == 0 ? "" : "-"),
+ ent[i]);
+ }
+ etag = ap_pstrcat(r->pool, etag, "\"", NULL);
+ }
+ else {
+ etag = ap_psprintf(r->pool, "%s\"%lx\"", weak,
+ (unsigned long) r->mtime);
+ }
+
+ return etag;
+}
+
+API_EXPORT(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);
+
+ /* If we get a blank etag back, don't set the header. */
+ if (!etag[0]) {
+ return;
+ }
+ }
+ 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);
+
+ /* If we get a blank etag back, don't append vlv and stop now. */
+ if (!variant_etag[0]) {
+ return;
+ }
+
+ /* merge variant_etag and vlv into a structured etag */
+ variant_etag[strlen(variant_etag) - 1] = '\0';
+ if (vlv_weak)
+ vlv += 3;
+ else
+ vlv++;
+ etag = ap_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
+ }
+
+ ap_table_setn(r->headers_out, "ETag", etag);
+}
+
+/*
+ * This function sets the Last-Modified output header field to the value
+ * of the mtime field in the request structure - rationalized to keep it from
+ * being in the future.
+ */
+API_EXPORT(void) ap_set_last_modified(request_rec *r)
+{
+ time_t mod_time = ap_rationalize_mtime(r, r->mtime);
+
+ ap_table_setn(r->headers_out, "Last-Modified",
+ ap_gm_timestr_822(r->pool, mod_time));
+}
+
+/* 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.
+ */
+API_EXPORT(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;
+}
+
+/* Get a line of protocol input, including any continuation lines
+ * caused by MIME folding (or broken clients) if fold != 0, and place it
+ * in the buffer s, of size n bytes, without the ending newline.
+ *
+ * Returns -1 on error, or the length of s.
+ *
+ * Note: Because bgets uses 1 char for newline and 1 char for NUL,
+ * the most we can get is (n - 2) actual characters if it
+ * was ended by a newline, or (n - 1) characters if the line
+ * length exceeded (n - 1). So, if the result == (n - 1),
+ * then the actual input line exceeded the buffer length,
+ * and it would be a good idea for the caller to puke 400 or 414.
+ */
+API_EXPORT(int) ap_getline(char *s, int n, BUFF *in, int fold)
+{
+ char *pos, next;
+ int retval;
+ int total = 0;
+#ifdef CHARSET_EBCDIC
+ /* When ap_getline() is called, the HTTP protocol is in a state
+ * where we MUST be reading "plain text" protocol stuff,
+ * (Request line, MIME headers, Chunk sizes) regardless of
+ * the MIME type and conversion setting of the document itself.
+ * Save the current setting of the ASCII-EBCDIC conversion flag
+ * for uploads, then temporarily set it to ON
+ * (and restore it before returning).
+ */
+ PUSH_EBCDIC_INPUTCONVERSION_STATE(in, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ pos = s;
+
+ do {
+ retval = ap_bgets(pos, n, in); /* retval == -1 if error, 0 if EOF */
+
+ if (retval <= 0) {
+ total = ((retval < 0) && (total == 0)) ? -1 : total;
+ break;
+ }
+
+ /* retval is the number of characters read, not including NUL */
+
+ n -= retval; /* Keep track of how much of s is full */
+ pos += (retval - 1); /* and where s ends */
+ total += retval; /* and how long s has become */
+
+ if (*pos == '\n') { /* Did we get a full line of input? */
+ /*
+ * Trim any extra trailing spaces or tabs except for the first
+ * space or tab at the beginning of a blank string. This makes
+ * it much easier to check field values for exact matches, and
+ * saves memory as well. Terminate string at end of line.
+ */
+ while (pos > (s + 1) && (*(pos - 1) == ' ' || *(pos - 1) == '\t')) {
+ --pos; /* trim extra trailing spaces or tabs */
+ --total; /* but not one at the beginning of line */
+ ++n;
+ }
+ *pos = '\0';
+ --total;
+ ++n;
+ }
+ else
+ break; /* if not, input line exceeded buffer size */
+
+ /* Continue appending if line folding is desired and
+ * the last line was not empty and we have room in the buffer and
+ * the next line begins with a continuation character.
+ */
+ } while (fold && (retval != 1) && (n > 1)
+ && (ap_blookc(&next, in) == 1)
+ && ((next == ' ') || (next == '\t')));
+
+#ifdef CHARSET_EBCDIC
+ /* restore ASCII->EBCDIC conversion state */
+ POP_EBCDIC_INPUTCONVERSION_STATE(in);
+#endif /*CHARSET_EBCDIC*/
+
+ return total;
+}
+
+/* parse_uri: break apart the uri
+ * Side Effects:
+ * - sets r->args to rest after '?' (or NULL if no '?')
+ * - sets r->uri to request uri (without r->args part)
+ * - sets r->hostname (if not set already) from request (scheme://host:port)
+ */
+CORE_EXPORT(void) ap_parse_uri(request_rec *r, const char *uri)
+{
+ int status = HTTP_OK;
+
+ r->unparsed_uri = ap_pstrdup(r->pool, uri);
+
+ if (r->method_number == M_CONNECT) {
+ status = ap_parse_hostinfo_components(r->pool, uri, &r->parsed_uri);
+ } else {
+ /* Simple syntax Errors in URLs are trapped by parse_uri_components(). */
+ status = ap_parse_uri_components(r->pool, uri, &r->parsed_uri);
+ }
+
+ if (ap_is_HTTP_SUCCESS(status)) {
+ /* if it has a scheme we may need to do absoluteURI vhost stuff */
+ if (r->parsed_uri.scheme
+ && !strcasecmp(r->parsed_uri.scheme, ap_http_method(r))) {
+ r->hostname = r->parsed_uri.hostname;
+ } else if (r->method_number == M_CONNECT) {
+ r->hostname = r->parsed_uri.hostname;
+ }
+ r->args = r->parsed_uri.query;
+ r->uri = r->parsed_uri.path ? r->parsed_uri.path
+ : ap_pstrdup(r->pool, "/");
+#if defined(OS2) || defined(WIN32)
+ /* Handle path translations for OS/2 and plug security hole.
+ * This will prevent "http://www.wherever.com/..\..\/" from
+ * returning a directory for the root drive.
+ */
+ {
+ char *x;
+
+ for (x = r->uri; (x = strchr(x, '\\')) != NULL; )
+ *x = '/';
+ }
+#endif /* OS2 || WIN32 */
+ }
+ else {
+ r->args = NULL;
+ r->hostname = NULL;
+ r->status = status; /* set error status */
+ r->uri = ap_pstrdup(r->pool, uri);
+ }
+}
+
+static int read_request_line(request_rec *r)
+{
+ char l[DEFAULT_LIMIT_REQUEST_LINE + 2]; /* ap_getline's two extra for \n\0 */
+ const char *ll = l;
+ const char *uri;
+ conn_rec *conn = r->connection;
+ unsigned int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */
+ int len = 0;
+ int valid_protocol = 1;
+
+ /* Read past empty lines until we get a real request line,
+ * a read error, the connection closes (EOF), or we timeout.
+ *
+ * We skip empty lines because browsers have to tack a CRLF on to the end
+ * of POSTs to support old CERN webservers. But note that we may not
+ * have flushed any previous response completely to the client yet.
+ * We delay the flush as long as possible so that we can improve
+ * performance for clients that are pipelining requests. If a request
+ * is pipelined then we won't block during the (implicit) read() below.
+ * If the requests aren't pipelined, then the client is still waiting
+ * for the final buffer flush from us, and we will block in the implicit
+ * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will
+ * have to block during a read.
+ */
+ ap_bsetflag(conn->client, B_SAFEREAD, 1);
+ while ((len = ap_getline(l, sizeof(l), conn->client, 0)) <= 0) {
+ if ((len < 0) || ap_bgetflag(conn->client, B_EOF) || !conn->keepalives) {
+ ap_bsetflag(conn->client, B_SAFEREAD, 0);
+ /* this is a hack to make sure that request time is set,
+ * it's not perfect, but it's better than nothing
+ */
+ r->request_time = time(0);
+ return 0;
+ }
+ }
+ /* we've probably got something to do, ignore graceful restart requests */
+#ifdef SIGUSR1
+ signal(SIGUSR1, SIG_IGN);
+#endif
+
+ ap_bsetflag(conn->client, B_SAFEREAD, 0);
+
+ r->request_time = time(NULL);
+ r->the_request = ap_pstrdup(r->pool, l);
+ r->method = ap_getword_white(r->pool, &ll);
+ uri = ap_getword_white(r->pool, &ll);
+
+ /* Provide quick information about the request method as soon as known */
+
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+
+ ap_parse_uri(r, uri);
+
+ /* ap_getline returns (size of max buffer - 1) if it fills up the
+ * buffer before finding the end-of-line. This is only going to
+ * happen if it exceeds the configured limit for a request-line.
+ */
+ if (len > r->server->limit_req_line) {
+ r->status = HTTP_REQUEST_URI_TOO_LARGE;
+ r->proto_num = HTTP_VERSION(1,0);
+ r->protocol = ap_pstrdup(r->pool, "HTTP/1.0");
+ return 0;
+ }
+
+ r->assbackwards = (ll[0] == '\0');
+ r->protocol = ap_pstrdup(r->pool, ll[0] ? ll : "HTTP/0.9");
+
+ /* Avoid sscanf in the common case */
+ if (strlen(r->protocol) == 8
+ && r->protocol[0] == 'H' && r->protocol[1] == 'T'
+ && r->protocol[2] == 'T' && r->protocol[3] == 'P'
+ && r->protocol[4] == '/' && ap_isdigit(r->protocol[5])
+ && r->protocol[6] == '.' && ap_isdigit(r->protocol[7])) {
+ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0');
+ }
+ else {
+ char lint[2];
+ char http[5];
+ if (3 == sscanf(r->protocol, "%4s/%u.%u%1s", http, &major, &minor, lint)
+ && (strcasecmp("http", http) == 0)
+ && (minor < HTTP_VERSION(1,0)) ) /* don't allow HTTP/0.1000 */
+ r->proto_num = HTTP_VERSION(major, minor);
+ else {
+ r->proto_num = HTTP_VERSION(1,0);
+ valid_protocol = 0;
+ }
+ }
+
+ /* Check for a valid protocol, and disallow everything but whitespace
+ * after the protocol string. A protocol string of nothing but
+ * whitespace is considered valid */
+ if (ap_protocol_req_check && !valid_protocol) {
+ int n = 0;
+ while (ap_isspace(r->protocol[n]))
+ ++n;
+ if (r->protocol[n] != '\0') {
+ r->status = HTTP_BAD_REQUEST;
+ r->proto_num = HTTP_VERSION(1,0);
+ r->protocol = ap_pstrdup(r->pool, "HTTP/1.0");
+ ap_table_setn(r->notes, "error-notes",
+ "The request line contained invalid characters "
+ "following the protocol string.<P>\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static void get_mime_headers(request_rec *r)
+{
+ char field[DEFAULT_LIMIT_REQUEST_FIELDSIZE + 2]; /* ap_getline's two extra */
+ conn_rec *c = r->connection;
+ char *value;
+ char *copy;
+ int len;
+ int fields_read = 0;
+ table *tmp_headers;
+
+ /* We'll use ap_overlap_tables later to merge these into r->headers_in. */
+ tmp_headers = ap_make_table(r->pool, 50);
+
+ /*
+ * Read header lines until we get the empty separator line, a read error,
+ * the connection closes (EOF), reach the server limit, or we timeout.
+ */
+ while ((len = ap_getline(field, sizeof(field), c->client, 1)) > 0) {
+
+ if (r->server->limit_req_fields &&
+ (++fields_read > r->server->limit_req_fields)) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_table_setn(r->notes, "error-notes",
+ "The number of request header fields exceeds "
+ "this server's limit.<P>\n");
+ return;
+ }
+ /* ap_getline returns (size of max buffer - 1) if it fills up the
+ * buffer before finding the end-of-line. This is only going to
+ * happen if it exceeds the configured limit for a field size.
+ */
+ if (len > r->server->limit_req_fieldsize) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_table_setn(r->notes, "error-notes", ap_pstrcat(r->pool,
+ "Size of a request header field exceeds server limit.<P>\n"
+ "<PRE>\n", ap_escape_html(r->pool, field), "</PRE>\n", NULL));
+ return;
+ }
+ copy = ap_palloc(r->pool, len + 1);
+ memcpy(copy, field, len + 1);
+
+ if (!(value = strchr(copy, ':'))) { /* Find the colon separator */
+ r->status = HTTP_BAD_REQUEST; /* or abort the bad request */
+ ap_table_setn(r->notes, "error-notes", ap_pstrcat(r->pool,
+ "Request header field is missing colon separator.<P>\n"
+ "<PRE>\n", ap_escape_html(r->pool, copy), "</PRE>\n", NULL));
+ return;
+ }
+
+ *value = '\0';
+ ++value;
+ while (*value == ' ' || *value == '\t')
+ ++value; /* Skip to start of value */
+
+ ap_table_addn(tmp_headers, copy, value);
+ }
+
+ ap_overlap_tables(r->headers_in, tmp_headers, AP_OVERLAP_TABLES_MERGE);
+}
+
+API_EXPORT(request_rec *) ap_read_request(conn_rec *conn)
+{
+ request_rec *r;
+ pool *p;
+ const char *expect;
+ int access_status;
+
+ p = ap_make_sub_pool(conn->pool);
+ r = ap_pcalloc(p, sizeof(request_rec));
+ r->pool = p;
+ r->connection = conn;
+ conn->server = conn->base_server;
+ r->server = conn->server;
+
+ conn->keptalive = conn->keepalive == 1;
+ conn->keepalive = 0;
+
+ conn->user = NULL;
+ conn->ap_auth_type = NULL;
+
+ r->headers_in = ap_make_table(r->pool, 50);
+ r->subprocess_env = ap_make_table(r->pool, 50);
+ r->headers_out = ap_make_table(r->pool, 12);
+ r->err_headers_out = ap_make_table(r->pool, 5);
+ r->notes = ap_make_table(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_REQUEST_TIME_OUT; /* Until we get a request */
+ r->the_request = NULL;
+
+#ifdef CHARSET_EBCDIC
+ ap_bsetflag(r->connection->client, B_ASCII2EBCDIC, r->ebcdic.conv_in = 1);
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1);
+#endif
+
+ /* Get the request... */
+
+ ap_keepalive_timeout("read request line", r);
+ if (!read_request_line(r)) {
+ ap_kill_timeout(r);
+ if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "request failed: URI too long");
+ r->connection->keepalive = 0;
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
+ else if (r->status == HTTP_BAD_REQUEST) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "request failed: erroneous characters after protocol string: %s",
+ ap_escape_logitem(r->pool, r->the_request));
+ r->connection->keepalive = 0;
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
+ return NULL;
+ }
+ if (!r->assbackwards) {
+ ap_hard_timeout("read request headers", r);
+ get_mime_headers(r);
+ ap_kill_timeout(r);
+ if (r->status != HTTP_REQUEST_TIME_OUT) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "request failed: error reading the headers");
+ r->connection->keepalive = 0;
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
+ if (ap_table_get(r->headers_in, "Transfer-Encoding")
+ && ap_table_get(r->headers_in, "Content-Length")) {
+ /* 2616 section 4.4, point 3: "if both Transfer-Encoding
+ * and Content-Length are received, the latter MUST be
+ * ignored"; so unset it here to prevent any confusion
+ * later. */
+ ap_table_unset(r->headers_in, "Content-Length");
+ }
+ }
+ else {
+ ap_kill_timeout(r);
+
+ if (r->header_only) {
+ /*
+ * Client asked for headers only with HTTP/0.9, which doesn't send
+ * headers! Have to dink things just to make sure the error message
+ * comes through...
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "client sent invalid HTTP/0.9 request: HEAD %s",
+ r->uri);
+ r->header_only = 0;
+ r->status = HTTP_BAD_REQUEST;
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
+ }
+
+ r->status = HTTP_OK; /* Until further notice. */
+
+ /* update what we think the virtual host is based on the headers we've
+ * now read. may update status.
+ */
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ conn->keptalive = 0; /* We now have a request to play with */
+
+ if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1,1))) ||
+ ((r->proto_num == HTTP_VERSION(1,1)) &&
+ !ap_table_get(r->headers_in, "Host"))) {
+ /*
+ * Client sent us an HTTP/1.1 or later request without telling us the
+ * hostname, either with a full URL or a Host: header. We therefore
+ * need to (as per the 1.1 spec) send an error. As a special case,
+ * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain
+ * a Host: header, and the server MUST respond with 400 if it doesn't.
+ */
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "client sent HTTP/1.1 request without hostname "
+ "(see RFC2616 section 14.23): %s", r->uri);
+ }
+ if (r->status != HTTP_OK) {
+ r->connection->keepalive = 0;
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
+
+ if ((access_status = ap_run_post_read_request(r))) {
+ ap_die(access_status, r);
+ ap_log_transaction(r);
+ return NULL;
+ }
+
+ if (((expect = ap_table_get(r->headers_in, "Expect")) != NULL) &&
+ (expect[0] != '\0')) {
+ /*
+ * The Expect header field was added to HTTP/1.1 after RFC 2068
+ * as a means to signal when a 100 response is desired and,
+ * unfortunately, to signal a poor man's mandatory extension that
+ * the server must understand or return 417 Expectation Failed.
+ */
+ if (strcasecmp(expect, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
+ "client sent an unrecognized expectation value of "
+ "Expect: %s", expect);
+ ap_send_error_response(r, 0);
+ (void) ap_discard_request_body(r);
+ ap_log_transaction(r);
+ return r;
+ }
+ }
+
+ return r;
+}
+
+/*
+ * A couple of other functions which initialize some of the fields of
+ * a request structure, as appropriate for adjuncts of one kind or another
+ * to a request in progress. Best here, rather than elsewhere, since
+ * *someone* has to set the protocol-specific fields...
+ */
+
+API_EXPORT(void) ap_set_sub_req_protocol(request_rec *rnew, const request_rec *r)
+{
+ rnew->the_request = r->the_request; /* Keep original request-line */
+
+ rnew->assbackwards = 1; /* Don't send headers from this. */
+ rnew->no_local_copy = 1; /* Don't try to send USE_LOCAL_COPY for a
+ * fragment. */
+ rnew->method = "GET";
+ rnew->method_number = M_GET;
+ rnew->protocol = "INCLUDED";
+
+ rnew->status = HTTP_OK;
+
+ rnew->headers_in = r->headers_in;
+ rnew->subprocess_env = ap_copy_table(rnew->pool, r->subprocess_env);
+ rnew->headers_out = ap_make_table(rnew->pool, 5);
+ rnew->err_headers_out = ap_make_table(rnew->pool, 5);
+ rnew->notes = ap_make_table(rnew->pool, 5);
+
+ rnew->expecting_100 = r->expecting_100;
+ rnew->read_length = r->read_length;
+ rnew->read_body = REQUEST_NO_BODY;
+
+ rnew->main = (request_rec *) r;
+}
+
+API_EXPORT(void) ap_finalize_sub_req_protocol(request_rec *sub)
+{
+ SET_BYTES_SENT(sub->main);
+}
+
+/*
+ * Support for the Basic authentication protocol, and a bit for Digest.
+ */
+
+API_EXPORT(void) ap_note_auth_failure(request_rec *r)
+{
+ if (!strcasecmp(ap_auth_type(r), "Basic"))
+ ap_note_basic_auth_failure(r);
+ else if (!strcasecmp(ap_auth_type(r), "Digest"))
+ ap_note_digest_auth_failure(r);
+}
+
+API_EXPORT(void) ap_note_basic_auth_failure(request_rec *r)
+{
+ if (strcasecmp(ap_auth_type(r), "Basic"))
+ ap_note_auth_failure(r);
+ else
+ ap_table_setn(r->err_headers_out,
+ r->proxyreq == STD_PROXY ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ ap_pstrcat(r->pool, "Basic realm=\"", ap_auth_name(r), "\"",
+ NULL));
+}
+
+API_EXPORT(void) ap_note_digest_auth_failure(request_rec *r)
+{
+ /* We need to create a nonce which:
+ * a) changes all the time (see r->request_time)
+ * below and
+ * b) of which we can verify that it is our own
+ * fairly easily when it comes to veryfing
+ * the digest coming back in the response.
+ * c) and which as a whole should not
+ * be unlikely to be in use anywhere else.
+ */
+ char * nonce_prefix = ap_md5(r->pool,
+ (unsigned char *)
+ ap_psprintf(r->pool, "%s%lu",
+ ap_auth_nonce(r), r->request_time));
+
+ ap_table_setn(r->err_headers_out,
+ r->proxyreq == STD_PROXY ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s%lu\"",
+ ap_auth_name(r), nonce_prefix, r->request_time));
+}
+
+API_EXPORT(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
+{
+ const char *auth_line = ap_table_get(r->headers_in,
+ r->proxyreq == STD_PROXY
+ ? "Proxy-Authorization"
+ : "Authorization");
+ const char *t;
+
+ if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic"))
+ return DECLINED;
+
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
+ r, "need AuthName: %s", r->uri);
+ return SERVER_ERROR;
+ }
+
+ if (!auth_line) {
+ ap_note_basic_auth_failure(r);
+ return AUTH_REQUIRED;
+ }
+
+ if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+ /* Client tried to authenticate using wrong auth scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "client used wrong authentication scheme: %s", r->uri);
+ ap_note_basic_auth_failure(r);
+ return AUTH_REQUIRED;
+ }
+
+ /* No CHARSET_EBCDIC Issue here because the line has already
+ * been converted to native text.
+ */
+ while (*auth_line== ' ' || *auth_line== '\t')
+ auth_line++;
+
+ t = ap_pbase64decode(r->pool, auth_line);
+ /* Note that this allocation has to be made from r->connection->pool
+ * because it has the lifetime of the connection. The other allocations
+ * are temporary and can be tossed away any time.
+ */
+ r->connection->user = ap_getword_nulls (r->connection->pool, &t, ':');
+ r->connection->ap_auth_type = "Basic";
+
+ *pw = t;
+
+ return OK;
+}
+
+/* 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];
+ */
+API_EXPORT(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 */
+}
+
+/* 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.
+ */
+API_EXPORT_NONSTD(int) ap_send_header_field(request_rec *r,
+ const char *fieldname,
+ const char *fieldval)
+{
+ if (strcasecmp(fieldname, "ETag") == 0) {
+ if (ap_table_get(r->notes, "no-etag") != NULL) {
+ return 1;
+ }
+ }
+ return (0 < ap_rvputs(r, fieldname, ": ", fieldval, CRLF, NULL));
+}
+
+API_EXPORT(void) ap_basic_http_header(request_rec *r)
+{
+ char *protocol;
+
+ if (r->assbackwards)
+ 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)
+ && ap_table_get(r->subprocess_env, "force-response-1.0")) {
+
+ protocol = "HTTP/1.0";
+ r->connection->keepalive = -1;
+ }
+ else
+ protocol = SERVER_PROTOCOL;
+
+#ifdef CHARSET_EBCDIC
+ PUSH_EBCDIC_OUTPUTCONVERSION_STATE_r(r, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ /* output the HTTP/1.x Status-Line */
+ ap_rvputs(r, protocol, " ", r->status_line, CRLF, NULL);
+
+ /* output the date header */
+ ap_send_header_field(r, "Date", ap_gm_timestr_822(r->pool, r->request_time));
+
+ /* keep the set-by-proxy server header, otherwise
+ * generate a new server header */
+ if (r->proxyreq) {
+ const char *server = ap_table_get(r->headers_out, "Server");
+ if (server) {
+ ap_send_header_field(r, "Server", server);
+ }
+ }
+ else {
+ ap_send_header_field(r, "Server", ap_get_server_version());
+ }
+
+ /* unset so we don't send them again */
+ ap_table_unset(r->headers_out, "Date"); /* Avoid bogosity */
+ ap_table_unset(r->headers_out, "Server");
+#ifdef CHARSET_EBCDIC
+ POP_EBCDIC_OUTPUTCONVERSION_STATE_r(r);
+#endif /*CHARSET_EBCDIC*/
+}
+
+/* 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(BUFF *client)
+{
+ long int bs;
+
+ ap_bgetopt(client, BO_BYTECT, &bs);
+ if (bs >= 255 && bs <= 257)
+ ap_bputs("X-Pad: avoid browser bug" CRLF, client);
+
+ ap_bputs(CRLF, client); /* Send the terminating empty line */
+}
+
+/* Build the Allow header from the request handler method mask.
+ * Note TRACE is tested on a per-server basis.
+ */
+static void set_allow_header(request_rec *r)
+{
+ core_server_config *conf =
+ ap_get_module_config(r->server->module_config, &core_module);
+
+ char *res = ap_pstrcat(r->pool,
+ (r->allowed & (1 << M_GET)) ? ", GET, HEAD" : "",
+ (r->allowed & (1 << M_POST)) ? ", POST" : "",
+ (r->allowed & (1 << M_PUT)) ? ", PUT" : "",
+ (r->allowed & (1 << M_DELETE)) ? ", DELETE" : "",
+ (r->allowed & (1 << M_CONNECT)) ? ", CONNECT" : "",
+ (r->allowed & (1 << M_OPTIONS)) ? ", OPTIONS" : "",
+ (r->allowed & (1 << M_PATCH)) ? ", PATCH" : "",
+ (r->allowed & (1 << M_PROPFIND)) ? ", PROPFIND" : "",
+ (r->allowed & (1 << M_PROPPATCH)) ? ", PROPPATCH" : "",
+ (r->allowed & (1 << M_MKCOL)) ? ", MKCOL" : "",
+ (r->allowed & (1 << M_COPY)) ? ", COPY" : "",
+ (r->allowed & (1 << M_MOVE)) ? ", MOVE" : "",
+ (r->allowed & (1 << M_LOCK)) ? ", LOCK" : "",
+ (r->allowed & (1 << M_UNLOCK)) ? ", UNLOCK" : "",
+ (conf->trace_enable != AP_TRACE_DISABLE) ? ", TRACE" : "",
+ NULL);
+
+ /* Cowardly attempt to avoid returning an empty Allow: header,
+ * but no matter how inaccurate, result code 405 demands it.
+ */
+ if (*res)
+ ap_table_setn(r->headers_out, "Allow", res + 2);
+ else if (r->status == METHOD_NOT_ALLOWED)
+ ap_table_setn(r->headers_out, "Allow", "");
+}
+
+API_EXPORT(int) ap_send_http_trace(request_rec *r)
+{
+ core_server_config *conf;
+ int rv;
+ int body;
+ char *bodyread, *bodyoff;
+ long bodylen = 0;
+ long bodybuf;
+ long res;
+
+ /* Get the original request */
+ while (r->prev)
+ r = r->prev;
+ conf = ap_get_module_config(r->server->module_config, &core_module);
+
+ if (conf->trace_enable == AP_TRACE_DISABLE) {
+ ap_table_setn(r->notes, "error-notes",
+ "TRACE forbidden by server configuration");
+ ap_table_setn(r->notes, "verbose-error-to", "*");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r,
+ "TRACE forbidden by server configuration");
+ return HTTP_FORBIDDEN;
+ }
+
+ if (conf->trace_enable == AP_TRACE_EXTENDED)
+ body = REQUEST_CHUNKED_PASS;
+ else
+ body = REQUEST_NO_BODY;
+
+ if ((rv = ap_setup_client_block(r, body))) {
+ if (rv == HTTP_REQUEST_ENTITY_TOO_LARGE)
+ ap_table_setn(r->notes, "error-notes",
+ "TRACE with a request body is not allowed");
+ return rv;
+ }
+
+ if (ap_should_client_block(r)) {
+
+ if (r->remaining > 0) {
+ if (r->remaining > 65536) {
+ ap_table_setn(r->notes, "error-notes",
+ "Extended TRACE request bodies cannot exceed 64k\n");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ /* always 32 extra bytes to catch chunk header exceptions */
+ bodybuf = r->remaining + 32;
+ }
+ else {
+ /* Add an extra 8192 for chunk headers */
+ bodybuf = 73730;
+ }
+
+ bodyoff = bodyread = ap_palloc(r->pool, bodybuf);
+
+ /* only while we have enough for a chunked header */
+ while ((!bodylen || bodybuf >= 32) &&
+ (res = ap_get_client_block(r, bodyoff, bodybuf)) > 0) {
+ bodylen += res;
+ bodybuf -= res;
+ bodyoff += res;
+ }
+ if (res > 0 && bodybuf < 32) {
+ /* discard_rest_of_request_body into our buffer */
+ while (ap_get_client_block(r, bodyread, bodylen) > 0)
+ ;
+ ap_table_setn(r->notes, "error-notes",
+ "Extended TRACE request bodies cannot exceed 64k\n");
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ if (res < 0) {
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ ap_hard_timeout("send TRACE", r);
+
+ r->content_type = "message/http";
+
+ ap_send_http_header(r);
+#ifdef CHARSET_EBCDIC
+ /* Server-generated response, converted */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1);
+#endif
+
+ /* Now we recreate the request, and echo it back */
+
+ ap_rvputs(r, r->the_request, CRLF, NULL);
+
+ ap_table_do((int (*) (void *, const char *, const char *))
+ ap_send_header_field, (void *) r, r->headers_in, NULL);
+ ap_rputs(CRLF, r);
+
+ /* If configured to accept a body, echo the body including chunks */
+ if (bodylen)
+ ap_rwrite(bodyread, bodylen, r);
+
+ ap_kill_timeout(r);
+ return OK;
+}
+
+API_EXPORT(int) ap_send_http_options(request_rec *r)
+{
+ const long int zero = 0L;
+
+ if (r->assbackwards)
+ return DECLINED;
+
+ ap_hard_timeout("send OPTIONS", r);
+
+ ap_basic_http_header(r);
+
+ ap_table_setn(r->headers_out, "Content-Length", "0");
+ set_allow_header(r);
+ ap_set_keepalive(r);
+
+ ap_table_do((int (*) (void *, const char *, const char *)) ap_send_header_field,
+ (void *) r, r->headers_out, NULL);
+
+ terminate_header(r->connection->client);
+
+ ap_kill_timeout(r);
+ ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
+
+ return OK;
+}
+
+/*
+ * 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 (ap_table_get(r->headers_in, "Request-Range") ||
+ ((ua = ap_table_get(r->headers_in, "User-Agent"))
+ && strstr(ua, "MSIE 3")));
+}
+
+/* This routine is called by ap_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)
+{
+ array_header *values;
+ char *start;
+ char *e;
+ char **strpp;
+ int i;
+
+ values = (array_header *)d;
+
+ e = ap_pstrdup(values->pool, val);
+
+ do {
+ /* Find a non-empty fieldname */
+
+ while (*e == ',' || ap_isspace(*e)) {
+ ++e;
+ }
+ if (*e == '\0') {
+ break;
+ }
+ start = e;
+ while (*e != '\0' && *e != ',' && !ap_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 **)ap_push_array(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)
+{
+ array_header *varies;
+
+ varies = ap_make_array(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.
+ */
+ ap_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) {
+ ap_table_setn(r->headers_out, "Vary",
+ ap_array_pstrcat(r->pool, varies, ','));
+ }
+}
+
+API_EXPORT(void) ap_send_http_header(request_rec *r)
+{
+ int i;
+ const long int zero = 0L;
+
+#ifdef CHARSET_EBCDIC
+ /* Use previously determined conversion (output): */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, ap_checkconv(r));
+#endif /*CHARSET_EBCDIC*/
+
+ if (r->assbackwards) {
+ if (!r->main)
+ ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
+ r->sent_bodyct = 1;
+ return;
+ }
+
+ /*
+ * 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 (!ap_is_empty_table(r->err_headers_out))
+ r->headers_out = ap_overlay_tables(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.
+ */
+ if (ap_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+ ap_table_unset(r->headers_out, "Vary");
+ r->proto_num = HTTP_VERSION(1,0);
+ ap_table_set(r->subprocess_env, "force-response-1.0", "1");
+ }
+ else {
+ fixup_vary(r);
+ }
+
+ ap_hard_timeout("send headers", r);
+
+ ap_basic_http_header(r);
+
+#ifdef CHARSET_EBCDIC
+ PUSH_EBCDIC_OUTPUTCONVERSION_STATE_r(r, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ ap_set_keepalive(r);
+
+ if (r->chunked) {
+ ap_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
+ ap_table_unset(r->headers_out, "Content-Length");
+ }
+
+ if (r->byterange > 1)
+ ap_table_setn(r->headers_out, "Content-Type",
+ ap_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/",
+ "byteranges; boundary=", r->boundary, NULL));
+ else ap_table_setn(r->headers_out, "Content-Type", make_content_type(r,
+ r->content_type));
+
+ if (r->content_encoding)
+ ap_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) {
+ ap_table_mergen(r->headers_out, "Content-Language",
+ ((char **) (r->content_languages->elts))[i]);
+ }
+ }
+ else if (r->content_language)
+ ap_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 && !ap_table_get(r->headers_out, "Expires"))
+ ap_table_addn(r->headers_out, "Expires",
+ ap_gm_timestr_822(r->pool, r->request_time));
+
+ /* Send the entire table of header fields, terminated by an empty line. */
+
+ ap_table_do((int (*) (void *, const char *, const char *)) ap_send_header_field,
+ (void *) r, r->headers_out, NULL);
+
+ terminate_header(r->connection->client);
+
+ ap_kill_timeout(r);
+
+ ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
+ r->sent_bodyct = 1; /* Whatever follows is real body stuff... */
+
+ /* Set buffer flags for the body */
+ if (r->chunked)
+ ap_bsetflag(r->connection->client, B_CHUNK, 1);
+#ifdef CHARSET_EBCDIC
+ POP_EBCDIC_OUTPUTCONVERSION_STATE_r(r);
+#endif /*CHARSET_EBCDIC*/
+}
+
+/* finalize_request_protocol is called at completion of sending the
+ * response. It's sole purpose is to send the terminating protocol
+ * information for any wrappers around the response message body
+ * (i.e., transfer encodings). It should have been named finalize_response.
+ */
+API_EXPORT(void) ap_finalize_request_protocol(request_rec *r)
+{
+ if (r->chunked && !r->connection->aborted) {
+#ifdef CHARSET_EBCDIC
+ PUSH_EBCDIC_OUTPUTCONVERSION_STATE_r(r, 1);
+#endif
+ /*
+ * Turn off chunked encoding --- we can only do this once.
+ */
+ r->chunked = 0;
+ ap_bsetflag(r->connection->client, B_CHUNK, 0);
+
+ ap_soft_timeout("send ending chunk", r);
+ ap_rputs("0" CRLF, r);
+ /* If we had footer "headers", we'd send them now */
+ ap_rputs(CRLF, r);
+ ap_kill_timeout(r);
+
+#ifdef CHARSET_EBCDIC
+ POP_EBCDIC_OUTPUTCONVERSION_STATE_r(r);
+#endif /*CHARSET_EBCDIC*/
+ }
+}
+
+/* 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.
+ * REQUEST_CHUNKED_PASS Pass the chunks to me without removal.
+ *
+ * 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.
+ */
+
+API_EXPORT(int) ap_setup_client_block(request_rec *r, int read_policy)
+{
+ const char *tenc = ap_table_get(r->headers_in, "Transfer-Encoding");
+ const char *lenp = ap_table_get(r->headers_in, "Content-Length");
+ unsigned 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, 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, r,
+ "chunked Transfer-Encoding forbidden: %s", r->uri);
+ return (lenp) ? HTTP_BAD_REQUEST : HTTP_LENGTH_REQUIRED;
+ }
+
+ r->read_chunked = 1;
+ }
+ else if (lenp) {
+ const char *pos = lenp;
+ int conversion_error = 0;
+
+ while (ap_isspace(*pos))
+ ++pos;
+
+ if (*pos == '\0') {
+ /* special case test - a C-L field NULL or all blanks is
+ * assumed OK and defaults to 0. Otherwise, we do a
+ * strict check of the field */
+ r->remaining = 0;
+ }
+ else {
+ char *endstr;
+ errno = 0;
+ r->remaining = ap_strtol(lenp, &endstr, 10);
+ if (errno || (endstr && *endstr) || (r->remaining < 0)) {
+ conversion_error = 1;
+ }
+ }
+
+ if (conversion_error) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Invalid Content-Length");
+ return HTTP_BAD_REQUEST;
+ }
+ }
+
+ if ((r->read_body == REQUEST_NO_BODY)
+ && (r->read_length || r->read_chunked || r->remaining)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 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 && ((unsigned long)r->remaining > max_body)
+ && (r->remaining >= 0)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Request content-length of %s is larger than the configured "
+ "limit of %lu", lenp, max_body);
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+#ifdef CHARSET_EBCDIC
+ {
+ /* Determine the EBCDIC conversion for the uploaded content
+ * by looking at the Content-Type MIME header.
+ * If no Content-Type header is found, text conversion is assumed.
+ */
+ ap_bsetflag(r->connection->client, B_ASCII2EBCDIC, ap_checkconv_in(r));
+ }
+#endif
+
+ return OK;
+}
+
+API_EXPORT(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)) {
+ /* sending 100 Continue interim response */
+ ap_rvputs(r, SERVER_PROTOCOL, " ", status_lines[0], CRLF CRLF,
+ NULL);
+ ap_rflush(r);
+ }
+
+ return 1;
+}
+
+/**
+ * Parse a chunk extension, detect overflow.
+ * There are two error cases:
+ * 1) If the conversion would require too many bits, a -1 is returned.
+ * 2) If the conversion used the correct number of bits, but an overflow
+ * caused only the sign bit to flip, then that negative number is
+ * returned.
+ * In general, any negative number can be considered an overflow error.
+ */
+API_EXPORT(long) ap_get_chunk_size(char *b)
+{
+ long chunksize = 0;
+ long chunkbits = sizeof(long) * 8;
+
+ /* Skip leading zeros */
+ while (*b == '0') {
+ ++b;
+ }
+
+ while (ap_isxdigit(*b) && (chunkbits > 0)) {
+ 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;
+ chunkbits -= 4;
+ ++b;
+ }
+ if (ap_isxdigit(*b) && (chunkbits <= 0)) {
+ /* overflow */
+ return -1;
+ }
+
+ 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.
+ */
+API_EXPORT(long) ap_get_client_block(request_rec *r, char *buffer, int bufsiz)
+{
+ int c;
+ long len_read, len_to_read;
+ long chunk_start = 0;
+ unsigned long max_body;
+
+ if (!r->read_chunked) { /* Content-length read */
+ len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining;
+ len_read = ap_bread(r->connection->client, buffer, len_to_read);
+ if (len_read <= 0) {
+ if (len_read < 0)
+ r->connection->keepalive = -1;
+ return len_read;
+ }
+ r->read_length += len_read;
+ r->remaining -= len_read;
+ return len_read;
+ }
+
+ /*
+ * Handle chunked reading Note: we are careful to shorten the input
+ * bufsiz so that there will always be enough space for us to add a CRLF
+ * (if necessary).
+ */
+ if (r->read_body == REQUEST_CHUNKED_PASS)
+ bufsiz -= 2;
+ if (bufsiz <= 0)
+ return -1; /* Cannot read chunked with a small buffer */
+
+ /* Check to see if we have already read too much request data.
+ * For efficiency reasons, we only check this at the top of each
+ * caller read pass, since the limit exists just to stop infinite
+ * length requests and nobody cares if it goes over by one buffer.
+ */
+ max_body = ap_get_limit_req_body(r);
+ if (max_body && ((unsigned long) r->read_length > max_body)
+ && (r->read_length >= 0)) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "Chunked request body is larger than the configured limit of %lu",
+ max_body);
+ r->connection->keepalive = -1;
+ return -1;
+ }
+
+ if (r->remaining == 0) { /* Start of new chunk */
+
+ chunk_start = ap_getline(buffer, bufsiz, r->connection->client, 0);
+ if ((chunk_start <= 0) || (chunk_start >= (bufsiz - 1))
+ || !ap_isxdigit(*buffer)) {
+ r->connection->keepalive = -1;
+ return -1;
+ }
+
+ len_to_read = ap_get_chunk_size(buffer);
+
+ if (len_to_read == 0) { /* Last chunk indicated, get footers */
+ if (r->read_body == REQUEST_CHUNKED_DECHUNK) {
+ get_mime_headers(r);
+ ap_snprintf(buffer, bufsiz, "%ld", r->read_length);
+ ap_table_unset(r->headers_in, "Transfer-Encoding");
+ ap_table_setn(r->headers_in, "Content-Length",
+ ap_pstrdup(r->pool, buffer));
+ return 0;
+ }
+ r->remaining = -1; /* Indicate footers in-progress */
+ }
+ else if (len_to_read < 0) {
+ r->connection->keepalive = -1;
+ return -1;
+ }
+ else {
+ r->remaining = len_to_read;
+ }
+ if (r->read_body == REQUEST_CHUNKED_PASS) {
+ buffer[chunk_start++] = CR; /* Restore chunk-size line end */
+ buffer[chunk_start++] = LF;
+ buffer += chunk_start; /* and pass line on to caller */
+ bufsiz -= chunk_start;
+ }
+ else {
+ /* REQUEST_CHUNKED_DECHUNK -- do not include the length of the
+ * header in the return value
+ */
+ chunk_start = 0;
+ }
+ }
+ /* When REQUEST_CHUNKED_PASS, we are */
+ if (r->remaining == -1) { /* reading footers until empty line */
+ len_read = chunk_start;
+
+ while ((bufsiz > 1) && ((len_read =
+ ap_getline(buffer, bufsiz, r->connection->client, 1)) > 0)) {
+
+ if (len_read != (bufsiz - 1)) {
+ buffer[len_read++] = CR; /* Restore footer line end */
+ buffer[len_read++] = LF;
+ }
+ chunk_start += len_read;
+ buffer += len_read;
+ bufsiz -= len_read;
+ }
+ if (len_read < 0) {
+ r->connection->keepalive = -1;
+ return -1;
+ }
+
+ if (len_read == 0) { /* Indicates an empty line */
+ buffer[0] = CR;
+ buffer[1] = LF;
+ chunk_start += 2;
+ r->remaining = -2;
+ }
+ r->read_length += chunk_start;
+ return chunk_start;
+ }
+ /* When REQUEST_CHUNKED_PASS, we */
+ if (r->remaining == -2) { /* finished footers when last called */
+ r->remaining = 0; /* so now we must signal EOF */
+ return 0;
+ }
+
+ /* Otherwise, we are in the midst of reading a chunk of data */
+
+ len_to_read = (r->remaining > bufsiz) ? bufsiz : r->remaining;
+
+ len_read = ap_bread(r->connection->client, buffer, len_to_read);
+ if (len_read <= 0) {
+ r->connection->keepalive = -1;
+ return -1;
+ }
+
+ r->remaining -= len_read;
+
+ if (r->remaining == 0) { /* End of chunk, get trailing CRLF */
+#ifdef CHARSET_EBCDIC
+ /* Chunk end is Protocol stuff! Set conversion = 1 to read CR LF: */
+ PUSH_EBCDIC_INPUTCONVERSION_STATE_r(r, 1);
+#endif /*CHARSET_EBCDIC*/
+
+ if ((c = ap_bgetc(r->connection->client)) == CR) {
+ c = ap_bgetc(r->connection->client);
+ }
+
+#ifdef CHARSET_EBCDIC
+ /* restore ASCII->EBCDIC conversion state */
+ POP_EBCDIC_INPUTCONVERSION_STATE_r(r);
+#endif /*CHARSET_EBCDIC*/
+
+ if (c != LF) {
+ r->connection->keepalive = -1;
+ return -1;
+ }
+ if (r->read_body == REQUEST_CHUNKED_PASS) {
+ buffer[len_read++] = CR;
+ buffer[len_read++] = LF;
+ }
+ }
+ r->read_length += (chunk_start + len_read);
+
+ return (chunk_start + len_read);
+}
+
+/* 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;
+ */
+API_EXPORT(int) ap_discard_request_body(request_rec *r)
+{
+ int rv;
+
+ if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_PASS)))
+ 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;
+ }
+ ap_hard_timeout("reading request body", r);
+ while ((rv = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN)) > 0)
+ continue;
+ ap_kill_timeout(r);
+
+ if (rv < 0)
+ return HTTP_BAD_REQUEST;
+ }
+ return OK;
+}
+
+/*
+ * Send the body of a response to the client.
+ */
+API_EXPORT(long) ap_send_fd(FILE *f, request_rec *r)
+{
+ return ap_send_fd_length(f, r, -1);
+}
+
+API_EXPORT(long) ap_send_fd_length(FILE *f, request_rec *r, long length)
+{
+ char buf[IOBUFSIZE];
+ long total_bytes_sent = 0;
+ register int n, w, o, len;
+
+ if (length == 0)
+ return 0;
+
+ ap_soft_timeout("send body", r);
+
+ while (!r->connection->aborted) {
+ if ((length > 0) && (total_bytes_sent + IOBUFSIZE) > length)
+ len = length - total_bytes_sent;
+ else
+ len = IOBUFSIZE;
+
+ while ((n = fread(buf, sizeof(char), len, f)) < 1
+ && ferror(f) && errno == EINTR && !r->connection->aborted)
+ continue;
+
+ if (n < 1) {
+ break;
+ }
+ o = 0;
+
+ while (n && !r->connection->aborted) {
+ w = ap_bwrite(r->connection->client, &buf[o], n);
+ if (w > 0) {
+ ap_reset_timeout(r); /* reset timeout after successful write */
+ total_bytes_sent += w;
+ n -= w;
+ o += w;
+ }
+ else if (w < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before send body completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ break;
+ }
+ }
+ }
+
+ ap_kill_timeout(r);
+ SET_BYTES_SENT(r);
+ return total_bytes_sent;
+}
+
+/*
+ * Send the body of a response to the client.
+ */
+API_EXPORT(long) ap_send_fb(BUFF *fb, request_rec *r)
+{
+ return ap_send_fb_length(fb, r, -1);
+}
+
+API_EXPORT(long) ap_send_fb_length(BUFF *fb, request_rec *r, long length)
+{
+ char buf[IOBUFSIZE];
+ long total_bytes_sent = 0;
+ register int n, w, o, len, fd;
+ fd_set fds;
+#ifdef TPF_HAVE_NONSOCKET_SELECT
+ struct timeval tv;
+#endif
+
+ if (length == 0)
+ return 0;
+
+ /* Make fb unbuffered and non-blocking */
+ ap_bsetflag(fb, B_RD, 0);
+#ifndef TPF_NO_NONSOCKET_SELECT
+ ap_bnonblock(fb, B_RD);
+#endif
+ fd = ap_bfileno(fb, B_RD);
+#ifdef CHECK_FD_SETSIZE
+ if (fd >= FD_SETSIZE) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
+ "send body: filedescriptor (%u) larger than FD_SETSIZE (%u) "
+ "found, you probably need to rebuild Apache with a "
+ "larger FD_SETSIZE", fd, FD_SETSIZE);
+ return 0;
+ }
+#endif
+
+ ap_soft_timeout("send body", r);
+
+ FD_ZERO(&fds);
+ while (!r->connection->aborted) {
+#ifdef NDELAY_PIPE_RETURNS_ZERO
+ /* Contributed by dwd@bell-labs.com for UTS 2.1.2, where the fcntl */
+ /* O_NDELAY flag causes read to return 0 when there's nothing */
+ /* available when reading from a pipe. That makes it tricky */
+ /* to detect end-of-file :-(. This stupid bug is even documented */
+ /* in the read(2) man page where it says that everything but */
+ /* pipes return -1 and EAGAIN. That makes it a feature, right? */
+ int afterselect = 0;
+#endif
+ if ((length > 0) && (total_bytes_sent + IOBUFSIZE) > length)
+ len = length - total_bytes_sent;
+ else
+ len = IOBUFSIZE;
+
+ do {
+ n = ap_bread(fb, buf, len);
+#ifdef NDELAY_PIPE_RETURNS_ZERO
+ if ((n > 0) || (n == 0 && afterselect))
+ break;
+#else
+ if (n >= 0)
+ break;
+#endif
+ if (r->connection->aborted)
+ break;
+ if (n < 0 && errno != EAGAIN)
+ break;
+
+ /* we need to block, so flush the output first */
+ if (ap_bflush(r->connection->client) < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before send body completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ break;
+ }
+ FD_SET(fd, &fds);
+ /*
+ * we don't care what select says, we might as well loop back
+ * around and try another read
+ */
+#ifdef TPF_HAVE_NONSOCKET_SELECT
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ ap_select(fd + 1, &fds, NULL, NULL, &tv);
+#else
+ ap_select(fd + 1, &fds, NULL, NULL, NULL);
+#endif
+#ifdef NDELAY_PIPE_RETURNS_ZERO
+ afterselect = 1;
+#endif
+ } while (!r->connection->aborted);
+
+ if (n < 1 || r->connection->aborted) {
+ break;
+ }
+ o = 0;
+
+ while (n && !r->connection->aborted) {
+ w = ap_bwrite(r->connection->client, &buf[o], n);
+ if (w > 0) {
+ ap_reset_timeout(r); /* reset timeout after successful write */
+ total_bytes_sent += w;
+ n -= w;
+ o += w;
+ }
+ else if (w < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before send body completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ break;
+ }
+ }
+ }
+
+ ap_kill_timeout(r);
+ SET_BYTES_SENT(r);
+ return total_bytes_sent;
+}
+
+
+
+/* The code writes MMAP_SEGMENT_SIZE bytes at a time. This is due to Apache's
+ * timeout model, which is a timeout per-write rather than a time for the
+ * entire transaction to complete. Essentially this should be small enough
+ * so that in one Timeout period, your slowest clients should be reasonably
+ * able to receive this many bytes.
+ *
+ * To take advantage of zero-copy TCP under Solaris 2.6 this should be a
+ * multiple of 16k. (And you need a SunATM2.0 network card.)
+ */
+#ifndef MMAP_SEGMENT_SIZE
+#define MMAP_SEGMENT_SIZE 32768
+#endif
+
+/* send data from an in-memory buffer */
+API_EXPORT(size_t) ap_send_mmap(void *mm, request_rec *r, size_t offset,
+ size_t length)
+{
+ size_t total_bytes_sent = 0;
+ int n, w;
+
+ if (length == 0)
+ return 0;
+
+ ap_soft_timeout("send mmap", r);
+
+ length += offset;
+ while (!r->connection->aborted && offset < length) {
+ if (length - offset > MMAP_SEGMENT_SIZE) {
+ n = MMAP_SEGMENT_SIZE;
+ }
+ else {
+ n = length - offset;
+ }
+
+ while (n && !r->connection->aborted) {
+ w = ap_bwrite(r->connection->client, (char *) mm + offset, n);
+ if (w > 0) {
+ ap_reset_timeout(r); /* reset timeout after successful write */
+ total_bytes_sent += w;
+ n -= w;
+ offset += w;
+ }
+ else if (w < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before send mmap completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ break;
+ }
+ }
+ }
+
+ ap_kill_timeout(r);
+ SET_BYTES_SENT(r);
+ return total_bytes_sent;
+}
+
+API_EXPORT(int) ap_rputc(int c, request_rec *r)
+{
+ if (r->connection->aborted)
+ return EOF;
+
+ if (ap_bputc(c, r->connection->client) < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rputc completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return EOF;
+ }
+ SET_BYTES_SENT(r);
+ return c;
+}
+
+API_EXPORT(int) ap_rputs(const char *str, request_rec *r)
+{
+ int rcode;
+
+ if (r->connection->aborted)
+ return EOF;
+
+ rcode = ap_bputs(str, r->connection->client);
+ if (rcode < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rputs completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return EOF;
+ }
+ SET_BYTES_SENT(r);
+ return rcode;
+}
+
+API_EXPORT(int) ap_rwrite(const void *buf, int nbyte, request_rec *r)
+{
+ int n;
+
+ if (r->connection->aborted)
+ return -1;
+
+ n = ap_bwrite(r->connection->client, buf, nbyte);
+ if (n < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rwrite completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return -1;
+ }
+ SET_BYTES_SENT(r);
+ return n;
+}
+
+API_EXPORT(int) ap_vrprintf(request_rec *r, const char *fmt, va_list ap)
+{
+ int n;
+
+ if (r->connection->aborted)
+ return -1;
+
+ n = ap_vbprintf(r->connection->client, fmt, ap);
+
+ if (n < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before vrprintf completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return -1;
+ }
+ SET_BYTES_SENT(r);
+ return n;
+}
+
+API_EXPORT_NONSTD(int) ap_rprintf(request_rec *r, const char *fmt,...)
+{
+ va_list vlist;
+ int n;
+
+ if (r->connection->aborted)
+ return -1;
+
+ va_start(vlist, fmt);
+ n = ap_vbprintf(r->connection->client, fmt, vlist);
+ va_end(vlist);
+
+ if (n < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rprintf completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return -1;
+ }
+ SET_BYTES_SENT(r);
+ return n;
+}
+
+API_EXPORT_NONSTD(int) ap_rvputs(request_rec *r,...)
+{
+ va_list args;
+ int i, j, k;
+ const char *x;
+ BUFF *fb = r->connection->client;
+
+ if (r->connection->aborted)
+ return EOF;
+
+ va_start(args, r);
+ for (k = 0;;) {
+ x = va_arg(args, const char *);
+ if (x == NULL)
+ break;
+ j = strlen(x);
+ i = ap_bwrite(fb, x, j);
+ if (i != j) {
+ va_end(args);
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rvputs completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return EOF;
+ }
+ k += i;
+ }
+ va_end(args);
+
+ SET_BYTES_SENT(r);
+ return k;
+}
+
+API_EXPORT(int) ap_rflush(request_rec *r)
+{
+ if (ap_bflush(r->connection->client) < 0) {
+ if (!r->connection->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, r,
+ "client stopped connection before rflush completed");
+ ap_bsetflag(r->connection->client, B_EOUT, 1);
+ r->connection->aborted = 1;
+ }
+ return EOF;
+ }
+ return 0;
+}
+
+/* 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.
+ */
+API_EXPORT(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 = ap_table_get(r->headers_out, "Location");
+#ifdef CHARSET_EBCDIC
+ /* Error Responses (builtin / string literal / redirection) are TEXT! */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1);
+#endif
+
+ /*
+ * 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 = ap_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) {
+ if (!ap_is_empty_table(r->err_headers_out))
+ r->headers_out = ap_overlay_tables(r->pool, r->err_headers_out,
+ r->headers_out);
+ ap_hard_timeout("send 304", r);
+
+ ap_basic_http_header(r);
+ ap_set_keepalive(r);
+
+ ap_table_do((int (*)(void *, const char *, const char *)) ap_send_header_field,
+ (void *) r, r->headers_out,
+ "Connection",
+ "Keep-Alive",
+ "ETag",
+ "Content-Location",
+ "Expires",
+ "Cache-Control",
+ "Vary",
+ "Warning",
+ "WWW-Authenticate",
+ "Proxy-Authenticate",
+ NULL);
+
+ terminate_header(r->connection->client);
+
+ ap_kill_timeout(r);
+ return;
+ }
+
+ if (status == HTTP_NO_CONTENT) {
+ ap_send_http_header(r);
+ ap_finalize_request_protocol(r);
+ return;
+ }
+
+ if (!r->assbackwards) {
+ table *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;
+ ap_clear_table(r->err_headers_out);
+
+ if (ap_is_HTTP_REDIRECT(status) || (status == HTTP_CREATED)) {
+ if ((location != NULL) && *location) {
+ ap_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;
+ if (ap_table_get(r->subprocess_env,
+ "suppress-error-charset") != NULL) {
+ r->content_type = "text/html";
+ }
+ else {
+ r->content_type = "text/html; charset=iso-8859-1";
+ }
+
+ if ((status == METHOD_NOT_ALLOWED) || (status == NOT_IMPLEMENTED))
+ set_allow_header(r);
+
+ ap_send_http_header(r);
+
+ if (r->header_only) {
+ ap_finalize_request_protocol(r);
+ ap_rflush(r);
+ return;
+ }
+ }
+
+#ifdef CHARSET_EBCDIC
+ /* Server-generated response, converted */
+ ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1);
+#endif
+
+ ap_hard_timeout("send error body", r);
+
+ 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_kill_timeout(r);
+ ap_finalize_request_protocol(r);
+ ap_rflush(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;
+ const char *error_notes;
+
+ /* 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 */
+ && ap_isdigit(r->status_line[0])
+ && ap_isdigit(r->status_line[1])
+ && ap_isdigit(r->status_line[2])
+ && ap_isspace(r->status_line[3])
+ && ap_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];
+
+ ap_rvputs(r,
+ DOCTYPE_HTML_2_0
+ "<HTML><HEAD>\n<TITLE>", title,
+ "</TITLE>\n</HEAD><BODY>\n<H1>", h1, "</H1>\n",
+ NULL);
+
+ switch (status) {
+ case HTTP_MOVED_PERMANENTLY:
+ case HTTP_MOVED_TEMPORARILY:
+ case HTTP_TEMPORARY_REDIRECT:
+ ap_rvputs(r, "The document has moved <A HREF=\"",
+ ap_escape_html(r->pool, location), "\">here</A>.<P>\n",
+ NULL);
+ break;
+ case HTTP_SEE_OTHER:
+ ap_rvputs(r, "The answer to your request is located <A HREF=\"",
+ ap_escape_html(r->pool, location), "\">here</A>.<P>\n",
+ NULL);
+ break;
+ case HTTP_USE_PROXY:
+ ap_rvputs(r, "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);
+ break;
+ case HTTP_PROXY_AUTHENTICATION_REQUIRED:
+ case AUTH_REQUIRED:
+ ap_rputs("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", r);
+ break;
+ case BAD_REQUEST:
+ ap_rputs("Your browser sent a request that "
+ "this server could not understand.<P>\n", r);
+ if ((error_notes = ap_table_get(r->notes, "error-notes")) != NULL) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ break;
+ case HTTP_FORBIDDEN:
+ ap_rvputs(r, "You don't have permission to access ",
+ ap_escape_html(r->pool, r->uri),
+ "\non this server.<P>\n", NULL);
+ break;
+ case NOT_FOUND:
+ ap_rvputs(r, "The requested URL ",
+ ap_escape_html(r->pool, r->uri),
+ " was not found on this server.<P>\n", NULL);
+ break;
+ case METHOD_NOT_ALLOWED:
+ ap_rvputs(r, "The requested method ", r->method,
+ " is not allowed "
+ "for the URL ", ap_escape_html(r->pool, r->uri),
+ ".<P>\n", NULL);
+ break;
+ case NOT_ACCEPTABLE:
+ ap_rvputs(r,
+ "An appropriate representation of the "
+ "requested resource ",
+ ap_escape_html(r->pool, r->uri),
+ " could not be found on this server.<P>\n", NULL);
+ /* fall through */
+ case MULTIPLE_CHOICES:
+ {
+ const char *list;
+ if ((list = ap_table_get(r->notes, "variant-list")))
+ ap_rputs(list, r);
+ }
+ break;
+ case LENGTH_REQUIRED:
+ ap_rvputs(r, "A request of the requested method ", r->method,
+ " requires a valid Content-length.<P>\n", NULL);
+ if ((error_notes = ap_table_get(r->notes, "error-notes")) != NULL) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ break;
+ case PRECONDITION_FAILED:
+ ap_rvputs(r, "The precondition on the request for the URL ",
+ ap_escape_html(r->pool, r->uri),
+ " evaluated to false.<P>\n", NULL);
+ break;
+ case HTTP_NOT_IMPLEMENTED:
+ ap_rvputs(r, ap_escape_html(r->pool, r->method), " to ",
+ ap_escape_html(r->pool, r->uri),
+ " not supported.<P>\n", NULL);
+ if ((error_notes = ap_table_get(r->notes, "error-notes")) != NULL) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ break;
+ case BAD_GATEWAY:
+ ap_rputs("The proxy server received an invalid" CRLF
+ "response from an upstream server.<P>" CRLF, r);
+ if ((error_notes = ap_table_get(r->notes, "error-notes")) != NULL) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ break;
+ case VARIANT_ALSO_VARIES:
+ ap_rvputs(r, "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);
+ break;
+ case HTTP_REQUEST_TIME_OUT:
+ ap_rputs("Server timeout waiting for the HTTP request from the client.\n", r);
+ break;
+ case HTTP_GONE:
+ ap_rvputs(r, "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);
+ break;
+ case HTTP_REQUEST_ENTITY_TOO_LARGE:
+ ap_rvputs(r, "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);
+ break;
+ case HTTP_REQUEST_URI_TOO_LARGE:
+ ap_rputs("The requested URL's length exceeds the capacity\n"
+ "limit for this server.<P>\n", r);
+ if ((error_notes = ap_table_get(r->notes, "error-notes")) != NULL) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ break;
+ case HTTP_UNSUPPORTED_MEDIA_TYPE:
+ ap_rputs("The supplied request data is not in a format\n"
+ "acceptable for processing by this resource.\n", r);
+ break;
+ case HTTP_RANGE_NOT_SATISFIABLE:
+ ap_rputs("None of the range-specifier values in the Range\n"
+ "request-header field overlap the current extent\n"
+ "of the selected resource.\n", r);
+ break;
+ case HTTP_EXPECTATION_FAILED:
+ ap_rvputs(r, "The expectation given in the Expect request-header"
+ "\nfield could not be met by this server.<P>\n"
+ "The client sent<PRE>\n Expect: ",
+ ap_escape_html(r->pool, ap_table_get(r->headers_in, "Expect")), "\n</PRE>\n"
+ "but we only allow the 100-continue expectation.\n",
+ NULL);
+ break;
+ case HTTP_UNPROCESSABLE_ENTITY:
+ ap_rputs("The server understands the media type of the\n"
+ "request entity, but was unable to process the\n"
+ "contained instructions.\n", r);
+ break;
+ case HTTP_LOCKED:
+ ap_rputs("The requested resource is currently locked.\n"
+ "The lock must be released or proper identification\n"
+ "given before the method can be applied.\n", r);
+ break;
+ case HTTP_FAILED_DEPENDENCY:
+ ap_rputs("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", r);
+ break;
+ case HTTP_INSUFFICIENT_STORAGE:
+ ap_rputs("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", r);
+ break;
+ case HTTP_SERVICE_UNAVAILABLE:
+ ap_rputs("The server is temporarily unable to service your\n"
+ "request due to maintenance downtime or capacity\n"
+ "problems. Please try again later.\n", r);
+ break;
+ case HTTP_GATEWAY_TIME_OUT:
+ ap_rputs("The proxy server did not receive a timely response\n"
+ "from the upstream server.\n", r);
+ break;
+ case HTTP_NOT_EXTENDED:
+ ap_rputs("A mandatory extension policy in the request is not\n"
+ "accepted by the server for this resource.\n", r);
+ break;
+ 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 = ap_table_get(r->notes, "error-notes")) != NULL)
+ && (h1 = ap_table_get(r->notes, "verbose-error-to")) != NULL
+ && (strcmp(h1, "*") == 0)) {
+ ap_rvputs(r, error_notes, "<P>\n", NULL);
+ }
+ else {
+ ap_rvputs(r, "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
+ * 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 = ap_table_get(r->notes, "error-notes")) != NULL) {
+ * ap_rvputs(r, error_notes, "<P>\n", NULL);
+ * }
+ */
+ break;
+ }
+
+ if (recursive_error) {
+ ap_rvputs(r, "<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_rputs(ap_psignature("<HR>\n", r), r);
+ ap_rputs("</BODY></HTML>\n", r);
+ }
+ ap_kill_timeout(r);
+ ap_finalize_request_protocol(r);
+ ap_rflush(r);
+}