1
/* Copyright 2006-2010 by Nils Maier
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
17
* mod_xsendfile.c: Process X-SENDFILE header cgi/scripts may set
18
* Written by Nils Maier, testnutzer123 at google mail, March 2006
20
* Whenever an X-SENDFILE header occures in the response headers drop
21
* the body and send the replacement file idenfitied by this header instead.
23
* Method inspired by lighttpd <http://lighttpd.net/>
24
* Code inspired by mod_headers, mod_rewrite and such
27
* apxs2 -cia mod_xsendfile.c
31
* v0.12 (peer-review still required)
38
#include "apr_strings.h"
39
#include "apr_buckets.h"
40
#include "apr_file_io.h"
43
#define APR_WANT_IOVEC
44
#define APR_WANT_STRFUNC
49
#include "http_config.h"
52
#include "http_request.h"
53
#include "http_core.h" /* needed for per-directory core-config */
54
#include "util_filter.h"
55
#include "http_protocol.h" /* ap_hook_insert_error_filter */
57
#define AP_XSENDFILE_HEADER "X-SENDFILE"
59
module AP_MODULE_DECLARE_DATA xsendfile_module;
63
XSENDFILE_ENABLED = 1<<0,
64
XSENDFILE_DISABLED = 1<<1
65
} xsendfile_conf_active_t;
67
typedef struct xsendfile_conf_t {
68
xsendfile_conf_active_t enabled;
69
xsendfile_conf_active_t ignoreETag;
70
xsendfile_conf_active_t ignoreLM;
71
apr_array_header_t *paths;
74
static xsendfile_conf_t *xsendfile_config_create(apr_pool_t *p) {
75
xsendfile_conf_t *conf;
77
conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
83
conf->paths = apr_array_make(p, 1, sizeof(char*));
88
static void *xsendfile_config_server_create(apr_pool_t *p, server_rec *s) {
89
return (void*)xsendfile_config_create(p);
92
#define XSENDFILE_CFLAG(x) conf->x = overrides->x != XSENDFILE_UNSET ? overrides->x : base->x
94
static void *xsendfile_config_merge(apr_pool_t *p, void *basev, void *overridesv) {
95
xsendfile_conf_t *base = (xsendfile_conf_t *)basev;
96
xsendfile_conf_t *overrides = (xsendfile_conf_t *)overridesv;
97
xsendfile_conf_t *conf;
99
conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
101
XSENDFILE_CFLAG(enabled);
102
XSENDFILE_CFLAG(ignoreETag);
103
XSENDFILE_CFLAG(ignoreLM);
105
conf->paths = apr_array_append(p, overrides->paths, base->paths);
110
static void *xsendfile_config_perdir_create(apr_pool_t *p, char *path) {
111
return (void*)xsendfile_config_create(p);
113
#undef XSENDFILE_CFLAG
115
static const char *xsendfile_cmd_flag(cmd_parms *cmd, void *perdir_confv, int flag) {
116
xsendfile_conf_t *conf = (xsendfile_conf_t *)perdir_confv;
117
if (cmd->path == NULL) {
118
conf = (xsendfile_conf_t*)ap_get_module_config(
119
cmd->server->module_config,
124
return "Cannot get configuration object";
126
if (!strcasecmp(cmd->cmd->name, "xsendfile")) {
127
conf->enabled = flag ? XSENDFILE_ENABLED : XSENDFILE_DISABLED;
129
else if (!strcasecmp(cmd->cmd->name, "xsendfileignoreetag")) {
130
conf->ignoreETag = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
132
else if (!strcasecmp(cmd->cmd->name, "xsendfileignorelastmodified")) {
133
conf->ignoreLM = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
136
return apr_psprintf(cmd->pool, "Not a valid command in this context: %s %s", cmd->cmd->name, flag ? "On": "Off");
142
static const char *xsendfile_cmd_path(cmd_parms *cmd, void *pdc, const char *arg) {
143
xsendfile_conf_t *conf = (xsendfile_conf_t*)ap_get_module_config(
144
cmd->server->module_config,
147
char **newpath = (char**)apr_array_push(conf->paths);
148
*newpath = apr_pstrdup(cmd->pool, arg);
154
little helper function to get the original request path
155
code borrowed from request.c and util_script.c
157
static const char *ap_xsendfile_get_orginal_path(request_rec *rec) {
159
*rv = rec->the_request,
165
/* skip method && spaces */
166
while (*rv && !apr_isspace(*rv)) {
169
while (apr_isspace(*rv)) {
172
/* first space is the request end */
174
while (*last && !apr_isspace(*last)) {
182
/* alright, lets see if the request_uri changed! */
183
if (strncmp(rv, rec->uri, uri_len) == 0) {
184
rv = apr_pstrdup(rec->pool, rec->filename);
185
dir = rec->finfo.filetype == APR_DIR;
188
/* need to lookup the url again as it changed */
189
request_rec *sr = ap_sub_req_lookup_uri(
190
apr_pstrmemdup(rec->pool, rv, uri_len),
197
rv = apr_pstrdup(rec->pool, sr->filename);
198
dir = rec->finfo.filetype == APR_DIR;
199
ap_destroy_sub_req(sr);
202
/* now we need to truncate so we only have the directory */
203
if (!dir && (last = ap_strrchr(rv, '/')) != NULL) {
204
*((char*)last + 1) = '\0';
210
little helper function to build the file path if available
212
static apr_status_t ap_xsendfile_get_filepath(request_rec *r, xsendfile_conf_t *conf, const char *file, /* out */ char **path) {
214
const char *root = ap_xsendfile_get_orginal_path(r);
217
apr_array_header_t *patharr;
223
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: path is %s", root);
226
/* merge the array */
228
patharr = apr_array_make(r->pool, conf->paths->nelts + 1, sizeof(char*));
229
*(const char**)(apr_array_push(patharr)) = root;
230
apr_array_cat(patharr, conf->paths);
232
patharr = conf->paths;
234
if (patharr->nelts == 0) {
237
paths = (const char**)patharr->elts;
239
for (i = 0; i < patharr->nelts; ++i) {
240
if ((rv = apr_filepath_merge(
244
APR_FILEPATH_TRUENAME | APR_FILEPATH_NOTABOVEROOT,
256
static apr_status_t ap_xsendfile_output_filter(ap_filter_t *f, apr_bucket_brigade *in) {
257
request_rec *r = f->r, *sr = NULL;
260
*dconf = (xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module),
261
*sconf = (xsendfile_conf_t *)ap_get_module_config(r->server->module_config, &xsendfile_module),
262
*conf = xsendfile_config_merge(r->pool, sconf, dconf);
264
core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
269
apr_file_t *fd = NULL;
272
const char *file = NULL;
273
char *translated = NULL;
278
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request);
281
should we proceed with this request?
284
* furthermore default-handled requests suck, as they actually shouldn't be able to set headers
289
|| (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */
292
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status);
294
ap_remove_output_filter(f);
295
return ap_pass_brigade(f->next, in);
299
alright, look for x-sendfile
301
file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER);
302
apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER);
304
/* cgi/fastcgi will put the stuff into err_headers_out */
305
if (!file || !*file) {
306
file = apr_table_get(r->err_headers_out, AP_XSENDFILE_HEADER);
307
apr_table_unset(r->err_headers_out, AP_XSENDFILE_HEADER);
310
/* nothing there :p */
311
if (!file || !*file) {
313
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found");
315
ap_remove_output_filter(f);
316
return ap_pass_brigade(f->next, in);
321
might be pretty expensive to generate content first that goes straight to the bitbucket,
322
but actually the scripts that might set this flag won't output too much anyway
324
while (!APR_BRIGADE_EMPTY(in)) {
325
e = APR_BRIGADE_FIRST(in);
326
apr_bucket_delete(e);
330
/* as we dropped all the content this field is not valid anymore! */
331
apr_table_unset(r->headers_out, "Content-Length");
332
apr_table_unset(r->err_headers_out, "Content-Length");
333
apr_table_unset(r->headers_out, "Content-Encoding");
334
apr_table_unset(r->err_headers_out, "Content-Encoding");
336
rv = ap_xsendfile_get_filepath(r, conf, file, &translated);
343
"xsendfile: unable to find file: %s",
346
ap_remove_output_filter(f);
347
ap_die(HTTP_NOT_FOUND, r);
348
return HTTP_NOT_FOUND;
352
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", translated);
358
if ((rv = apr_file_open(
361
APR_READ | APR_BINARY
363
| (coreconf->enable_sendfile != ENABLE_SENDFILE_OFF ? APR_SENDFILE_ENABLED : 0)
374
"xsendfile: cannot open file: %s",
377
ap_remove_output_filter(f);
378
ap_die(HTTP_NOT_FOUND, r);
379
return HTTP_NOT_FOUND;
381
#if APR_HAS_SENDFILE && defined(_DEBUG)
382
if (coreconf->enable_sendfile == ENABLE_SENDFILE_OFF) {
388
"xsendfile: sendfile configured, but not active %d",
389
coreconf->enable_sendfile
393
/* stat (for etag/cache/content-length stuff) */
394
if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS) {
400
"xsendfile: unable to stat file: %s",
404
ap_remove_output_filter(f);
405
ap_die(HTTP_FORBIDDEN, r);
406
return HTTP_FORBIDDEN;
408
/* no inclusion of directories! we're serving files! */
409
if (finfo.filetype != APR_REG) {
415
"xsendfile: not a file %s",
419
ap_remove_output_filter(f);
420
ap_die(HTTP_NOT_FOUND, r);
421
return HTTP_NOT_FOUND;
425
need to cheat here a bit
426
as etag generator will use those ;)
427
and we want local_copy and cache
429
r->finfo.inode = finfo.inode;
430
r->finfo.size = finfo.size;
435
r->no_cache = r->no_local_copy = 0;
437
/* some script (f?cgi) place stuff in err_headers_out */
439
conf->ignoreLM == XSENDFILE_ENABLED
441
!apr_table_get(r->headers_out, "last-modified")
442
&& !apr_table_get(r->headers_out, "last-modified")
445
apr_table_unset(r->err_headers_out, "last-modified");
446
ap_update_mtime(r, finfo.mtime);
447
ap_set_last_modified(r);
450
conf->ignoreETag == XSENDFILE_ENABLED
452
!apr_table_get(r->headers_out, "etag")
453
&& !apr_table_get(r->err_headers_out, "etag")
456
apr_table_unset(r->err_headers_out, "etag");
460
ap_set_content_length(r, finfo.size);
462
/* cache or something? */
463
if ((errcode = ap_meets_conditions(r)) != OK) {
470
"xsendfile: met condition %d for %s",
479
/* For platforms where the size of the file may be larger than
480
* that which can be stored in a single bucket (where the
481
* length field is an apr_size_t), split it into several
483
if (sizeof(apr_off_t) > sizeof(apr_size_t)
484
&& finfo.size > AP_MAX_SENDFILE) {
485
apr_off_t fsize = finfo.size;
486
e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool,
488
while (fsize > AP_MAX_SENDFILE) {
490
apr_bucket_copy(e, &ce);
491
APR_BRIGADE_INSERT_TAIL(in, ce);
492
e->start += AP_MAX_SENDFILE;
493
fsize -= AP_MAX_SENDFILE;
495
e->length = (apr_size_t)fsize; /* Resize just the last bucket */
498
e = apr_bucket_file_create(fd, 0, (apr_size_t)finfo.size,
499
r->pool, in->bucket_alloc);
504
if (coreconf->enable_mmap == ENABLE_MMAP_ON) {
505
apr_bucket_file_enable_mmap(e, 0);
514
"xsendfile: mmap configured, but not active %d",
515
coreconf->enable_mmap
519
#endif /* APR_HAS_MMAP */
520
APR_BRIGADE_INSERT_TAIL(in, e);
523
e = apr_bucket_eos_create(in->bucket_alloc);
524
APR_BRIGADE_INSERT_TAIL(in, e);
526
/* remove ourselves from the filter chain */
527
ap_remove_output_filter(f);
530
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size);
533
/* send the data up the stack */
534
return ap_pass_brigade(f->next, in);
537
static void ap_xsendfile_insert_output_filter(request_rec *r) {
538
xsendfile_conf_active_t enabled = ((xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module))->enabled;
539
if (XSENDFILE_UNSET == enabled) {
540
enabled = ((xsendfile_conf_t*)ap_get_module_config(r->server->module_config, &xsendfile_module))->enabled;
543
if (XSENDFILE_ENABLED != enabled) {
547
ap_add_output_filter(
554
static const command_rec xsendfile_command_table[] = {
560
"On|Off - Enable/disable(default) processing"
563
"XSendFileIgnoreEtag",
567
"On|Off - Ignore script provided Etag headers (default: Off)"
570
"XSendFileIgnoreLastModified",
574
"On|Off - Ignore script provided Last-Modified headers (default: Off)"
580
RSRC_CONF|ACCESS_CONF,
581
"Allow to serve files from that Path. Must be absolute"
585
static void xsendfile_register_hooks(apr_pool_t *p) {
586
ap_register_output_filter(
588
ap_xsendfile_output_filter,
593
ap_hook_insert_filter(
594
ap_xsendfile_insert_output_filter,
600
module AP_MODULE_DECLARE_DATA xsendfile_module = {
601
STANDARD20_MODULE_STUFF,
602
xsendfile_config_perdir_create,
603
xsendfile_config_merge,
604
xsendfile_config_server_create,
605
xsendfile_config_merge,
606
xsendfile_command_table,
607
xsendfile_register_hooks