1#include "base.h" 2#include "log.h" 3#include "buffer.h" 4#include "response.h" 5 6#include "plugin.h" 7#include "stat_cache.h" 8 9#include <ctype.h> 10#include <stdlib.h> 11#include <string.h> 12#include <time.h> 13 14/** 15 * this is a expire module for a lighttpd 16 * 17 * set 'Expires:' HTTP Headers on demand 18 */ 19 20 21 22/* plugin config for all request/connections */ 23 24typedef struct { 25 array *expire_url; 26} plugin_config; 27 28typedef struct { 29 PLUGIN_DATA; 30 31 buffer *expire_tstmp; 32 33 plugin_config **config_storage; 34 35 plugin_config conf; 36} plugin_data; 37 38/* init the plugin data */ 39INIT_FUNC(mod_expire_init) { 40 plugin_data *p; 41 42 p = calloc(1, sizeof(*p)); 43 44 p->expire_tstmp = buffer_init(); 45 46 buffer_string_prepare_copy(p->expire_tstmp, 255); 47 48 return p; 49} 50 51/* detroy the plugin data */ 52FREE_FUNC(mod_expire_free) { 53 plugin_data *p = p_d; 54 55 UNUSED(srv); 56 57 if (!p) return HANDLER_GO_ON; 58 59 buffer_free(p->expire_tstmp); 60 61 if (p->config_storage) { 62 size_t i; 63 for (i = 0; i < srv->config_context->used; i++) { 64 plugin_config *s = p->config_storage[i]; 65 66 if (NULL == s) continue; 67 68 array_free(s->expire_url); 69 free(s); 70 } 71 free(p->config_storage); 72 } 73 74 free(p); 75 76 return HANDLER_GO_ON; 77} 78 79static int mod_expire_get_offset(server *srv, plugin_data *p, buffer *expire, time_t *offset) { 80 char *ts; 81 int type = -1; 82 time_t retts = 0; 83 84 UNUSED(p); 85 86 /* 87 * parse 88 * 89 * '(access|now|modification) [plus] {<num> <type>}*' 90 * 91 * e.g. 'access 1 years' 92 */ 93 94 if (buffer_string_is_empty(expire)) { 95 log_error_write(srv, __FILE__, __LINE__, "s", 96 "empty:"); 97 return -1; 98 } 99 100 ts = expire->ptr; 101 102 if (0 == strncmp(ts, "access ", 7)) { 103 type = 0; 104 ts += 7; 105 } else if (0 == strncmp(ts, "now ", 4)) { 106 type = 0; 107 ts += 4; 108 } else if (0 == strncmp(ts, "modification ", 13)) { 109 type = 1; 110 ts += 13; 111 } else { 112 /* invalid type-prefix */ 113 log_error_write(srv, __FILE__, __LINE__, "ss", 114 "invalid <base>:", ts); 115 return -1; 116 } 117 118 if (0 == strncmp(ts, "plus ", 5)) { 119 /* skip the optional plus */ 120 ts += 5; 121 } 122 123 /* the rest is just <number> (years|months|weeks|days|hours|minutes|seconds) */ 124 while (1) { 125 char *space, *err; 126 int num; 127 128 if (NULL == (space = strchr(ts, ' '))) { 129 log_error_write(srv, __FILE__, __LINE__, "ss", 130 "missing space after <num>:", ts); 131 return -1; 132 } 133 134 num = strtol(ts, &err, 10); 135 if (*err != ' ') { 136 log_error_write(srv, __FILE__, __LINE__, "ss", 137 "missing <type> after <num>:", ts); 138 return -1; 139 } 140 141 ts = space + 1; 142 143 if (NULL != (space = strchr(ts, ' '))) { 144 int slen; 145 /* */ 146 147 slen = space - ts; 148 149 if (slen == 5 && 150 0 == strncmp(ts, "years", slen)) { 151 num *= 60 * 60 * 24 * 30 * 12; 152 } else if (slen == 6 && 153 0 == strncmp(ts, "months", slen)) { 154 num *= 60 * 60 * 24 * 30; 155 } else if (slen == 5 && 156 0 == strncmp(ts, "weeks", slen)) { 157 num *= 60 * 60 * 24 * 7; 158 } else if (slen == 4 && 159 0 == strncmp(ts, "days", slen)) { 160 num *= 60 * 60 * 24; 161 } else if (slen == 5 && 162 0 == strncmp(ts, "hours", slen)) { 163 num *= 60 * 60; 164 } else if (slen == 7 && 165 0 == strncmp(ts, "minutes", slen)) { 166 num *= 60; 167 } else if (slen == 7 && 168 0 == strncmp(ts, "seconds", slen)) { 169 num *= 1; 170 } else { 171 log_error_write(srv, __FILE__, __LINE__, "ss", 172 "unknown type:", ts); 173 return -1; 174 } 175 176 retts += num; 177 178 ts = space + 1; 179 } else { 180 if (0 == strcmp(ts, "years")) { 181 num *= 60 * 60 * 24 * 30 * 12; 182 } else if (0 == strcmp(ts, "months")) { 183 num *= 60 * 60 * 24 * 30; 184 } else if (0 == strcmp(ts, "weeks")) { 185 num *= 60 * 60 * 24 * 7; 186 } else if (0 == strcmp(ts, "days")) { 187 num *= 60 * 60 * 24; 188 } else if (0 == strcmp(ts, "hours")) { 189 num *= 60 * 60; 190 } else if (0 == strcmp(ts, "minutes")) { 191 num *= 60; 192 } else if (0 == strcmp(ts, "seconds")) { 193 num *= 1; 194 } else { 195 log_error_write(srv, __FILE__, __LINE__, "ss", 196 "unknown type:", ts); 197 return -1; 198 } 199 200 retts += num; 201 202 break; 203 } 204 } 205 206 if (offset != NULL) *offset = retts; 207 208 return type; 209} 210 211 212/* handle plugin config and check values */ 213 214SETDEFAULTS_FUNC(mod_expire_set_defaults) { 215 plugin_data *p = p_d; 216 size_t i = 0, k; 217 218 config_values_t cv[] = { 219 { "expire.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 220 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 221 }; 222 223 if (!p) return HANDLER_ERROR; 224 225 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); 226 227 for (i = 0; i < srv->config_context->used; i++) { 228 data_config const* config = (data_config const*)srv->config_context->data[i]; 229 plugin_config *s; 230 231 s = calloc(1, sizeof(plugin_config)); 232 s->expire_url = array_init(); 233 234 cv[0].destination = s->expire_url; 235 236 p->config_storage[i] = s; 237 238 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 239 return HANDLER_ERROR; 240 } 241 242 for (k = 0; k < s->expire_url->used; k++) { 243 data_string *ds = (data_string *)s->expire_url->data[k]; 244 245 /* parse lines */ 246 if (-1 == mod_expire_get_offset(srv, p, ds->value, NULL)) { 247 log_error_write(srv, __FILE__, __LINE__, "sb", 248 "parsing expire.url failed:", ds->value); 249 return HANDLER_ERROR; 250 } 251 } 252 } 253 254 255 return HANDLER_GO_ON; 256} 257 258#define PATCH(x) \ 259 p->conf.x = s->x; 260static int mod_expire_patch_connection(server *srv, connection *con, plugin_data *p) { 261 size_t i, j; 262 plugin_config *s = p->config_storage[0]; 263 264 PATCH(expire_url); 265 266 /* skip the first, the global context */ 267 for (i = 1; i < srv->config_context->used; i++) { 268 data_config *dc = (data_config *)srv->config_context->data[i]; 269 s = p->config_storage[i]; 270 271 /* condition didn't match */ 272 if (!config_check_cond(srv, con, dc)) continue; 273 274 /* merge config */ 275 for (j = 0; j < dc->value->used; j++) { 276 data_unset *du = dc->value->data[j]; 277 278 if (buffer_is_equal_string(du->key, CONST_STR_LEN("expire.url"))) { 279 PATCH(expire_url); 280 } 281 } 282 } 283 284 return 0; 285} 286#undef PATCH 287 288URIHANDLER_FUNC(mod_expire_path_handler) { 289 plugin_data *p = p_d; 290 int s_len; 291 size_t k; 292 293 if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON; 294 295 mod_expire_patch_connection(srv, con, p); 296 297 s_len = buffer_string_length(con->uri.path); 298 299 for (k = 0; k < p->conf.expire_url->used; k++) { 300 data_string *ds = (data_string *)p->conf.expire_url->data[k]; 301 int ct_len = buffer_string_length(ds->key); 302 303 if (ct_len > s_len) continue; 304 if (buffer_is_empty(ds->key)) continue; 305 306 if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, ct_len)) { 307 time_t ts, expires; 308 stat_cache_entry *sce = NULL; 309 310 /* if stat fails => sce == NULL, ignore return value */ 311 (void) stat_cache_get_entry(srv, con, con->physical.path, &sce); 312 313 switch(mod_expire_get_offset(srv, p, ds->value, &ts)) { 314 case 0: 315 /* access */ 316 expires = (ts + srv->cur_ts); 317 break; 318 case 1: 319 /* modification */ 320 321 /* can't set modification based expire header if 322 * mtime is not available 323 */ 324 if (NULL == sce) return HANDLER_GO_ON; 325 326 expires = (ts + sce->st.st_mtime); 327 break; 328 default: 329 /* -1 is handled at parse-time */ 330 return HANDLER_ERROR; 331 } 332 333 /* expires should be at least srv->cur_ts */ 334 if (expires < srv->cur_ts) expires = srv->cur_ts; 335 336 buffer_string_prepare_copy(p->expire_tstmp, 255); 337 buffer_append_strftime(p->expire_tstmp, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(expires))); 338 339 /* HTTP/1.0 */ 340 response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p->expire_tstmp)); 341 342 /* HTTP/1.1 */ 343 buffer_copy_string_len(p->expire_tstmp, CONST_STR_LEN("max-age=")); 344 buffer_append_int(p->expire_tstmp, expires - srv->cur_ts); /* as expires >= srv->cur_ts the difference is >= 0 */ 345 346 response_header_append(srv, con, CONST_STR_LEN("Cache-Control"), CONST_BUF_LEN(p->expire_tstmp)); 347 348 return HANDLER_GO_ON; 349 } 350 } 351 352 /* not found */ 353 return HANDLER_GO_ON; 354} 355 356/* this function is called at dlopen() time and inits the callbacks */ 357 358int mod_expire_plugin_init(plugin *p); 359int mod_expire_plugin_init(plugin *p) { 360 p->version = LIGHTTPD_VERSION_ID; 361 p->name = buffer_init_string("expire"); 362 363 p->init = mod_expire_init; 364 p->handle_subrequest_start = mod_expire_path_handler; 365 p->set_defaults = mod_expire_set_defaults; 366 p->cleanup = mod_expire_free; 367 368 p->data = NULL; 369 370 return 0; 371} 372