Annotation of embedaddon/lighttpd/src/mod_staticfile.c, revision 1.1
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:
! 94: p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
! 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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>