| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file core_filters.c |
| * @brief 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) |
| |
| /** |
| * 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; \ |
| } \ |
| else { \ |
| e = APR_BUCKET_NEXT(e); \ |
| } \ |
| } while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \ |
| } while (0) |
| |
| |
| /** |
| * Split the contents of a brigade after bucket 'e' to an existing brigade |
| * |
| * XXXX: Should this function be added to APR-Util? |
| */ |
| static void brigade_move(apr_bucket_brigade *b, apr_bucket_brigade *a, |
| apr_bucket *e) |
| { |
| apr_bucket *f; |
| |
| if (e != APR_BRIGADE_SENTINEL(b)) { |
| f = APR_RING_LAST(&b->list); |
| APR_RING_UNSPLICE(e, f, link); |
| APR_RING_SPLICE_HEAD(&a->list, e, f, apr_bucket, link); |
| } |
| |
| APR_BRIGADE_CHECK_CONSISTENCY(a); |
| APR_BRIGADE_CHECK_CONSISTENCY(b); |
| } |
| |
| int ap_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); |
| ctx->tmpbb = apr_brigade_create(ctx->b->p, ctx->b->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; |
| |
| 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 move before CONCAT */ |
| brigade_move(ctx->b, ctx->tmpbb, 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, ctx->tmpbb); |
| } |
| 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; |
| } |
| |
| #ifndef APR_MAX_IOVEC_SIZE |
| #define MAX_IOVEC_TO_WRITE 16 |
| #else |
| #if APR_MAX_IOVEC_SIZE > 16 |
| #define MAX_IOVEC_TO_WRITE 16 |
| #else |
| #define MAX_IOVEC_TO_WRITE APR_MAX_IOVEC_SIZE |
| #endif |
| #endif |
| |
| /* 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 ap_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; |
| |
| /* Fail quickly if the connection has already been aborted. */ |
| if (c->aborted) { |
| apr_brigade_cleanup(b); |
| return APR_ECONNABORTED; |
| } |
| |
| 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 |
| */ |
| temp_brig = apr_brigade_create(f->c->pool, |
| f->c->bucket_alloc); |
| 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_BRIGADE_PREPEND(b, temp_brig); |
| brigade_move(temp_brig, b, APR_BUCKET_NEXT(last_merged_bucket)); |
| } |
| |
| 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_cleanup(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_cleanup(more); |
| |
| /* No need to check for SUCCESS, we did that above. */ |
| if (!APR_STATUS_IS_EAGAIN(rv)) { |
| c->aborted = 1; |
| return APR_ECONNABORTED; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| b = more; |
| more = NULL; |
| } /* end while () */ |
| |
| return APR_SUCCESS; |
| } |