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