Return to mod_staticfile.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src |
1.1 misho 1: #include "base.h"
2: #include "log.h"
3: #include "buffer.h"
4:
5: #include "plugin.h"
6:
7: #include "stat_cache.h"
8: #include "etag.h"
9: #include "http_chunk.h"
10: #include "response.h"
11:
12: #include <ctype.h>
13: #include <stdlib.h>
14: #include <stdio.h>
15: #include <string.h>
16:
17: /**
18: * this is a staticfile for a lighttpd plugin
19: *
20: */
21:
22:
23:
24: /* plugin config for all request/connections */
25:
26: typedef struct {
27: array *exclude_ext;
28: unsigned short etags_used;
29: unsigned short disable_pathinfo;
30: } plugin_config;
31:
32: typedef struct {
33: PLUGIN_DATA;
34:
35: buffer *range_buf;
36:
37: plugin_config **config_storage;
38:
39: plugin_config conf;
40: } plugin_data;
41:
42: /* init the plugin data */
43: INIT_FUNC(mod_staticfile_init) {
44: plugin_data *p;
45:
46: p = calloc(1, sizeof(*p));
47:
48: p->range_buf = buffer_init();
49:
50: return p;
51: }
52:
53: /* detroy the plugin data */
54: FREE_FUNC(mod_staticfile_free) {
55: plugin_data *p = p_d;
56:
57: UNUSED(srv);
58:
59: if (!p) return HANDLER_GO_ON;
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: array_free(s->exclude_ext);
67:
68: free(s);
69: }
70: free(p->config_storage);
71: }
72: buffer_free(p->range_buf);
73:
74: free(p);
75:
76: return HANDLER_GO_ON;
77: }
78:
79: /* handle plugin config and check values */
80:
81: SETDEFAULTS_FUNC(mod_staticfile_set_defaults) {
82: plugin_data *p = p_d;
83: size_t i = 0;
84:
85: config_values_t cv[] = {
86: { "static-file.exclude-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
87: { "static-file.etags", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
88: { "static-file.disable-pathinfo", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
89: { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
90: };
91:
92: if (!p) return HANDLER_ERROR;
93:
1.1.1.2 ! misho 94: p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1 misho 95:
96: for (i = 0; i < srv->config_context->used; i++) {
97: plugin_config *s;
98:
99: s = calloc(1, sizeof(plugin_config));
100: s->exclude_ext = array_init();
101: s->etags_used = 1;
102: s->disable_pathinfo = 0;
103:
104: cv[0].destination = s->exclude_ext;
105: cv[1].destination = &(s->etags_used);
106: cv[2].destination = &(s->disable_pathinfo);
107:
108: p->config_storage[i] = s;
109:
110: if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
111: return HANDLER_ERROR;
112: }
113: }
114:
115: return HANDLER_GO_ON;
116: }
117:
118: #define PATCH(x) \
119: p->conf.x = s->x;
120: static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) {
121: size_t i, j;
122: plugin_config *s = p->config_storage[0];
123:
124: PATCH(exclude_ext);
125: PATCH(etags_used);
126: PATCH(disable_pathinfo);
127:
128: /* skip the first, the global context */
129: for (i = 1; i < srv->config_context->used; i++) {
130: data_config *dc = (data_config *)srv->config_context->data[i];
131: s = p->config_storage[i];
132:
133: /* condition didn't match */
134: if (!config_check_cond(srv, con, dc)) continue;
135:
136: /* merge config */
137: for (j = 0; j < dc->value->used; j++) {
138: data_unset *du = dc->value->data[j];
139:
140: if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.exclude-extensions"))) {
141: PATCH(exclude_ext);
142: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.etags"))) {
143: PATCH(etags_used);
144: } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("static-file.disable-pathinfo"))) {
145: PATCH(disable_pathinfo);
146: }
147: }
148: }
149:
150: return 0;
151: }
152: #undef PATCH
153:
154: static int http_response_parse_range(server *srv, connection *con, plugin_data *p) {
155: int multipart = 0;
156: int error;
157: off_t start, end;
158: const char *s, *minus;
159: char *boundary = "fkj49sn38dcn3";
160: data_string *ds;
161: stat_cache_entry *sce = NULL;
162: buffer *content_type = NULL;
163:
164: if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
165: SEGFAULT();
166: }
167:
168: start = 0;
169: end = sce->st.st_size - 1;
170:
171: con->response.content_length = 0;
172:
173: if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
174: content_type = ds->value;
175: }
176:
177: for (s = con->request.http_range, error = 0;
178: !error && *s && NULL != (minus = strchr(s, '-')); ) {
179: char *err;
180: off_t la, le;
181:
182: if (s == minus) {
183: /* -<stop> */
184:
185: le = strtoll(s, &err, 10);
186:
187: if (le == 0) {
188: /* RFC 2616 - 14.35.1 */
189:
190: con->http_status = 416;
191: error = 1;
192: } else if (*err == '\0') {
193: /* end */
194: s = err;
195:
196: end = sce->st.st_size - 1;
197: start = sce->st.st_size + le;
198: } else if (*err == ',') {
199: multipart = 1;
200: s = err + 1;
201:
202: end = sce->st.st_size - 1;
203: start = sce->st.st_size + le;
204: } else {
205: error = 1;
206: }
207:
208: } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
209: /* <start>- */
210:
211: la = strtoll(s, &err, 10);
212:
213: if (err == minus) {
214: /* ok */
215:
216: if (*(err + 1) == '\0') {
217: s = err + 1;
218:
219: end = sce->st.st_size - 1;
220: start = la;
221:
222: } else if (*(err + 1) == ',') {
223: multipart = 1;
224: s = err + 2;
225:
226: end = sce->st.st_size - 1;
227: start = la;
228: } else {
229: error = 1;
230: }
231: } else {
232: /* error */
233: error = 1;
234: }
235: } else {
236: /* <start>-<stop> */
237:
238: la = strtoll(s, &err, 10);
239:
240: if (err == minus) {
241: le = strtoll(minus+1, &err, 10);
242:
243: /* RFC 2616 - 14.35.1 */
244: if (la > le) {
245: error = 1;
246: }
247:
248: if (*err == '\0') {
249: /* ok, end*/
250: s = err;
251:
252: end = le;
253: start = la;
254: } else if (*err == ',') {
255: multipart = 1;
256: s = err + 1;
257:
258: end = le;
259: start = la;
260: } else {
261: /* error */
262:
263: error = 1;
264: }
265: } else {
266: /* error */
267:
268: error = 1;
269: }
270: }
271:
272: if (!error) {
273: if (start < 0) start = 0;
274:
275: /* RFC 2616 - 14.35.1 */
276: if (end > sce->st.st_size - 1) end = sce->st.st_size - 1;
277:
278: if (start > sce->st.st_size - 1) {
279: error = 1;
280:
281: con->http_status = 416;
282: }
283: }
284:
285: if (!error) {
286: if (multipart) {
287: /* write boundary-header */
288: buffer *b;
289:
290: b = chunkqueue_get_append_buffer(con->write_queue);
291:
292: buffer_copy_string_len(b, CONST_STR_LEN("\r\n--"));
293: buffer_append_string(b, boundary);
294:
295: /* write Content-Range */
296: buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes "));
297: buffer_append_off_t(b, start);
298: buffer_append_string_len(b, CONST_STR_LEN("-"));
299: buffer_append_off_t(b, end);
300: buffer_append_string_len(b, CONST_STR_LEN("/"));
301: buffer_append_off_t(b, sce->st.st_size);
302:
303: buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: "));
304: buffer_append_string_buffer(b, content_type);
305:
306: /* write END-OF-HEADER */
307: buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
308:
309: con->response.content_length += b->used - 1;
310:
311: }
312:
313: chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
314: con->response.content_length += end - start + 1;
315: }
316: }
317:
318: /* something went wrong */
319: if (error) return -1;
320:
321: if (multipart) {
322: /* add boundary end */
323: buffer *b;
324:
325: b = chunkqueue_get_append_buffer(con->write_queue);
326:
327: buffer_copy_string_len(b, "\r\n--", 4);
328: buffer_append_string(b, boundary);
329: buffer_append_string_len(b, "--\r\n", 4);
330:
331: con->response.content_length += b->used - 1;
332:
333: /* set header-fields */
334:
335: buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary="));
336: buffer_append_string(p->range_buf, boundary);
337:
338: /* overwrite content-type */
339: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf));
340: } else {
341: /* add Content-Range-header */
342:
343: buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes "));
344: buffer_append_off_t(p->range_buf, start);
345: buffer_append_string_len(p->range_buf, CONST_STR_LEN("-"));
346: buffer_append_off_t(p->range_buf, end);
347: buffer_append_string_len(p->range_buf, CONST_STR_LEN("/"));
348: buffer_append_off_t(p->range_buf, sce->st.st_size);
349:
350: response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf));
351: }
352:
353: /* ok, the file is set-up */
354: return 0;
355: }
356:
357: URIHANDLER_FUNC(mod_staticfile_subrequest) {
358: plugin_data *p = p_d;
359: size_t k;
360: stat_cache_entry *sce = NULL;
361: buffer *mtime = NULL;
362: data_string *ds;
363: int allow_caching = 1;
364:
365: /* someone else has done a decision for us */
366: if (con->http_status != 0) return HANDLER_GO_ON;
367: if (con->uri.path->used == 0) return HANDLER_GO_ON;
368: if (con->physical.path->used == 0) return HANDLER_GO_ON;
369:
370: /* someone else has handled this request */
371: if (con->mode != DIRECT) return HANDLER_GO_ON;
372:
373: /* we only handle GET, POST and HEAD */
374: switch(con->request.http_method) {
375: case HTTP_METHOD_GET:
376: case HTTP_METHOD_POST:
377: case HTTP_METHOD_HEAD:
378: break;
379: default:
380: return HANDLER_GO_ON;
381: }
382:
383: mod_staticfile_patch_connection(srv, con, p);
384:
385: if (p->conf.disable_pathinfo && 0 != con->request.pathinfo->used) {
386: if (con->conf.log_request_handling) {
387: log_error_write(srv, __FILE__, __LINE__, "s", "-- NOT handling file as static file, pathinfo forbidden");
388: }
389: return HANDLER_GO_ON;
390: }
391:
392: /* ignore certain extensions */
393: for (k = 0; k < p->conf.exclude_ext->used; k++) {
394: ds = (data_string *)p->conf.exclude_ext->data[k];
395:
396: if (ds->value->used == 0) continue;
397:
398: if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
399: if (con->conf.log_request_handling) {
400: log_error_write(srv, __FILE__, __LINE__, "s", "-- NOT handling file as static file, extension forbidden");
401: }
402: return HANDLER_GO_ON;
403: }
404: }
405:
406:
407: if (con->conf.log_request_handling) {
408: log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file");
409: }
410:
411: if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
412: con->http_status = 403;
413:
414: log_error_write(srv, __FILE__, __LINE__, "sbsb",
415: "not a regular file:", con->uri.path,
416: "->", con->physical.path);
417:
418: return HANDLER_FINISHED;
419: }
420:
421: /* we only handline regular files */
422: #ifdef HAVE_LSTAT
423: if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
424: con->http_status = 403;
425:
426: if (con->conf.log_request_handling) {
427: log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction");
428: log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
429: }
430:
431: buffer_reset(con->physical.path);
432: return HANDLER_FINISHED;
433: }
434: #endif
435: if (!S_ISREG(sce->st.st_mode)) {
436: con->http_status = 404;
437:
438: if (con->conf.log_file_not_found) {
439: log_error_write(srv, __FILE__, __LINE__, "sbsb",
440: "not a regular file:", con->uri.path,
441: "->", sce->name);
442: }
443:
444: return HANDLER_FINISHED;
445: }
446:
447: /* mod_compress might set several data directly, don't overwrite them */
448:
449: /* set response content-type, if not set already */
450:
451: if (NULL == array_get_element(con->response.headers, "Content-Type")) {
452: if (buffer_is_empty(sce->content_type)) {
453: /* we are setting application/octet-stream, but also announce that
454: * this header field might change in the seconds few requests
455: *
456: * This should fix the aggressive caching of FF and the script download
457: * seen by the first installations
458: */
459: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
460:
461: allow_caching = 0;
462: } else {
463: response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
464: }
465: }
466:
467: if (con->conf.range_requests) {
468: response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
469: }
470:
471: if (allow_caching) {
472: if (p->conf.etags_used && con->etag_flags != 0 && !buffer_is_empty(sce->etag)) {
473: if (NULL == array_get_element(con->response.headers, "ETag")) {
474: /* generate e-tag */
475: etag_mutate(con->physical.etag, sce->etag);
476:
477: response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
478: }
479: }
480:
481: /* prepare header */
482: if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) {
483: mtime = strftime_cache_get(srv, sce->st.st_mtime);
484: response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
485: } else {
486: mtime = ds->value;
487: }
488:
489: if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
490: return HANDLER_FINISHED;
491: }
492: }
493:
494: if (con->request.http_range && con->conf.range_requests) {
495: int do_range_request = 1;
496: /* check if we have a conditional GET */
497:
498: if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) {
499: /* if the value is the same as our ETag, we do a Range-request,
500: * otherwise a full 200 */
501:
502: if (ds->value->ptr[0] == '"') {
503: /**
504: * client wants a ETag
505: */
506: if (!con->physical.etag) {
507: do_range_request = 0;
508: } else if (!buffer_is_equal(ds->value, con->physical.etag)) {
509: do_range_request = 0;
510: }
511: } else if (!mtime) {
512: /**
513: * we don't have a Last-Modified and can match the If-Range:
514: *
515: * sending all
516: */
517: do_range_request = 0;
518: } else if (!buffer_is_equal(ds->value, mtime)) {
519: do_range_request = 0;
520: }
521: }
522:
523: if (do_range_request) {
524: /* content prepared, I'm done */
525: con->file_finished = 1;
526:
527: if (0 == http_response_parse_range(srv, con, p)) {
528: con->http_status = 206;
529: }
530: return HANDLER_FINISHED;
531: }
532: }
533:
534: /* if we are still here, prepare body */
535:
536: /* we add it here for all requests
537: * the HEAD request will drop it afterwards again
538: */
539: http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
540:
541: con->http_status = 200;
542: con->file_finished = 1;
543:
544: return HANDLER_FINISHED;
545: }
546:
547: /* this function is called at dlopen() time and inits the callbacks */
548:
549: int mod_staticfile_plugin_init(plugin *p);
550: int mod_staticfile_plugin_init(plugin *p) {
551: p->version = LIGHTTPD_VERSION_ID;
552: p->name = buffer_init_string("staticfile");
553:
554: p->init = mod_staticfile_init;
555: p->handle_subrequest_start = mod_staticfile_subrequest;
556: p->set_defaults = mod_staticfile_set_defaults;
557: p->cleanup = mod_staticfile_free;
558:
559: p->data = NULL;
560:
561: return 0;
562: }