~xibo-maintainers/xibo/tempel

602.3.3 by Dan Garner
Work on PHP7 support and dev docker file
1
/* Copyright 2006-2010 by Nils Maier
2
 *
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
6
 *
7
 *     http://www.apache.org/licenses/LICENSE-2.0
8
 *
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.
14
 */
15
16
/*
17
 * mod_xsendfile.c: Process X-SENDFILE header cgi/scripts may set
18
 *     Written by Nils Maier, testnutzer123 at google mail, March 2006
19
 *
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.
22
 *
23
 * Method inspired by lighttpd <http://lighttpd.net/>
24
 * Code inspired by mod_headers, mod_rewrite and such
25
 *
26
 * Installation:
27
 *     apxs2 -cia mod_xsendfile.c
28
 */
29
30
/*
31
 * v0.12 (peer-review still required)
32
 *
33
 * $Id$
34
 */
35
36
#include "apr.h"
37
#include "apr_lib.h"
38
#include "apr_strings.h"
39
#include "apr_buckets.h"
40
#include "apr_file_io.h"
41
42
#include "apr_hash.h"
43
#define APR_WANT_IOVEC
44
#define APR_WANT_STRFUNC
45
#include "apr_want.h"
46
47
#include "httpd.h"
48
#include "http_log.h"
49
#include "http_config.h"
50
#include "http_log.h"
51
#define CORE_PRIVATE
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 */
56
57
#define AP_XSENDFILE_HEADER "X-SENDFILE"
58
59
module AP_MODULE_DECLARE_DATA xsendfile_module;
60
61
typedef enum {
62
  XSENDFILE_UNSET = 0,
63
  XSENDFILE_ENABLED = 1<<0,
64
  XSENDFILE_DISABLED = 1<<1
65
} xsendfile_conf_active_t;
66
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;
72
} xsendfile_conf_t;
73
74
static xsendfile_conf_t *xsendfile_config_create(apr_pool_t *p) {
75
  xsendfile_conf_t *conf;
76
77
  conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
78
  conf->ignoreETag =
79
    conf->ignoreLM =
80
    conf->enabled =
81
    XSENDFILE_UNSET;
82
83
  conf->paths = apr_array_make(p, 1, sizeof(char*));
84
85
  return conf;
86
}
87
88
static void *xsendfile_config_server_create(apr_pool_t *p, server_rec *s) {
89
  return (void*)xsendfile_config_create(p);
90
}
91
92
#define XSENDFILE_CFLAG(x) conf->x = overrides->x != XSENDFILE_UNSET ? overrides->x : base->x
93
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;
98
99
  conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
100
101
  XSENDFILE_CFLAG(enabled);
102
  XSENDFILE_CFLAG(ignoreETag);
103
  XSENDFILE_CFLAG(ignoreLM);
104
105
  conf->paths = apr_array_append(p, overrides->paths, base->paths);
106
107
  return (void*)conf;
108
}
109
110
static void *xsendfile_config_perdir_create(apr_pool_t *p, char *path) {
111
  return (void*)xsendfile_config_create(p);
112
}
113
#undef XSENDFILE_CFLAG
114
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,
120
      &xsendfile_module
121
      );
122
  }
123
  if (!conf) {
124
    return "Cannot get configuration object";
125
  }
126
  if (!strcasecmp(cmd->cmd->name, "xsendfile")) {
127
    conf->enabled = flag ? XSENDFILE_ENABLED : XSENDFILE_DISABLED;
128
  }
129
  else if (!strcasecmp(cmd->cmd->name, "xsendfileignoreetag")) {
130
    conf->ignoreETag = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
131
  }
132
  else if (!strcasecmp(cmd->cmd->name, "xsendfileignorelastmodified")) {
133
    conf->ignoreLM = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
134
  }
135
  else {
136
    return apr_psprintf(cmd->pool, "Not a valid command in this context: %s %s", cmd->cmd->name, flag ? "On": "Off");
137
  }
138
139
  return NULL;
140
}
141
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,
145
    &xsendfile_module
146
    );
147
  char **newpath = (char**)apr_array_push(conf->paths);
148
  *newpath = apr_pstrdup(cmd->pool, arg);
149
150
  return NULL;
151
}
152
153
/*
154
  little helper function to get the original request path
155
  code borrowed from request.c and util_script.c
156
*/
157
static const char *ap_xsendfile_get_orginal_path(request_rec *rec) {
158
  const char
159
    *rv = rec->the_request,
160
    *last;
161
162
  int dir = 0;
163
  size_t uri_len;
164
165
  /* skip method && spaces */
166
  while (*rv && !apr_isspace(*rv)) {
167
    ++rv;
168
  }
169
  while (apr_isspace(*rv)) {
170
    ++rv;
171
  }
172
  /* first space is the request end */
173
  last = rv;
174
  while (*last && !apr_isspace(*last)) {
175
    ++last;
176
  }
177
  uri_len = last - rv;
178
  if (!uri_len) {
179
    return NULL;
180
  }
181
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;
186
  }
187
  else {
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),
191
      rec,
192
      NULL
193
      );
194
    if (!sr) {
195
      return NULL;
196
    }
197
    rv = apr_pstrdup(rec->pool, sr->filename);
198
    dir = rec->finfo.filetype == APR_DIR;
199
    ap_destroy_sub_req(sr);
200
  }
201
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';
205
  }
206
  return rv;
207
}
208
209
/*
210
  little helper function to build the file path if available
211
*/
212
static apr_status_t ap_xsendfile_get_filepath(request_rec *r, xsendfile_conf_t *conf, const char *file, /* out */ char **path) {
213
214
  const char *root = ap_xsendfile_get_orginal_path(r);
215
  apr_status_t rv;
216
217
  apr_array_header_t *patharr;
218
  const char **paths;
219
  int i;
220
221
222
#ifdef _DEBUG
223
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: path is %s", root);
224
#endif
225
226
  /* merge the array */
227
  if (root) {
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);
231
  } else {
232
    patharr = conf->paths;
233
  }
234
  if (patharr->nelts == 0) {
235
    return APR_EBADPATH;
236
  }
237
  paths = (const char**)patharr->elts;
238
239
  for (i = 0; i < patharr->nelts; ++i) {
240
    if ((rv = apr_filepath_merge(
241
      path,
242
      paths[i],
243
      file,
244
      APR_FILEPATH_TRUENAME | APR_FILEPATH_NOTABOVEROOT,
245
      r->pool
246
    )) == OK) {
247
      break;
248
    }
249
  }
250
  if (rv != OK) {
251
    *path = NULL;
252
  }
253
  return rv;
254
}
255
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;
258
259
  xsendfile_conf_t
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);
263
264
  core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
265
266
  apr_status_t rv;
267
  apr_bucket *e;
268
269
  apr_file_t *fd = NULL;
270
  apr_finfo_t finfo;
271
272
  const char *file = NULL;
273
  char *translated = NULL;
274
275
  int errcode;
276
277
#ifdef _DEBUG
278
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request);
279
#endif
280
  /*
281
    should we proceed with this request?
282
283
    * sub-requests suck
284
    * furthermore default-handled requests suck, as they actually shouldn't be able to set headers
285
  */
286
  if (
287
    r->status != HTTP_OK
288
    || r->main
289
    || (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */
290
  ) {
291
#ifdef _DEBUG
292
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status);
293
#endif
294
    ap_remove_output_filter(f);
295
    return ap_pass_brigade(f->next, in);
296
  }
297
298
  /*
299
    alright, look for x-sendfile
300
  */
301
  file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER);
302
  apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER);
303
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);
308
  }
309
310
  /* nothing there :p */
311
  if (!file || !*file) {
312
#ifdef _DEBUG
313
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found");
314
#endif
315
    ap_remove_output_filter(f);
316
    return ap_pass_brigade(f->next, in);
317
  }
318
319
  /*
320
    drop *everything*
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
323
  */
324
  while (!APR_BRIGADE_EMPTY(in)) {
325
    e = APR_BRIGADE_FIRST(in);
326
    apr_bucket_delete(e);
327
  }
328
  r->eos_sent = 0;
329
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");
335
336
  rv = ap_xsendfile_get_filepath(r, conf, file, &translated);
337
  if (rv != OK) {
338
    ap_log_rerror(
339
      APLOG_MARK,
340
      APLOG_ERR,
341
      rv,
342
      r,
343
      "xsendfile: unable to find file: %s",
344
      file
345
      );
346
    ap_remove_output_filter(f);
347
    ap_die(HTTP_NOT_FOUND, r);
348
    return HTTP_NOT_FOUND;
349
  }
350
351
#ifdef _DEBUG
352
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", translated);
353
#endif
354
355
  /*
356
    try open the file
357
  */
358
  if ((rv = apr_file_open(
359
    &fd,
360
    translated,
361
    APR_READ | APR_BINARY
362
#if APR_HAS_SENDFILE
363
    | (coreconf->enable_sendfile != ENABLE_SENDFILE_OFF ? APR_SENDFILE_ENABLED : 0)
364
#endif
365
    ,
366
    0,
367
    r->pool
368
  )) != APR_SUCCESS) {
369
    ap_log_rerror(
370
      APLOG_MARK,
371
      APLOG_ERR,
372
      rv,
373
      r,
374
      "xsendfile: cannot open file: %s",
375
      translated
376
      );
377
    ap_remove_output_filter(f);
378
    ap_die(HTTP_NOT_FOUND, r);
379
    return HTTP_NOT_FOUND;
380
  }
381
#if APR_HAS_SENDFILE && defined(_DEBUG)
382
  if (coreconf->enable_sendfile == ENABLE_SENDFILE_OFF) {
383
    ap_log_error(
384
      APLOG_MARK,
385
      APLOG_WARNING,
386
      0,
387
      r->server,
388
      "xsendfile: sendfile configured, but not active %d",
389
      coreconf->enable_sendfile
390
      );
391
    }
392
#endif
393
  /* stat (for etag/cache/content-length stuff) */
394
  if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS) {
395
    ap_log_rerror(
396
      APLOG_MARK,
397
      APLOG_ERR,
398
      rv,
399
      r,
400
      "xsendfile: unable to stat file: %s",
401
      translated
402
      );
403
    apr_file_close(fd);
404
    ap_remove_output_filter(f);
405
    ap_die(HTTP_FORBIDDEN, r);
406
    return HTTP_FORBIDDEN;
407
  }
408
  /* no inclusion of directories! we're serving files! */
409
  if (finfo.filetype != APR_REG) {
410
    ap_log_rerror(
411
      APLOG_MARK,
412
      APLOG_ERR,
413
      APR_EBADPATH,
414
      r,
415
      "xsendfile: not a file %s",
416
      translated
417
      );
418
    apr_file_close(fd);
419
    ap_remove_output_filter(f);
420
    ap_die(HTTP_NOT_FOUND, r);
421
    return HTTP_NOT_FOUND;
422
  }
423
424
  /*
425
    need to cheat here a bit
426
    as etag generator will use those ;)
427
    and we want local_copy and cache
428
  */
429
  r->finfo.inode = finfo.inode;
430
  r->finfo.size = finfo.size;
431
432
  /*
433
    caching? why not :p
434
  */
435
  r->no_cache = r->no_local_copy = 0;
436
437
  /* some script (f?cgi) place stuff in err_headers_out */
438
  if (
439
    conf->ignoreLM == XSENDFILE_ENABLED
440
    || (
441
      !apr_table_get(r->headers_out, "last-modified")
442
      && !apr_table_get(r->headers_out, "last-modified")
443
    )
444
  ) {
445
    apr_table_unset(r->err_headers_out, "last-modified");
446
    ap_update_mtime(r, finfo.mtime);
447
    ap_set_last_modified(r);
448
  }
449
  if (
450
    conf->ignoreETag == XSENDFILE_ENABLED
451
    || (
452
      !apr_table_get(r->headers_out, "etag")
453
      && !apr_table_get(r->err_headers_out, "etag")
454
    )
455
  ) {
456
    apr_table_unset(r->err_headers_out, "etag");
457
    ap_set_etag(r);
458
  }
459
460
  ap_set_content_length(r, finfo.size);
461
462
  /* cache or something? */
463
  if ((errcode = ap_meets_conditions(r)) != OK) {
464
#ifdef _DEBUG
465
    ap_log_error(
466
      APLOG_MARK,
467
      APLOG_DEBUG,
468
      0,
469
      r->server,
470
      "xsendfile: met condition %d for %s",
471
      errcode,
472
      file
473
      );
474
#endif
475
    apr_file_close(fd);
476
    r->status = errcode;
477
  }
478
  else {
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
482
     * buckets: */
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,
487
                                   in->bucket_alloc);
488
        while (fsize > AP_MAX_SENDFILE) {
489
            apr_bucket *ce;
490
            apr_bucket_copy(e, &ce);
491
            APR_BRIGADE_INSERT_TAIL(in, ce);
492
            e->start += AP_MAX_SENDFILE;
493
            fsize -= AP_MAX_SENDFILE;
494
        }
495
        e->length = (apr_size_t)fsize; /* Resize just the last bucket */
496
    }
497
    else {
498
        e = apr_bucket_file_create(fd, 0, (apr_size_t)finfo.size,
499
                                   r->pool, in->bucket_alloc);
500
    }
501
502
503
#if APR_HAS_MMAP
504
    if (coreconf->enable_mmap == ENABLE_MMAP_ON) {
505
      apr_bucket_file_enable_mmap(e, 0);
506
    }
507
#if defined(_DEBUG)
508
    else {
509
      ap_log_error(
510
        APLOG_MARK,
511
        APLOG_WARNING,
512
        0,
513
        r->server,
514
        "xsendfile: mmap configured, but not active %d",
515
        coreconf->enable_mmap
516
        );
517
      }
518
#endif /* _DEBUG */
519
#endif /* APR_HAS_MMAP */
520
    APR_BRIGADE_INSERT_TAIL(in, e);
521
  }
522
523
  e = apr_bucket_eos_create(in->bucket_alloc);
524
  APR_BRIGADE_INSERT_TAIL(in, e);
525
526
  /* remove ourselves from the filter chain */
527
  ap_remove_output_filter(f);
528
529
#ifdef _DEBUG
530
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size);
531
#endif
532
533
  /* send the data up the stack */
534
  return ap_pass_brigade(f->next, in);
535
}
536
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;
541
  }
542
543
  if (XSENDFILE_ENABLED != enabled) {
544
    return;
545
  }
546
547
  ap_add_output_filter(
548
    "XSENDFILE",
549
    NULL,
550
    r,
551
    r->connection
552
	  );
553
}
554
static const command_rec xsendfile_command_table[] = {
555
  AP_INIT_FLAG(
556
    "XSendFile",
557
    xsendfile_cmd_flag,
558
    NULL,
559
    OR_FILEINFO,
560
    "On|Off - Enable/disable(default) processing"
561
    ),
562
  AP_INIT_FLAG(
563
    "XSendFileIgnoreEtag",
564
    xsendfile_cmd_flag,
565
    NULL,
566
    OR_FILEINFO,
567
    "On|Off - Ignore script provided Etag headers (default: Off)"
568
    ),
569
  AP_INIT_FLAG(
570
    "XSendFileIgnoreLastModified",
571
    xsendfile_cmd_flag,
572
    NULL,
573
    OR_FILEINFO,
574
    "On|Off - Ignore script provided Last-Modified headers (default: Off)"
575
    ),
576
  AP_INIT_TAKE1(
577
    "XSendFilePath",
578
    xsendfile_cmd_path,
579
    NULL,
580
    RSRC_CONF|ACCESS_CONF,
581
    "Allow to serve files from that Path. Must be absolute"
582
    ),
583
  { NULL }
584
};
585
static void xsendfile_register_hooks(apr_pool_t *p) {
586
  ap_register_output_filter(
587
    "XSENDFILE",
588
    ap_xsendfile_output_filter,
589
    NULL,
590
    AP_FTYPE_CONTENT_SET
591
    );
592
593
  ap_hook_insert_filter(
594
    ap_xsendfile_insert_output_filter,
595
    NULL,
596
    NULL,
597
    APR_HOOK_LAST + 1
598
    );
599
}
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
608
};