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