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
29 const unsigned char *
lws_token_to_string(enum lws_token_indexes token)30 lws_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
42 int
lws_http_string_to_known_header(const char * s,size_t slen)43 lws_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
55 int
lws_wsi_is_h2(struct lws * wsi)56 lws_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
68 int
lws_add_http_header_by_name(struct lws * wsi,const unsigned char * name,const unsigned char * value,int length,unsigned char ** p,unsigned char * end)69 lws_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
lws_finalize_http_header(struct lws * wsi,unsigned char ** p,unsigned char * end)104 int 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
121 int
lws_finalize_write_http_header(struct lws * wsi,unsigned char * start,unsigned char ** pp,unsigned char * end)122 lws_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
140 int
lws_add_http_header_by_token(struct lws * wsi,enum lws_token_indexes token,const unsigned char * value,int length,unsigned char ** p,unsigned char * end)141 lws_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
158 int
lws_add_http_header_content_length(struct lws * wsi,lws_filepos_t content_length,unsigned char ** p,unsigned char * end)159 lws_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
181 int
lws_add_http_common_headers(struct lws * wsi,unsigned int code,const char * content_type,lws_filepos_t content_len,unsigned char ** p,unsigned char * end)182 lws_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
259 static 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
280 static 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
291 static const
292 struct 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
309 int
lws_add_http_header_status(struct lws * wsi,unsigned int _code,unsigned char ** p,unsigned char * end)310 lws_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
414 int
lws_return_http_status(struct lws * wsi,unsigned int code,const char * html_body)415 lws_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
528 int
lws_http_redirect(struct lws * wsi,int code,const unsigned char * loc,int len,unsigned char ** p,unsigned char * end)529 lws_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)
562 int
lws_http_compression_apply(struct lws * wsi,const char * name,unsigned char ** p,unsigned char * end,char decomp)563 lws_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
576 int
lws_http_headers_detach(struct lws * wsi)577 lws_http_headers_detach(struct lws *wsi)
578 {
579 return lws_header_table_detach(wsi, 0);
580 }
581
582 #if defined(LWS_WITH_SERVER)
583
584 void
lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t * sul)585 lws_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