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