/* Regression test for PR 34708, where a file bucket will keep * duplicating itself on being read() when EOF is reached * prematurely. */ static void test_truncfile(abts_case *tc, void *data) { apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p); apr_bucket_brigade *bb = apr_brigade_create(p, ba); apr_file_t *f = make_test_file(tc, "testfile.txt", "hello"); apr_bucket *e; const char *buf; apr_size_t len; apr_brigade_insert_file(bb, f, 0, 5, p); apr_file_trunc(f, 0); e = APR_BRIGADE_FIRST(bb); ABTS_ASSERT(tc, "single bucket in brigade", APR_BUCKET_NEXT(e) == APR_BRIGADE_SENTINEL(bb)); apr_bucket_file_enable_mmap(e, 0); ABTS_ASSERT(tc, "read gave APR_EOF", apr_bucket_read(e, &buf, &len, APR_BLOCK_READ) == APR_EOF); ABTS_ASSERT(tc, "read length 0", len == 0); ABTS_ASSERT(tc, "still a single bucket in brigade", APR_BUCKET_NEXT(e) == APR_BRIGADE_SENTINEL(bb)); apr_file_close(f); apr_brigade_destroy(bb); apr_bucket_alloc_destroy(ba); }
static apr_status_t ap_xsendfile_output_filter(ap_filter_t *f, apr_bucket_brigade *in) { request_rec *r = f->r, *sr = NULL; xsendfile_conf_t *dconf = (xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module), *sconf = (xsendfile_conf_t *)ap_get_module_config(r->server->module_config, &xsendfile_module), *conf = xsendfile_config_merge(r->pool, sconf, dconf); core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module); apr_status_t rv; apr_bucket *e; apr_file_t *fd = NULL; apr_finfo_t finfo; const char *file = NULL; char *translated = NULL; int errcode; #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request); #endif /* should we proceed with this request? * sub-requests suck * furthermore default-handled requests suck, as they actually shouldn't be able to set headers */ if ( r->status != HTTP_OK || r->main || (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */ ) { #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status); #endif ap_remove_output_filter(f); return ap_pass_brigade(f->next, in); } /* alright, look for x-sendfile */ file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER); apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER); /* cgi/fastcgi will put the stuff into err_headers_out */ if (!file || !*file) { file = apr_table_get(r->err_headers_out, AP_XSENDFILE_HEADER); apr_table_unset(r->err_headers_out, AP_XSENDFILE_HEADER); } /* nothing there :p */ if (!file || !*file) { #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found"); #endif ap_remove_output_filter(f); return ap_pass_brigade(f->next, in); } /* drop *everything* might be pretty expensive to generate content first that goes straight to the bitbucket, but actually the scripts that might set this flag won't output too much anyway */ while (!APR_BRIGADE_EMPTY(in)) { e = APR_BRIGADE_FIRST(in); apr_bucket_delete(e); } r->eos_sent = 0; rv = ap_xsendfile_get_filepath(r, conf, file, &translated); if (rv != OK) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: unable to find file: %s", file ); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", translated); #endif /* ry open the file */ if ((rv = apr_file_open( &fd, translated, APR_READ | APR_BINARY #if APR_HAS_SENDFILE | (coreconf->enable_sendfile == ENABLE_SENDFILE_ON ? APR_SENDFILE_ENABLED : 0) #endif , 0, r->pool )) != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: cannot open file: %s", translated ); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } #if APR_HAS_SENDFILE && defined(_DEBUG) if (coreconf->enable_sendfile != ENABLE_SENDFILE_ON) { ap_log_error( APLOG_MARK, APLOG_WARNING, 0, r->server, "xsendfile: sendfile configured, but not active %d", coreconf->enable_sendfile ); } #endif /* stat (for etag/cache/content-length stuff) */ if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: unable to stat file: %s", translated ); apr_file_close(fd); ap_remove_output_filter(f); ap_die(HTTP_FORBIDDEN, r); return HTTP_FORBIDDEN; } /* no inclusion of directories! we're serving files! */ if (finfo.filetype != APR_REG) { ap_log_rerror( APLOG_MARK, APLOG_ERR, APR_EBADPATH, r, "xsendfile: not a file %s", translated ); apr_file_close(fd); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } /* need to cheat here a bit as etag generator will use those ;) and we want local_copy and cache */ r->finfo.inode = finfo.inode; r->finfo.size = finfo.size; /* caching? why not :p */ r->no_cache = r->no_local_copy = 0; /* some script (f?cgi) place stuff in err_headers_out */ if ( conf->ignoreLM == XSENDFILE_ENABLED || ( !apr_table_get(r->headers_out, "last-modified") && !apr_table_get(r->headers_out, "last-modified") ) ) { apr_table_unset(r->err_headers_out, "last-modified"); ap_update_mtime(r, finfo.mtime); ap_set_last_modified(r); } if ( conf->ignoreETag == XSENDFILE_ENABLED || ( !apr_table_get(r->headers_out, "etag") && !apr_table_get(r->err_headers_out, "etag") ) ) { apr_table_unset(r->err_headers_out, "etag"); ap_set_etag(r); } apr_table_unset(r->err_headers_out, "content-length"); ap_set_content_length(r, finfo.size); /* as we dropped all the content this field is not valid anymore! */ apr_table_unset(r->headers_out, "Content-Encoding"); apr_table_unset(r->err_headers_out, "Content-Encoding"); /* cache or something? */ if ((errcode = ap_meets_conditions(r)) != OK) { #ifdef _DEBUG ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: met condition %d for %s", errcode, file ); #endif apr_file_close(fd); r->status = errcode; } else { /* For platforms where the size of the file may be larger than * that which can be stored in a single bucket (where the * length field is an apr_size_t), split it into several * buckets: */ if (sizeof(apr_off_t) > sizeof(apr_size_t) && finfo.size > AP_MAX_SENDFILE) { apr_off_t fsize = finfo.size; e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool, in->bucket_alloc); while (fsize > AP_MAX_SENDFILE) { apr_bucket *ce; apr_bucket_copy(e, &ce); APR_BRIGADE_INSERT_TAIL(in, ce); e->start += AP_MAX_SENDFILE; fsize -= AP_MAX_SENDFILE; } e->length = (apr_size_t)fsize; /* Resize just the last bucket */ } else { e = apr_bucket_file_create(fd, 0, (apr_size_t)finfo.size, r->pool, in->bucket_alloc); } #if APR_HAS_MMAP if (coreconf->enable_mmap == ENABLE_MMAP_ON) { apr_bucket_file_enable_mmap(e, 0); } #if defined(_DEBUG) else { ap_log_error( APLOG_MARK, APLOG_WARNING, 0, r->server, "xsendfile: mmap configured, but not active %d", coreconf->enable_mmap ); } #endif /* _DEBUG */ #endif /* APR_HAS_MMAP */ APR_BRIGADE_INSERT_TAIL(in, e); } e = apr_bucket_eos_create(in->bucket_alloc); APR_BRIGADE_INSERT_TAIL(in, e); /* remove ourselves from the filter chain */ ap_remove_output_filter(f); #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size); #endif /* send the data up the stack */ return ap_pass_brigade(f->next, in); }
apr_status_t h2_beam_receive(h2_bucket_beam *beam, apr_bucket_brigade *bb, apr_read_type_e block, apr_off_t readbytes) { h2_beam_lock bl; apr_bucket *bred, *bgreen, *ng; int transferred = 0; apr_status_t status = APR_SUCCESS; apr_off_t remain = readbytes; /* Called from the green thread to take buckets from the beam */ if (enter_yellow(beam, &bl) == APR_SUCCESS) { transfer: if (beam->aborted) { if (beam->green && !APR_BRIGADE_EMPTY(beam->green)) { apr_brigade_cleanup(beam->green); } status = APR_ECONNABORTED; goto leave; } /* transfer enough buckets from our green brigade, if we have one */ while (beam->green && !APR_BRIGADE_EMPTY(beam->green) && (readbytes <= 0 || remain >= 0)) { bgreen = APR_BRIGADE_FIRST(beam->green); if (readbytes > 0 && bgreen->length > 0 && remain <= 0) { break; } APR_BUCKET_REMOVE(bgreen); APR_BRIGADE_INSERT_TAIL(bb, bgreen); remain -= bgreen->length; ++transferred; } /* transfer from our red brigade, transforming red buckets to * green ones until we have enough */ while (!H2_BLIST_EMPTY(&beam->red) && (readbytes <= 0 || remain >= 0)) { bred = H2_BLIST_FIRST(&beam->red); bgreen = NULL; if (readbytes > 0 && bred->length > 0 && remain <= 0) { break; } if (APR_BUCKET_IS_METADATA(bred)) { if (APR_BUCKET_IS_EOS(bred)) { bgreen = apr_bucket_eos_create(bb->bucket_alloc); beam->close_sent = 1; } else if (APR_BUCKET_IS_FLUSH(bred)) { bgreen = apr_bucket_flush_create(bb->bucket_alloc); } else { /* put red into hold, no green sent out */ } } else if (APR_BUCKET_IS_FILE(bred)) { /* This is set aside into the target brigade pool so that * any read operation messes with that pool and not * the red one. */ apr_bucket_file *f = (apr_bucket_file *)bred->data; apr_file_t *fd = f->fd; int setaside = (f->readpool != bb->p); if (setaside) { status = apr_file_setaside(&fd, fd, bb->p); if (status != APR_SUCCESS) { goto leave; } ++beam->files_beamed; } ng = apr_brigade_insert_file(bb, fd, bred->start, bred->length, bb->p); #if APR_HAS_MMAP /* disable mmap handling as this leads to segfaults when * the underlying file is changed while memory pointer has * been handed out. See also PR 59348 */ apr_bucket_file_enable_mmap(ng, 0); #endif remain -= bred->length; ++transferred; APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->hold, bred); ++transferred; continue; } else { /* create a "green" standin bucket. we took care about the * underlying red bucket and its data when we placed it into * the red brigade. * the beam bucket will notify us on destruction that bred is * no longer needed. */ bgreen = h2_beam_bucket_create(beam, bred, bb->bucket_alloc, beam->buckets_sent++); } /* Place the red bucket into our hold, to be destroyed when no * green bucket references it any more. */ APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->hold, bred); beam->received_bytes += bred->length; if (bgreen) { APR_BRIGADE_INSERT_TAIL(bb, bgreen); remain -= bgreen->length; ++transferred; } } if (readbytes > 0 && remain < 0) { /* too much, put some back */ remain = readbytes; for (bgreen = APR_BRIGADE_FIRST(bb); bgreen != APR_BRIGADE_SENTINEL(bb); bgreen = APR_BUCKET_NEXT(bgreen)) { remain -= bgreen->length; if (remain < 0) { apr_bucket_split(bgreen, bgreen->length+remain); beam->green = apr_brigade_split_ex(bb, APR_BUCKET_NEXT(bgreen), beam->green); break; } } } if (beam->closed && (!beam->green || APR_BRIGADE_EMPTY(beam->green)) && H2_BLIST_EMPTY(&beam->red)) { /* beam is closed and we have nothing more to receive */ if (!beam->close_sent) { apr_bucket *b = apr_bucket_eos_create(bb->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); beam->close_sent = 1; ++transferred; status = APR_SUCCESS; } } if (transferred) { status = APR_SUCCESS; } else if (beam->closed) { status = APR_EOF; } else if (block == APR_BLOCK_READ && bl.mutex && beam->m_cond) { status = wait_cond(beam, bl.mutex); if (status != APR_SUCCESS) { goto leave; } goto transfer; } else { status = APR_EAGAIN; } leave: leave_yellow(beam, &bl); } return status; }