9 #include "stat_cache.h"
17 * this is a expire module for a lighttpd
19 * set 'Expires:' HTTP Headers on demand
24 /* plugin config for all request/connections */
35 plugin_config
**config_storage
;
40 /* init the plugin data */
41 INIT_FUNC(mod_expire_init
) {
44 p
= calloc(1, sizeof(*p
));
46 p
->expire_tstmp
= buffer_init();
48 buffer_string_prepare_copy(p
->expire_tstmp
, 255);
53 /* detroy the plugin data */
54 FREE_FUNC(mod_expire_free
) {
59 if (!p
) return HANDLER_GO_ON
;
61 buffer_free(p
->expire_tstmp
);
63 if (p
->config_storage
) {
65 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
66 plugin_config
*s
= p
->config_storage
[i
];
68 if (NULL
== s
) continue;
70 array_free(s
->expire_url
);
73 free(p
->config_storage
);
81 static int mod_expire_get_offset(server
*srv
, plugin_data
*p
, buffer
*expire
, time_t *offset
) {
91 * '(access|now|modification) [plus] {<num> <type>}*'
93 * e.g. 'access 1 years'
96 if (buffer_string_is_empty(expire
)) {
97 log_error_write(srv
, __FILE__
, __LINE__
, "s",
104 if (0 == strncmp(ts
, "access ", 7)) {
107 } else if (0 == strncmp(ts
, "now ", 4)) {
110 } else if (0 == strncmp(ts
, "modification ", 13)) {
114 /* invalid type-prefix */
115 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
116 "invalid <base>:", ts
);
120 if (0 == strncmp(ts
, "plus ", 5)) {
121 /* skip the optional plus */
125 /* the rest is just <number> (years|months|weeks|days|hours|minutes|seconds) */
130 if (NULL
== (space
= strchr(ts
, ' '))) {
131 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
132 "missing space after <num>:", ts
);
136 num
= strtol(ts
, &err
, 10);
138 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
139 "missing <type> after <num>:", ts
);
145 if (NULL
!= (space
= strchr(ts
, ' '))) {
152 0 == strncmp(ts
, "years", slen
)) {
153 num
*= 60 * 60 * 24 * 30 * 12;
154 } else if (slen
== 6 &&
155 0 == strncmp(ts
, "months", slen
)) {
156 num
*= 60 * 60 * 24 * 30;
157 } else if (slen
== 5 &&
158 0 == strncmp(ts
, "weeks", slen
)) {
159 num
*= 60 * 60 * 24 * 7;
160 } else if (slen
== 4 &&
161 0 == strncmp(ts
, "days", slen
)) {
163 } else if (slen
== 5 &&
164 0 == strncmp(ts
, "hours", slen
)) {
166 } else if (slen
== 7 &&
167 0 == strncmp(ts
, "minutes", slen
)) {
169 } else if (slen
== 7 &&
170 0 == strncmp(ts
, "seconds", slen
)) {
173 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
174 "unknown type:", ts
);
182 if (0 == strcmp(ts
, "years")) {
183 num
*= 60 * 60 * 24 * 30 * 12;
184 } else if (0 == strcmp(ts
, "months")) {
185 num
*= 60 * 60 * 24 * 30;
186 } else if (0 == strcmp(ts
, "weeks")) {
187 num
*= 60 * 60 * 24 * 7;
188 } else if (0 == strcmp(ts
, "days")) {
190 } else if (0 == strcmp(ts
, "hours")) {
192 } else if (0 == strcmp(ts
, "minutes")) {
194 } else if (0 == strcmp(ts
, "seconds")) {
197 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
198 "unknown type:", ts
);
208 if (offset
!= NULL
) *offset
= retts
;
214 /* handle plugin config and check values */
216 SETDEFAULTS_FUNC(mod_expire_set_defaults
) {
217 plugin_data
*p
= p_d
;
220 config_values_t cv
[] = {
221 { "expire.url", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
222 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
225 if (!p
) return HANDLER_ERROR
;
227 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
229 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
230 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
233 s
= calloc(1, sizeof(plugin_config
));
234 s
->expire_url
= array_init();
236 cv
[0].destination
= s
->expire_url
;
238 p
->config_storage
[i
] = s
;
240 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
241 return HANDLER_ERROR
;
244 for (k
= 0; k
< s
->expire_url
->used
; k
++) {
245 data_string
*ds
= (data_string
*)s
->expire_url
->data
[k
];
248 if (-1 == mod_expire_get_offset(srv
, p
, ds
->value
, NULL
)) {
249 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
250 "parsing expire.url failed:", ds
->value
);
251 return HANDLER_ERROR
;
257 return HANDLER_GO_ON
;
262 static int mod_expire_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
264 plugin_config
*s
= p
->config_storage
[0];
268 /* skip the first, the global context */
269 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
270 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
271 s
= p
->config_storage
[i
];
273 /* condition didn't match */
274 if (!config_check_cond(srv
, con
, dc
)) continue;
277 for (j
= 0; j
< dc
->value
->used
; j
++) {
278 data_unset
*du
= dc
->value
->data
[j
];
280 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("expire.url"))) {
290 URIHANDLER_FUNC(mod_expire_path_handler
) {
291 plugin_data
*p
= p_d
;
295 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
297 mod_expire_patch_connection(srv
, con
, p
);
299 s_len
= buffer_string_length(con
->uri
.path
);
301 for (k
= 0; k
< p
->conf
.expire_url
->used
; k
++) {
302 data_string
*ds
= (data_string
*)p
->conf
.expire_url
->data
[k
];
303 int ct_len
= buffer_string_length(ds
->key
);
305 if (ct_len
> s_len
) continue;
306 if (buffer_is_empty(ds
->key
)) continue;
308 if (0 == strncmp(con
->uri
.path
->ptr
, ds
->key
->ptr
, ct_len
)) {
310 stat_cache_entry
*sce
= NULL
;
312 /* if stat fails => sce == NULL, ignore return value */
313 (void) stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
);
315 switch(mod_expire_get_offset(srv
, p
, ds
->value
, &ts
)) {
318 expires
= (ts
+ srv
->cur_ts
);
323 /* can't set modification based expire header if
324 * mtime is not available
326 if (NULL
== sce
) return HANDLER_GO_ON
;
328 expires
= (ts
+ sce
->st
.st_mtime
);
331 /* -1 is handled at parse-time */
332 return HANDLER_ERROR
;
335 /* expires should be at least srv->cur_ts */
336 if (expires
< srv
->cur_ts
) expires
= srv
->cur_ts
;
338 buffer_string_prepare_copy(p
->expire_tstmp
, 255);
339 buffer_append_strftime(p
->expire_tstmp
, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(expires
)));
342 response_header_overwrite(srv
, con
, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p
->expire_tstmp
));
345 buffer_copy_string_len(p
->expire_tstmp
, CONST_STR_LEN("max-age="));
346 buffer_append_int(p
->expire_tstmp
, expires
- srv
->cur_ts
); /* as expires >= srv->cur_ts the difference is >= 0 */
348 response_header_append(srv
, con
, CONST_STR_LEN("Cache-Control"), CONST_BUF_LEN(p
->expire_tstmp
));
350 return HANDLER_GO_ON
;
355 return HANDLER_GO_ON
;
358 /* this function is called at dlopen() time and inits the callbacks */
360 int mod_expire_plugin_init(plugin
*p
);
361 int mod_expire_plugin_init(plugin
*p
) {
362 p
->version
= LIGHTTPD_VERSION_ID
;
363 p
->name
= buffer_init_string("expire");
365 p
->init
= mod_expire_init
;
366 p
->handle_subrequest_start
= mod_expire_path_handler
;
367 p
->set_defaults
= mod_expire_set_defaults
;
368 p
->cleanup
= mod_expire_free
;