1/* 2 * libwebsockets - small server side websockets and web server implementation 3 * 4 * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to 8 * deal in the Software without restriction, including without limitation the 9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 * sell copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 * IN THE SOFTWARE. 23 */ 24 25#include "private-lib-core.h" 26#include "lextable-strings.h" 27 28 29const unsigned char * 30lws_token_to_string(enum lws_token_indexes token) 31{ 32 if ((unsigned int)token >= LWS_ARRAY_SIZE(set)) 33 return NULL; 34 35 return (unsigned char *)set[token]; 36} 37 38/* 39 * Return http header index if one matches slen chars of s, or -1 40 */ 41 42int 43lws_http_string_to_known_header(const char *s, size_t slen) 44{ 45 int n; 46 47 for (n = 0; n < (int)LWS_ARRAY_SIZE(set); n++) 48 if (!strncmp(set[n], s, slen)) 49 return n; 50 51 return LWS_HTTP_NO_KNOWN_HEADER; 52} 53 54#ifdef LWS_WITH_HTTP2 55int 56lws_wsi_is_h2(struct lws *wsi) 57{ 58 return wsi->upgraded_to_http2 || 59 wsi->mux_substream || 60#if defined(LWS_WITH_CLIENT) 61 wsi->client_mux_substream || 62#endif 63 lwsi_role_h2(wsi) || 64 lwsi_role_h2_ENCAPSULATION(wsi); 65} 66#endif 67 68int 69lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, 70 const unsigned char *value, int length, 71 unsigned char **p, unsigned char *end) 72{ 73#ifdef LWS_WITH_HTTP2 74 if (lws_wsi_is_h2(wsi)) 75 return lws_add_http2_header_by_name(wsi, name, 76 value, length, p, end); 77#else 78 (void)wsi; 79#endif 80 if (name) { 81 char has_colon = 0; 82 while (*p < end && *name) { 83 has_colon = has_colon || *name == ':'; 84 *((*p)++) = *name++; 85 } 86 if (*p + (has_colon ? 1 : 2) >= end) 87 return 1; 88 if (!has_colon) 89 *((*p)++) = ':'; 90 *((*p)++) = ' '; 91 } 92 if (*p + length + 3 >= end) 93 return 1; 94 95 if (value) 96 memcpy(*p, value, (unsigned int)length); 97 *p += length; 98 *((*p)++) = '\x0d'; 99 *((*p)++) = '\x0a'; 100 101 return 0; 102} 103 104int lws_finalize_http_header(struct lws *wsi, unsigned char **p, 105 unsigned char *end) 106{ 107#ifdef LWS_WITH_HTTP2 108 if (lws_wsi_is_h2(wsi)) 109 return 0; 110#else 111 (void)wsi; 112#endif 113 if ((lws_intptr_t)(end - *p) < 3) 114 return 1; 115 *((*p)++) = '\x0d'; 116 *((*p)++) = '\x0a'; 117 118 return 0; 119} 120 121int 122lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, 123 unsigned char **pp, unsigned char *end) 124{ 125 unsigned char *p; 126 int len; 127 128 if (lws_finalize_http_header(wsi, pp, end)) 129 return 1; 130 131 p = *pp; 132 len = lws_ptr_diff(p, start); 133 134 if (lws_write(wsi, start, (unsigned int)len, LWS_WRITE_HTTP_HEADERS) != len) 135 return 1; 136 137 return 0; 138} 139 140int 141lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, 142 const unsigned char *value, int length, 143 unsigned char **p, unsigned char *end) 144{ 145 const unsigned char *name; 146#ifdef LWS_WITH_HTTP2 147 if (lws_wsi_is_h2(wsi)) 148 return lws_add_http2_header_by_token(wsi, token, value, 149 length, p, end); 150#endif 151 name = lws_token_to_string(token); 152 if (!name) 153 return 1; 154 155 return lws_add_http_header_by_name(wsi, name, value, length, p, end); 156} 157 158int 159lws_add_http_header_content_length(struct lws *wsi, 160 lws_filepos_t content_length, 161 unsigned char **p, unsigned char *end) 162{ 163 char b[24]; 164 int n; 165 166 n = lws_snprintf(b, sizeof(b) - 1, "%llu", (unsigned long long)content_length); 167 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, 168 (unsigned char *)b, n, p, end)) 169 return 1; 170 wsi->http.tx_content_length = content_length; 171 wsi->http.tx_content_remain = content_length; 172 173 lwsl_info("%s: %s: tx_content_length/remain %llu\n", __func__, 174 lws_wsi_tag(wsi), (unsigned long long)content_length); 175 176 return 0; 177} 178 179#if defined(LWS_WITH_SERVER) 180 181int 182lws_add_http_common_headers(struct lws *wsi, unsigned int code, 183 const char *content_type, lws_filepos_t content_len, 184 unsigned char **p, unsigned char *end) 185{ 186 const char *ka[] = { "close", "keep-alive" }; 187 int types[] = { HTTP_CONNECTION_CLOSE, HTTP_CONNECTION_KEEP_ALIVE }, 188 t = 0; 189 190 if (lws_add_http_header_status(wsi, code, p, end)) 191 return 1; 192 193 if (content_type && 194 lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, 195 (unsigned char *)content_type, 196 (int)strlen(content_type), p, end)) 197 return 1; 198 199#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) 200 if (!wsi->http.lcs && content_type && 201 (!strncmp(content_type, "text/", 5) || 202 !strcmp(content_type, "application/javascript") || 203 !strcmp(content_type, "image/svg+xml"))) 204 lws_http_compression_apply(wsi, NULL, p, end, 0); 205#endif 206 207 /* 208 * if we decided to compress it, we don't know the content length... 209 * the compressed data will go out chunked on h1 210 */ 211 if ( 212#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) 213 !wsi->http.lcs && 214#endif 215 content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN) { 216 if (lws_add_http_header_content_length(wsi, content_len, 217 p, end)) 218 return 1; 219 } else { 220 /* there was no length... it normally means CONNECTION_CLOSE */ 221#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) 222 223 if (!wsi->mux_substream && wsi->http.lcs) { 224 /* so... 225 * - h1 connection 226 * - http compression transform active 227 * - did not send content length 228 * 229 * then mark as chunked... 230 */ 231 wsi->http.comp_ctx.chunking = 1; 232 if (lws_add_http_header_by_token(wsi, 233 WSI_TOKEN_HTTP_TRANSFER_ENCODING, 234 (unsigned char *)"chunked", 7, p, end)) 235 return -1; 236 237 /* ... but h1 compression is chunked, if active we can 238 * still pipeline 239 */ 240 if (wsi->http.lcs && 241 wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE) 242 t = 1; 243 } 244#endif 245 if (!wsi->mux_substream) { 246 if (lws_add_http_header_by_token(wsi, 247 WSI_TOKEN_CONNECTION, 248 (unsigned char *)ka[t], 249 (int)strlen(ka[t]), p, end)) 250 return 1; 251 252 wsi->http.conn_type = (enum http_conn_type)types[t]; 253 } 254 } 255 256 return 0; 257} 258 259static const char * const err400[] = { 260 "Bad Request", 261 "Unauthorized", 262 "Payment Required", 263 "Forbidden", 264 "Not Found", 265 "Method Not Allowed", 266 "Not Acceptable", 267 "Proxy Auth Required", 268 "Request Timeout", 269 "Conflict", 270 "Gone", 271 "Length Required", 272 "Precondition Failed", 273 "Request Entity Too Large", 274 "Request URI too Long", 275 "Unsupported Media Type", 276 "Requested Range Not Satisfiable", 277 "Expectation Failed" 278}; 279 280static const char * const err500[] = { 281 "Internal Server Error", 282 "Not Implemented", 283 "Bad Gateway", 284 "Service Unavailable", 285 "Gateway Timeout", 286 "HTTP Version Not Supported" 287}; 288 289/* security best practices from Mozilla Observatory */ 290 291static const 292struct lws_protocol_vhost_options pvo_hsbph[] = {{ 293 NULL, NULL, "referrer-policy:", "no-referrer" 294}, { 295 &pvo_hsbph[0], NULL, "x-frame-options:", "deny" 296}, { 297 &pvo_hsbph[1], NULL, "x-xss-protection:", "1; mode=block" 298}, { 299 &pvo_hsbph[2], NULL, "x-content-type-options:", "nosniff" 300}, { 301 &pvo_hsbph[3], NULL, "content-security-policy:", 302 "default-src 'none'; img-src 'self' data: ; " 303 "script-src 'self'; font-src 'self'; " 304 "style-src 'self'; connect-src 'self' ws: wss:; " 305 "frame-ancestors 'none'; base-uri 'none';" 306 "form-action 'self';" 307}}; 308 309int 310lws_add_http_header_status(struct lws *wsi, unsigned int _code, 311 unsigned char **p, unsigned char *end) 312{ 313 static const char * const hver[] = { 314 "HTTP/1.0", "HTTP/1.1", "HTTP/2" 315 }; 316 const struct lws_protocol_vhost_options *headers; 317 unsigned int code = _code & LWSAHH_CODE_MASK; 318 const char *description = "", *p1; 319 unsigned char code_and_desc[60]; 320 int n; 321 322 wsi->http.response_code = code; 323#ifdef LWS_WITH_ACCESS_LOG 324 wsi->http.access_log.response = (int)code; 325#endif 326 327#ifdef LWS_WITH_HTTP2 328 if (lws_wsi_is_h2(wsi)) { 329 n = lws_add_http2_header_status(wsi, code, p, end); 330 if (n) 331 return n; 332 } else 333#endif 334 { 335 if (code >= 400 && code < (400 + LWS_ARRAY_SIZE(err400))) 336 description = err400[code - 400]; 337 if (code >= 500 && code < (500 + LWS_ARRAY_SIZE(err500))) 338 description = err500[code - 500]; 339 340 if (code == 100) 341 description = "Continue"; 342 if (code == 200) 343 description = "OK"; 344 if (code == 304) 345 description = "Not Modified"; 346 else 347 if (code >= 300 && code < 400) 348 description = "Redirect"; 349 350 if (wsi->http.request_version < LWS_ARRAY_SIZE(hver)) 351 p1 = hver[wsi->http.request_version]; 352 else 353 p1 = hver[0]; 354 355 n = lws_snprintf((char *)code_and_desc, 356 sizeof(code_and_desc) - 1, "%s %u %s", 357 p1, code, description); 358 359 if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p, 360 end)) 361 return 1; 362 } 363 364 headers = wsi->a.vhost->headers; 365 while (headers) { 366 if (lws_add_http_header_by_name(wsi, 367 (const unsigned char *)headers->name, 368 (unsigned char *)headers->value, 369 (int)strlen(headers->value), p, end)) 370 return 1; 371 372 headers = headers->next; 373 } 374 375 if (wsi->a.vhost->options & 376 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE) { 377 headers = &pvo_hsbph[LWS_ARRAY_SIZE(pvo_hsbph) - 1]; 378 while (headers) { 379 if (lws_add_http_header_by_name(wsi, 380 (const unsigned char *)headers->name, 381 (unsigned char *)headers->value, 382 (int)strlen(headers->value), p, end)) 383 return 1; 384 385 headers = headers->next; 386 } 387 } 388 389 if (wsi->a.context->server_string && 390 !(_code & LWSAHH_FLAG_NO_SERVER_NAME)) { 391 assert(wsi->a.context->server_string_len > 0); 392 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, 393 (unsigned char *)wsi->a.context->server_string, 394 wsi->a.context->server_string_len, p, end)) 395 return 1; 396 } 397 398 if (wsi->a.vhost->options & LWS_SERVER_OPTION_STS) 399 if (lws_add_http_header_by_name(wsi, (unsigned char *) 400 "Strict-Transport-Security:", 401 (unsigned char *)"max-age=15768000 ; " 402 "includeSubDomains", 36, p, end)) 403 return 1; 404 405 if (*p >= (end - 2)) { 406 lwsl_err("%s: reached end of buffer\n", __func__); 407 408 return 1; 409 } 410 411 return 0; 412} 413 414int 415lws_return_http_status(struct lws *wsi, unsigned int code, 416 const char *html_body) 417{ 418 struct lws_context *context = lws_get_context(wsi); 419 struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; 420 unsigned char *p = pt->serv_buf + LWS_PRE; 421 unsigned char *start = p; 422 unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; 423 char *body = (char *)start + context->pt_serv_buf_size - 512; 424 int n = 0, m = 0, len; 425 char slen[20]; 426 427 if (!wsi->a.vhost) { 428 lwsl_err("%s: wsi not bound to vhost\n", __func__); 429 430 return 1; 431 } 432#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) 433 if (!wsi->handling_404 && 434 wsi->a.vhost->http.error_document_404 && 435 code == HTTP_STATUS_NOT_FOUND) 436 /* we should do a redirect, and do the 404 there */ 437 if (lws_http_redirect(wsi, HTTP_STATUS_FOUND, 438 (uint8_t *)wsi->a.vhost->http.error_document_404, 439 (int)strlen(wsi->a.vhost->http.error_document_404), 440 &p, end) > 0) 441 return 0; 442#endif 443 444 /* if the redirect failed, just do a simple status */ 445 p = start; 446 447 if (!html_body) 448 html_body = ""; 449 450 if (lws_add_http_header_status(wsi, code, &p, end)) 451 return 1; 452 453 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, 454 (unsigned char *)"text/html", 9, 455 &p, end)) 456 return 1; 457 458 len = lws_snprintf(body, 510, "<html><head>" 459 "<meta charset=utf-8 http-equiv=\"Content-Language\" " 460 "content=\"en\"/>" 461 "<link rel=\"stylesheet\" type=\"text/css\" " 462 "href=\"/error.css\"/>" 463 "</head><body><h1>%u</h1>%s</body></html>", code, html_body); 464 465 466 n = lws_snprintf(slen, 12, "%d", len); 467 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, 468 (unsigned char *)slen, n, &p, end)) 469 return 1; 470 471 if (lws_finalize_http_header(wsi, &p, end)) 472 return 1; 473 474#if defined(LWS_WITH_HTTP2) 475 if (wsi->mux_substream) { 476 477 /* 478 * for HTTP/2, the headers must be sent separately, since they 479 * go out in their own frame. That puts us in a bind that 480 * we won't always be able to get away with two lws_write()s in 481 * sequence, since the first may use up the writability due to 482 * the pipe being choked or SSL_WANT_. 483 * 484 * However we do need to send the human-readable body, and the 485 * END_STREAM. 486 * 487 * Solve it by writing the headers now... 488 */ 489 m = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), 490 LWS_WRITE_HTTP_HEADERS); 491 if (m != lws_ptr_diff(p, start)) 492 return 1; 493 494 /* 495 * ... but stash the body and send it as a priority next 496 * handle_POLLOUT 497 */ 498 wsi->http.tx_content_length = (unsigned int)len; 499 wsi->http.tx_content_remain = (unsigned int)len; 500 501 wsi->h2.pending_status_body = lws_malloc((unsigned int)len + LWS_PRE + 1, 502 "pending status body"); 503 if (!wsi->h2.pending_status_body) 504 return -1; 505 506 strcpy(wsi->h2.pending_status_body + LWS_PRE, body); 507 lws_callback_on_writable(wsi); 508 509 return 0; 510 } else 511#endif 512 { 513 /* 514 * for http/1, we can just append the body after the finalized 515 * headers and send it all in one go. 516 */ 517 518 n = lws_ptr_diff(p, start) + len; 519 memcpy(p, body, (unsigned int)len); 520 m = lws_write(wsi, start, (unsigned int)n, LWS_WRITE_HTTP); 521 if (m != n) 522 return 1; 523 } 524 525 return m != n; 526} 527 528int 529lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, 530 unsigned char **p, unsigned char *end) 531{ 532 unsigned char *start = *p; 533 534 if (lws_add_http_header_status(wsi, (unsigned int)code, p, end)) 535 return -1; 536 537 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, loc, len, 538 p, end)) 539 return -1; 540 /* 541 * if we're going with http/1.1 and keepalive, we have to give fake 542 * content metadata so the client knows we completed the transaction and 543 * it can do the redirect... 544 */ 545 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, 546 (unsigned char *)"text/html", 9, p, 547 end)) 548 return -1; 549 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, 550 (unsigned char *)"0", 1, p, end)) 551 return -1; 552 553 if (lws_finalize_http_header(wsi, p, end)) 554 return -1; 555 556 return lws_write(wsi, start, lws_ptr_diff_size_t(*p, start), 557 LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); 558} 559#endif 560 561#if !defined(LWS_WITH_HTTP_STREAM_COMPRESSION) 562int 563lws_http_compression_apply(struct lws *wsi, const char *name, 564 unsigned char **p, unsigned char *end, char decomp) 565{ 566 (void)wsi; 567 (void)name; 568 (void)p; 569 (void)end; 570 (void)decomp; 571 572 return 0; 573} 574#endif 575 576int 577lws_http_headers_detach(struct lws *wsi) 578{ 579 return lws_header_table_detach(wsi, 0); 580} 581 582#if defined(LWS_WITH_SERVER) 583 584void 585lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t *sul) 586{ 587 struct allocated_headers *ah; 588 struct lws_context_per_thread *pt = lws_container_of(sul, 589 struct lws_context_per_thread, sul_ah_lifecheck); 590 struct lws *wsi; 591 time_t now; 592 int m; 593 594 now = time(NULL); 595 596 lws_pt_lock(pt, __func__); 597 598 ah = pt->http.ah_list; 599 while (ah) { 600 int len; 601 char buf[256]; 602 const unsigned char *c; 603 604 if (!ah->in_use || !ah->wsi || !ah->assigned || 605 (ah->wsi->a.vhost && 606 (now - ah->assigned) < 607 ah->wsi->a.vhost->timeout_secs_ah_idle + 360)) { 608 ah = ah->next; 609 continue; 610 } 611 612 /* 613 * a single ah session somehow got held for 614 * an unreasonable amount of time. 615 * 616 * Dump info on the connection... 617 */ 618 wsi = ah->wsi; 619 buf[0] = '\0'; 620#if !defined(LWS_PLAT_OPTEE) 621 lws_get_peer_simple(wsi, buf, sizeof(buf)); 622#else 623 buf[0] = '\0'; 624#endif 625 lwsl_notice("%s: ah excessive hold: wsi %p\n" 626 " peer address: %s\n" 627 " ah pos %lu\n", __func__, lws_wsi_tag(wsi), 628 buf, (unsigned long)ah->pos); 629 buf[0] = '\0'; 630 m = 0; 631 do { 632 c = lws_token_to_string((enum lws_token_indexes)m); 633 if (!c) 634 break; 635 if (!(*c)) 636 break; 637 638 len = lws_hdr_total_length(wsi, (enum lws_token_indexes)m); 639 if (!len || len > (int)sizeof(buf) - 1) { 640 m++; 641 continue; 642 } 643 644 if (lws_hdr_copy(wsi, buf, sizeof buf, (enum lws_token_indexes)m) > 0) { 645 buf[sizeof(buf) - 1] = '\0'; 646 647 lwsl_notice(" %s = %s\n", 648 (const char *)c, buf); 649 } 650 m++; 651 } while (1); 652 653 /* explicitly detach the ah */ 654 lws_header_table_detach(wsi, 0); 655 656 /* ... and then drop the connection */ 657 658 __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, 659 "excessive ah"); 660 661 ah = pt->http.ah_list; 662 } 663 664 lws_pt_unlock(pt); 665} 666#endif 667