/* 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; 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; }
.htaccess Tutorial
Find information you are looking for on the AskApache Home Page.