/src/libwebsockets/lib/roles/http/header.c
Line | Count | Source |
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 * |
30 | | lws_token_to_string(enum lws_token_indexes token) |
31 | 0 | { |
32 | 0 | if ((unsigned int)token >= LWS_ARRAY_SIZE(set)) |
33 | 0 | return NULL; |
34 | | |
35 | 0 | return (unsigned char *)set[token]; |
36 | 0 | } |
37 | | |
38 | | /* |
39 | | * Return http header index if one matches slen chars of s, or -1 |
40 | | */ |
41 | | |
42 | | int |
43 | | lws_http_string_to_known_header(const char *s, size_t slen) |
44 | 0 | { |
45 | 0 | int n; |
46 | |
|
47 | 0 | for (n = 0; n < (int)LWS_ARRAY_SIZE(set); n++) |
48 | 0 | if (!strncmp(set[n], s, slen)) |
49 | 0 | return n; |
50 | | |
51 | 0 | return LWS_HTTP_NO_KNOWN_HEADER; |
52 | 0 | } |
53 | | |
54 | | #ifdef LWS_WITH_HTTP2 |
55 | | int |
56 | | lws_wsi_is_h2(struct lws *wsi) |
57 | 0 | { |
58 | 0 | return wsi->upgraded_to_http2 || |
59 | 0 | wsi->mux_substream || |
60 | 0 | #if defined(LWS_WITH_CLIENT) |
61 | 0 | wsi->client_mux_substream || |
62 | 0 | #endif |
63 | 0 | lwsi_role_h2(wsi) || |
64 | 0 | lwsi_role_h2_ENCAPSULATION(wsi); |
65 | 0 | } |
66 | | #endif |
67 | | |
68 | | int |
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 | 0 | { |
73 | 0 | #ifdef LWS_WITH_HTTP2 |
74 | 0 | if (lws_wsi_is_h2(wsi)) |
75 | 0 | return lws_add_http2_header_by_name(wsi, name, |
76 | 0 | value, length, p, end); |
77 | | #else |
78 | | (void)wsi; |
79 | | #endif |
80 | 0 | if (name) { |
81 | 0 | char has_colon = 0; |
82 | 0 | while (*p < end && *name) { |
83 | 0 | has_colon = has_colon || *name == ':'; |
84 | 0 | *((*p)++) = *name++; |
85 | 0 | } |
86 | 0 | if (*p + (has_colon ? 1 : 2) >= end) |
87 | 0 | return 1; |
88 | 0 | if (!has_colon) |
89 | 0 | *((*p)++) = ':'; |
90 | 0 | *((*p)++) = ' '; |
91 | 0 | } |
92 | 0 | if (*p + length + 3 >= end) |
93 | 0 | return 1; |
94 | | |
95 | 0 | if (value) |
96 | 0 | memcpy(*p, value, (unsigned int)length); |
97 | 0 | *p += length; |
98 | 0 | *((*p)++) = '\x0d'; |
99 | 0 | *((*p)++) = '\x0a'; |
100 | |
|
101 | 0 | return 0; |
102 | 0 | } |
103 | | |
104 | | int lws_finalize_http_header(struct lws *wsi, unsigned char **p, |
105 | | unsigned char *end) |
106 | 0 | { |
107 | 0 | #ifdef LWS_WITH_HTTP2 |
108 | 0 | if (lws_wsi_is_h2(wsi)) |
109 | 0 | return 0; |
110 | | #else |
111 | | (void)wsi; |
112 | | #endif |
113 | 0 | if ((lws_intptr_t)(end - *p) < 3) |
114 | 0 | return 1; |
115 | 0 | *((*p)++) = '\x0d'; |
116 | 0 | *((*p)++) = '\x0a'; |
117 | |
|
118 | 0 | return 0; |
119 | 0 | } |
120 | | |
121 | | int |
122 | | lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, |
123 | | unsigned char **pp, unsigned char *end) |
124 | 0 | { |
125 | 0 | unsigned char *p; |
126 | 0 | int len; |
127 | |
|
128 | 0 | if (lws_finalize_http_header(wsi, pp, end)) |
129 | 0 | return 1; |
130 | | |
131 | 0 | p = *pp; |
132 | 0 | len = lws_ptr_diff(p, start); |
133 | |
|
134 | 0 | if (lws_write(wsi, start, (unsigned int)len, LWS_WRITE_HTTP_HEADERS) != len) |
135 | 0 | return 1; |
136 | | |
137 | 0 | return 0; |
138 | 0 | } |
139 | | |
140 | | int |
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 | 0 | { |
145 | 0 | const unsigned char *name; |
146 | 0 | #ifdef LWS_WITH_HTTP2 |
147 | 0 | if (lws_wsi_is_h2(wsi)) |
148 | 0 | return lws_add_http2_header_by_token(wsi, token, value, |
149 | 0 | length, p, end); |
150 | 0 | #endif |
151 | 0 | name = lws_token_to_string(token); |
152 | 0 | if (!name) |
153 | 0 | return 1; |
154 | | |
155 | 0 | return lws_add_http_header_by_name(wsi, name, value, length, p, end); |
156 | 0 | } |
157 | | |
158 | | int |
159 | | lws_add_http_header_content_length(struct lws *wsi, |
160 | | lws_filepos_t content_length, |
161 | | unsigned char **p, unsigned char *end) |
162 | 0 | { |
163 | 0 | char b[24]; |
164 | 0 | int n; |
165 | |
|
166 | 0 | n = lws_snprintf(b, sizeof(b) - 1, "%llu", (unsigned long long)content_length); |
167 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, |
168 | 0 | (unsigned char *)b, n, p, end)) |
169 | 0 | return 1; |
170 | 0 | wsi->http.tx_content_length = content_length; |
171 | 0 | wsi->http.tx_content_remain = content_length; |
172 | |
|
173 | 0 | lwsl_info("%s: %s: tx_content_length/remain %llu\n", __func__, |
174 | 0 | lws_wsi_tag(wsi), (unsigned long long)content_length); |
175 | |
|
176 | 0 | return 0; |
177 | 0 | } |
178 | | |
179 | | #if defined(LWS_WITH_SERVER) |
180 | | |
181 | | int |
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 | 0 | { |
186 | 0 | const char *ka[] = { "close", "keep-alive" }; |
187 | 0 | int types[] = { HTTP_CONNECTION_CLOSE, HTTP_CONNECTION_KEEP_ALIVE }, |
188 | 0 | t = 0; |
189 | |
|
190 | 0 | if (lws_add_http_header_status(wsi, code, p, end)) |
191 | 0 | return 1; |
192 | | |
193 | 0 | if (content_type && |
194 | 0 | lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, |
195 | 0 | (unsigned char *)content_type, |
196 | 0 | (int)strlen(content_type), p, end)) |
197 | 0 | 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 | 0 | if ( |
212 | | #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) |
213 | | !wsi->http.lcs && |
214 | | #endif |
215 | 0 | content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN) { |
216 | 0 | if (lws_add_http_header_content_length(wsi, content_len, |
217 | 0 | p, end)) |
218 | 0 | return 1; |
219 | 0 | } 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 | 0 | if (!wsi->mux_substream) { |
246 | 0 | if (lws_add_http_header_by_token(wsi, |
247 | 0 | WSI_TOKEN_CONNECTION, |
248 | 0 | (unsigned char *)ka[t], |
249 | 0 | (int)strlen(ka[t]), p, end)) |
250 | 0 | return 1; |
251 | | |
252 | 0 | wsi->http.conn_type = (enum http_conn_type)types[t]; |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | 0 | return 0; |
257 | 0 | } |
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 |
310 | | lws_add_http_header_status(struct lws *wsi, unsigned int _code, |
311 | | unsigned char **p, unsigned char *end) |
312 | 0 | { |
313 | 0 | static const char * const hver[] = { |
314 | 0 | "HTTP/1.0", "HTTP/1.1", "HTTP/2" |
315 | 0 | }; |
316 | 0 | const struct lws_protocol_vhost_options *headers, *ho; |
317 | 0 | unsigned int code = _code & LWSAHH_CODE_MASK; |
318 | 0 | const char *description = "", *p1; |
319 | 0 | unsigned char code_and_desc[60]; |
320 | 0 | int n; |
321 | |
|
322 | 0 | wsi->http.response_code = code; |
323 | | #ifdef LWS_WITH_ACCESS_LOG |
324 | | wsi->http.access_log.response = (int)code; |
325 | | #endif |
326 | |
|
327 | 0 | #ifdef LWS_WITH_HTTP2 |
328 | 0 | if (lws_wsi_is_h2(wsi)) { |
329 | 0 | n = lws_add_http2_header_status(wsi, code, p, end); |
330 | 0 | if (n) |
331 | 0 | return n; |
332 | 0 | } else |
333 | 0 | #endif |
334 | 0 | { |
335 | 0 | if (code >= 400 && code < (400 + LWS_ARRAY_SIZE(err400))) |
336 | 0 | description = err400[code - 400]; |
337 | 0 | if (code >= 500 && code < (500 + LWS_ARRAY_SIZE(err500))) |
338 | 0 | description = err500[code - 500]; |
339 | |
|
340 | 0 | if (code == 100) |
341 | 0 | description = "Continue"; |
342 | 0 | if (code == 200) |
343 | 0 | description = "OK"; |
344 | 0 | if (code == 304) |
345 | 0 | description = "Not Modified"; |
346 | 0 | else |
347 | 0 | if (code >= 300 && code < 400) |
348 | 0 | description = "Redirect"; |
349 | |
|
350 | 0 | if (wsi->http.request_version < LWS_ARRAY_SIZE(hver)) |
351 | 0 | p1 = hver[wsi->http.request_version]; |
352 | 0 | else |
353 | 0 | p1 = hver[0]; |
354 | |
|
355 | 0 | n = lws_snprintf((char *)code_and_desc, |
356 | 0 | sizeof(code_and_desc) - 1, "%s %u %s", |
357 | 0 | p1, code, description); |
358 | |
|
359 | 0 | if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p, |
360 | 0 | end)) |
361 | 0 | return 1; |
362 | 0 | } |
363 | | |
364 | 0 | headers = wsi->a.vhost->headers; |
365 | |
|
366 | 0 | while (headers) { |
367 | | /* |
368 | | * Give mount headers the chance to individually override |
369 | | * each vhost header |
370 | | */ |
371 | 0 | ho = wsi->http.mount_specific_headers; |
372 | 0 | while (ho) { |
373 | 0 | if (!strcmp(ho->name, headers->name)) |
374 | 0 | break; |
375 | 0 | ho = ho->next; |
376 | 0 | } |
377 | | /* if there was no mount header of same name, use vhost one */ |
378 | 0 | if (!ho) |
379 | 0 | ho = headers; |
380 | |
|
381 | 0 | if (lws_add_http_header_by_name(wsi, |
382 | 0 | (const unsigned char *)ho->name, |
383 | 0 | (unsigned char *)ho->value, |
384 | 0 | (int)strlen(ho->value), p, end)) |
385 | 0 | return 1; |
386 | | |
387 | 0 | headers = headers->next; |
388 | 0 | } |
389 | | |
390 | 0 | if (wsi->a.vhost->options & |
391 | 0 | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE) { |
392 | 0 | headers = &pvo_hsbph[LWS_ARRAY_SIZE(pvo_hsbph) - 1]; |
393 | 0 | while (headers) { |
394 | 0 | if (lws_add_http_header_by_name(wsi, |
395 | 0 | (const unsigned char *)headers->name, |
396 | 0 | (unsigned char *)headers->value, |
397 | 0 | (int)strlen(headers->value), p, end)) |
398 | 0 | return 1; |
399 | | |
400 | 0 | headers = headers->next; |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | 0 | if (wsi->a.context->server_string && |
405 | 0 | !(_code & LWSAHH_FLAG_NO_SERVER_NAME)) { |
406 | 0 | assert(wsi->a.context->server_string_len > 0); |
407 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, |
408 | 0 | (unsigned char *)wsi->a.context->server_string, |
409 | 0 | wsi->a.context->server_string_len, p, end)) |
410 | 0 | return 1; |
411 | 0 | } |
412 | | |
413 | 0 | if (wsi->a.vhost->options & LWS_SERVER_OPTION_STS) |
414 | 0 | if (lws_add_http_header_by_name(wsi, (unsigned char *) |
415 | 0 | "Strict-Transport-Security:", |
416 | 0 | (unsigned char *)"max-age=15768000 ; " |
417 | 0 | "includeSubDomains", 36, p, end)) |
418 | 0 | return 1; |
419 | | |
420 | 0 | if (*p >= (end - 2)) { |
421 | 0 | lwsl_err("%s: reached end of buffer\n", __func__); |
422 | |
|
423 | 0 | return 1; |
424 | 0 | } |
425 | | |
426 | 0 | return 0; |
427 | 0 | } |
428 | | |
429 | | int |
430 | | lws_return_http_status(struct lws *wsi, unsigned int code, |
431 | | const char *html_body) |
432 | 0 | { |
433 | 0 | struct lws_context *context = lws_get_context(wsi); |
434 | 0 | struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; |
435 | 0 | unsigned char *p = pt->serv_buf + LWS_PRE; |
436 | 0 | unsigned char *start = p; |
437 | 0 | unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; |
438 | 0 | char *body = (char *)start + context->pt_serv_buf_size - 512; |
439 | 0 | int n = 0, m = 0, len; |
440 | 0 | char slen[20]; |
441 | |
|
442 | 0 | if (!wsi->a.vhost) { |
443 | 0 | lwsl_err("%s: wsi not bound to vhost\n", __func__); |
444 | |
|
445 | 0 | return 1; |
446 | 0 | } |
447 | 0 | #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) |
448 | 0 | if (!wsi->handling_404 && |
449 | 0 | wsi->a.vhost->http.error_document_404 && |
450 | 0 | code == HTTP_STATUS_NOT_FOUND) |
451 | | /* we should do a redirect, and do the 404 there */ |
452 | 0 | if (lws_http_redirect(wsi, HTTP_STATUS_FOUND, |
453 | 0 | (uint8_t *)wsi->a.vhost->http.error_document_404, |
454 | 0 | (int)strlen(wsi->a.vhost->http.error_document_404), |
455 | 0 | &p, end) > 0) |
456 | 0 | return 0; |
457 | 0 | #endif |
458 | | |
459 | | /* if the redirect failed, just do a simple status */ |
460 | 0 | p = start; |
461 | |
|
462 | 0 | if (!html_body) |
463 | 0 | html_body = ""; |
464 | |
|
465 | 0 | if (lws_add_http_header_status(wsi, code, &p, end)) |
466 | 0 | return 1; |
467 | | |
468 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, |
469 | 0 | (unsigned char *)"text/html", 9, |
470 | 0 | &p, end)) |
471 | 0 | return 1; |
472 | | |
473 | 0 | len = lws_snprintf(body, 510, "<html><head>" |
474 | 0 | "<meta charset=utf-8 http-equiv=\"Content-Language\" " |
475 | 0 | "content=\"en\"/>" |
476 | 0 | "<link rel=\"stylesheet\" type=\"text/css\" " |
477 | 0 | "href=\"/error.css\"/>" |
478 | 0 | "</head><body><h1>%u</h1>%s</body></html>", code, html_body); |
479 | | |
480 | |
|
481 | 0 | n = lws_snprintf(slen, 12, "%d", len); |
482 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, |
483 | 0 | (unsigned char *)slen, n, &p, end)) |
484 | 0 | return 1; |
485 | | |
486 | 0 | if (lws_finalize_http_header(wsi, &p, end)) |
487 | 0 | return 1; |
488 | | |
489 | 0 | #if defined(LWS_WITH_HTTP2) |
490 | 0 | if (wsi->mux_substream) { |
491 | | |
492 | | /* |
493 | | * for HTTP/2, the headers must be sent separately, since they |
494 | | * go out in their own frame. That puts us in a bind that |
495 | | * we won't always be able to get away with two lws_write()s in |
496 | | * sequence, since the first may use up the writability due to |
497 | | * the pipe being choked or SSL_WANT_. |
498 | | * |
499 | | * However we do need to send the human-readable body, and the |
500 | | * END_STREAM. |
501 | | * |
502 | | * Solve it by writing the headers now... |
503 | | */ |
504 | 0 | m = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), |
505 | 0 | LWS_WRITE_HTTP_HEADERS); |
506 | 0 | if (m != lws_ptr_diff(p, start)) |
507 | 0 | return 1; |
508 | | |
509 | | /* |
510 | | * ... but stash the body and send it as a priority next |
511 | | * handle_POLLOUT |
512 | | */ |
513 | 0 | wsi->http.tx_content_length = (unsigned int)len; |
514 | 0 | wsi->http.tx_content_remain = (unsigned int)len; |
515 | |
|
516 | 0 | wsi->h2.pending_status_body = lws_malloc((unsigned int)len + LWS_PRE + 1, |
517 | 0 | "pending status body"); |
518 | 0 | if (!wsi->h2.pending_status_body) |
519 | 0 | return -1; |
520 | | |
521 | 0 | strcpy(wsi->h2.pending_status_body + LWS_PRE, body); |
522 | 0 | lws_callback_on_writable(wsi); |
523 | |
|
524 | 0 | return 0; |
525 | 0 | } else |
526 | 0 | #endif |
527 | 0 | { |
528 | | /* |
529 | | * for http/1, we can just append the body after the finalized |
530 | | * headers and send it all in one go. |
531 | | */ |
532 | |
|
533 | 0 | n = lws_ptr_diff(p, start) + len; |
534 | 0 | memcpy(p, body, (unsigned int)len); |
535 | 0 | m = lws_write(wsi, start, (unsigned int)n, LWS_WRITE_HTTP); |
536 | 0 | if (m != n) |
537 | 0 | return 1; |
538 | 0 | } |
539 | | |
540 | 0 | return m != n; |
541 | 0 | } |
542 | | |
543 | | int |
544 | | lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, |
545 | | unsigned char **p, unsigned char *end) |
546 | 0 | { |
547 | 0 | unsigned char *start = *p; |
548 | |
|
549 | 0 | if (lws_add_http_header_status(wsi, (unsigned int)code, p, end)) |
550 | 0 | return -1; |
551 | | |
552 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, loc, len, |
553 | 0 | p, end)) |
554 | 0 | return -1; |
555 | | /* |
556 | | * if we're going with http/1.1 and keepalive, we have to give fake |
557 | | * content metadata so the client knows we completed the transaction and |
558 | | * it can do the redirect... |
559 | | */ |
560 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, |
561 | 0 | (unsigned char *)"text/html", 9, p, |
562 | 0 | end)) |
563 | 0 | return -1; |
564 | 0 | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, |
565 | 0 | (unsigned char *)"0", 1, p, end)) |
566 | 0 | return -1; |
567 | | |
568 | 0 | if (lws_finalize_http_header(wsi, p, end)) |
569 | 0 | return -1; |
570 | | |
571 | 0 | return lws_write(wsi, start, lws_ptr_diff_size_t(*p, start), |
572 | 0 | LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); |
573 | 0 | } |
574 | | #endif |
575 | | |
576 | | #if !defined(LWS_WITH_HTTP_STREAM_COMPRESSION) |
577 | | int |
578 | | lws_http_compression_apply(struct lws *wsi, const char *name, |
579 | | unsigned char **p, unsigned char *end, char decomp) |
580 | 0 | { |
581 | 0 | (void)wsi; |
582 | 0 | (void)name; |
583 | 0 | (void)p; |
584 | 0 | (void)end; |
585 | 0 | (void)decomp; |
586 | |
|
587 | 0 | return 0; |
588 | 0 | } |
589 | | #endif |
590 | | |
591 | | int |
592 | | lws_http_headers_detach(struct lws *wsi) |
593 | 0 | { |
594 | 0 | return lws_header_table_detach(wsi, 0); |
595 | 0 | } |
596 | | |
597 | | #if defined(LWS_WITH_SERVER) |
598 | | |
599 | | void |
600 | | lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t *sul) |
601 | 0 | { |
602 | 0 | struct allocated_headers *ah; |
603 | 0 | struct lws_context_per_thread *pt = lws_container_of(sul, |
604 | 0 | struct lws_context_per_thread, sul_ah_lifecheck); |
605 | 0 | struct lws *wsi; |
606 | 0 | time_t now; |
607 | 0 | int m; |
608 | |
|
609 | 0 | now = time(NULL); |
610 | |
|
611 | 0 | lws_pt_lock(pt, __func__); |
612 | |
|
613 | 0 | ah = pt->http.ah_list; |
614 | 0 | while (ah) { |
615 | 0 | int len; |
616 | 0 | char buf[256]; |
617 | 0 | const unsigned char *c; |
618 | |
|
619 | 0 | if (!ah->in_use || !ah->wsi || !ah->assigned || |
620 | 0 | (ah->wsi->a.vhost && |
621 | 0 | (now - ah->assigned) < |
622 | 0 | ah->wsi->a.vhost->timeout_secs_ah_idle + 360)) { |
623 | 0 | ah = ah->next; |
624 | 0 | continue; |
625 | 0 | } |
626 | | |
627 | | /* |
628 | | * a single ah session somehow got held for |
629 | | * an unreasonable amount of time. |
630 | | * |
631 | | * Dump info on the connection... |
632 | | */ |
633 | 0 | wsi = ah->wsi; |
634 | 0 | buf[0] = '\0'; |
635 | 0 | #if !defined(LWS_PLAT_OPTEE) |
636 | 0 | lws_get_peer_simple(wsi, buf, sizeof(buf)); |
637 | | #else |
638 | | buf[0] = '\0'; |
639 | | #endif |
640 | 0 | lwsl_notice("%s: ah excessive hold: wsi %p\n" |
641 | 0 | " peer address: %s\n" |
642 | 0 | " ah pos %lu\n", __func__, lws_wsi_tag(wsi), |
643 | 0 | buf, (unsigned long)ah->pos); |
644 | 0 | buf[0] = '\0'; |
645 | 0 | m = 0; |
646 | 0 | do { |
647 | 0 | c = lws_token_to_string((enum lws_token_indexes)m); |
648 | 0 | if (!c) |
649 | 0 | break; |
650 | 0 | if (!(*c)) |
651 | 0 | break; |
652 | | |
653 | 0 | len = lws_hdr_total_length(wsi, (enum lws_token_indexes)m); |
654 | 0 | if (!len || len > (int)sizeof(buf) - 1) { |
655 | 0 | m++; |
656 | 0 | continue; |
657 | 0 | } |
658 | | |
659 | 0 | if (lws_hdr_copy(wsi, buf, sizeof buf, (enum lws_token_indexes)m) > 0) { |
660 | 0 | buf[sizeof(buf) - 1] = '\0'; |
661 | |
|
662 | 0 | lwsl_notice(" %s = %s\n", |
663 | 0 | (const char *)c, buf); |
664 | 0 | } |
665 | 0 | m++; |
666 | 0 | } while (1); |
667 | | |
668 | | /* explicitly detach the ah */ |
669 | 0 | lws_header_table_detach(wsi, 0); |
670 | | |
671 | | /* ... and then drop the connection */ |
672 | |
|
673 | 0 | __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, |
674 | 0 | "excessive ah"); |
675 | |
|
676 | 0 | ah = pt->http.ah_list; |
677 | 0 | } |
678 | |
|
679 | 0 | lws_pt_unlock(pt); |
680 | 0 | } |
681 | | #endif |
682 | | |
683 | | int |
684 | | lws_http_zap_header(struct lws *wsi, const char *name) |
685 | 0 | { |
686 | 0 | int n = (int)strlen(name); |
687 | 0 | int index; |
688 | |
|
689 | 0 | if (!wsi->http.ah) |
690 | 0 | return 0; |
691 | | |
692 | 0 | index = lws_http_string_to_known_header(name, (size_t)n); |
693 | 0 | if (index != LWS_HTTP_NO_KNOWN_HEADER && index < WSI_TOKEN_COUNT) { |
694 | 0 | wsi->http.ah->frag_index[index] = 0; |
695 | 0 | return 0; |
696 | 0 | } |
697 | | |
698 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
699 | 0 | { |
700 | 0 | ah_data_idx_t ll = wsi->http.ah->unk_ll_head, prev = 0; |
701 | |
|
702 | 0 | while (ll) { |
703 | 0 | if (ll >= wsi->http.ah->data_length) |
704 | 0 | return 1; |
705 | | |
706 | 0 | if (n == lws_ser_ru16be( |
707 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]) && |
708 | 0 | !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], (unsigned int)n)) { |
709 | | /* found it, remove from list */ |
710 | 0 | ah_data_idx_t next = lws_ser_ru32be( |
711 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_LL]); |
712 | |
|
713 | 0 | if (!prev) |
714 | 0 | wsi->http.ah->unk_ll_head = next; |
715 | 0 | else |
716 | 0 | lws_ser_wu32be( |
717 | 0 | (uint8_t *)&wsi->http.ah->data[prev + UHO_LL], |
718 | 0 | next); |
719 | |
|
720 | 0 | if (!next) |
721 | 0 | wsi->http.ah->unk_ll_tail = prev; |
722 | |
|
723 | 0 | return 0; |
724 | 0 | } |
725 | | |
726 | 0 | prev = ll; |
727 | 0 | ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]); |
728 | 0 | } |
729 | 0 | } |
730 | 0 | #endif |
731 | | |
732 | 0 | return 0; |
733 | 0 | } |