1
0
mirror of https://github.com/apache/httpd.git synced 2025-08-08 15:02:10 +03:00

Initial pass at refactoring some files to eliminate our 150K C source behemoths.

* Makefile.in: Change order of dependencies to bring in exports.o first so that
  we have every symbol 'used' before the linker starts processing.
* build/rules.mk.in: Add a 'program-install' target which just copies httpd.

* server/Makefile.in, modules/http/config2.m4: Add in new file targets.

* NWGNUmakefile, libhttpd.dsp: Blind updates for Netware and Win32.  (I tried.)

* server/core.c: Move core_input_filter, net_time_filter, and core_output_filter  and all supporting functions to...
* server/core_filters.c (copied): ...here.

* modules/http/http_protocol.c: Move functions from here to there...namely:
* modules/http/byterange_filter.c (copied): Relocate ap_byterange_filter() and
  friends.
* modules/http/chunk_filter.c (copied): Relocate chunk_filter().
* modules/http/http_etag.c (copied): Relocate ap_set_etag and ap_make_etag().
* modules/http/http_filters.c (copied): Relocate ap_http_filter(),
  ap_http_header_filter(), ap_discard_request_body(), ap_setup_client_block(),
  ap_should_client_block(), and ap_get_client_block().


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@106692 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Justin Erenkrantz
2004-11-27 08:07:44 +00:00
parent 9e804e259e
commit 439221058c
13 changed files with 2989 additions and 2588 deletions

View File

@@ -6,9 +6,9 @@ PROGRAM_NAME = $(progname)
PROGRAM_SOURCES = modules.c PROGRAM_SOURCES = modules.c
PROGRAM_LDADD = $(HTTPD_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS) $(LIBS) PROGRAM_LDADD = $(HTTPD_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS) $(LIBS)
PROGRAM_DEPENDENCIES = \ PROGRAM_DEPENDENCIES = \
server/libmain.la \
$(BUILTIN_LIBS) \ $(BUILTIN_LIBS) \
$(MPM_LIB) \ $(MPM_LIB) \
server/libmain.la \
os/$(OS_DIR)/libos.la os/$(OS_DIR)/libos.la
PROGRAMS = $(PROGRAM_NAME) PROGRAMS = $(PROGRAM_NAME)

View File

@@ -195,11 +195,16 @@ FILES_nlm_objs = \
$(OBJDIR)/config.o \ $(OBJDIR)/config.o \
$(OBJDIR)/connection.o \ $(OBJDIR)/connection.o \
$(OBJDIR)/core.o \ $(OBJDIR)/core.o \
$(OBJDIR)/core_filters.o \
$(OBJDIR)/eoc_bucket.o \ $(OBJDIR)/eoc_bucket.o \
$(OBJDIR)/error_bucket.o \ $(OBJDIR)/error_bucket.o \
$(OBJDIR)/http_core.o \ $(OBJDIR)/http_core.o \
$(OBJDIR)/http_protocol.o \ $(OBJDIR)/http_protocol.o \
$(OBJDIR)/http_request.o \ $(OBJDIR)/http_request.o \
$(OBJDIR)/byterange_filter.o \
$(OBJDIR)/chunk_filter.o \
$(OBJDIR)/http_etag.o \
$(OBJDIR)/http_filters.o \
$(OBJDIR)/listen.o \ $(OBJDIR)/listen.o \
$(OBJDIR)/log.o \ $(OBJDIR)/log.o \
$(OBJDIR)/main.o \ $(OBJDIR)/main.o \

View File

@@ -160,7 +160,7 @@ local-extraclean: local-distclean x-local-extraclean
rm -f $(EXTRACLEAN_TARGETS) ; \ rm -f $(EXTRACLEAN_TARGETS) ; \
fi fi
local-install: $(TARGETS) $(SHARED_TARGETS) $(INSTALL_TARGETS) program-install: $(TARGETS) $(SHARED_TARGETS)
@if test -n '$(PROGRAMS)'; then \ @if test -n '$(PROGRAMS)'; then \
test -d $(DESTDIR)$(sbindir) || $(MKINSTALLDIRS) $(DESTDIR)$(sbindir); \ test -d $(DESTDIR)$(sbindir) || $(MKINSTALLDIRS) $(DESTDIR)$(sbindir); \
list='$(PROGRAMS)'; for i in $$list; do \ list='$(PROGRAMS)'; for i in $$list; do \
@@ -168,6 +168,8 @@ local-install: $(TARGETS) $(SHARED_TARGETS) $(INSTALL_TARGETS)
done; \ done; \
fi fi
local-install: program-install $(INSTALL_TARGETS)
# to be filled in by the actual Makefile if extra commands are needed # to be filled in by the actual Makefile if extra commands are needed
x-local-depend x-local-clean x-local-distclean x-local-extraclean: x-local-depend x-local-clean x-local-distclean x-local-extraclean:

View File

@@ -382,6 +382,10 @@ SOURCE=.\server\core.c
# End Source File # End Source File
# Begin Source File # Begin Source File
SOURCE=.\server\core_filters.c
# End Source File
# Begin Source File
SOURCE=.\modules\http\http_core.c SOURCE=.\modules\http\http_core.c
# End Source File # End Source File
# Begin Source File # Begin Source File
@@ -394,6 +398,22 @@ SOURCE=.\modules\http\http_request.c
# End Source File # End Source File
# Begin Source File # Begin Source File
SOURCE=.\modules\http\byterange_filter.c
# End Source File
# Begin Source File
SOURCE=.\modules\http\chunk_filter.c
# End Source File
# Begin Source File
SOURCE=.\modules\http\http_etag.c
# End Source File
# Begin Source File
SOURCE=.\modules\http\http_filters.c
# End Source File
# Begin Source File
SOURCE=.\server\log.c SOURCE=.\server\log.c
# End Source File # End Source File
# Begin Source File # Begin Source File

View File

@@ -0,0 +1,388 @@
/* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed 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.
*/
/*
* byterange_filter.c --- HTTP byterange filter and friends.
*/
#include "apr.h"
#include "apr_strings.h"
#include "apr_buckets.h"
#include "apr_lib.h"
#include "apr_signal.h"
#define APR_WANT_STDIO /* for sscanf */
#define APR_WANT_STRFUNC
#define APR_WANT_MEMFUNC
#include "apr_want.h"
#define CORE_PRIVATE
#include "util_filter.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_request.h"
#include "http_vhost.h"
#include "http_log.h" /* For errors detected in basic auth common
* support code... */
#include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */
#include "util_charset.h"
#include "util_ebcdic.h"
#include "util_time.h"
#include "mod_core.h"
#if APR_HAVE_STDARG_H
#include <stdarg.h>
#endif
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif
static int parse_byterange(char *range, apr_off_t clength,
apr_off_t *start, apr_off_t *end)
{
char *dash = strchr(range, '-');
char *errp;
apr_off_t number;
if (!dash) {
return 0;
}
if ((dash == range)) {
/* In the form "-5" */
if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) {
return 0;
}
*start = clength - number;
*end = clength - 1;
}
else {
*dash++ = '\0';
if (apr_strtoff(&number, range, &errp, 10) || *errp) {
return 0;
}
*start = number;
if (*dash) {
if (apr_strtoff(&number, dash, &errp, 10) || *errp) {
return 0;
}
*end = number;
}
else { /* "5-" */
*end = clength - 1;
}
}
if (*start < 0) {
*start = 0;
}
if (*end >= clength) {
*end = clength - 1;
}
if (*start > *end) {
return -1;
}
return (*start > 0 || *end < clength);
}
static int ap_set_byterange(request_rec *r);
typedef struct byterange_ctx {
apr_bucket_brigade *bb;
int num_ranges;
char *boundary;
char *bound_head;
} byterange_ctx;
/*
* Here we try to be compatible with clients that want multipart/x-byteranges
* instead of multipart/byteranges (also see above), as per HTTP/1.1. We
* look for the Request-Range header (e.g. Netscape 2 and 3) as an indication
* that the browser supports an older protocol. We also check User-Agent
* for Microsoft Internet Explorer 3, which needs this as well.
*/
static int use_range_x(request_rec *r)
{
const char *ua;
return (apr_table_get(r->headers_in, "Request-Range")
|| ((ua = apr_table_get(r->headers_in, "User-Agent"))
&& ap_strstr_c(ua, "MSIE 3")));
}
#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
#define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
"[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
apr_bucket_brigade *bb)
{
#define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
request_rec *r = f->r;
conn_rec *c = r->connection;
byterange_ctx *ctx = f->ctx;
apr_bucket *e;
apr_bucket_brigade *bsend;
apr_off_t range_start;
apr_off_t range_end;
char *current;
apr_off_t bb_length;
apr_off_t clength = 0;
apr_status_t rv;
int found = 0;
if (!ctx) {
int num_ranges = ap_set_byterange(r);
/* We have nothing to do, get out of the way. */
if (num_ranges == 0) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
ctx->num_ranges = num_ranges;
/* create a brigade in case we never call ap_save_brigade() */
ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
if (ctx->num_ranges > 1) {
/* Is ap_make_content_type required here? */
const char *orig_ct = ap_make_content_type(r, r->content_type);
ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
(apr_uint64_t)r->request_time, (long) getpid());
ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
use_range_x(r) ? "/x-" : "/",
"byteranges; boundary=",
ctx->boundary, NULL));
ctx->bound_head = apr_pstrcat(r->pool,
CRLF "--", ctx->boundary,
CRLF "Content-type: ",
orig_ct,
CRLF "Content-range: bytes ",
NULL);
ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
}
}
/* We can't actually deal with byte-ranges until we have the whole brigade
* because the byte-ranges can be in any order, and according to the RFC,
* we SHOULD return the data in the same order it was requested.
*
* XXX: We really need to dump all bytes prior to the start of the earliest
* range, and only slurp up to the end of the latest range. By this we
* mean that we should peek-ahead at the lowest first byte of any range,
* and the highest last byte of any range.
*/
if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
ap_save_brigade(f, &ctx->bb, &bb, r->pool);
return APR_SUCCESS;
}
/* Prepend any earlier saved brigades. */
APR_BRIGADE_PREPEND(bb, ctx->bb);
/* It is possible that we won't have a content length yet, so we have to
* compute the length before we can actually do the byterange work.
*/
apr_brigade_length(bb, 1, &bb_length);
clength = (apr_off_t)bb_length;
/* this brigade holds what we will be sending */
bsend = apr_brigade_create(r->pool, c->bucket_alloc);
while ((current = ap_getword(r->pool, &r->range, ','))
&& (rv = parse_byterange(current, clength, &range_start,
&range_end))) {
apr_bucket *e2;
apr_bucket *ec;
if (rv == -1) {
continue;
}
/* these calls to apr_brigade_partition() should theoretically
* never fail because of the above call to apr_brigade_length(),
* but what the heck, we'll check for an error anyway */
if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
PARTITION_ERR_FMT, range_start, clength);
continue;
}
if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
PARTITION_ERR_FMT, range_end+1, clength);
continue;
}
found = 1;
/* For single range requests, we must produce Content-Range header.
* Otherwise, we need to produce the multipart boundaries.
*/
if (ctx->num_ranges == 1) {
apr_table_setn(r->headers_out, "Content-Range",
apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
range_start, range_end, clength));
}
else {
char *ts;
e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF,
range_start, range_end, clength);
ap_xlate_proto_to_ascii(ts, strlen(ts));
e = apr_bucket_pool_create(ts, strlen(ts), r->pool,
c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
}
do {
apr_bucket *foo;
const char *str;
apr_size_t len;
if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
/* this shouldn't ever happen due to the call to
* apr_brigade_length() above which normalizes
* indeterminate-length buckets. just to be sure,
* though, this takes care of uncopyable buckets that
* do somehow manage to slip through.
*/
/* XXX: check for failure? */
apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
apr_bucket_copy(ec, &foo);
}
APR_BRIGADE_INSERT_TAIL(bsend, foo);
ec = APR_BUCKET_NEXT(ec);
} while (ec != e2);
}
if (found == 0) {
ap_remove_output_filter(f);
r->status = HTTP_OK;
/* bsend is assumed to be empty if we get here. */
e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
e = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
return ap_pass_brigade(f->next, bsend);
}
if (ctx->num_ranges > 1) {
char *end;
/* add the final boundary */
end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
ap_xlate_proto_to_ascii(end, strlen(end));
e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
}
e = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
/* we're done with the original content - all of our data is in bsend. */
apr_brigade_destroy(bb);
/* send our multipart output */
return ap_pass_brigade(f->next, bsend);
}
static int ap_set_byterange(request_rec *r)
{
const char *range;
const char *if_range;
const char *match;
const char *ct;
int num_ranges;
if (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 = apr_table_get(r->headers_in, "Range"))) {
range = apr_table_get(r->headers_in, "Request-Range");
}
if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) {
return 0;
}
/* is content already a single range? */
if (apr_table_get(r->headers_out, "Content-Range")) {
return 0;
}
/* is content already a multiple range? */
if ((ct = apr_table_get(r->headers_out, "Content-Type"))
&& (!strncasecmp(ct, "multipart/byteranges", 20)
|| !strncasecmp(ct, "multipart/x-byteranges", 22))) {
return 0;
}
/* Check the If-Range header for Etag or Date.
* Note that this check will return false (as required) if either
* of the two etags are weak.
*/
if ((if_range = apr_table_get(r->headers_in, "If-Range"))) {
if (if_range[0] == '"') {
if (!(match = apr_table_get(r->headers_out, "Etag"))
|| (strcmp(if_range, match) != 0)) {
return 0;
}
}
else if (!(match = apr_table_get(r->headers_out, "Last-Modified"))
|| (strcmp(if_range, match) != 0)) {
return 0;
}
}
if (!ap_strchr_c(range, ',')) {
/* a single range */
num_ranges = 1;
}
else {
/* a multiple range */
num_ranges = 2;
}
r->status = HTTP_PARTIAL_CONTENT;
r->range = range + 6;
return num_ranges;
}

167
modules/http/chunk_filter.c Normal file
View File

@@ -0,0 +1,167 @@
/* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed 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.
*/
/*
* chunk_filter.c --- HTTP/1.1 chunked transfer encoding filter.
*/
#include "apr_strings.h"
#include "apr_thread_proc.h" /* for RLIMIT stuff */
#define APR_WANT_STRFUNC
#include "apr_want.h"
#define CORE_PRIVATE
#include "httpd.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_core.h"
#include "http_protocol.h" /* For index_of_response(). Grump. */
#include "http_request.h"
#include "util_filter.h"
#include "util_ebcdic.h"
#include "ap_mpm.h"
#include "scoreboard.h"
#include "mod_core.h"
static apr_status_t chunk_filter(ap_filter_t *f, apr_bucket_brigade *b)
{
#define ASCII_CRLF "\015\012"
#define ASCII_ZERO "\060"
conn_rec *c = f->r->connection;
apr_bucket_brigade *more;
apr_bucket *e;
apr_status_t rv;
for (more = NULL; b; b = more, more = NULL) {
apr_off_t bytes = 0;
apr_bucket *eos = NULL;
apr_bucket *flush = NULL;
/* XXX: chunk_hdr must remain at this scope since it is used in a
* transient bucket.
*/
char chunk_hdr[20]; /* enough space for the snprintf below */
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
{
if (APR_BUCKET_IS_EOS(e)) {
/* there shouldn't be anything after the eos */
eos = e;
break;
}
if (APR_BUCKET_IS_FLUSH(e)) {
flush = e;
}
else if (e->length == (apr_size_t)-1) {
/* unknown amount of data (e.g. a pipe) */
const char *data;
apr_size_t len;
rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
if (rv != APR_SUCCESS) {
return rv;
}
if (len > 0) {
/*
* There may be a new next bucket representing the
* rest of the data stream on which a read() may
* block so we pass down what we have so far.
*/
bytes += len;
more = apr_brigade_split(b, APR_BUCKET_NEXT(e));
break;
}
else {
/* If there was nothing in this bucket then we can
* safely move on to the next one without pausing
* to pass down what we have counted up so far.
*/
continue;
}
}
else {
bytes += e->length;
}
}
/*
* XXX: if there aren't very many bytes at this point it may
* be a good idea to set them aside and return for more,
* unless we haven't finished counting this brigade yet.
*/
/* if there are content bytes, then wrap them in a chunk */
if (bytes > 0) {
apr_size_t hdr_len;
/*
* Insert the chunk header, specifying the number of bytes in
* the chunk.
*/
hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr),
"%" APR_UINT64_T_HEX_FMT CRLF, (apr_uint64_t)bytes);
ap_xlate_proto_to_ascii(chunk_hdr, hdr_len);
e = apr_bucket_transient_create(chunk_hdr, hdr_len,
c->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(b, e);
/*
* Insert the end-of-chunk CRLF before an EOS or
* FLUSH bucket, or appended to the brigade
*/
e = apr_bucket_immortal_create(ASCII_CRLF, 2, c->bucket_alloc);
if (eos != NULL) {
APR_BUCKET_INSERT_BEFORE(eos, e);
}
else if (flush != NULL) {
APR_BUCKET_INSERT_BEFORE(flush, e);
}
else {
APR_BRIGADE_INSERT_TAIL(b, e);
}
}
/* RFC 2616, Section 3.6.1
*
* If there is an EOS bucket, then prefix it with:
* 1) the last-chunk marker ("0" CRLF)
* 2) the trailer
* 3) the end-of-chunked body CRLF
*
* If there is no EOS bucket, then do nothing.
*
* XXX: it would be nice to combine this with the end-of-chunk
* marker above, but this is a bit more straight-forward for
* now.
*/
if (eos != NULL) {
/* XXX: (2) trailers ... does not yet exist */
e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
/* <trailers> */
ASCII_CRLF, 5, c->bucket_alloc);
APR_BUCKET_INSERT_BEFORE(eos, e);
}
/* pass the brigade to the next filter. */
rv = ap_pass_brigade(f->next, b);
if (rv != APR_SUCCESS || eos != NULL) {
return rv;
}
}
return APR_SUCCESS;
}

View File

@@ -2,7 +2,7 @@ dnl modules enabled in this directory by default
APACHE_MODPATH_INIT(http) APACHE_MODPATH_INIT(http)
http_objects="http_core.lo http_protocol.lo http_request.lo" http_objects="http_core.lo http_protocol.lo http_request.lo http_filters.lo chunk_filter.lo byterange_filter.lo http_etag.lo"
dnl mod_http should only be built as a static module for now. dnl mod_http should only be built as a static module for now.
dnl this will hopefully be "fixed" at some point in the future by dnl this will hopefully be "fixed" at some point in the future by

220
modules/http/http_etag.c Normal file
View File

@@ -0,0 +1,220 @@
/* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed 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.
*/
#include "apr_strings.h"
#include "apr_thread_proc.h" /* for RLIMIT stuff */
#define APR_WANT_STRFUNC
#include "apr_want.h"
#define CORE_PRIVATE
#include "httpd.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_core.h"
#include "http_protocol.h" /* For index_of_response(). Grump. */
#include "http_request.h"
/* Generate the human-readable hex representation of an unsigned long
* (basically a faster version of 'sprintf("%lx")')
*/
#define HEX_DIGITS "0123456789abcdef"
static char *etag_ulong_to_hex(char *next, unsigned long u)
{
int printing = 0;
int shift = sizeof(unsigned long) * 8 - 4;
do {
unsigned long next_digit = ((u >> shift) & (unsigned long)0xf);
if (next_digit) {
*next++ = HEX_DIGITS[next_digit];
printing = 1;
}
else if (printing) {
*next++ = HEX_DIGITS[next_digit];
}
shift -= 4;
} while (shift);
*next++ = HEX_DIGITS[u & (unsigned long)0xf];
return next;
}
#define ETAG_WEAK "W/"
#define CHARS_PER_UNSIGNED_LONG (sizeof(unsigned long) * 2)
/*
* Construct an entity tag (ETag) from resource information. If it's a real
* file, build in some of the file characteristics. If the modification time
* is newer than (request-time minus 1 second), mark the ETag as weak - it
* could be modified again in as short an interval. We rationalize the
* modification time we're given to keep it from being in the future.
*/
AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak)
{
char *weak;
apr_size_t weak_len;
char *etag;
char *next;
core_dir_config *cfg;
etag_components_t etag_bits;
etag_components_t bits_added;
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 it's a file (or we wouldn't be here) and no ETags
* should be set for files, return an empty string and
* note it for the header-sender to ignore.
*/
if (etag_bits & ETAG_NONE) {
apr_table_setn(r->notes, "no-etag", "omit");
return "";
}
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.
*/
if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
!force_weak) {
weak = NULL;
weak_len = 0;
}
else {
weak = ETAG_WEAK;
weak_len = sizeof(ETAG_WEAK);
}
if (r->finfo.filetype != 0) {
/*
* ETag gets set to [W/]"inode-size-mtime", modulo any
* FileETag keywords.
*/
etag = apr_palloc(r->pool, weak_len + sizeof("\"--\"") +
3 * CHARS_PER_UNSIGNED_LONG + 1);
next = etag;
if (weak) {
while (*weak) {
*next++ = *weak++;
}
}
*next++ = '"';
bits_added = 0;
if (etag_bits & ETAG_INODE) {
next = etag_ulong_to_hex(next, (unsigned long)r->finfo.inode);
bits_added |= ETAG_INODE;
}
if (etag_bits & ETAG_SIZE) {
if (bits_added != 0) {
*next++ = '-';
}
next = etag_ulong_to_hex(next, (unsigned long)r->finfo.size);
bits_added |= ETAG_SIZE;
}
if (etag_bits & ETAG_MTIME) {
if (bits_added != 0) {
*next++ = '-';
}
next = etag_ulong_to_hex(next, (unsigned long)r->mtime);
}
*next++ = '"';
*next = '\0';
}
else {
/*
* Not a file document, so just use the mtime: [W/]"mtime"
*/
etag = apr_palloc(r->pool, weak_len + sizeof("\"\"") +
CHARS_PER_UNSIGNED_LONG + 1);
next = etag;
if (weak) {
while (*weak) {
*next++ = *weak++;
}
}
*next++ = '"';
next = etag_ulong_to_hex(next, (unsigned long)r->mtime);
*next++ = '"';
*next = '\0';
}
return etag;
}
AP_DECLARE(void) ap_set_etag(request_rec *r)
{
char *etag;
char *variant_etag, *vlv;
int vlv_weak;
if (!r->vlist_validator) {
etag = ap_make_etag(r, 0);
/* 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 = apr_pstrcat(r->pool, variant_etag, ";", vlv, NULL);
}
apr_table_setn(r->headers_out, "ETag", etag);
}

1247
modules/http/http_filters.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ LTLIBRARY_SOURCES = \
mpm_common.c util_charset.c util_debug.c util_xml.c \ mpm_common.c util_charset.c util_debug.c util_xml.c \
util_filter.c exports.c buildmark.c \ util_filter.c exports.c buildmark.c \
scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \
eoc_bucket.c eoc_bucket.c core_filters.c
TARGETS = delete-exports $(LTLIBRARY_NAME) $(CORE_IMPLIB_FILE) export_vars.h httpd.exp TARGETS = delete-exports $(LTLIBRARY_NAME) $(CORE_IMPLIB_FILE) export_vars.h httpd.exp

View File

@@ -93,6 +93,12 @@ AP_DECLARE_DATA ap_filter_rec_t *ap_content_length_filter_handle;
AP_DECLARE_DATA ap_filter_rec_t *ap_net_time_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_net_time_filter_handle;
AP_DECLARE_DATA ap_filter_rec_t *ap_core_input_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_core_input_filter_handle;
extern int core_input_filter(ap_filter_t *, apr_bucket_brigade *,
ap_input_mode_t, apr_read_type_e, apr_off_t);
extern int net_time_filter(ap_filter_t *, apr_bucket_brigade *,
ap_input_mode_t, apr_read_type_e, apr_off_t);
extern apr_status_t core_output_filter(ap_filter_t *, apr_bucket_brigade *);
/* magic pointer for ErrorDocument xxx "default" */ /* magic pointer for ErrorDocument xxx "default" */
static char errordocument_default; static char errordocument_default;
@@ -3008,218 +3014,6 @@ void ap_add_output_filters_by_type(request_rec *r)
return; return;
} }
static apr_status_t writev_it_all(apr_socket_t *s,
struct iovec *vec, int nvec,
apr_size_t len, apr_size_t *nbytes)
{
apr_size_t bytes_written = 0;
apr_status_t rv;
apr_size_t n = len;
int i = 0;
*nbytes = 0;
/* XXX handle checking for non-blocking socket */
while (bytes_written != len) {
rv = apr_socket_sendv(s, vec + i, nvec - i, &n);
*nbytes += n;
bytes_written += n;
if (rv != APR_SUCCESS)
return rv;
/* If the write did not complete, adjust the iovecs and issue
* apr_socket_sendv again
*/
if (bytes_written < len) {
/* Skip over the vectors that have already been written */
apr_size_t cnt = vec[i].iov_len;
while (n >= cnt && i + 1 < nvec) {
i++;
cnt += vec[i].iov_len;
}
if (n < cnt) {
/* Handle partial write of vec i */
vec[i].iov_base = (char *) vec[i].iov_base +
(vec[i].iov_len - (cnt - n));
vec[i].iov_len = cnt -n;
}
}
n = len - bytes_written;
}
return APR_SUCCESS;
}
/* sendfile_it_all()
* send the entire file using sendfile()
* handle partial writes
* return only when all bytes have been sent or an error is encountered.
*/
#if APR_HAS_SENDFILE
static apr_status_t sendfile_it_all(core_net_rec *c,
apr_file_t *fd,
apr_hdtr_t *hdtr,
apr_off_t file_offset,
apr_size_t file_bytes_left,
apr_size_t total_bytes_left,
apr_size_t *bytes_sent,
apr_int32_t flags)
{
apr_status_t rv;
#ifdef AP_DEBUG
apr_interval_time_t timeout = 0;
#endif
AP_DEBUG_ASSERT((apr_socket_timeout_get(c->client_socket, &timeout)
== APR_SUCCESS)
&& timeout > 0); /* socket must be in timeout mode */
/* Reset the bytes_sent field */
*bytes_sent = 0;
do {
apr_size_t tmplen = file_bytes_left;
rv = apr_socket_sendfile(c->client_socket, fd, hdtr, &file_offset, &tmplen,
flags);
*bytes_sent += tmplen;
total_bytes_left -= tmplen;
if (!total_bytes_left || rv != APR_SUCCESS) {
return rv; /* normal case & error exit */
}
AP_DEBUG_ASSERT(total_bytes_left > 0 && tmplen > 0);
/* partial write, oooh noooo...
* Skip over any header data which was written
*/
while (tmplen && hdtr->numheaders) {
if (tmplen >= hdtr->headers[0].iov_len) {
tmplen -= hdtr->headers[0].iov_len;
--hdtr->numheaders;
++hdtr->headers;
}
else {
char *iov_base = (char *)hdtr->headers[0].iov_base;
hdtr->headers[0].iov_len -= tmplen;
iov_base += tmplen;
hdtr->headers[0].iov_base = iov_base;
tmplen = 0;
}
}
/* Skip over any file data which was written */
if (tmplen <= file_bytes_left) {
file_offset += tmplen;
file_bytes_left -= tmplen;
continue;
}
tmplen -= file_bytes_left;
file_bytes_left = 0;
file_offset = 0;
/* Skip over any trailer data which was written */
while (tmplen && hdtr->numtrailers) {
if (tmplen >= hdtr->trailers[0].iov_len) {
tmplen -= hdtr->trailers[0].iov_len;
--hdtr->numtrailers;
++hdtr->trailers;
}
else {
char *iov_base = (char *)hdtr->trailers[0].iov_base;
hdtr->trailers[0].iov_len -= tmplen;
iov_base += tmplen;
hdtr->trailers[0].iov_base = iov_base;
tmplen = 0;
}
}
} while (1);
}
#endif
/*
* emulate_sendfile()
* Sends the contents of file fd along with header/trailer bytes, if any,
* to the network. emulate_sendfile will return only when all the bytes have been
* sent (i.e., it handles partial writes) or on a network error condition.
*/
static apr_status_t emulate_sendfile(core_net_rec *c, apr_file_t *fd,
apr_hdtr_t *hdtr, apr_off_t offset,
apr_size_t length, apr_size_t *nbytes)
{
apr_status_t rv = APR_SUCCESS;
apr_size_t togo; /* Remaining number of bytes in the file to send */
apr_size_t sendlen = 0;
apr_size_t bytes_sent;
apr_int32_t i;
apr_off_t o; /* Track the file offset for partial writes */
char buffer[8192];
*nbytes = 0;
/* Send the headers
* writev_it_all handles partial writes.
* XXX: optimization... if headers are less than MIN_WRITE_SIZE, copy
* them into buffer
*/
if (hdtr && hdtr->numheaders > 0 ) {
for (i = 0; i < hdtr->numheaders; i++) {
sendlen += hdtr->headers[i].iov_len;
}
rv = writev_it_all(c->client_socket, hdtr->headers, hdtr->numheaders,
sendlen, &bytes_sent);
*nbytes += bytes_sent; /* track total bytes sent */
}
/* Seek the file to 'offset' */
if (offset >= 0 && rv == APR_SUCCESS) {
rv = apr_file_seek(fd, APR_SET, &offset);
}
/* Send the file, making sure to handle partial writes */
togo = length;
while (rv == APR_SUCCESS && togo) {
sendlen = togo > sizeof(buffer) ? sizeof(buffer) : togo;
o = 0;
rv = apr_file_read(fd, buffer, &sendlen);
while (rv == APR_SUCCESS && sendlen) {
bytes_sent = sendlen;
rv = apr_socket_send(c->client_socket, &buffer[o], &bytes_sent);
*nbytes += bytes_sent;
if (rv == APR_SUCCESS) {
sendlen -= bytes_sent; /* sendlen != bytes_sent ==> partial write */
o += bytes_sent; /* o is where we are in the buffer */
togo -= bytes_sent; /* track how much of the file we've sent */
}
}
}
/* Send the trailers
* XXX: optimization... if it will fit, send this on the last send in the
* loop above
*/
sendlen = 0;
if ( rv == APR_SUCCESS && hdtr && hdtr->numtrailers > 0 ) {
for (i = 0; i < hdtr->numtrailers; i++) {
sendlen += hdtr->trailers[i].iov_len;
}
rv = writev_it_all(c->client_socket, hdtr->trailers, hdtr->numtrailers,
sendlen, &bytes_sent);
*nbytes += bytes_sent;
}
return rv;
}
/* Note --- ErrorDocument will now work from .htaccess files. /* Note --- ErrorDocument will now work from .htaccess files.
* The AllowOverride of Fileinfo allows webmasters to turn it off * The AllowOverride of Fileinfo allows webmasters to turn it off
*/ */
@@ -3574,8 +3368,6 @@ static int core_override_type(request_rec *r)
return OK; return OK;
} }
static int default_handler(request_rec *r) static int default_handler(request_rec *r)
{ {
conn_rec *c = r->connection; conn_rec *c = r->connection;
@@ -3733,664 +3525,10 @@ static int default_handler(request_rec *r)
} }
} }
typedef struct net_time_filter_ctx {
apr_socket_t *csd;
int first_line;
} net_time_filter_ctx_t;
static int net_time_filter(ap_filter_t *f, apr_bucket_brigade *b,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
net_time_filter_ctx_t *ctx = f->ctx;
int keptalive = f->c->keepalive == AP_CONN_KEEPALIVE;
if (!ctx) {
f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
ctx->first_line = 1;
ctx->csd = ap_get_module_config(f->c->conn_config, &core_module);
}
if (mode != AP_MODE_INIT && mode != AP_MODE_EATCRLF) {
if (ctx->first_line) {
apr_socket_timeout_set(ctx->csd,
keptalive
? f->c->base_server->keep_alive_timeout
: f->c->base_server->timeout);
ctx->first_line = 0;
}
else {
if (keptalive) {
apr_socket_timeout_set(ctx->csd, f->c->base_server->timeout);
}
}
}
return ap_get_brigade(f->next, b, mode, block, readbytes);
}
/**
* Remove all zero length buckets from the brigade.
*/
#define BRIGADE_NORMALIZE(b) \
do { \
apr_bucket *e = APR_BRIGADE_FIRST(b); \
do { \
if (e->length == 0 && !APR_BUCKET_IS_METADATA(e)) { \
apr_bucket *d; \
d = APR_BUCKET_NEXT(e); \
apr_bucket_delete(e); \
e = d; \
} \
e = APR_BUCKET_NEXT(e); \
} while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \
} while (0)
static int core_input_filter(ap_filter_t *f, apr_bucket_brigade *b,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
apr_bucket *e;
apr_status_t rv;
core_net_rec *net = f->ctx;
core_ctx_t *ctx = net->in_ctx;
const char *str;
apr_size_t len;
if (mode == AP_MODE_INIT) {
/*
* this mode is for filters that might need to 'initialize'
* a connection before reading request data from a client.
* NNTP over SSL for example needs to handshake before the
* server sends the welcome message.
* such filters would have changed the mode before this point
* is reached. however, protocol modules such as NNTP should
* not need to know anything about SSL. given the example, if
* SSL is not in the filter chain, AP_MODE_INIT is a noop.
*/
return APR_SUCCESS;
}
if (!ctx)
{
ctx = apr_pcalloc(f->c->pool, sizeof(*ctx));
ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
/* seed the brigade with the client socket. */
e = apr_bucket_socket_create(net->client_socket, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ctx->b, e);
net->in_ctx = ctx;
}
else if (APR_BRIGADE_EMPTY(ctx->b)) {
return APR_EOF;
}
/* ### This is bad. */
BRIGADE_NORMALIZE(ctx->b);
/* check for empty brigade again *AFTER* BRIGADE_NORMALIZE()
* If we have lost our socket bucket (see above), we are EOF.
*
* Ideally, this should be returning SUCCESS with EOS bucket, but
* some higher-up APIs (spec. read_request_line via ap_rgetline)
* want an error code. */
if (APR_BRIGADE_EMPTY(ctx->b)) {
return APR_EOF;
}
if (mode == AP_MODE_GETLINE) {
/* we are reading a single LF line, e.g. the HTTP headers */
rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN);
/* We should treat EAGAIN here the same as we do for EOF (brigade is
* empty). We do this by returning whatever we have read. This may
* or may not be bogus, but is consistent (for now) with EOF logic.
*/
if (APR_STATUS_IS_EAGAIN(rv)) {
rv = APR_SUCCESS;
}
return rv;
}
/* ### AP_MODE_PEEK is a horrific name for this mode because we also
* eat any CRLFs that we see. That's not the obvious intention of
* this mode. Determine whether anyone actually uses this or not. */
if (mode == AP_MODE_EATCRLF) {
apr_bucket *e;
const char *c;
/* The purpose of this loop is to ignore any CRLF (or LF) at the end
* of a request. Many browsers send extra lines at the end of POST
* requests. We use the PEEK method to determine if there is more
* data on the socket, so that we know if we should delay sending the
* end of one request until we have served the second request in a
* pipelined situation. We don't want to actually delay sending a
* response if the server finds a CRLF (or LF), becuause that doesn't
* mean that there is another request, just a blank line.
*/
while (1) {
if (APR_BRIGADE_EMPTY(ctx->b))
return APR_EOF;
e = APR_BRIGADE_FIRST(ctx->b);
rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ);
if (rv != APR_SUCCESS)
return rv;
c = str;
while (c < str + len) {
if (*c == APR_ASCII_LF)
c++;
else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF)
c += 2;
else
return APR_SUCCESS;
}
/* If we reach here, we were a bucket just full of CRLFs, so
* just toss the bucket. */
/* FIXME: Is this the right thing to do in the core? */
apr_bucket_delete(e);
}
return APR_SUCCESS;
}
/* If mode is EXHAUSTIVE, we want to just read everything until the end
* of the brigade, which in this case means the end of the socket.
* To do this, we attach the brigade that has currently been setaside to
* the brigade that was passed down, and send that brigade back.
*
* NOTE: This is VERY dangerous to use, and should only be done with
* extreme caution. However, the Perchild MPM needs this feature
* if it is ever going to work correctly again. With this, the Perchild
* MPM can easily request the socket and all data that has been read,
* which means that it can pass it to the correct child process.
*/
if (mode == AP_MODE_EXHAUSTIVE) {
apr_bucket *e;
/* Tack on any buckets that were set aside. */
APR_BRIGADE_CONCAT(b, ctx->b);
/* Since we've just added all potential buckets (which will most
* likely simply be the socket bucket) we know this is the end,
* so tack on an EOS too. */
/* We have read until the brigade was empty, so we know that we
* must be EOS. */
e = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(b, e);
return APR_SUCCESS;
}
/* read up to the amount they specified. */
if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) {
apr_bucket *e;
apr_bucket_brigade *newbb;
AP_DEBUG_ASSERT(readbytes > 0);
e = APR_BRIGADE_FIRST(ctx->b);
rv = apr_bucket_read(e, &str, &len, block);
if (APR_STATUS_IS_EAGAIN(rv)) {
return APR_SUCCESS;
}
else if (rv != APR_SUCCESS) {
return rv;
}
else if (block == APR_BLOCK_READ && len == 0) {
/* We wanted to read some bytes in blocking mode. We read
* 0 bytes. Hence, we now assume we are EOS.
*
* When we are in normal mode, return an EOS bucket to the
* caller.
* When we are in speculative mode, leave ctx->b empty, so
* that the next call returns an EOS bucket.
*/
apr_bucket_delete(e);
if (mode == AP_MODE_READBYTES) {
e = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(b, e);
}
return APR_SUCCESS;
}
/* We can only return at most what we read. */
if (len < readbytes) {
readbytes = len;
}
rv = apr_brigade_partition(ctx->b, readbytes, &e);
if (rv != APR_SUCCESS) {
return rv;
}
/* Must do split before CONCAT */
newbb = apr_brigade_split(ctx->b, e);
if (mode == AP_MODE_READBYTES) {
APR_BRIGADE_CONCAT(b, ctx->b);
}
else if (mode == AP_MODE_SPECULATIVE) {
apr_bucket *copy_bucket;
for (e = APR_BRIGADE_FIRST(ctx->b);
e != APR_BRIGADE_SENTINEL(ctx->b);
e = APR_BUCKET_NEXT(e))
{
rv = apr_bucket_copy(e, &copy_bucket);
if (rv != APR_SUCCESS) {
return rv;
}
APR_BRIGADE_INSERT_TAIL(b, copy_bucket);
}
}
/* Take what was originally there and place it back on ctx->b */
APR_BRIGADE_CONCAT(ctx->b, newbb);
}
return APR_SUCCESS;
}
#define MAX_IOVEC_TO_WRITE 16
/* Optional function coming from mod_logio, used for logging of output /* Optional function coming from mod_logio, used for logging of output
* traffic * traffic
*/ */
static APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *logio_add_bytes_out; APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *logio_add_bytes_out;
static apr_status_t core_output_filter(ap_filter_t *f, apr_bucket_brigade *b)
{
apr_status_t rv;
apr_bucket_brigade *more;
conn_rec *c = f->c;
core_net_rec *net = f->ctx;
core_output_filter_ctx_t *ctx = net->out_ctx;
apr_read_type_e eblock = APR_NONBLOCK_READ;
apr_pool_t *input_pool = b->p;
if (ctx == NULL) {
ctx = apr_pcalloc(c->pool, sizeof(*ctx));
net->out_ctx = ctx;
}
/* If we have a saved brigade, concatenate the new brigade to it */
if (ctx->b) {
APR_BRIGADE_CONCAT(ctx->b, b);
b = ctx->b;
ctx->b = NULL;
}
/* Perform multiple passes over the brigade, sending batches of output
to the connection. */
while (b && !APR_BRIGADE_EMPTY(b)) {
apr_size_t nbytes = 0;
apr_bucket *last_e = NULL; /* initialized for debugging */
apr_bucket *e;
/* one group of iovecs per pass over the brigade */
apr_size_t nvec = 0;
apr_size_t nvec_trailers = 0;
struct iovec vec[MAX_IOVEC_TO_WRITE];
struct iovec vec_trailers[MAX_IOVEC_TO_WRITE];
/* one file per pass over the brigade */
apr_file_t *fd = NULL;
apr_size_t flen = 0;
apr_off_t foffset = 0;
/* keep track of buckets that we've concatenated
* to avoid small writes
*/
apr_bucket *last_merged_bucket = NULL;
/* tail of brigade if we need another pass */
more = NULL;
/* Iterate over the brigade: collect iovecs and/or a file */
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
{
/* keep track of the last bucket processed */
last_e = e;
if (APR_BUCKET_IS_EOS(e) || AP_BUCKET_IS_EOC(e)) {
break;
}
else if (APR_BUCKET_IS_FLUSH(e)) {
if (e != APR_BRIGADE_LAST(b)) {
more = apr_brigade_split(b, APR_BUCKET_NEXT(e));
}
break;
}
/* It doesn't make any sense to use sendfile for a file bucket
* that represents 10 bytes.
*/
else if (APR_BUCKET_IS_FILE(e)
&& (e->length >= AP_MIN_SENDFILE_BYTES)) {
apr_bucket_file *a = e->data;
/* We can't handle more than one file bucket at a time
* so we split here and send the file we have already
* found.
*/
if (fd) {
more = apr_brigade_split(b, e);
break;
}
fd = a->fd;
flen = e->length;
foffset = e->start;
}
else {
const char *str;
apr_size_t n;
rv = apr_bucket_read(e, &str, &n, eblock);
if (APR_STATUS_IS_EAGAIN(rv)) {
/* send what we have so far since we shouldn't expect more
* output for a while... next time we read, block
*/
more = apr_brigade_split(b, e);
eblock = APR_BLOCK_READ;
break;
}
eblock = APR_NONBLOCK_READ;
if (n) {
if (!fd) {
if (nvec == MAX_IOVEC_TO_WRITE) {
/* woah! too many. buffer them up, for use later. */
apr_bucket *temp, *next;
apr_bucket_brigade *temp_brig;
if (nbytes >= AP_MIN_BYTES_TO_WRITE) {
/* We have enough data in the iovec
* to justify doing a writev
*/
more = apr_brigade_split(b, e);
break;
}
/* Create a temporary brigade as a means
* of concatenating a bunch of buckets together
*/
if (last_merged_bucket) {
/* If we've concatenated together small
* buckets already in a previous pass,
* the initial buckets in this brigade
* are heap buckets that may have extra
* space left in them (because they
* were created by apr_brigade_write()).
* We can take advantage of this by
* building the new temp brigade out of
* these buckets, so that the content
* in them doesn't have to be copied again.
*/
apr_bucket_brigade *bb;
bb = apr_brigade_split(b,
APR_BUCKET_NEXT(last_merged_bucket));
temp_brig = b;
b = bb;
}
else {
temp_brig = apr_brigade_create(f->c->pool,
f->c->bucket_alloc);
}
temp = APR_BRIGADE_FIRST(b);
while (temp != e) {
apr_bucket *d;
rv = apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
apr_brigade_write(temp_brig, NULL, NULL, str, n);
d = temp;
temp = APR_BUCKET_NEXT(temp);
apr_bucket_delete(d);
}
nvec = 0;
nbytes = 0;
temp = APR_BRIGADE_FIRST(temp_brig);
APR_BUCKET_REMOVE(temp);
APR_BRIGADE_INSERT_HEAD(b, temp);
apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
/* Just in case the temporary brigade has
* multiple buckets, recover the rest of
* them and put them in the brigade that
* we're sending.
*/
for (next = APR_BRIGADE_FIRST(temp_brig);
next != APR_BRIGADE_SENTINEL(temp_brig);
next = APR_BRIGADE_FIRST(temp_brig)) {
APR_BUCKET_REMOVE(next);
APR_BUCKET_INSERT_AFTER(temp, next);
temp = next;
apr_bucket_read(next, &str, &n,
APR_BLOCK_READ);
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
}
apr_brigade_destroy(temp_brig);
last_merged_bucket = temp;
e = temp;
last_e = e;
}
else {
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
}
}
else {
/* The bucket is a trailer to a file bucket */
if (nvec_trailers == MAX_IOVEC_TO_WRITE) {
/* woah! too many. stop now. */
more = apr_brigade_split(b, e);
break;
}
vec_trailers[nvec_trailers].iov_base = (char*) str;
vec_trailers[nvec_trailers].iov_len = n;
nvec_trailers++;
}
nbytes += n;
}
}
}
/* Completed iterating over the brigade, now determine if we want
* to buffer the brigade or send the brigade out on the network.
*
* Save if we haven't accumulated enough bytes to send, the connection
* is not about to be closed, and:
*
* 1) we didn't see a file, we don't have more passes over the
* brigade to perform, AND we didn't stop at a FLUSH bucket.
* (IOW, we will save plain old bytes such as HTTP headers)
* or
* 2) we hit the EOS and have a keep-alive connection
* (IOW, this response is a bit more complex, but we save it
* with the hope of concatenating with another response)
*/
if (nbytes + flen < AP_MIN_BYTES_TO_WRITE
&& !AP_BUCKET_IS_EOC(last_e)
&& ((!fd && !more && !APR_BUCKET_IS_FLUSH(last_e))
|| (APR_BUCKET_IS_EOS(last_e)
&& c->keepalive == AP_CONN_KEEPALIVE))) {
/* NEVER save an EOS in here. If we are saving a brigade with
* an EOS bucket, then we are doing keepalive connections, and
* we want to process to second request fully.
*/
if (APR_BUCKET_IS_EOS(last_e)) {
apr_bucket *bucket;
int file_bucket_saved = 0;
apr_bucket_delete(last_e);
for (bucket = APR_BRIGADE_FIRST(b);
bucket != APR_BRIGADE_SENTINEL(b);
bucket = APR_BUCKET_NEXT(bucket)) {
/* Do a read on each bucket to pull in the
* data from pipe and socket buckets, so
* that we don't leave their file descriptors
* open indefinitely. Do the same for file
* buckets, with one exception: allow the
* first file bucket in the brigade to remain
* a file bucket, so that we don't end up
* doing an mmap+memcpy every time a client
* requests a <8KB file over a keepalive
* connection.
*/
if (APR_BUCKET_IS_FILE(bucket) && !file_bucket_saved) {
file_bucket_saved = 1;
}
else {
const char *buf;
apr_size_t len = 0;
rv = apr_bucket_read(bucket, &buf, &len,
APR_BLOCK_READ);
if (rv != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv,
c, "core_output_filter:"
" Error reading from bucket.");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
}
if (!ctx->deferred_write_pool) {
apr_pool_create(&ctx->deferred_write_pool, c->pool);
apr_pool_tag(ctx->deferred_write_pool, "deferred_write");
}
ap_save_brigade(f, &ctx->b, &b, ctx->deferred_write_pool);
return APR_SUCCESS;
}
if (fd) {
apr_hdtr_t hdtr;
apr_size_t bytes_sent;
#if APR_HAS_SENDFILE
apr_int32_t flags = 0;
#endif
memset(&hdtr, '\0', sizeof(hdtr));
if (nvec) {
hdtr.numheaders = nvec;
hdtr.headers = vec;
}
if (nvec_trailers) {
hdtr.numtrailers = nvec_trailers;
hdtr.trailers = vec_trailers;
}
#if APR_HAS_SENDFILE
if (apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) {
if (c->keepalive == AP_CONN_CLOSE && APR_BUCKET_IS_EOS(last_e)) {
/* Prepare the socket to be reused */
flags |= APR_SENDFILE_DISCONNECT_SOCKET;
}
rv = sendfile_it_all(net, /* the network information */
fd, /* the file to send */
&hdtr, /* header and trailer iovecs */
foffset, /* offset in the file to begin
sending from */
flen, /* length of file */
nbytes + flen, /* total length including
headers */
&bytes_sent, /* how many bytes were
sent */
flags); /* apr_sendfile flags */
}
else
#endif
{
rv = emulate_sendfile(net, fd, &hdtr, foffset, flen,
&bytes_sent);
}
if (logio_add_bytes_out && bytes_sent > 0)
logio_add_bytes_out(c, bytes_sent);
fd = NULL;
}
else {
apr_size_t bytes_sent;
rv = writev_it_all(net->client_socket,
vec, nvec,
nbytes, &bytes_sent);
if (logio_add_bytes_out && bytes_sent > 0)
logio_add_bytes_out(c, bytes_sent);
}
apr_brigade_destroy(b);
/* drive cleanups for resources which were set aside
* this may occur before or after termination of the request which
* created the resource
*/
if (ctx->deferred_write_pool) {
if (more && more->p == ctx->deferred_write_pool) {
/* "more" belongs to the deferred_write_pool,
* which is about to be cleared.
*/
if (APR_BRIGADE_EMPTY(more)) {
more = NULL;
}
else {
/* uh oh... change more's lifetime
* to the input brigade's lifetime
*/
apr_bucket_brigade *tmp_more = more;
more = NULL;
ap_save_brigade(f, &more, &tmp_more, input_pool);
}
}
apr_pool_clear(ctx->deferred_write_pool);
}
if (rv != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c,
"core_output_filter: writing data to the network");
if (more)
apr_brigade_destroy(more);
/* No need to check for SUCCESS, we did that above. */
if (!APR_STATUS_IS_EAGAIN(rv)) {
c->aborted = 1;
}
/* The client has aborted, but the request was successful. We
* will report success, and leave it to the access and error
* logs to note that the connection was aborted.
*/
return APR_SUCCESS;
}
b = more;
more = NULL;
} /* end while () */
return APR_SUCCESS;
}
static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{ {

929
server/core_filters.c Normal file
View File

@@ -0,0 +1,929 @@
/* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed 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.
*/
/*
* core_filters.c --- Core input/output network filters.
*/
#include "apr.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_fnmatch.h"
#include "apr_hash.h"
#include "apr_thread_proc.h" /* for RLIMIT stuff */
#include "apr_hooks.h"
#define APR_WANT_IOVEC
#define APR_WANT_STRFUNC
#define APR_WANT_MEMFUNC
#include "apr_want.h"
#define CORE_PRIVATE
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h" /* For index_of_response(). Grump. */
#include "http_request.h"
#include "http_vhost.h"
#include "http_main.h" /* For the default_handler below... */
#include "http_log.h"
#include "util_md5.h"
#include "http_connection.h"
#include "apr_buckets.h"
#include "util_filter.h"
#include "util_ebcdic.h"
#include "mpm.h"
#include "mpm_common.h"
#include "scoreboard.h"
#include "mod_core.h"
#include "mod_proxy.h"
#include "ap_listen.h"
#include "mod_so.h" /* for ap_find_loaded_module_symbol */
#define AP_MIN_SENDFILE_BYTES (256)
typedef struct net_time_filter_ctx {
apr_socket_t *csd;
int first_line;
} net_time_filter_ctx_t;
int net_time_filter(ap_filter_t *f, apr_bucket_brigade *b,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
net_time_filter_ctx_t *ctx = f->ctx;
int keptalive = f->c->keepalive == AP_CONN_KEEPALIVE;
if (!ctx) {
f->ctx = ctx = apr_palloc(f->r->pool, sizeof(*ctx));
ctx->first_line = 1;
ctx->csd = ap_get_module_config(f->c->conn_config, &core_module);
}
if (mode != AP_MODE_INIT && mode != AP_MODE_EATCRLF) {
if (ctx->first_line) {
apr_socket_timeout_set(ctx->csd,
keptalive
? f->c->base_server->keep_alive_timeout
: f->c->base_server->timeout);
ctx->first_line = 0;
}
else {
if (keptalive) {
apr_socket_timeout_set(ctx->csd, f->c->base_server->timeout);
}
}
}
return ap_get_brigade(f->next, b, mode, block, readbytes);
}
/**
* Remove all zero length buckets from the brigade.
*/
#define BRIGADE_NORMALIZE(b) \
do { \
apr_bucket *e = APR_BRIGADE_FIRST(b); \
do { \
if (e->length == 0 && !APR_BUCKET_IS_METADATA(e)) { \
apr_bucket *d; \
d = APR_BUCKET_NEXT(e); \
apr_bucket_delete(e); \
e = d; \
} \
e = APR_BUCKET_NEXT(e); \
} while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \
} while (0)
int core_input_filter(ap_filter_t *f, apr_bucket_brigade *b,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
apr_bucket *e;
apr_status_t rv;
core_net_rec *net = f->ctx;
core_ctx_t *ctx = net->in_ctx;
const char *str;
apr_size_t len;
if (mode == AP_MODE_INIT) {
/*
* this mode is for filters that might need to 'initialize'
* a connection before reading request data from a client.
* NNTP over SSL for example needs to handshake before the
* server sends the welcome message.
* such filters would have changed the mode before this point
* is reached. however, protocol modules such as NNTP should
* not need to know anything about SSL. given the example, if
* SSL is not in the filter chain, AP_MODE_INIT is a noop.
*/
return APR_SUCCESS;
}
if (!ctx)
{
ctx = apr_pcalloc(f->c->pool, sizeof(*ctx));
ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
/* seed the brigade with the client socket. */
e = apr_bucket_socket_create(net->client_socket, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(ctx->b, e);
net->in_ctx = ctx;
}
else if (APR_BRIGADE_EMPTY(ctx->b)) {
return APR_EOF;
}
/* ### This is bad. */
BRIGADE_NORMALIZE(ctx->b);
/* check for empty brigade again *AFTER* BRIGADE_NORMALIZE()
* If we have lost our socket bucket (see above), we are EOF.
*
* Ideally, this should be returning SUCCESS with EOS bucket, but
* some higher-up APIs (spec. read_request_line via ap_rgetline)
* want an error code. */
if (APR_BRIGADE_EMPTY(ctx->b)) {
return APR_EOF;
}
if (mode == AP_MODE_GETLINE) {
/* we are reading a single LF line, e.g. the HTTP headers */
rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN);
/* We should treat EAGAIN here the same as we do for EOF (brigade is
* empty). We do this by returning whatever we have read. This may
* or may not be bogus, but is consistent (for now) with EOF logic.
*/
if (APR_STATUS_IS_EAGAIN(rv)) {
rv = APR_SUCCESS;
}
return rv;
}
/* ### AP_MODE_PEEK is a horrific name for this mode because we also
* eat any CRLFs that we see. That's not the obvious intention of
* this mode. Determine whether anyone actually uses this or not. */
if (mode == AP_MODE_EATCRLF) {
apr_bucket *e;
const char *c;
/* The purpose of this loop is to ignore any CRLF (or LF) at the end
* of a request. Many browsers send extra lines at the end of POST
* requests. We use the PEEK method to determine if there is more
* data on the socket, so that we know if we should delay sending the
* end of one request until we have served the second request in a
* pipelined situation. We don't want to actually delay sending a
* response if the server finds a CRLF (or LF), becuause that doesn't
* mean that there is another request, just a blank line.
*/
while (1) {
if (APR_BRIGADE_EMPTY(ctx->b))
return APR_EOF;
e = APR_BRIGADE_FIRST(ctx->b);
rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ);
if (rv != APR_SUCCESS)
return rv;
c = str;
while (c < str + len) {
if (*c == APR_ASCII_LF)
c++;
else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF)
c += 2;
else
return APR_SUCCESS;
}
/* If we reach here, we were a bucket just full of CRLFs, so
* just toss the bucket. */
/* FIXME: Is this the right thing to do in the core? */
apr_bucket_delete(e);
}
return APR_SUCCESS;
}
/* If mode is EXHAUSTIVE, we want to just read everything until the end
* of the brigade, which in this case means the end of the socket.
* To do this, we attach the brigade that has currently been setaside to
* the brigade that was passed down, and send that brigade back.
*
* NOTE: This is VERY dangerous to use, and should only be done with
* extreme caution. However, the Perchild MPM needs this feature
* if it is ever going to work correctly again. With this, the Perchild
* MPM can easily request the socket and all data that has been read,
* which means that it can pass it to the correct child process.
*/
if (mode == AP_MODE_EXHAUSTIVE) {
apr_bucket *e;
/* Tack on any buckets that were set aside. */
APR_BRIGADE_CONCAT(b, ctx->b);
/* Since we've just added all potential buckets (which will most
* likely simply be the socket bucket) we know this is the end,
* so tack on an EOS too. */
/* We have read until the brigade was empty, so we know that we
* must be EOS. */
e = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(b, e);
return APR_SUCCESS;
}
/* read up to the amount they specified. */
if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) {
apr_bucket *e;
apr_bucket_brigade *newbb;
AP_DEBUG_ASSERT(readbytes > 0);
e = APR_BRIGADE_FIRST(ctx->b);
rv = apr_bucket_read(e, &str, &len, block);
if (APR_STATUS_IS_EAGAIN(rv)) {
return APR_SUCCESS;
}
else if (rv != APR_SUCCESS) {
return rv;
}
else if (block == APR_BLOCK_READ && len == 0) {
/* We wanted to read some bytes in blocking mode. We read
* 0 bytes. Hence, we now assume we are EOS.
*
* When we are in normal mode, return an EOS bucket to the
* caller.
* When we are in speculative mode, leave ctx->b empty, so
* that the next call returns an EOS bucket.
*/
apr_bucket_delete(e);
if (mode == AP_MODE_READBYTES) {
e = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(b, e);
}
return APR_SUCCESS;
}
/* We can only return at most what we read. */
if (len < readbytes) {
readbytes = len;
}
rv = apr_brigade_partition(ctx->b, readbytes, &e);
if (rv != APR_SUCCESS) {
return rv;
}
/* Must do split before CONCAT */
newbb = apr_brigade_split(ctx->b, e);
if (mode == AP_MODE_READBYTES) {
APR_BRIGADE_CONCAT(b, ctx->b);
}
else if (mode == AP_MODE_SPECULATIVE) {
apr_bucket *copy_bucket;
for (e = APR_BRIGADE_FIRST(ctx->b);
e != APR_BRIGADE_SENTINEL(ctx->b);
e = APR_BUCKET_NEXT(e))
{
rv = apr_bucket_copy(e, &copy_bucket);
if (rv != APR_SUCCESS) {
return rv;
}
APR_BRIGADE_INSERT_TAIL(b, copy_bucket);
}
}
/* Take what was originally there and place it back on ctx->b */
APR_BRIGADE_CONCAT(ctx->b, newbb);
}
return APR_SUCCESS;
}
static apr_status_t writev_it_all(apr_socket_t *s,
struct iovec *vec, int nvec,
apr_size_t len, apr_size_t *nbytes)
{
apr_size_t bytes_written = 0;
apr_status_t rv;
apr_size_t n = len;
int i = 0;
*nbytes = 0;
/* XXX handle checking for non-blocking socket */
while (bytes_written != len) {
rv = apr_socket_sendv(s, vec + i, nvec - i, &n);
*nbytes += n;
bytes_written += n;
if (rv != APR_SUCCESS)
return rv;
/* If the write did not complete, adjust the iovecs and issue
* apr_socket_sendv again
*/
if (bytes_written < len) {
/* Skip over the vectors that have already been written */
apr_size_t cnt = vec[i].iov_len;
while (n >= cnt && i + 1 < nvec) {
i++;
cnt += vec[i].iov_len;
}
if (n < cnt) {
/* Handle partial write of vec i */
vec[i].iov_base = (char *) vec[i].iov_base +
(vec[i].iov_len - (cnt - n));
vec[i].iov_len = cnt -n;
}
}
n = len - bytes_written;
}
return APR_SUCCESS;
}
/* sendfile_it_all()
* send the entire file using sendfile()
* handle partial writes
* return only when all bytes have been sent or an error is encountered.
*/
#if APR_HAS_SENDFILE
static apr_status_t sendfile_it_all(core_net_rec *c,
apr_file_t *fd,
apr_hdtr_t *hdtr,
apr_off_t file_offset,
apr_size_t file_bytes_left,
apr_size_t total_bytes_left,
apr_size_t *bytes_sent,
apr_int32_t flags)
{
apr_status_t rv;
#ifdef AP_DEBUG
apr_interval_time_t timeout = 0;
#endif
AP_DEBUG_ASSERT((apr_socket_timeout_get(c->client_socket, &timeout)
== APR_SUCCESS)
&& timeout > 0); /* socket must be in timeout mode */
/* Reset the bytes_sent field */
*bytes_sent = 0;
do {
apr_size_t tmplen = file_bytes_left;
rv = apr_socket_sendfile(c->client_socket, fd, hdtr, &file_offset, &tmplen,
flags);
*bytes_sent += tmplen;
total_bytes_left -= tmplen;
if (!total_bytes_left || rv != APR_SUCCESS) {
return rv; /* normal case & error exit */
}
AP_DEBUG_ASSERT(total_bytes_left > 0 && tmplen > 0);
/* partial write, oooh noooo...
* Skip over any header data which was written
*/
while (tmplen && hdtr->numheaders) {
if (tmplen >= hdtr->headers[0].iov_len) {
tmplen -= hdtr->headers[0].iov_len;
--hdtr->numheaders;
++hdtr->headers;
}
else {
char *iov_base = (char *)hdtr->headers[0].iov_base;
hdtr->headers[0].iov_len -= tmplen;
iov_base += tmplen;
hdtr->headers[0].iov_base = iov_base;
tmplen = 0;
}
}
/* Skip over any file data which was written */
if (tmplen <= file_bytes_left) {
file_offset += tmplen;
file_bytes_left -= tmplen;
continue;
}
tmplen -= file_bytes_left;
file_bytes_left = 0;
file_offset = 0;
/* Skip over any trailer data which was written */
while (tmplen && hdtr->numtrailers) {
if (tmplen >= hdtr->trailers[0].iov_len) {
tmplen -= hdtr->trailers[0].iov_len;
--hdtr->numtrailers;
++hdtr->trailers;
}
else {
char *iov_base = (char *)hdtr->trailers[0].iov_base;
hdtr->trailers[0].iov_len -= tmplen;
iov_base += tmplen;
hdtr->trailers[0].iov_base = iov_base;
tmplen = 0;
}
}
} while (1);
}
#endif
/*
* emulate_sendfile()
* Sends the contents of file fd along with header/trailer bytes, if any,
* to the network. emulate_sendfile will return only when all the bytes have been
* sent (i.e., it handles partial writes) or on a network error condition.
*/
static apr_status_t emulate_sendfile(core_net_rec *c, apr_file_t *fd,
apr_hdtr_t *hdtr, apr_off_t offset,
apr_size_t length, apr_size_t *nbytes)
{
apr_status_t rv = APR_SUCCESS;
apr_size_t togo; /* Remaining number of bytes in the file to send */
apr_size_t sendlen = 0;
apr_size_t bytes_sent;
apr_int32_t i;
apr_off_t o; /* Track the file offset for partial writes */
char buffer[8192];
*nbytes = 0;
/* Send the headers
* writev_it_all handles partial writes.
* XXX: optimization... if headers are less than MIN_WRITE_SIZE, copy
* them into buffer
*/
if (hdtr && hdtr->numheaders > 0 ) {
for (i = 0; i < hdtr->numheaders; i++) {
sendlen += hdtr->headers[i].iov_len;
}
rv = writev_it_all(c->client_socket, hdtr->headers, hdtr->numheaders,
sendlen, &bytes_sent);
*nbytes += bytes_sent; /* track total bytes sent */
}
/* Seek the file to 'offset' */
if (offset >= 0 && rv == APR_SUCCESS) {
rv = apr_file_seek(fd, APR_SET, &offset);
}
/* Send the file, making sure to handle partial writes */
togo = length;
while (rv == APR_SUCCESS && togo) {
sendlen = togo > sizeof(buffer) ? sizeof(buffer) : togo;
o = 0;
rv = apr_file_read(fd, buffer, &sendlen);
while (rv == APR_SUCCESS && sendlen) {
bytes_sent = sendlen;
rv = apr_socket_send(c->client_socket, &buffer[o], &bytes_sent);
*nbytes += bytes_sent;
if (rv == APR_SUCCESS) {
sendlen -= bytes_sent; /* sendlen != bytes_sent ==> partial write */
o += bytes_sent; /* o is where we are in the buffer */
togo -= bytes_sent; /* track how much of the file we've sent */
}
}
}
/* Send the trailers
* XXX: optimization... if it will fit, send this on the last send in the
* loop above
*/
sendlen = 0;
if ( rv == APR_SUCCESS && hdtr && hdtr->numtrailers > 0 ) {
for (i = 0; i < hdtr->numtrailers; i++) {
sendlen += hdtr->trailers[i].iov_len;
}
rv = writev_it_all(c->client_socket, hdtr->trailers, hdtr->numtrailers,
sendlen, &bytes_sent);
*nbytes += bytes_sent;
}
return rv;
}
#define MAX_IOVEC_TO_WRITE 16
/* Optional function coming from mod_logio, used for logging of output
* traffic
*/
extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *logio_add_bytes_out;
apr_status_t core_output_filter(ap_filter_t *f, apr_bucket_brigade *b)
{
apr_status_t rv;
apr_bucket_brigade *more;
conn_rec *c = f->c;
core_net_rec *net = f->ctx;
core_output_filter_ctx_t *ctx = net->out_ctx;
apr_read_type_e eblock = APR_NONBLOCK_READ;
apr_pool_t *input_pool = b->p;
if (ctx == NULL) {
ctx = apr_pcalloc(c->pool, sizeof(*ctx));
net->out_ctx = ctx;
}
/* If we have a saved brigade, concatenate the new brigade to it */
if (ctx->b) {
APR_BRIGADE_CONCAT(ctx->b, b);
b = ctx->b;
ctx->b = NULL;
}
/* Perform multiple passes over the brigade, sending batches of output
to the connection. */
while (b && !APR_BRIGADE_EMPTY(b)) {
apr_size_t nbytes = 0;
apr_bucket *last_e = NULL; /* initialized for debugging */
apr_bucket *e;
/* one group of iovecs per pass over the brigade */
apr_size_t nvec = 0;
apr_size_t nvec_trailers = 0;
struct iovec vec[MAX_IOVEC_TO_WRITE];
struct iovec vec_trailers[MAX_IOVEC_TO_WRITE];
/* one file per pass over the brigade */
apr_file_t *fd = NULL;
apr_size_t flen = 0;
apr_off_t foffset = 0;
/* keep track of buckets that we've concatenated
* to avoid small writes
*/
apr_bucket *last_merged_bucket = NULL;
/* tail of brigade if we need another pass */
more = NULL;
/* Iterate over the brigade: collect iovecs and/or a file */
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
{
/* keep track of the last bucket processed */
last_e = e;
if (APR_BUCKET_IS_EOS(e) || AP_BUCKET_IS_EOC(e)) {
break;
}
else if (APR_BUCKET_IS_FLUSH(e)) {
if (e != APR_BRIGADE_LAST(b)) {
more = apr_brigade_split(b, APR_BUCKET_NEXT(e));
}
break;
}
/* It doesn't make any sense to use sendfile for a file bucket
* that represents 10 bytes.
*/
else if (APR_BUCKET_IS_FILE(e)
&& (e->length >= AP_MIN_SENDFILE_BYTES)) {
apr_bucket_file *a = e->data;
/* We can't handle more than one file bucket at a time
* so we split here and send the file we have already
* found.
*/
if (fd) {
more = apr_brigade_split(b, e);
break;
}
fd = a->fd;
flen = e->length;
foffset = e->start;
}
else {
const char *str;
apr_size_t n;
rv = apr_bucket_read(e, &str, &n, eblock);
if (APR_STATUS_IS_EAGAIN(rv)) {
/* send what we have so far since we shouldn't expect more
* output for a while... next time we read, block
*/
more = apr_brigade_split(b, e);
eblock = APR_BLOCK_READ;
break;
}
eblock = APR_NONBLOCK_READ;
if (n) {
if (!fd) {
if (nvec == MAX_IOVEC_TO_WRITE) {
/* woah! too many. buffer them up, for use later. */
apr_bucket *temp, *next;
apr_bucket_brigade *temp_brig;
if (nbytes >= AP_MIN_BYTES_TO_WRITE) {
/* We have enough data in the iovec
* to justify doing a writev
*/
more = apr_brigade_split(b, e);
break;
}
/* Create a temporary brigade as a means
* of concatenating a bunch of buckets together
*/
if (last_merged_bucket) {
/* If we've concatenated together small
* buckets already in a previous pass,
* the initial buckets in this brigade
* are heap buckets that may have extra
* space left in them (because they
* were created by apr_brigade_write()).
* We can take advantage of this by
* building the new temp brigade out of
* these buckets, so that the content
* in them doesn't have to be copied again.
*/
apr_bucket_brigade *bb;
bb = apr_brigade_split(b,
APR_BUCKET_NEXT(last_merged_bucket));
temp_brig = b;
b = bb;
}
else {
temp_brig = apr_brigade_create(f->c->pool,
f->c->bucket_alloc);
}
temp = APR_BRIGADE_FIRST(b);
while (temp != e) {
apr_bucket *d;
rv = apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
apr_brigade_write(temp_brig, NULL, NULL, str, n);
d = temp;
temp = APR_BUCKET_NEXT(temp);
apr_bucket_delete(d);
}
nvec = 0;
nbytes = 0;
temp = APR_BRIGADE_FIRST(temp_brig);
APR_BUCKET_REMOVE(temp);
APR_BRIGADE_INSERT_HEAD(b, temp);
apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
/* Just in case the temporary brigade has
* multiple buckets, recover the rest of
* them and put them in the brigade that
* we're sending.
*/
for (next = APR_BRIGADE_FIRST(temp_brig);
next != APR_BRIGADE_SENTINEL(temp_brig);
next = APR_BRIGADE_FIRST(temp_brig)) {
APR_BUCKET_REMOVE(next);
APR_BUCKET_INSERT_AFTER(temp, next);
temp = next;
apr_bucket_read(next, &str, &n,
APR_BLOCK_READ);
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
}
apr_brigade_destroy(temp_brig);
last_merged_bucket = temp;
e = temp;
last_e = e;
}
else {
vec[nvec].iov_base = (char*) str;
vec[nvec].iov_len = n;
nvec++;
}
}
else {
/* The bucket is a trailer to a file bucket */
if (nvec_trailers == MAX_IOVEC_TO_WRITE) {
/* woah! too many. stop now. */
more = apr_brigade_split(b, e);
break;
}
vec_trailers[nvec_trailers].iov_base = (char*) str;
vec_trailers[nvec_trailers].iov_len = n;
nvec_trailers++;
}
nbytes += n;
}
}
}
/* Completed iterating over the brigade, now determine if we want
* to buffer the brigade or send the brigade out on the network.
*
* Save if we haven't accumulated enough bytes to send, the connection
* is not about to be closed, and:
*
* 1) we didn't see a file, we don't have more passes over the
* brigade to perform, AND we didn't stop at a FLUSH bucket.
* (IOW, we will save plain old bytes such as HTTP headers)
* or
* 2) we hit the EOS and have a keep-alive connection
* (IOW, this response is a bit more complex, but we save it
* with the hope of concatenating with another response)
*/
if (nbytes + flen < AP_MIN_BYTES_TO_WRITE
&& !AP_BUCKET_IS_EOC(last_e)
&& ((!fd && !more && !APR_BUCKET_IS_FLUSH(last_e))
|| (APR_BUCKET_IS_EOS(last_e)
&& c->keepalive == AP_CONN_KEEPALIVE))) {
/* NEVER save an EOS in here. If we are saving a brigade with
* an EOS bucket, then we are doing keepalive connections, and
* we want to process to second request fully.
*/
if (APR_BUCKET_IS_EOS(last_e)) {
apr_bucket *bucket;
int file_bucket_saved = 0;
apr_bucket_delete(last_e);
for (bucket = APR_BRIGADE_FIRST(b);
bucket != APR_BRIGADE_SENTINEL(b);
bucket = APR_BUCKET_NEXT(bucket)) {
/* Do a read on each bucket to pull in the
* data from pipe and socket buckets, so
* that we don't leave their file descriptors
* open indefinitely. Do the same for file
* buckets, with one exception: allow the
* first file bucket in the brigade to remain
* a file bucket, so that we don't end up
* doing an mmap+memcpy every time a client
* requests a <8KB file over a keepalive
* connection.
*/
if (APR_BUCKET_IS_FILE(bucket) && !file_bucket_saved) {
file_bucket_saved = 1;
}
else {
const char *buf;
apr_size_t len = 0;
rv = apr_bucket_read(bucket, &buf, &len,
APR_BLOCK_READ);
if (rv != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv,
c, "core_output_filter:"
" Error reading from bucket.");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
}
if (!ctx->deferred_write_pool) {
apr_pool_create(&ctx->deferred_write_pool, c->pool);
apr_pool_tag(ctx->deferred_write_pool, "deferred_write");
}
ap_save_brigade(f, &ctx->b, &b, ctx->deferred_write_pool);
return APR_SUCCESS;
}
if (fd) {
apr_hdtr_t hdtr;
apr_size_t bytes_sent;
#if APR_HAS_SENDFILE
apr_int32_t flags = 0;
#endif
memset(&hdtr, '\0', sizeof(hdtr));
if (nvec) {
hdtr.numheaders = nvec;
hdtr.headers = vec;
}
if (nvec_trailers) {
hdtr.numtrailers = nvec_trailers;
hdtr.trailers = vec_trailers;
}
#if APR_HAS_SENDFILE
if (apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) {
if (c->keepalive == AP_CONN_CLOSE && APR_BUCKET_IS_EOS(last_e)) {
/* Prepare the socket to be reused */
flags |= APR_SENDFILE_DISCONNECT_SOCKET;
}
rv = sendfile_it_all(net, /* the network information */
fd, /* the file to send */
&hdtr, /* header and trailer iovecs */
foffset, /* offset in the file to begin
sending from */
flen, /* length of file */
nbytes + flen, /* total length including
headers */
&bytes_sent, /* how many bytes were
sent */
flags); /* apr_sendfile flags */
}
else
#endif
{
rv = emulate_sendfile(net, fd, &hdtr, foffset, flen,
&bytes_sent);
}
if (logio_add_bytes_out && bytes_sent > 0)
logio_add_bytes_out(c, bytes_sent);
fd = NULL;
}
else {
apr_size_t bytes_sent;
rv = writev_it_all(net->client_socket,
vec, nvec,
nbytes, &bytes_sent);
if (logio_add_bytes_out && bytes_sent > 0)
logio_add_bytes_out(c, bytes_sent);
}
apr_brigade_destroy(b);
/* drive cleanups for resources which were set aside
* this may occur before or after termination of the request which
* created the resource
*/
if (ctx->deferred_write_pool) {
if (more && more->p == ctx->deferred_write_pool) {
/* "more" belongs to the deferred_write_pool,
* which is about to be cleared.
*/
if (APR_BRIGADE_EMPTY(more)) {
more = NULL;
}
else {
/* uh oh... change more's lifetime
* to the input brigade's lifetime
*/
apr_bucket_brigade *tmp_more = more;
more = NULL;
ap_save_brigade(f, &more, &tmp_more, input_pool);
}
}
apr_pool_clear(ctx->deferred_write_pool);
}
if (rv != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c,
"core_output_filter: writing data to the network");
if (more)
apr_brigade_destroy(more);
/* No need to check for SUCCESS, we did that above. */
if (!APR_STATUS_IS_EAGAIN(rv)) {
c->aborted = 1;
}
/* The client has aborted, but the request was successful. We
* will report success, and leave it to the access and error
* logs to note that the connection was aborted.
*/
return APR_SUCCESS;
}
b = more;
more = NULL;
} /* end while () */
return APR_SUCCESS;
}