check pointer before usage in new etag compare
[lighttpd.git] / src / mod_staticfile.c
blob22929bb1847554705e6fc2a9d4adcc8a495c84e8
1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
5 #include "plugin.h"
7 #include "stat_cache.h"
8 #include "etag.h"
9 #include "http_chunk.h"
10 #include "response.h"
12 #include <ctype.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
17 /**
18 * this is a staticfile for a lighttpd plugin
24 /* plugin config for all request/connections */
26 typedef struct {
27 array *exclude_ext;
28 unsigned short etags_used;
29 unsigned short disable_pathinfo;
30 } plugin_config;
32 typedef struct {
33 PLUGIN_DATA;
35 buffer *range_buf;
37 plugin_config **config_storage;
39 plugin_config conf;
40 } plugin_data;
42 /* init the plugin data */
43 INIT_FUNC(mod_staticfile_init) {
44 plugin_data *p;
46 p = calloc(1, sizeof(*p));
48 p->range_buf = buffer_init();
50 return p;
53 /* detroy the plugin data */
54 FREE_FUNC(mod_staticfile_free) {
55 plugin_data *p = p_d;
57 UNUSED(srv);
59 if (!p) return HANDLER_GO_ON;
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];
66 if (NULL == s) continue;
68 array_free(s->exclude_ext);
70 free(s);
72 free(p->config_storage);
74 buffer_free(p->range_buf);
76 free(p);
78 return HANDLER_GO_ON;
81 /* handle plugin config and check values */
83 SETDEFAULTS_FUNC(mod_staticfile_set_defaults) {
84 plugin_data *p = p_d;
85 size_t i = 0;
87 config_values_t cv[] = {
88 { "static-file.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
89 { "static-file.etags", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
90 { "static-file.disable-pathinfo", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
91 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
94 if (!p) return HANDLER_ERROR;
96 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
98 for (i = 0; i < srv->config_context->used; i++) {
99 plugin_config *s;
101 s = calloc(1, sizeof(plugin_config));
102 s->exclude_ext = array_init();
103 s->etags_used = 1;
104 s->disable_pathinfo = 0;
106 cv[0].destination = s->exclude_ext;
107 cv[1].destination = &(s->etags_used);
108 cv[2].destination = &(s->disable_pathinfo);
110 p->config_storage[i] = s;
112 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
113 return HANDLER_ERROR;
117 return HANDLER_GO_ON;
120 #define PATCH(x) \
121 p->conf.x = s->x;
122 static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) {
123 size_t i, j;
124 plugin_config *s = p->config_storage[0];
126 PATCH(exclude_ext);
127 PATCH(etags_used);
128 PATCH(disable_pathinfo);
130 /* skip the first, the global context */
131 for (i = 1; i < srv->config_context->used; i++) {
132 data_config *dc = (data_config *)srv->config_context->data[i];
133 s = p->config_storage[i];
135 /* condition didn't match */
136 if (!config_check_cond(srv, con, dc)) continue;
138 /* merge config */
139 for (j = 0; j < dc->value->used; j++) {
140 data_unset *du = dc->value->data[j];
142 if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.exclude-extensions"))) {
143 PATCH(exclude_ext);
144 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.etags"))) {
145 PATCH(etags_used);
146 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.disable-pathinfo"))) {
147 PATCH(disable_pathinfo);
152 return 0;
154 #undef PATCH
156 static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
157 int multipart = 0;
158 int error;
159 off_t start, end;
160 const char *s, *minus;
161 char *boundary = "fkj49sn38dcn3";
162 data_string *ds;
163 stat_cache_entry *sce = NULL;
164 buffer *content_type = NULL;
166 if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
167 SEGFAULT();
170 start = 0;
171 end = sce->st.st_size - 1;
173 con->response.content_length = 0;
175 if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
176 content_type = ds->value;
179 for (s = con->request.http_range, error = 0;
180 !error && *s && NULL != (minus = strchr(s, '-')); ) {
181 char *err;
182 off_t la, le;
184 if (s == minus) {
185 /* -<stop> */
187 le = strtoll(s, &err, 10);
189 if (le == 0) {
190 /* RFC 2616 - 14.35.1 */
192 con->http_status = 416;
193 error = 1;
194 } else if (*err == '\0') {
195 /* end */
196 s = err;
198 end = sce->st.st_size - 1;
199 start = sce->st.st_size + le;
200 } else if (*err == ',') {
201 multipart = 1;
202 s = err + 1;
204 end = sce->st.st_size - 1;
205 start = sce->st.st_size + le;
206 } else {
207 error = 1;
210 } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
211 /* <start>- */
213 la = strtoll(s, &err, 10);
215 if (err == minus) {
216 /* ok */
218 if (*(err + 1) == '\0') {
219 s = err + 1;
221 end = sce->st.st_size - 1;
222 start = la;
224 } else if (*(err + 1) == ',') {
225 multipart = 1;
226 s = err + 2;
228 end = sce->st.st_size - 1;
229 start = la;
230 } else {
231 error = 1;
233 } else {
234 /* error */
235 error = 1;
237 } else {
238 /* <start>-<stop> */
240 la = strtoll(s, &err, 10);
242 if (err == minus) {
243 le = strtoll(minus+1, &err, 10);
245 /* RFC 2616 - 14.35.1 */
246 if (la > le) {
247 error = 1;
250 if (*err == '\0') {
251 /* ok, end*/
252 s = err;
254 end = le;
255 start = la;
256 } else if (*err == ',') {
257 multipart = 1;
258 s = err + 1;
260 end = le;
261 start = la;
262 } else {
263 /* error */
265 error = 1;
267 } else {
268 /* error */
270 error = 1;
274 if (!error) {
275 if (start < 0) start = 0;
277 /* RFC 2616 - 14.35.1 */
278 if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
280 if (start > sce->st.st_size - 1) {
281 error = 1;
283 con->http_status = 416;
287 if (!error) {
288 if (multipart) {
289 /* write boundary-header */
290 buffer *b = buffer_init();
292 buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
293 buffer_append_string(b, boundary);
295 /* write Content-Range */
296 buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
297 buffer_append_int(b, start);
298 buffer_append_string_len(b, CONST_STR_LEN("-"));
299 buffer_append_int(b, end);
300 buffer_append_string_len(b, CONST_STR_LEN("/"));
301 buffer_append_int(b, sce->st.st_size);
303 buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
304 buffer_append_string_buffer(b, content_type);
306 /* write END-OF-HEADER */
307 buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
309 con->response.content_length += buffer_string_length(b);
310 chunkqueue_append_buffer(con->write_queue, b);
311 buffer_free(b);
314 chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
315 con->response.content_length += end - start + 1;
319 /* something went wrong */
320 if (error) return -1;
322 if (multipart) {
323 /* add boundary end */
324 buffer *b = buffer_init();
326 buffer_copy_string_len(b, "\r\n--", 4);
327 buffer_append_string(b, boundary);
328 buffer_append_string_len(b, "--\r\n", 4);
330 con->response.content_length += buffer_string_length(b);
331 chunkqueue_append_buffer(con->write_queue, b);
332 buffer_free(b);
334 /* set header-fields */
336 buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary="));
337 buffer_append_string(p->range_buf, boundary);
339 /* overwrite content-type */
340 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
341 } else {
342 /* add Content-Range-header */
344 buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes "));
345 buffer_append_int(p->range_buf, start);
346 buffer_append_string_len(p->range_buf, CONST_STR_LEN("-"));
347 buffer_append_int(p->range_buf, end);
348 buffer_append_string_len(p->range_buf, CONST_STR_LEN("/"));
349 buffer_append_int(p->range_buf, sce->st.st_size);
351 response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
354 /* ok, the file is set-up */
355 return 0;
358 URIHANDLER_FUNC(mod_staticfile_subrequest) {
359 plugin_data *p = p_d;
360 size_t k;
361 stat_cache_entry *sce = NULL;
362 buffer *mtime = NULL;
363 data_string *ds;
364 int allow_caching = 1;
366 /* someone else has done a decision for us */
367 if (con->http_status != 0) return HANDLER_GO_ON;
368 if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON;
369 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
371 /* someone else has handled this request */
372 if (con->mode != DIRECT) return HANDLER_GO_ON;
374 /* we only handle GET, POST and HEAD */
375 switch(con->request.http_method) {
376 case HTTP_METHOD_GET:
377 case HTTP_METHOD_POST:
378 case HTTP_METHOD_HEAD:
379 break;
380 default:
381 return HANDLER_GO_ON;
384 mod_staticfile_patch_connection(srv, con, p);
386 if (p->conf.disable_pathinfo && !buffer_string_is_empty(con->request.pathinfo)) {
387 if (con->conf.log_request_handling) {
388 log_error_write(srv, __FILE__, __LINE__, "s", "-- NOT handling file as static file, pathinfo forbidden");
390 return HANDLER_GO_ON;
393 /* ignore certain extensions */
394 for (k = 0; k < p->conf.exclude_ext->used; k++) {
395 ds = (data_string *)p->conf.exclude_ext->data[k];
397 if (buffer_is_empty(ds->value)) continue;
399 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
400 if (con->conf.log_request_handling) {
401 log_error_write(srv, __FILE__, __LINE__, "s", "-- NOT handling file as static file, extension forbidden");
403 return HANDLER_GO_ON;
408 if (con->conf.log_request_handling) {
409 log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file");
412 if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
413 con->http_status = 403;
415 log_error_write(srv, __FILE__, __LINE__, "sbsb",
416 "not a regular file:", con->uri.path,
417 "->", con->physical.path);
419 return HANDLER_FINISHED;
422 /* we only handline regular files */
423 #ifdef HAVE_LSTAT
424 if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
425 con->http_status = 403;
427 if (con->conf.log_request_handling) {
428 log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
429 log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
432 buffer_reset(con->physical.path);
433 return HANDLER_FINISHED;
435 #endif
436 if (!S_ISREG(sce->st.st_mode)) {
437 con->http_status = 404;
439 if (con->conf.log_file_not_found) {
440 log_error_write(srv, __FILE__, __LINE__, "sbsb",
441 "not a regular file:", con->uri.path,
442 "->", sce->name);
445 return HANDLER_FINISHED;
448 /* mod_compress might set several data directly, don't overwrite them */
450 /* set response content-type, if not set already */
452 if (NULL == array_get_element(con->response.headers, "Content-Type")) {
453 if (buffer_string_is_empty(sce->content_type)) {
454 /* we are setting application/octet-stream, but also announce that
455 * this header field might change in the seconds few requests
457 * This should fix the aggressive caching of FF and the script download
458 * seen by the first installations
460 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
462 allow_caching = 0;
463 } else {
464 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
468 if (con->conf.range_requests) {
469 response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
472 if (allow_caching) {
473 if (p->conf.etags_used && con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) {
474 if (NULL == array_get_element(con->response.headers, "ETag")) {
475 /* generate e-tag */
476 etag_mutate(con->physical.etag, sce->etag);
478 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
482 /* prepare header */
483 if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
484 mtime = strftime_cache_get(srv, sce->st.st_mtime);
485 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
486 } else {
487 mtime = ds->value;
490 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
491 return HANDLER_FINISHED;
495 if (con->request.http_range && con->conf.range_requests) {
496 int do_range_request = 1;
497 /* check if we have a conditional GET */
499 if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
500 /* if the value is the same as our ETag, we do a Range-request,
501 * otherwise a full 200 */
503 if (ds->value->ptr[0] == '"') {
505 * client wants a ETag
507 if (!con->physical.etag) {
508 do_range_request = 0;
509 } else if (!buffer_is_equal(ds->value, con->physical.etag)) {
510 do_range_request = 0;
512 } else if (!mtime) {
514 * we don't have a Last-Modified and can match the If-Range:
516 * sending all
518 do_range_request = 0;
519 } else if (!buffer_is_equal(ds->value, mtime)) {
520 do_range_request = 0;
524 if (do_range_request) {
525 /* content prepared, I'm done */
526 con->file_finished = 1;
528 if (0 == http_response_parse_range(srv, con, p)) {
529 con->http_status = 206;
531 return HANDLER_FINISHED;
535 /* if we are still here, prepare body */
537 /* we add it here for all requests
538 * the HEAD request will drop it afterwards again
540 http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
542 con->http_status = 200;
543 con->file_finished = 1;
545 return HANDLER_FINISHED;
548 /* this function is called at dlopen() time and inits the callbacks */
550 int mod_staticfile_plugin_init(plugin *p);
551 int mod_staticfile_plugin_init(plugin *p) {
552 p->version = LIGHTTPD_VERSION_ID;
553 p->name = buffer_init_string("staticfile");
555 p->init = mod_staticfile_init;
556 p->handle_subrequest_start = mod_staticfile_subrequest;
557 p->set_defaults = mod_staticfile_set_defaults;
558 p->cleanup = mod_staticfile_free;
560 p->data = NULL;
562 return 0;