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