1 #include "first.h" 2 3 #include "response.h" 4 #include "request.h" 5 #include "base.h" 6 #include "burl.h" 7 #include "fdevent.h" 8 #include "http_header.h" 9 #include "http_kv.h" 10 #include "log.h" 11 #include "stat_cache.h" 12 #include "chunk.h" 13 14 #include "plugin.h" 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 19 #include <limits.h> 20 #include <errno.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <time.h> 24 25 __attribute_cold__ 26 static int http_response_omit_header(request_st * const r, const data_string * const ds) { 27 const size_t klen = buffer_string_length(&ds->key); 28 if (klen == sizeof("X-Sendfile")-1 29 && buffer_eq_icase_ssn(ds->key.ptr, CONST_STR_LEN("X-Sendfile"))) 30 return 1; 31 if (klen >= sizeof("X-LIGHTTPD-")-1 32 && buffer_eq_icase_ssn(ds->key.ptr, CONST_STR_LEN("X-LIGHTTPD-"))) { 33 if (klen == sizeof("X-LIGHTTPD-KBytes-per-second")-1 34 && buffer_eq_icase_ssn(ds->key.ptr+sizeof("X-LIGHTTPD-")-1, 35 CONST_STR_LEN("KBytes-per-second"))) { 36 /* "X-LIGHTTPD-KBytes-per-second" */ 37 off_t limit = strtol(ds->value.ptr, NULL, 10) << 10; /*(*=1024)*/ 38 if (limit > 0 39 && (limit < r->conf.bytes_per_second 40 || 0 == r->conf.bytes_per_second)) { 41 r->conf.bytes_per_second = limit; 42 } 43 } 44 return 1; 45 } 46 return 0; 47 } 48 49 int http_response_write_header(request_st * const r) { 50 chunkqueue * const cq = r->write_queue; 51 buffer * const b = chunkqueue_prepend_buffer_open(cq); 52 53 if (r->http_version == HTTP_VERSION_1_1) { 54 buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 ")); 55 } else { 56 buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 ")); 57 } 58 http_status_append(b, r->http_status); 59 60 /* disable keep-alive if requested */ 61 62 if (r->con->request_count > r->conf.max_keep_alive_requests || 0 == r->conf.max_keep_alive_idle) { 63 r->keep_alive = 0; 64 } else if (0 != r->reqbody_length 65 && r->reqbody_length != r->reqbody_queue->bytes_in 66 && (NULL == r->handler_module || 0 == r->conf.stream_request_body)) { 67 r->keep_alive = 0; 68 } else { 69 r->con->keep_alive_idle = r->conf.max_keep_alive_idle; 70 } 71 72 if ((r->resp_htags & HTTP_HEADER_UPGRADE) && r->http_version == HTTP_VERSION_1_1) { 73 http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade")); 74 } else if (0 == r->keep_alive) { 75 http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close")); 76 } else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive != 0)*/ 77 http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive")); 78 } 79 80 if (304 == r->http_status && (r->resp_htags & HTTP_HEADER_CONTENT_ENCODING)) { 81 http_header_response_unset(r, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding")); 82 } 83 84 /* add all headers */ 85 for (size_t i = 0; i < r->resp_headers.used; ++i) { 86 const data_string * const ds = (data_string *)r->resp_headers.data[i]; 87 88 if (buffer_string_is_empty(&ds->value)) continue; 89 if (buffer_string_is_empty(&ds->key)) continue; 90 if ((ds->key.ptr[0] & 0xdf) == 'X' && http_response_omit_header(r, ds)) 91 continue; 92 93 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 94 buffer_append_string_buffer(b, &ds->key); 95 buffer_append_string_len(b, CONST_STR_LEN(": ")); 96 buffer_append_string_buffer(b, &ds->value); 97 } 98 99 if (!(r->resp_htags & HTTP_HEADER_DATE)) { 100 static time_t tlast; 101 static char tstr[32]; /* 30-chars for "%a, %d %b %Y %H:%M:%S GMT" */ 102 static size_t tlen; 103 104 /* HTTP/1.1 requires a Date: header */ 105 buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: ")); 106 107 /* cache the generated timestamp */ 108 const time_t cur_ts = log_epoch_secs; 109 if (tlast != cur_ts) { 110 tlast = cur_ts; 111 tlen = strftime(tstr, sizeof(tstr), 112 "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tlast)); 113 } 114 115 buffer_append_string_len(b, tstr, tlen); 116 } 117 118 if (!(r->resp_htags & HTTP_HEADER_SERVER)) { 119 if (!buffer_string_is_empty(r->conf.server_tag)) { 120 buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: ")); 121 buffer_append_string_len(b, CONST_BUF_LEN(r->conf.server_tag)); 122 } 123 } 124 125 buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); 126 127 r->resp_header_len = buffer_string_length(b); 128 129 if (r->conf.log_response_header) { 130 log_error(r->conf.errh,__FILE__,__LINE__,"Response-Header:\n%s",b->ptr); 131 } 132 133 chunkqueue_prepend_buffer_commit(cq); 134 return 0; 135 } 136 137 static handler_t http_response_physical_path_check(request_st * const r) { 138 stat_cache_entry *sce = stat_cache_get_entry(&r->physical.path); 139 140 if (sce) { 141 /* file exists */ 142 } else { 143 char *pathinfo = NULL; 144 switch (errno) { 145 case EACCES: 146 r->http_status = 403; 147 148 if (r->conf.log_request_handling) { 149 log_error(r->conf.errh, __FILE__, __LINE__, 150 "-- access denied"); 151 log_error(r->conf.errh, __FILE__, __LINE__, 152 "Path : %s", r->physical.path.ptr); 153 } 154 155 buffer_reset(&r->physical.path); 156 return HANDLER_FINISHED; 157 case ENAMETOOLONG: 158 /* file name to be read was too long. return 404 */ 159 case ENOENT: 160 if (r->http_method == HTTP_METHOD_OPTIONS 161 && NULL != http_header_response_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Allow"))) { 162 r->http_status = 200; 163 return HANDLER_FINISHED; 164 } 165 166 r->http_status = 404; 167 168 if (r->conf.log_request_handling) { 169 log_error(r->conf.errh, __FILE__, __LINE__, 170 "-- file not found"); 171 log_error(r->conf.errh, __FILE__, __LINE__, 172 "Path : %s", r->physical.path.ptr); 173 } 174 175 buffer_reset(&r->physical.path); 176 return HANDLER_FINISHED; 177 case ENOTDIR: 178 /* PATH_INFO ! :) */ 179 break; 180 default: 181 /* we have no idea what happened. let's tell the user so. */ 182 log_error(r->conf.errh, __FILE__, __LINE__, 183 "file not found ... or so: %s -> %s", 184 r->uri.path.ptr, r->physical.path.ptr); 185 186 r->http_status = 500; 187 buffer_reset(&r->physical.path); 188 189 return HANDLER_FINISHED; 190 } 191 192 /* not found, perhaps PATHINFO */ 193 194 { 195 /*(might check at startup that s->document_root does not end in '/')*/ 196 size_t len = buffer_string_length(&r->physical.basedir); 197 if (len > 0 && '/' == r->physical.basedir.ptr[len-1]) --len; 198 pathinfo = r->physical.path.ptr + len; 199 if ('/' != *pathinfo) { 200 pathinfo = NULL; 201 } 202 else if (pathinfo == r->physical.path.ptr) { /*(basedir is "/")*/ 203 pathinfo = strchr(pathinfo+1, '/'); 204 } 205 } 206 207 buffer * const tb = r->tmp_buf; 208 for (char *pprev = pathinfo; pathinfo; pprev = pathinfo, pathinfo = strchr(pathinfo+1, '/')) { 209 buffer_copy_string_len(tb, r->physical.path.ptr, pathinfo - r->physical.path.ptr); 210 stat_cache_entry *nsce = stat_cache_get_entry(tb); 211 if (NULL == nsce) { 212 pathinfo = pathinfo != pprev ? pprev : NULL; 213 break; 214 } 215 sce = nsce; 216 if (!S_ISDIR(sce->st.st_mode)) break; 217 } 218 219 if (NULL == pathinfo || !S_ISREG(sce->st.st_mode)) { 220 /* no it really doesn't exists */ 221 r->http_status = 404; 222 223 if (r->conf.log_file_not_found) { 224 log_error(r->conf.errh, __FILE__, __LINE__, 225 "file not found: %s -> %s", 226 r->uri.path.ptr, r->physical.path.ptr); 227 } 228 229 buffer_reset(&r->physical.path); 230 231 return HANDLER_FINISHED; 232 } 233 234 /* we have a PATHINFO */ 235 if (pathinfo) { 236 size_t len = strlen(pathinfo), reqlen; 237 if (r->conf.force_lowercase_filenames 238 && len <= (reqlen = buffer_string_length(&r->target)) 239 && buffer_eq_icase_ssn(r->target.ptr + reqlen - len, pathinfo, len)) { 240 /* attempt to preserve case-insensitive PATH_INFO 241 * (works in common case where mod_alias, mod_magnet, and other modules 242 * have not modified the PATH_INFO portion of request URI, or did so 243 * with exactly the PATH_INFO desired) */ 244 buffer_copy_string_len(&r->pathinfo, r->target.ptr + reqlen - len, len); 245 } else { 246 buffer_copy_string_len(&r->pathinfo, pathinfo, len); 247 } 248 249 /* 250 * shorten uri.path 251 */ 252 253 buffer_string_set_length(&r->uri.path, buffer_string_length(&r->uri.path) - len); 254 buffer_string_set_length(&r->physical.path, (size_t)(pathinfo - r->physical.path.ptr)); 255 } 256 } 257 258 if (!r->conf.follow_symlink 259 && 0 != stat_cache_path_contains_symlink(&r->physical.path, r->conf.errh)) { 260 r->http_status = 403; 261 262 if (r->conf.log_request_handling) { 263 log_error(r->conf.errh, __FILE__, __LINE__, 264 "-- access denied due symlink restriction"); 265 log_error(r->conf.errh, __FILE__, __LINE__, 266 "Path : %s", r->physical.path.ptr); 267 } 268 269 buffer_reset(&r->physical.path); 270 return HANDLER_FINISHED; 271 } 272 273 if (S_ISDIR(sce->st.st_mode)) { 274 if (r->uri.path.ptr[buffer_string_length(&r->uri.path) - 1] != '/') { 275 /* redirect to .../ */ 276 277 http_response_redirect_to_directory(r, 301); 278 279 return HANDLER_FINISHED; 280 } 281 } else if (!S_ISREG(sce->st.st_mode)) { 282 /* any special handling of non-reg files ?*/ 283 } 284 285 return HANDLER_GO_ON; 286 } 287 288 __attribute_cold__ 289 __attribute_noinline__ 290 static handler_t http_status_set_error_close (request_st * const r, int status) { 291 r->keep_alive = 0; 292 r->resp_body_finished = 1; 293 r->handler_module = NULL; 294 r->http_status = status; 295 return HANDLER_FINISHED; 296 } 297 298 static handler_t http_response_config (request_st * const r) { 299 if (r->conf.log_condition_handling) 300 log_error(r->conf.errh, __FILE__, __LINE__, "run condition"); 301 302 config_cond_cache_reset(r); 303 config_patch_config(r); 304 305 /* do we have to downgrade to 1.0 ? */ 306 if (!r->conf.allow_http11) 307 r->http_version = HTTP_VERSION_1_0; 308 309 /* r->conf.max_request_size is in kBytes */ 310 if (0 != r->conf.max_request_size && 311 (off_t)r->reqbody_length > ((off_t)r->conf.max_request_size << 10)) { 312 log_error(r->conf.errh, __FILE__, __LINE__, 313 "request-size too long: %lld -> 413", (long long) r->reqbody_length); 314 return /* 413 Payload Too Large */ 315 http_status_set_error_close(r, 413); 316 } 317 318 return HANDLER_GO_ON; 319 } 320 321 handler_t http_response_prepare(request_st * const r) { 322 handler_t rc; 323 324 do { 325 326 /* looks like someone has already made a decision */ 327 if (r->http_status != 0 && r->http_status != 200) { 328 if (0 == r->resp_body_finished) 329 http_response_body_clear(r, 0); 330 return HANDLER_FINISHED; 331 } 332 333 /* no decision yet, build conf->filename */ 334 if (buffer_is_empty(&r->physical.path)) { 335 336 if (!r->async_callback) { 337 rc = http_response_config(r); 338 if (HANDLER_GO_ON != rc) continue; 339 } 340 r->async_callback = 0; /* reset */ 341 342 /* we only come here when we have the parse the full request again 343 * 344 * a HANDLER_COMEBACK from mod_rewrite and mod_fastcgi might be a 345 * problem here as mod_setenv might get called multiple times 346 * 347 * fastcgi-auth might lead to a COMEBACK too 348 * fastcgi again dead server too 349 * 350 * mod_compress might add headers twice too 351 * 352 */ 353 354 if (r->conf.log_request_handling) { 355 log_error(r->conf.errh, __FILE__, __LINE__, 356 "-- parsed Request-URI"); 357 log_error(r->conf.errh, __FILE__, __LINE__, 358 "Request-URI : %s", r->target.ptr); 359 log_error(r->conf.errh, __FILE__, __LINE__, 360 "URI-scheme : %s", r->uri.scheme.ptr); 361 log_error(r->conf.errh, __FILE__, __LINE__, 362 "URI-authority : %s", r->uri.authority.ptr); 363 log_error(r->conf.errh, __FILE__, __LINE__, 364 "URI-path (clean): %s", r->uri.path.ptr); 365 log_error(r->conf.errh, __FILE__, __LINE__, 366 "URI-query : %.*s", 367 BUFFER_INTLEN_PTR(&r->uri.query)); 368 } 369 370 371 /** 372 * 373 * call plugins 374 * 375 * - based on the raw URL 376 * 377 */ 378 379 rc = plugins_call_handle_uri_raw(r); 380 if (HANDLER_GO_ON != rc) continue; 381 382 /** 383 * 384 * call plugins 385 * 386 * - based on the clean URL 387 * 388 */ 389 390 rc = plugins_call_handle_uri_clean(r); 391 if (HANDLER_GO_ON != rc) continue; 392 393 if (r->http_method == HTTP_METHOD_OPTIONS && 394 r->uri.path.ptr[0] == '*' && r->uri.path.ptr[1] == '\0') { 395 /* option requests are handled directly without checking of the path */ 396 397 http_header_response_append(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); 398 399 r->http_status = 200; 400 r->resp_body_finished = 1; 401 402 return HANDLER_FINISHED; 403 } 404 405 if (r->http_method == HTTP_METHOD_CONNECT && NULL == r->handler_module) { 406 return /* 405 Method Not Allowed */ 407 http_status_set_error_close(r, 405); 408 } 409 410 /*** 411 * 412 * border 413 * 414 * logical filename (URI) becomes a physical filename here 415 * 416 * 417 * 418 */ 419 420 421 422 423 /* 1. stat() 424 * ... ISREG() -> ok, go on 425 * ... ISDIR() -> index-file -> redirect 426 * 427 * 2. pathinfo() 428 * ... ISREG() 429 * 430 * 3. -> 404 431 * 432 */ 433 434 /* 435 * SEARCH DOCUMENT ROOT 436 */ 437 438 /* set a default */ 439 440 buffer_copy_buffer(&r->physical.doc_root, r->conf.document_root); 441 buffer_copy_buffer(&r->physical.rel_path, &r->uri.path); 442 443 #if defined(__WIN32) || defined(__CYGWIN__) 444 /* strip dots from the end and spaces 445 * 446 * windows/dos handle those filenames as the same file 447 * 448 * foo == foo. == foo..... == "foo... " == "foo.. ./" 449 * 450 * This will affect in some cases PATHINFO 451 * 452 * on native windows we could prepend the filename with \\?\ to circumvent 453 * this behaviour. I have no idea how to push this through cygwin 454 * 455 * */ 456 457 if (!buffer_string_is_empty(&r->physical.rel_path)) { 458 buffer *b = &r->physical.rel_path; 459 size_t len = buffer_string_length(b); 460 461 /* strip trailing " /" or "./" once */ 462 if (len > 1 && 463 b->ptr[len - 1] == '/' && 464 (b->ptr[len - 2] == ' ' || b->ptr[len - 2] == '.')) { 465 len -= 2; 466 } 467 /* strip all trailing " " and "." */ 468 while (len > 0 && ( ' ' == b->ptr[len-1] || '.' == b->ptr[len-1] ) ) --len; 469 buffer_string_set_length(b, len); 470 } 471 #endif 472 473 if (r->conf.log_request_handling) { 474 log_error(r->conf.errh, __FILE__, __LINE__, 475 "-- before doc_root"); 476 log_error(r->conf.errh, __FILE__, __LINE__, 477 "Doc-Root : %s", r->physical.doc_root.ptr); 478 log_error(r->conf.errh, __FILE__, __LINE__, 479 "Rel-Path : %s", r->physical.rel_path.ptr); 480 log_error(r->conf.errh, __FILE__, __LINE__, 481 "Path : %s", r->physical.path.ptr); 482 } 483 /* the docroot plugin should set the doc_root and might also set the physical.path 484 * for us (all vhost-plugins are supposed to set the doc_root) 485 * */ 486 rc = plugins_call_handle_docroot(r); 487 if (HANDLER_GO_ON != rc) continue; 488 489 /* MacOS X and Windows can't distinguish between upper and lower-case 490 * 491 * convert to lower-case 492 */ 493 if (r->conf.force_lowercase_filenames) { 494 buffer_to_lower(&r->physical.rel_path); 495 } 496 497 /* the docroot plugins might set the servername, if they don't we take http-host */ 498 if (buffer_string_is_empty(r->server_name)) { 499 r->server_name = &r->uri.authority; 500 } 501 502 /** 503 * create physical filename 504 * -> physical.path = docroot + rel_path 505 * 506 */ 507 508 buffer_copy_buffer(&r->physical.basedir, &r->physical.doc_root); 509 buffer_copy_buffer(&r->physical.path, &r->physical.doc_root); 510 buffer_append_path_len(&r->physical.path, CONST_BUF_LEN(&r->physical.rel_path)); 511 512 if (r->conf.log_request_handling) { 513 log_error(r->conf.errh, __FILE__, __LINE__, 514 "-- after doc_root"); 515 log_error(r->conf.errh, __FILE__, __LINE__, 516 "Doc-Root : %s", r->physical.doc_root.ptr); 517 log_error(r->conf.errh, __FILE__, __LINE__, 518 "Rel-Path : %s", r->physical.rel_path.ptr); 519 log_error(r->conf.errh, __FILE__, __LINE__, 520 "Path : %s", r->physical.path.ptr); 521 } 522 523 if (r->http_method == HTTP_METHOD_CONNECT) { 524 /* do not permit CONNECT requests to hit filesystem hooks 525 * since the CONNECT URI bypassed path normalization */ 526 /* (This check is located here so that r->physical.path 527 * is filled in above to avoid repeating work next time 528 * http_response_prepare() is called while processing request) */ 529 } else { 530 rc = plugins_call_handle_physical(r); 531 if (HANDLER_GO_ON != rc) continue; 532 533 if (r->conf.log_request_handling) { 534 log_error(r->conf.errh, __FILE__, __LINE__, 535 "-- logical -> physical"); 536 log_error(r->conf.errh, __FILE__, __LINE__, 537 "Doc-Root : %s", r->physical.doc_root.ptr); 538 log_error(r->conf.errh, __FILE__, __LINE__, 539 "Basedir : %s", r->physical.basedir.ptr); 540 log_error(r->conf.errh, __FILE__, __LINE__, 541 "Rel-Path : %s", r->physical.rel_path.ptr); 542 log_error(r->conf.errh, __FILE__, __LINE__, 543 "Path : %s", r->physical.path.ptr); 544 } 545 } 546 } 547 548 if (NULL != r->handler_module) return HANDLER_GO_ON; 549 550 /* 551 * No module grabbed the request yet (like mod_access) 552 * 553 * Go on and check of the file exists at all 554 */ 555 556 if (r->conf.log_request_handling) { 557 log_error(r->conf.errh, __FILE__, __LINE__, 558 "-- handling physical path"); 559 log_error(r->conf.errh, __FILE__, __LINE__, 560 "Path : %s", r->physical.path.ptr); 561 } 562 563 rc = http_response_physical_path_check(r); 564 if (HANDLER_GO_ON != rc) continue; 565 566 if (r->conf.log_request_handling) { 567 log_error(r->conf.errh, __FILE__, __LINE__, 568 "-- handling subrequest"); 569 log_error(r->conf.errh, __FILE__, __LINE__, 570 "Path : %s", r->physical.path.ptr); 571 log_error(r->conf.errh, __FILE__, __LINE__, 572 "URI : %s", r->uri.path.ptr); 573 log_error(r->conf.errh, __FILE__, __LINE__, 574 "Pathinfo : %s", r->pathinfo.ptr); 575 } 576 577 /* call the handlers */ 578 rc = plugins_call_handle_subrequest_start(r); 579 if (HANDLER_GO_ON != rc) { 580 if (r->conf.log_request_handling) { 581 log_error(r->conf.errh, __FILE__, __LINE__, 582 "-- subrequest finished"); 583 } 584 continue; 585 } 586 587 /* if we are still here, no one wanted the file, status 403 is ok I think */ 588 if (NULL == r->handler_module && 0 == r->http_status) { 589 r->http_status = (r->http_method != HTTP_METHOD_OPTIONS) ? 403 : 200; 590 return HANDLER_FINISHED; 591 } 592 593 return HANDLER_GO_ON; 594 595 } while (HANDLER_COMEBACK == rc 596 && HANDLER_GO_ON ==(rc = http_response_comeback(r))); 597 598 return rc; 599 } 600 601 handler_t http_response_comeback (request_st * const r) 602 { 603 if (NULL != r->handler_module || !buffer_is_empty(&r->physical.path)) 604 return HANDLER_GO_ON; 605 606 config_reset_config(r); 607 608 buffer_copy_buffer(&r->uri.authority,r->http_host);/*copy even if NULL*/ 609 buffer_to_lower(&r->uri.authority); 610 611 int status = http_request_parse_target(r, r->con->proto_default_port); 612 if (0 == status) { 613 r->conditional_is_valid = (1 << COMP_SERVER_SOCKET) 614 | (1 << COMP_HTTP_SCHEME) 615 | (1 << COMP_HTTP_HOST) 616 | (1 << COMP_HTTP_REMOTE_IP) 617 | (1 << COMP_HTTP_REQUEST_METHOD) 618 | (1 << COMP_HTTP_URL) 619 | (1 << COMP_HTTP_QUERY_STRING) 620 | (1 << COMP_HTTP_REQUEST_HEADER); 621 return HANDLER_GO_ON; 622 } 623 else { 624 r->conditional_is_valid = (1 << COMP_SERVER_SOCKET) 625 | (1 << COMP_HTTP_REMOTE_IP); 626 config_cond_cache_reset(r); 627 return http_status_set_error_close(r, status); 628 } 629 } 630