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:
@@ -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)
|
||||||
|
@@ -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 \
|
||||||
|
@@ -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:
|
||||||
|
|
||||||
|
20
libhttpd.dsp
20
libhttpd.dsp
@@ -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
|
||||||
|
388
modules/http/byterange_filter.c
Normal file
388
modules/http/byterange_filter.c
Normal 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
167
modules/http/chunk_filter.c
Normal 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;
|
||||||
|
}
|
@@ -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
220
modules/http/http_etag.c
Normal 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
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
@@ -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
|
||||||
|
|
||||||
|
876
server/core.c
876
server/core.c
@@ -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, ©_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
929
server/core_filters.c
Normal 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, ©_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;
|
||||||
|
}
|
Reference in New Issue
Block a user