• 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 		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