1
2 /*
3 * Copyright (C) Igor Sysoev
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 typedef struct ngx_http_header_val_s ngx_http_header_val_t;
13
14 typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
15 ngx_http_header_val_t *hv, ngx_str_t *value);
16
17
18 typedef struct {
19 ngx_str_t name;
20 ngx_uint_t offset;
21 ngx_http_set_header_pt handler;
22 } ngx_http_set_header_t;
23
24
25 struct ngx_http_header_val_s {
26 ngx_http_complex_value_t value;
27 ngx_uint_t hash;
28 ngx_str_t key;
29 ngx_http_set_header_pt handler;
30 ngx_uint_t offset;
31 };
32
33
34 #define NGX_HTTP_EXPIRES_OFF 0
35 #define NGX_HTTP_EXPIRES_EPOCH 1
36 #define NGX_HTTP_EXPIRES_MAX 2
37 #define NGX_HTTP_EXPIRES_ACCESS 3
38 #define NGX_HTTP_EXPIRES_MODIFIED 4
39 #define NGX_HTTP_EXPIRES_DAILY 5
40
41
42 typedef struct {
43 ngx_uint_t expires;
44 time_t expires_time;
45 ngx_array_t *headers;
46 } ngx_http_headers_conf_t;
47
48
49 static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r,
50 ngx_http_headers_conf_t *conf);
51 static ngx_int_t ngx_http_add_cache_control(ngx_http_request_t *r,
52 ngx_http_header_val_t *hv, ngx_str_t *value);
53 static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r,
54 ngx_http_header_val_t *hv, ngx_str_t *value);
55
56 static void *ngx_http_headers_create_conf(ngx_conf_t *cf);
57 static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
58 void *parent, void *child);
59 static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf);
60 static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd,
61 void *conf);
62 static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd,
63 void *conf);
64
65
66 static ngx_http_set_header_t ngx_http_set_headers[] = {
67
68 { ngx_string("Cache-Control"), 0, ngx_http_add_cache_control },
69
70 { ngx_string("Last-Modified"),
71 offsetof(ngx_http_headers_out_t, last_modified),
72 ngx_http_set_last_modified },
73
74 { ngx_null_string, 0, NULL }
75 };
76
77
78 static ngx_command_t ngx_http_headers_filter_commands[] = {
79
80 { ngx_string("expires"),
81 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
82 |NGX_CONF_TAKE12,
83 ngx_http_headers_expires,
84 NGX_HTTP_LOC_CONF_OFFSET,
85 0,
86 NULL},
87
88 { ngx_string("add_header"),
89 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
90 |NGX_CONF_TAKE2,
91 ngx_http_headers_add,
92 NGX_HTTP_LOC_CONF_OFFSET,
93 0,
94 NULL},
95
96 ngx_null_command
97 };
98
99
100 static ngx_http_module_t ngx_http_headers_filter_module_ctx = {
101 NULL, /* preconfiguration */
102 ngx_http_headers_filter_init, /* postconfiguration */
103
104 NULL, /* create main configuration */
105 NULL, /* init main configuration */
106
107 NULL, /* create server configuration */
108 NULL, /* merge server configuration */
109
110 ngx_http_headers_create_conf, /* create location configuration */
111 ngx_http_headers_merge_conf /* merge location configuration */
112 };
113
114
115 ngx_module_t ngx_http_headers_filter_module = {
116 NGX_MODULE_V1,
117 &ngx_http_headers_filter_module_ctx, /* module context */
118 ngx_http_headers_filter_commands, /* module directives */
119 NGX_HTTP_MODULE, /* module type */
120 NULL, /* init master */
121 NULL, /* init module */
122 NULL, /* init process */
123 NULL, /* init thread */
124 NULL, /* exit thread */
125 NULL, /* exit process */
126 NULL, /* exit master */
127 NGX_MODULE_V1_PADDING
128 };
129
130
131 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
132
133
134 static ngx_int_t
135 ngx_http_headers_filter(ngx_http_request_t *r)
136 {
137 ngx_str_t value;
138 ngx_uint_t i;
139 ngx_http_header_val_t *h;
140 ngx_http_headers_conf_t *conf;
141
142 conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
143
144 if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL)
145 || r != r->main
146 || (r->headers_out.status != NGX_HTTP_OK
147 && r->headers_out.status != NGX_HTTP_NO_CONTENT
148 && r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY
149 && r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY
150 && r->headers_out.status != NGX_HTTP_NOT_MODIFIED))
151 {
152 return ngx_http_next_header_filter(r);
153 }
154
155 if (conf->expires != NGX_HTTP_EXPIRES_OFF) {
156 if (ngx_http_set_expires(r, conf) != NGX_OK) {
157 return NGX_ERROR;
158 }
159 }
160
161 if (conf->headers) {
162 h = conf->headers->elts;
163 for (i = 0; i < conf->headers->nelts; i++) {
164
165 if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
166 return NGX_ERROR;
167 }
168
169 if (h[i].handler(r, &h[i], &value) != NGX_OK) {
170 return NGX_ERROR;
171 }
172 }
173 }
174
175 return ngx_http_next_header_filter(r);
176 }
177
178
179 static ngx_int_t
180 ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf)
181 {
182 size_t len;
183 time_t now, expires_time, max_age;
184 ngx_uint_t i;
185 ngx_table_elt_t *expires, *cc, **ccp;
186
187 expires = r->headers_out.expires;
188
189 if (expires == NULL) {
190
191 expires = ngx_list_push(&r->headers_out.headers);
192 if (expires == NULL) {
193 return NGX_ERROR;
194 }
195
196 r->headers_out.expires = expires;
197
198 expires->hash = 1;
199 expires->key.len = sizeof("Expires") - 1;
200 expires->key.data = (u_char *) "Expires";
201 }
202
203 len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
204 expires->value.len = len - 1;
205
206 ccp = r->headers_out.cache_control.elts;
207
208 if (ccp == NULL) {
209
210 if (ngx_array_init(&r->headers_out.cache_control, r->pool,
211 1, sizeof(ngx_table_elt_t *))
212 != NGX_OK)
213 {
214 return NGX_ERROR;
215 }
216
217 ccp = ngx_array_push(&r->headers_out.cache_control);
218 if (ccp == NULL) {
219 return NGX_ERROR;
220 }
221
222 cc = ngx_list_push(&r->headers_out.headers);
223 if (cc == NULL) {
224 return NGX_ERROR;
225 }
226
227 cc->hash = 1;
228 cc->key.len = sizeof("Cache-Control") - 1;
229 cc->key.data = (u_char *) "Cache-Control";
230
231 *ccp = cc;
232
233 } else {
234 for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
235 ccp[i]->hash = 0;
236 }
237
238 cc = ccp[0];
239 }
240
241 if (conf->expires == NGX_HTTP_EXPIRES_EPOCH) {
242 expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
243
244 cc->value.len = sizeof("no-cache") - 1;
245 cc->value.data = (u_char *) "no-cache";
246
247 return NGX_OK;
248 }
249
250 if (conf->expires == NGX_HTTP_EXPIRES_MAX) {
251 expires->value.data = (u_char *) "Thu, 31 Dec 2037 23:55:55 GMT";
252
253 /* 10 years */
254 cc->value.len = sizeof("max-age=315360000") - 1;
255 cc->value.data = (u_char *) "max-age=315360000";
256
257 return NGX_OK;
258 }
259
260 expires->value.data = ngx_pnalloc(r->pool, len);
261 if (expires->value.data == NULL) {
262 return NGX_ERROR;
263 }
264
265 if (conf->expires_time == 0) {
266 ngx_memcpy(expires->value.data, ngx_cached_http_time.data,
267 ngx_cached_http_time.len + 1);
268
269 cc->value.len = sizeof("max-age=0") - 1;
270 cc->value.data = (u_char *) "max-age=0";
271
272 return NGX_OK;
273 }
274
275 now = ngx_time();
276
277 if (conf->expires == NGX_HTTP_EXPIRES_ACCESS
278 || r->headers_out.last_modified_time == -1)
279 {
280 expires_time = now + conf->expires_time;
281 max_age = conf->expires_time;
282
283 } else if (conf->expires == NGX_HTTP_EXPIRES_DAILY) {
284 expires_time = ngx_next_time(conf->expires_time);
285 max_age = expires_time - now;
286
287 } else {
288 expires_time = r->headers_out.last_modified_time + conf->expires_time;
289 max_age = expires_time - now;
290 }
291
292 ngx_http_time(expires->value.data, expires_time);
293
294 if (conf->expires_time < 0) {
295 cc->value.len = sizeof("no-cache") - 1;
296 cc->value.data = (u_char *) "no-cache";
297
298 return NGX_OK;
299 }
300
301 cc->value.data = ngx_pnalloc(r->pool,
302 sizeof("max-age=") + NGX_TIME_T_LEN + 1);
303 if (cc->value.data == NULL) {
304 return NGX_ERROR;
305 }
306
307 cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age)
308 - cc->value.data;
309
310 return NGX_OK;
311 }
312
313
314 static ngx_int_t
315 ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
316 ngx_str_t *value)
317 {
318 ngx_table_elt_t *h;
319
320 if (value->len) {
321 h = ngx_list_push(&r->headers_out.headers);
322 if (h == NULL) {
323 return NGX_ERROR;
324 }
325
326 h->hash = hv->hash;
327 h->key = hv->key;
328 h->value = *value;
329 }
330
331 return NGX_OK;
332 }
333
334
335 static ngx_int_t
336 ngx_http_add_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv,
337 ngx_str_t *value)
338 {
339 ngx_table_elt_t *cc, **ccp;
340
341 ccp = r->headers_out.cache_control.elts;
342
343 if (ccp == NULL) {
344
345 if (ngx_array_init(&r->headers_out.cache_control, r->pool,
346 1, sizeof(ngx_table_elt_t *))
347 != NGX_OK)
348 {
349 return NGX_ERROR;
350 }
351 }
352
353 ccp = ngx_array_push(&r->headers_out.cache_control);
354 if (ccp == NULL) {
355 return NGX_ERROR;
356 }
357
358 cc = ngx_list_push(&r->headers_out.headers);
359 if (cc == NULL) {
360 return NGX_ERROR;
361 }
362
363 cc->hash = 1;
364 cc->key.len = sizeof("Cache-Control") - 1;
365 cc->key.data = (u_char *) "Cache-Control";
366 cc->value = *value;
367
368 *ccp = cc;
369
370 return NGX_OK;
371 }
372
373
374 static ngx_int_t
375 ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv,
376 ngx_str_t *value)
377 {
378 ngx_table_elt_t *h, **old;
379
380 if (hv->offset) {
381 old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
382
383 } else {
384 old = NULL;
385 }
386
387 r->headers_out.last_modified_time = -1;
388
389 if (old == NULL || *old == NULL) {
390
391 if (value->len == 0) {
392 return NGX_OK;
393 }
394
395 h = ngx_list_push(&r->headers_out.headers);
396 if (h == NULL) {
397 return NGX_ERROR;
398 }
399
400 } else {
401 h = *old;
402
403 if (value->len == 0) {
404 h->hash = 0;
405 return NGX_OK;
406 }
407 }
408
409 h->hash = hv->hash;
410 h->key = hv->key;
411 h->value = *value;
412
413 return NGX_OK;
414 }
415
416
417 static void *
418 ngx_http_headers_create_conf(ngx_conf_t *cf)
419 {
420 ngx_http_headers_conf_t *conf;
421
422 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t));
423 if (conf == NULL) {
424 return NULL;
425 }
426
427 /*
428 * set by ngx_pcalloc():
429 *
430 * conf->headers = NULL;
431 * conf->expires_time = 0;
432 */
433
434 conf->expires = NGX_CONF_UNSET_UINT;
435
436 return conf;
437 }
438
439
440 static char *
441 ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child)
442 {
443 ngx_http_headers_conf_t *prev = parent;
444 ngx_http_headers_conf_t *conf = child;
445
446 if (conf->expires == NGX_CONF_UNSET_UINT) {
447 conf->expires = prev->expires;
448 conf->expires_time = prev->expires_time;
449
450 if (conf->expires == NGX_CONF_UNSET_UINT) {
451 conf->expires = NGX_HTTP_EXPIRES_OFF;
452 }
453 }
454
455 if (conf->headers == NULL) {
456 conf->headers = prev->headers;
457 }
458
459 return NGX_CONF_OK;
460 }
461
462
463 static ngx_int_t
464 ngx_http_headers_filter_init(ngx_conf_t *cf)
465 {
466 ngx_http_next_header_filter = ngx_http_top_header_filter;
467 ngx_http_top_header_filter = ngx_http_headers_filter;
468
469 return NGX_OK;
470 }
471
472
473 static char *
474 ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
475 {
476 ngx_http_headers_conf_t *hcf = conf;
477
478 ngx_uint_t minus, n;
479 ngx_str_t *value;
480
481 if (hcf->expires != NGX_CONF_UNSET_UINT) {
482 return "is duplicate";
483 }
484
485 value = cf->args->elts;
486
487 if (cf->args->nelts == 2) {
488
489 if (ngx_strcmp(value[1].data, "epoch") == 0) {
490 hcf->expires = NGX_HTTP_EXPIRES_EPOCH;
491 return NGX_CONF_OK;
492 }
493
494 if (ngx_strcmp(value[1].data, "max") == 0) {
495 hcf->expires = NGX_HTTP_EXPIRES_MAX;
496 return NGX_CONF_OK;
497 }
498
499 if (ngx_strcmp(value[1].data, "off") == 0) {
500 hcf->expires = NGX_HTTP_EXPIRES_OFF;
501 return NGX_CONF_OK;
502 }
503
504 hcf->expires = NGX_HTTP_EXPIRES_ACCESS;
505
506 n = 1;
507
508 } else { /* cf->args->nelts == 3 */
509
510 if (ngx_strcmp(value[1].data, "modified") != 0) {
511 return "invalid value";
512 }
513
514 hcf->expires = NGX_HTTP_EXPIRES_MODIFIED;
515
516 n = 2;
517 }
518
519 if (value[n].data[0] == '@') {
520 value[n].data++;
521 value[n].len--;
522 minus = 0;
523
524 if (hcf->expires == NGX_HTTP_EXPIRES_MODIFIED) {
525 return "daily time can not be used with \"modified\" parameter";
526 }
527
528 hcf->expires = NGX_HTTP_EXPIRES_DAILY;
529
530 } else if (value[n].data[0] == '+') {
531 value[n].data++;
532 value[n].len--;
533 minus = 0;
534
535 } else if (value[n].data[0] == '-') {
536 value[n].data++;
537 value[n].len--;
538 minus = 1;
539
540 } else {
541 minus = 0;
542 }
543
544 hcf->expires_time = ngx_parse_time(&value[n], 1);
545
546 if (hcf->expires_time == NGX_ERROR) {
547 return "invalid value";
548 }
549
550 if (hcf->expires == NGX_HTTP_EXPIRES_DAILY
551 && hcf->expires_time > 24 * 60 * 60)
552 {
553 return "daily time value must be less than 24 hours";
554 }
555
556 if (hcf->expires_time == NGX_PARSE_LARGE_TIME) {
557 return "value must be less than 68 years";
558 }
559
560 if (minus) {
561 hcf->expires_time = - hcf->expires_time;
562 }
563
564 return NGX_CONF_OK;
565 }
566
567
568 static char *
569 ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
570 {
571 ngx_http_headers_conf_t *hcf = conf;
572
573 ngx_str_t *value;
574 ngx_uint_t i;
575 ngx_http_header_val_t *hv;
576 ngx_http_set_header_t *set;
577 ngx_http_compile_complex_value_t ccv;
578
579 value = cf->args->elts;
580
581 if (hcf->headers == NULL) {
582 hcf->headers = ngx_array_create(cf->pool, 1,
583 sizeof(ngx_http_header_val_t));
584 if (hcf->headers == NULL) {
585 return NGX_CONF_ERROR;
586 }
587 }
588
589 hv = ngx_array_push(hcf->headers);
590 if (hv == NULL) {
591 return NGX_CONF_ERROR;
592 }
593
594 hv->hash = 1;
595 hv->key = value[1];
596 hv->handler = ngx_http_add_header;
597 hv->offset = 0;
598
599 set = ngx_http_set_headers;
600 for (i = 0; set[i].name.len; i++) {
601 if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
602 continue;
603 }
604
605 hv->offset = set[i].offset;
606 hv->handler = set[i].handler;
607
608 break;
609 }
610
611 if (value[2].len == 0) {
612 ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
613 return NGX_CONF_OK;
614 }
615
616 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
617
618 ccv.cf = cf;
619 ccv.value = &value[2];
620 ccv.complex_value = &hv->value;
621
622 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
623 return NGX_CONF_ERROR;
624 }
625
626 return NGX_CONF_OK;
627 }
628
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.