/src/libwebsockets/lib/roles/http/parsers.c
Line | Count | Source (jump to first uncovered line) |
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 | | |
27 | | static const unsigned char lextable_h1[] = { |
28 | | #include "lextable.h" |
29 | | }; |
30 | | |
31 | 0 | #define FAIL_CHAR 0x08 |
32 | | |
33 | | #if defined(LWS_WITH_CUSTOM_HEADERS) |
34 | | |
35 | 0 | #define UHO_NLEN 0 |
36 | 0 | #define UHO_VLEN 2 |
37 | 0 | #define UHO_LL 4 |
38 | 0 | #define UHO_NAME 8 |
39 | | |
40 | | #endif |
41 | | |
42 | | static struct allocated_headers * |
43 | | _lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) |
44 | 0 | { |
45 | 0 | struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct"); |
46 | |
|
47 | 0 | if (!ah) |
48 | 0 | return NULL; |
49 | | |
50 | 0 | ah->data = lws_malloc(data_size, "ah data"); |
51 | 0 | if (!ah->data) { |
52 | 0 | lws_free(ah); |
53 | |
|
54 | 0 | return NULL; |
55 | 0 | } |
56 | 0 | ah->next = pt->http.ah_list; |
57 | 0 | pt->http.ah_list = ah; |
58 | 0 | ah->data_length = data_size; |
59 | 0 | pt->http.ah_pool_length++; |
60 | |
|
61 | 0 | lwsl_info("%s: created ah %p (size %d): pool length %u\n", __func__, |
62 | 0 | ah, (int)data_size, (unsigned int)pt->http.ah_pool_length); |
63 | |
|
64 | 0 | return ah; |
65 | 0 | } |
66 | | |
67 | | int |
68 | | _lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) |
69 | 0 | { |
70 | 0 | lws_start_foreach_llp(struct allocated_headers **, a, pt->http.ah_list) { |
71 | 0 | if ((*a) == ah) { |
72 | 0 | *a = ah->next; |
73 | 0 | pt->http.ah_pool_length--; |
74 | 0 | lwsl_info("%s: freed ah %p : pool length %u\n", |
75 | 0 | __func__, ah, |
76 | 0 | (unsigned int)pt->http.ah_pool_length); |
77 | 0 | if (ah->data) |
78 | 0 | lws_free(ah->data); |
79 | 0 | lws_free(ah); |
80 | |
|
81 | 0 | return 0; |
82 | 0 | } |
83 | 0 | } lws_end_foreach_llp(a, next); |
84 | |
|
85 | 0 | return 1; |
86 | 0 | } |
87 | | |
88 | | void |
89 | | _lws_header_table_reset(struct allocated_headers *ah) |
90 | 0 | { |
91 | | /* init the ah to reflect no headers or data have appeared yet */ |
92 | 0 | memset(ah->frag_index, 0, sizeof(ah->frag_index)); |
93 | 0 | memset(ah->frags, 0, sizeof(ah->frags)); |
94 | 0 | ah->nfrag = 0; |
95 | 0 | ah->pos = 0; |
96 | 0 | ah->http_response = 0; |
97 | 0 | ah->parser_state = WSI_TOKEN_NAME_PART; |
98 | 0 | ah->lextable_pos = 0; |
99 | 0 | ah->unk_pos = 0; |
100 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
101 | 0 | ah->unk_ll_head = 0; |
102 | 0 | ah->unk_ll_tail = 0; |
103 | 0 | #endif |
104 | 0 | } |
105 | | |
106 | | // doesn't scrub the ah rxbuffer by default, parent must do if needed |
107 | | |
108 | | void |
109 | | __lws_header_table_reset(struct lws *wsi, int autoservice) |
110 | 0 | { |
111 | 0 | struct allocated_headers *ah = wsi->http.ah; |
112 | 0 | struct lws_context_per_thread *pt; |
113 | 0 | struct lws_pollfd *pfd; |
114 | | |
115 | | /* if we have the idea we're resetting 'our' ah, must be bound to one */ |
116 | 0 | assert(ah); |
117 | | /* ah also concurs with ownership */ |
118 | 0 | assert(ah->wsi == wsi); |
119 | | |
120 | 0 | _lws_header_table_reset(ah); |
121 | | |
122 | | /* since we will restart the ah, our new headers are not completed */ |
123 | 0 | wsi->hdr_parsing_completed = 0; |
124 | | |
125 | | /* while we hold the ah, keep a timeout on the wsi */ |
126 | 0 | __lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, |
127 | 0 | wsi->a.vhost->timeout_secs_ah_idle); |
128 | |
|
129 | 0 | time(&ah->assigned); |
130 | |
|
131 | 0 | if (wsi->position_in_fds_table != LWS_NO_FDS_POS && |
132 | 0 | lws_buflist_next_segment_len(&wsi->buflist, NULL) && |
133 | 0 | autoservice) { |
134 | 0 | lwsl_debug("%s: service on readbuf ah\n", __func__); |
135 | |
|
136 | 0 | pt = &wsi->a.context->pt[(int)wsi->tsi]; |
137 | | /* |
138 | | * Unlike a normal connect, we have the headers already |
139 | | * (or the first part of them anyway) |
140 | | */ |
141 | 0 | pfd = &pt->fds[wsi->position_in_fds_table]; |
142 | 0 | pfd->revents |= LWS_POLLIN; |
143 | 0 | lwsl_err("%s: calling service\n", __func__); |
144 | 0 | lws_service_fd_tsi(wsi->a.context, pfd, wsi->tsi); |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | void |
149 | | lws_header_table_reset(struct lws *wsi, int autoservice) |
150 | 0 | { |
151 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
152 | |
|
153 | 0 | lws_pt_lock(pt, __func__); |
154 | |
|
155 | 0 | __lws_header_table_reset(wsi, autoservice); |
156 | |
|
157 | 0 | lws_pt_unlock(pt); |
158 | 0 | } |
159 | | |
160 | | static void |
161 | | _lws_header_ensure_we_are_on_waiting_list(struct lws *wsi) |
162 | 0 | { |
163 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
164 | 0 | struct lws_pollargs pa; |
165 | 0 | struct lws **pwsi = &pt->http.ah_wait_list; |
166 | |
|
167 | 0 | while (*pwsi) { |
168 | 0 | if (*pwsi == wsi) |
169 | 0 | return; |
170 | 0 | pwsi = &(*pwsi)->http.ah_wait_list; |
171 | 0 | } |
172 | | |
173 | 0 | lwsl_info("%s: wsi: %s\n", __func__, lws_wsi_tag(wsi)); |
174 | 0 | wsi->http.ah_wait_list = pt->http.ah_wait_list; |
175 | 0 | pt->http.ah_wait_list = wsi; |
176 | 0 | pt->http.ah_wait_list_length++; |
177 | | |
178 | | /* we cannot accept input then */ |
179 | |
|
180 | 0 | _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa); |
181 | 0 | } |
182 | | |
183 | | static int |
184 | | __lws_remove_from_ah_waiting_list(struct lws *wsi) |
185 | 0 | { |
186 | 0 | struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; |
187 | 0 | struct lws **pwsi =&pt->http.ah_wait_list; |
188 | |
|
189 | 0 | while (*pwsi) { |
190 | 0 | if (*pwsi == wsi) { |
191 | 0 | lwsl_info("%s: wsi %s\n", __func__, lws_wsi_tag(wsi)); |
192 | | /* point prev guy to our next */ |
193 | 0 | *pwsi = wsi->http.ah_wait_list; |
194 | | /* we shouldn't point anywhere now */ |
195 | 0 | wsi->http.ah_wait_list = NULL; |
196 | 0 | pt->http.ah_wait_list_length--; |
197 | |
|
198 | 0 | return 1; |
199 | 0 | } |
200 | 0 | pwsi = &(*pwsi)->http.ah_wait_list; |
201 | 0 | } |
202 | | |
203 | 0 | return 0; |
204 | 0 | } |
205 | | |
206 | | int LWS_WARN_UNUSED_RESULT |
207 | | lws_header_table_attach(struct lws *wsi, int autoservice) |
208 | 0 | { |
209 | 0 | struct lws_context *context = wsi->a.context; |
210 | 0 | struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; |
211 | 0 | struct lws_pollargs pa; |
212 | 0 | int n; |
213 | |
|
214 | | #if defined(LWS_ROLE_MQTT) && defined(LWS_WITH_CLIENT) |
215 | | if (lwsi_role_mqtt(wsi)) |
216 | | goto connect_via_info2; |
217 | | #endif |
218 | |
|
219 | 0 | lwsl_info("%s: %s: ah %p (tsi %d, count = %d) in\n", __func__, |
220 | 0 | lws_wsi_tag(wsi), (void *)wsi->http.ah, wsi->tsi, |
221 | 0 | pt->http.ah_count_in_use); |
222 | |
|
223 | 0 | if (!lwsi_role_http(wsi)) { |
224 | 0 | lwsl_err("%s: bad role %s\n", __func__, wsi->role_ops->name); |
225 | 0 | assert(0); |
226 | 0 | return -1; |
227 | 0 | } |
228 | | |
229 | 0 | lws_pt_lock(pt, __func__); |
230 | | |
231 | | /* if we are already bound to one, just clear it down */ |
232 | 0 | if (wsi->http.ah) { |
233 | 0 | lwsl_info("%s: cleardown\n", __func__); |
234 | 0 | goto reset; |
235 | 0 | } |
236 | | |
237 | 0 | n = pt->http.ah_count_in_use == (int)context->max_http_header_pool; |
238 | | #if defined(LWS_WITH_PEER_LIMITS) |
239 | | if (!n) |
240 | | n = lws_peer_confirm_ah_attach_ok(context, wsi->peer); |
241 | | #endif |
242 | 0 | if (n) { |
243 | | /* |
244 | | * Pool is either all busy, or we don't want to give this |
245 | | * particular guy an ah right now... |
246 | | * |
247 | | * Make sure we are on the waiting list, and return that we |
248 | | * weren't able to provide the ah |
249 | | */ |
250 | 0 | _lws_header_ensure_we_are_on_waiting_list(wsi); |
251 | |
|
252 | 0 | goto bail; |
253 | 0 | } |
254 | | |
255 | 0 | __lws_remove_from_ah_waiting_list(wsi); |
256 | |
|
257 | 0 | wsi->http.ah = _lws_create_ah(pt, context->max_http_header_data); |
258 | 0 | if (!wsi->http.ah) { /* we could not create an ah */ |
259 | 0 | _lws_header_ensure_we_are_on_waiting_list(wsi); |
260 | |
|
261 | 0 | goto bail; |
262 | 0 | } |
263 | | |
264 | 0 | wsi->http.ah->in_use = 1; |
265 | 0 | wsi->http.ah->wsi = wsi; /* mark our owner */ |
266 | 0 | pt->http.ah_count_in_use++; |
267 | |
|
268 | | #if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \ |
269 | | defined(LWS_ROLE_H2)) |
270 | | lws_context_lock(context, "ah attach"); /* <========================= */ |
271 | | if (wsi->peer) |
272 | | wsi->peer->http.count_ah++; |
273 | | lws_context_unlock(context); /* ====================================> */ |
274 | | #endif |
275 | |
|
276 | 0 | _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); |
277 | |
|
278 | 0 | lwsl_info("%s: did attach wsi %s: ah %p: count %d (on exit)\n", __func__, |
279 | 0 | lws_wsi_tag(wsi), (void *)wsi->http.ah, pt->http.ah_count_in_use); |
280 | |
|
281 | 0 | reset: |
282 | 0 | __lws_header_table_reset(wsi, autoservice); |
283 | |
|
284 | 0 | lws_pt_unlock(pt); |
285 | |
|
286 | 0 | #if defined(LWS_WITH_CLIENT) |
287 | | #if defined(LWS_ROLE_MQTT) |
288 | | connect_via_info2: |
289 | | #endif |
290 | 0 | if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) |
291 | 0 | if (!lws_http_client_connect_via_info2(wsi)) |
292 | | /* our client connect has failed, the wsi |
293 | | * has been closed |
294 | | */ |
295 | 0 | return -1; |
296 | 0 | #endif |
297 | | |
298 | 0 | return 0; |
299 | | |
300 | 0 | bail: |
301 | 0 | lws_pt_unlock(pt); |
302 | |
|
303 | 0 | return 1; |
304 | 0 | } |
305 | | |
306 | | int __lws_header_table_detach(struct lws *wsi, int autoservice) |
307 | 0 | { |
308 | 0 | struct lws_context *context = wsi->a.context; |
309 | 0 | struct allocated_headers *ah = wsi->http.ah; |
310 | 0 | struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; |
311 | 0 | struct lws_pollargs pa; |
312 | 0 | struct lws **pwsi, **pwsi_eligible; |
313 | 0 | time_t now; |
314 | |
|
315 | 0 | __lws_remove_from_ah_waiting_list(wsi); |
316 | |
|
317 | 0 | if (!ah) |
318 | 0 | return 0; |
319 | | |
320 | 0 | lwsl_info("%s: %s: ah %p (tsi=%d, count = %d)\n", __func__, |
321 | 0 | lws_wsi_tag(wsi), (void *)ah, wsi->tsi, |
322 | 0 | pt->http.ah_count_in_use); |
323 | | |
324 | | /* we did have an ah attached */ |
325 | 0 | time(&now); |
326 | 0 | if (ah->assigned && now - ah->assigned > 3) { |
327 | | /* |
328 | | * we're detaching the ah, but it was held an |
329 | | * unreasonably long time |
330 | | */ |
331 | 0 | lwsl_debug("%s: %s: ah held %ds, role/state 0x%lx 0x%x," |
332 | 0 | "\n", __func__, lws_wsi_tag(wsi), |
333 | 0 | (int)(now - ah->assigned), |
334 | 0 | (unsigned long)lwsi_role(wsi), lwsi_state(wsi)); |
335 | 0 | } |
336 | |
|
337 | 0 | ah->assigned = 0; |
338 | | |
339 | | /* if we think we're detaching one, there should be one in use */ |
340 | 0 | assert(pt->http.ah_count_in_use > 0); |
341 | | /* and this specific one should have been in use */ |
342 | 0 | assert(ah->in_use); |
343 | 0 | memset(&wsi->http.ah, 0, sizeof(wsi->http.ah)); |
344 | |
|
345 | | #if defined(LWS_WITH_PEER_LIMITS) |
346 | | if (ah->wsi) |
347 | | lws_peer_track_ah_detach(context, wsi->peer); |
348 | | #endif |
349 | 0 | ah->wsi = NULL; /* no owner */ |
350 | 0 | wsi->http.ah = NULL; |
351 | |
|
352 | 0 | pwsi = &pt->http.ah_wait_list; |
353 | | |
354 | | /* oh there is nobody on the waiting list... leave the ah unattached */ |
355 | 0 | if (!*pwsi) |
356 | 0 | goto nobody_usable_waiting; |
357 | | |
358 | | /* |
359 | | * at least one wsi on the same tsi is waiting, give it to oldest guy |
360 | | * who is allowed to take it (if any) |
361 | | */ |
362 | 0 | lwsl_info("%s: pt wait list %s\n", __func__, lws_wsi_tag(*pwsi)); |
363 | 0 | wsi = NULL; |
364 | 0 | pwsi_eligible = NULL; |
365 | |
|
366 | 0 | while (*pwsi) { |
367 | | #if defined(LWS_WITH_PEER_LIMITS) |
368 | | /* are we willing to give this guy an ah? */ |
369 | | if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer)) |
370 | | #endif |
371 | 0 | { |
372 | 0 | wsi = *pwsi; |
373 | 0 | pwsi_eligible = pwsi; |
374 | 0 | } |
375 | |
|
376 | 0 | pwsi = &(*pwsi)->http.ah_wait_list; |
377 | 0 | } |
378 | |
|
379 | 0 | if (!wsi) /* everybody waiting already has too many ah... */ |
380 | 0 | goto nobody_usable_waiting; |
381 | | |
382 | 0 | lwsl_info("%s: transferring ah to last eligible wsi in wait list " |
383 | 0 | "%s (wsistate 0x%lx)\n", __func__, lws_wsi_tag(wsi), |
384 | 0 | (unsigned long)wsi->wsistate); |
385 | |
|
386 | 0 | wsi->http.ah = ah; |
387 | 0 | ah->wsi = wsi; /* new owner */ |
388 | |
|
389 | 0 | __lws_header_table_reset(wsi, autoservice); |
390 | | #if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \ |
391 | | defined(LWS_ROLE_H2)) |
392 | | lws_context_lock(context, "ah detach"); /* <========================= */ |
393 | | if (wsi->peer) |
394 | | wsi->peer->http.count_ah++; |
395 | | lws_context_unlock(context); /* ====================================> */ |
396 | | #endif |
397 | | |
398 | | /* clients acquire the ah and then insert themselves in fds table... */ |
399 | 0 | if (wsi->position_in_fds_table != LWS_NO_FDS_POS) { |
400 | 0 | lwsl_info("%s: Enabling %s POLLIN\n", __func__, lws_wsi_tag(wsi)); |
401 | | |
402 | | /* he has been stuck waiting for an ah, but now his wait is |
403 | | * over, let him progress */ |
404 | |
|
405 | 0 | _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); |
406 | 0 | } |
407 | | |
408 | | /* point prev guy to next guy in list instead */ |
409 | 0 | *pwsi_eligible = wsi->http.ah_wait_list; |
410 | | /* the guy who got one is out of the list */ |
411 | 0 | wsi->http.ah_wait_list = NULL; |
412 | 0 | pt->http.ah_wait_list_length--; |
413 | |
|
414 | 0 | #if defined(LWS_WITH_CLIENT) |
415 | 0 | if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) { |
416 | 0 | lws_pt_unlock(pt); |
417 | |
|
418 | 0 | if (!lws_http_client_connect_via_info2(wsi)) { |
419 | | /* our client connect has failed, the wsi |
420 | | * has been closed |
421 | | */ |
422 | |
|
423 | 0 | return -1; |
424 | 0 | } |
425 | 0 | return 0; |
426 | 0 | } |
427 | 0 | #endif |
428 | | |
429 | 0 | assert(!!pt->http.ah_wait_list_length == |
430 | 0 | !!(lws_intptr_t)pt->http.ah_wait_list); |
431 | 0 | bail: |
432 | 0 | lwsl_info("%s: %s: ah %p (tsi=%d, count = %d)\n", __func__, |
433 | 0 | lws_wsi_tag(wsi), (void *)ah, pt->tid, pt->http.ah_count_in_use); |
434 | |
|
435 | 0 | return 0; |
436 | | |
437 | 0 | nobody_usable_waiting: |
438 | 0 | lwsl_info("%s: nobody usable waiting\n", __func__); |
439 | 0 | _lws_destroy_ah(pt, ah); |
440 | 0 | pt->http.ah_count_in_use--; |
441 | |
|
442 | 0 | goto bail; |
443 | 0 | } |
444 | | |
445 | | int lws_header_table_detach(struct lws *wsi, int autoservice) |
446 | 0 | { |
447 | 0 | struct lws_context *context = wsi->a.context; |
448 | 0 | struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; |
449 | 0 | int n; |
450 | |
|
451 | 0 | lws_pt_lock(pt, __func__); |
452 | 0 | n = __lws_header_table_detach(wsi, autoservice); |
453 | 0 | lws_pt_unlock(pt); |
454 | |
|
455 | 0 | return n; |
456 | 0 | } |
457 | | |
458 | | int |
459 | | lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx) |
460 | 0 | { |
461 | 0 | int n; |
462 | |
|
463 | 0 | if (!wsi->http.ah) |
464 | 0 | return 0; |
465 | | |
466 | 0 | n = wsi->http.ah->frag_index[h]; |
467 | 0 | if (!n) |
468 | 0 | return 0; |
469 | 0 | do { |
470 | 0 | if (!frag_idx) |
471 | 0 | return wsi->http.ah->frags[n].len; |
472 | 0 | n = wsi->http.ah->frags[n].nfrag; |
473 | 0 | } while (frag_idx-- && n); |
474 | | |
475 | 0 | return 0; |
476 | 0 | } |
477 | | |
478 | | int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h) |
479 | 0 | { |
480 | 0 | int n; |
481 | 0 | int len = 0; |
482 | |
|
483 | 0 | if (!wsi->http.ah) |
484 | 0 | return 0; |
485 | | |
486 | 0 | n = wsi->http.ah->frag_index[h]; |
487 | 0 | if (!n) |
488 | 0 | return 0; |
489 | 0 | do { |
490 | 0 | len += wsi->http.ah->frags[n].len; |
491 | 0 | n = wsi->http.ah->frags[n].nfrag; |
492 | |
|
493 | 0 | if (n) |
494 | 0 | len++; |
495 | |
|
496 | 0 | } while (n); |
497 | |
|
498 | 0 | return len; |
499 | 0 | } |
500 | | |
501 | | int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len, |
502 | | enum lws_token_indexes h, int frag_idx) |
503 | 0 | { |
504 | 0 | int n = 0; |
505 | 0 | int f; |
506 | |
|
507 | 0 | if (!wsi->http.ah) |
508 | 0 | return -1; |
509 | | |
510 | 0 | f = wsi->http.ah->frag_index[h]; |
511 | |
|
512 | 0 | if (!f) |
513 | 0 | return -1; |
514 | | |
515 | 0 | while (n < frag_idx) { |
516 | 0 | f = wsi->http.ah->frags[f].nfrag; |
517 | 0 | if (!f) |
518 | 0 | return -1; |
519 | 0 | n++; |
520 | 0 | } |
521 | | |
522 | 0 | if (wsi->http.ah->frags[f].len >= len) |
523 | 0 | return -1; |
524 | | |
525 | 0 | memcpy(dst, wsi->http.ah->data + wsi->http.ah->frags[f].offset, |
526 | 0 | wsi->http.ah->frags[f].len); |
527 | 0 | dst[wsi->http.ah->frags[f].len] = '\0'; |
528 | |
|
529 | 0 | return wsi->http.ah->frags[f].len; |
530 | 0 | } |
531 | | |
532 | | int lws_hdr_copy(struct lws *wsi, char *dst, int len, |
533 | | enum lws_token_indexes h) |
534 | 0 | { |
535 | 0 | int toklen = lws_hdr_total_length(wsi, h), n, comma; |
536 | |
|
537 | 0 | *dst = '\0'; |
538 | 0 | if (!toklen) |
539 | 0 | return 0; |
540 | | |
541 | 0 | if (toklen >= len) |
542 | 0 | return -1; |
543 | | |
544 | 0 | if (!wsi->http.ah) |
545 | 0 | return -1; |
546 | | |
547 | 0 | n = wsi->http.ah->frag_index[h]; |
548 | 0 | if (!n) |
549 | 0 | return 0; |
550 | 0 | do { |
551 | 0 | comma = (wsi->http.ah->frags[n].nfrag) ? 1 : 0; |
552 | |
|
553 | 0 | if (h == WSI_TOKEN_HTTP_URI_ARGS) |
554 | 0 | lwsl_notice("%s: WSI_TOKEN_HTTP_URI_ARGS '%.*s'\n", |
555 | 0 | __func__, (int)wsi->http.ah->frags[n].len, |
556 | 0 | &wsi->http.ah->data[ |
557 | 0 | wsi->http.ah->frags[n].offset]); |
558 | |
|
559 | 0 | if (wsi->http.ah->frags[n].len + comma >= len) { |
560 | 0 | lwsl_notice("blowout len\n"); |
561 | 0 | return -1; |
562 | 0 | } |
563 | 0 | strncpy(dst, &wsi->http.ah->data[wsi->http.ah->frags[n].offset], |
564 | 0 | wsi->http.ah->frags[n].len); |
565 | 0 | dst += wsi->http.ah->frags[n].len; |
566 | 0 | len -= wsi->http.ah->frags[n].len; |
567 | 0 | n = wsi->http.ah->frags[n].nfrag; |
568 | | |
569 | | /* |
570 | | * Note if you change this logic, take care about updating len |
571 | | * and make sure lws_hdr_total_length() gives the same resulting |
572 | | * length |
573 | | */ |
574 | |
|
575 | 0 | if (comma) { |
576 | 0 | if (h == WSI_TOKEN_HTTP_COOKIE || |
577 | 0 | h == WSI_TOKEN_HTTP_SET_COOKIE) |
578 | 0 | *dst++ = ';'; |
579 | 0 | else |
580 | 0 | if (h == WSI_TOKEN_HTTP_URI_ARGS) |
581 | 0 | *dst++ = '&'; |
582 | 0 | else |
583 | 0 | *dst++ = ','; |
584 | 0 | len--; |
585 | 0 | } |
586 | | |
587 | 0 | } while (n); |
588 | 0 | *dst = '\0'; |
589 | |
|
590 | 0 | if (h == WSI_TOKEN_HTTP_URI_ARGS) |
591 | 0 | lwsl_err("%s: WSI_TOKEN_HTTP_URI_ARGS toklen %d\n", __func__, (int)toklen); |
592 | |
|
593 | 0 | return toklen; |
594 | 0 | } |
595 | | |
596 | | #if defined(LWS_WITH_CUSTOM_HEADERS) |
597 | | int |
598 | | lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen) |
599 | 0 | { |
600 | 0 | ah_data_idx_t ll; |
601 | |
|
602 | 0 | if (!wsi->http.ah || wsi->mux_substream) |
603 | 0 | return -1; |
604 | | |
605 | 0 | ll = wsi->http.ah->unk_ll_head; |
606 | 0 | while (ll) { |
607 | 0 | if (ll >= wsi->http.ah->data_length) |
608 | 0 | return -1; |
609 | 0 | if (nlen == lws_ser_ru16be( |
610 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]) && |
611 | 0 | !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], (unsigned int)nlen)) |
612 | 0 | return lws_ser_ru16be( |
613 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_VLEN]); |
614 | | |
615 | 0 | ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]); |
616 | 0 | } |
617 | | |
618 | 0 | return -1; |
619 | 0 | } |
620 | | |
621 | | int |
622 | | lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name, |
623 | | int nlen) |
624 | 0 | { |
625 | 0 | ah_data_idx_t ll; |
626 | 0 | int n; |
627 | |
|
628 | 0 | if (!wsi->http.ah || wsi->mux_substream) |
629 | 0 | return -1; |
630 | | |
631 | 0 | *dst = '\0'; |
632 | |
|
633 | 0 | ll = wsi->http.ah->unk_ll_head; |
634 | 0 | while (ll) { |
635 | 0 | if (ll >= wsi->http.ah->data_length) |
636 | 0 | return -1; |
637 | 0 | if (nlen == lws_ser_ru16be( |
638 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]) && |
639 | 0 | !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], (unsigned int)nlen)) { |
640 | 0 | n = lws_ser_ru16be( |
641 | 0 | (uint8_t *)&wsi->http.ah->data[ll + UHO_VLEN]); |
642 | 0 | if (n + 1 > len) |
643 | 0 | return -1; |
644 | 0 | strncpy(dst, &wsi->http.ah->data[ll + UHO_NAME + (unsigned int)nlen], (unsigned int)n); |
645 | 0 | dst[n] = '\0'; |
646 | |
|
647 | 0 | return n; |
648 | 0 | } |
649 | 0 | ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]); |
650 | 0 | } |
651 | | |
652 | 0 | return -1; |
653 | 0 | } |
654 | | |
655 | | int |
656 | | lws_hdr_custom_name_foreach(struct lws *wsi, lws_hdr_custom_fe_cb_t cb, |
657 | | void *custom) |
658 | 0 | { |
659 | 0 | ah_data_idx_t ll; |
660 | |
|
661 | 0 | if (!wsi->http.ah || wsi->mux_substream) |
662 | 0 | return -1; |
663 | | |
664 | 0 | ll = wsi->http.ah->unk_ll_head; |
665 | |
|
666 | 0 | while (ll) { |
667 | 0 | if (ll >= wsi->http.ah->data_length) |
668 | 0 | return -1; |
669 | | |
670 | 0 | cb(&wsi->http.ah->data[ll + UHO_NAME], |
671 | 0 | lws_ser_ru16be((uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]), |
672 | 0 | custom); |
673 | |
|
674 | 0 | ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]); |
675 | 0 | } |
676 | | |
677 | 0 | return 0; |
678 | 0 | } |
679 | | #endif |
680 | | |
681 | | char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) |
682 | 0 | { |
683 | 0 | int n; |
684 | |
|
685 | 0 | if (!wsi->http.ah) |
686 | 0 | return NULL; |
687 | | |
688 | 0 | n = wsi->http.ah->frag_index[h]; |
689 | 0 | if (!n) |
690 | 0 | return NULL; |
691 | | |
692 | 0 | return wsi->http.ah->data + wsi->http.ah->frags[n].offset; |
693 | 0 | } |
694 | | |
695 | | static int LWS_WARN_UNUSED_RESULT |
696 | | lws_pos_in_bounds(struct lws *wsi) |
697 | 0 | { |
698 | 0 | if (!wsi->http.ah) |
699 | 0 | return -1; |
700 | | |
701 | 0 | if (wsi->http.ah->pos < |
702 | 0 | (unsigned int)wsi->a.context->max_http_header_data) |
703 | 0 | return 0; |
704 | | |
705 | 0 | if ((int)wsi->http.ah->pos >= (int)wsi->a.context->max_http_header_data - 1) { |
706 | 0 | lwsl_err("Ran out of header data space\n"); |
707 | 0 | return 1; |
708 | 0 | } |
709 | | |
710 | | /* |
711 | | * with these tests everywhere, it should never be able to exceed |
712 | | * the limit, only meet it |
713 | | */ |
714 | 0 | lwsl_err("%s: pos %ld, limit %ld\n", __func__, |
715 | 0 | (unsigned long)wsi->http.ah->pos, |
716 | 0 | (unsigned long)wsi->a.context->max_http_header_data); |
717 | 0 | assert(0); |
718 | | |
719 | 0 | return 1; |
720 | 0 | } |
721 | | |
722 | | int LWS_WARN_UNUSED_RESULT |
723 | | lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) |
724 | 0 | { |
725 | 0 | if (!*s) { |
726 | | /* |
727 | | * If we get an empty string, then remove any entry for the |
728 | | * header |
729 | | */ |
730 | 0 | wsi->http.ah->frag_index[h] = 0; |
731 | |
|
732 | 0 | return 0; |
733 | 0 | } |
734 | | |
735 | 0 | wsi->http.ah->nfrag++; |
736 | 0 | if (wsi->http.ah->nfrag == LWS_ARRAY_SIZE(wsi->http.ah->frags)) { |
737 | 0 | lwsl_warn("More hdr frags than we can deal with, dropping\n"); |
738 | 0 | return -1; |
739 | 0 | } |
740 | | |
741 | 0 | wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag; |
742 | |
|
743 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].offset = wsi->http.ah->pos; |
744 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len = 0; |
745 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].nfrag = 0; |
746 | |
|
747 | 0 | do { |
748 | 0 | if (lws_pos_in_bounds(wsi)) |
749 | 0 | return -1; |
750 | | |
751 | 0 | wsi->http.ah->data[wsi->http.ah->pos++] = *s; |
752 | 0 | if (*s) |
753 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len++; |
754 | 0 | } while (*s++); |
755 | | |
756 | 0 | return 0; |
757 | 0 | } |
758 | | |
759 | | static int LWS_WARN_UNUSED_RESULT |
760 | | issue_char(struct lws *wsi, unsigned char c) |
761 | 0 | { |
762 | 0 | unsigned short frag_len; |
763 | |
|
764 | 0 | if (lws_pos_in_bounds(wsi)) |
765 | 0 | return -1; |
766 | | |
767 | 0 | frag_len = wsi->http.ah->frags[wsi->http.ah->nfrag].len; |
768 | | /* |
769 | | * If we haven't hit the token limit, just copy the character into |
770 | | * the header |
771 | | */ |
772 | 0 | if (!wsi->http.ah->current_token_limit || |
773 | 0 | frag_len < wsi->http.ah->current_token_limit) { |
774 | 0 | wsi->http.ah->data[wsi->http.ah->pos++] = (char)c; |
775 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len++; |
776 | 0 | return 0; |
777 | 0 | } |
778 | | |
779 | | /* Insert a null character when we *hit* the limit: */ |
780 | 0 | if (frag_len == wsi->http.ah->current_token_limit) { |
781 | 0 | if (lws_pos_in_bounds(wsi)) |
782 | 0 | return -1; |
783 | | |
784 | 0 | wsi->http.ah->data[wsi->http.ah->pos++] = '\0'; |
785 | 0 | lwsl_warn("header %li exceeds limit %ld\n", |
786 | 0 | (long)wsi->http.ah->parser_state, |
787 | 0 | (long)wsi->http.ah->current_token_limit); |
788 | 0 | } |
789 | | |
790 | 0 | return 1; |
791 | 0 | } |
792 | | |
793 | | int |
794 | | lws_parse_urldecode(struct lws *wsi, uint8_t *_c) |
795 | 0 | { |
796 | 0 | struct allocated_headers *ah = wsi->http.ah; |
797 | 0 | unsigned int enc = 0; |
798 | 0 | uint8_t c = *_c; |
799 | | |
800 | | // lwsl_notice("ah->ups %d\n", ah->ups); |
801 | | |
802 | | /* |
803 | | * PRIORITY 1 |
804 | | * special URI processing... convert %xx |
805 | | */ |
806 | 0 | switch (ah->ues) { |
807 | 0 | case URIES_IDLE: |
808 | 0 | if (c == '%') { |
809 | 0 | ah->ues = URIES_SEEN_PERCENT; |
810 | 0 | goto swallow; |
811 | 0 | } |
812 | 0 | break; |
813 | 0 | case URIES_SEEN_PERCENT: |
814 | 0 | if (char_to_hex((char)c) < 0) |
815 | | /* illegal post-% char */ |
816 | 0 | goto forbid; |
817 | | |
818 | 0 | ah->esc_stash = (char)c; |
819 | 0 | ah->ues = URIES_SEEN_PERCENT_H1; |
820 | 0 | goto swallow; |
821 | | |
822 | 0 | case URIES_SEEN_PERCENT_H1: |
823 | 0 | if (char_to_hex((char)c) < 0) |
824 | | /* illegal post-% char */ |
825 | 0 | goto forbid; |
826 | | |
827 | 0 | *_c = (uint8_t)(unsigned int)((char_to_hex(ah->esc_stash) << 4) | |
828 | 0 | char_to_hex((char)c)); |
829 | 0 | c = *_c; |
830 | 0 | enc = 1; |
831 | 0 | ah->ues = URIES_IDLE; |
832 | 0 | break; |
833 | 0 | } |
834 | | |
835 | | /* |
836 | | * PRIORITY 2 |
837 | | * special URI processing... |
838 | | * convert /.. or /... or /../ etc to / |
839 | | * convert /./ to / |
840 | | * convert // or /// etc to / |
841 | | * leave /.dir or whatever alone |
842 | | */ |
843 | | |
844 | 0 | if (!c && (!ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] || |
845 | 0 | !ah->post_literal_equal)) { |
846 | | /* |
847 | | * Since user code is typically going to parse the path using |
848 | | * NUL-terminated apis, it's too dangerous to allow NUL |
849 | | * injection here. |
850 | | * |
851 | | * It's allowed in the urlargs, because the apis to access |
852 | | * those only allow retreival with explicit length. |
853 | | */ |
854 | 0 | lwsl_warn("%s: saw NUL outside of uri args\n", __func__); |
855 | 0 | return -1; |
856 | 0 | } |
857 | | |
858 | 0 | switch (ah->ups) { |
859 | 0 | case URIPS_IDLE: |
860 | | |
861 | | /* genuine delimiter */ |
862 | 0 | if ((c == '&' || c == ';') && !enc) { |
863 | 0 | if (issue_char(wsi, '\0') < 0) |
864 | 0 | return -1; |
865 | | /* don't account for it */ |
866 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len--; |
867 | | /* link to next fragment */ |
868 | 0 | ah->frags[ah->nfrag].nfrag = (uint8_t)(ah->nfrag + 1); |
869 | 0 | ah->nfrag++; |
870 | 0 | if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frags)) |
871 | 0 | goto excessive; |
872 | | /* start next fragment after the & */ |
873 | 0 | ah->post_literal_equal = 0; |
874 | 0 | ah->frags[ah->nfrag].offset = ++ah->pos; |
875 | 0 | ah->frags[ah->nfrag].len = 0; |
876 | 0 | ah->frags[ah->nfrag].nfrag = 0; |
877 | 0 | goto swallow; |
878 | 0 | } |
879 | | /* uriencoded = in the name part, disallow */ |
880 | 0 | if (c == '=' && enc && |
881 | 0 | ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && |
882 | 0 | !ah->post_literal_equal) { |
883 | 0 | c = '_'; |
884 | 0 | *_c =c; |
885 | 0 | } |
886 | | |
887 | | /* after the real =, we don't care how many = */ |
888 | 0 | if (c == '=' && !enc) |
889 | 0 | ah->post_literal_equal = 1; |
890 | | |
891 | | /* + to space */ |
892 | 0 | if (c == '+' && !enc) { |
893 | 0 | c = ' '; |
894 | 0 | *_c = c; |
895 | 0 | } |
896 | | /* issue the first / always */ |
897 | 0 | if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) |
898 | 0 | ah->ups = URIPS_SEEN_SLASH; |
899 | 0 | break; |
900 | 0 | case URIPS_SEEN_SLASH: |
901 | | /* swallow subsequent slashes */ |
902 | 0 | if (c == '/') |
903 | 0 | goto swallow; |
904 | | /* track and swallow the first . after / */ |
905 | 0 | if (c == '.') { |
906 | 0 | ah->ups = URIPS_SEEN_SLASH_DOT; |
907 | 0 | goto swallow; |
908 | 0 | } |
909 | 0 | ah->ups = URIPS_IDLE; |
910 | 0 | break; |
911 | 0 | case URIPS_SEEN_SLASH_DOT: |
912 | | /* swallow second . */ |
913 | 0 | if (c == '.') { |
914 | 0 | ah->ups = URIPS_SEEN_SLASH_DOT_DOT; |
915 | 0 | goto swallow; |
916 | 0 | } |
917 | | /* change /./ to / */ |
918 | 0 | if (c == '/') { |
919 | 0 | ah->ups = URIPS_SEEN_SLASH; |
920 | 0 | goto swallow; |
921 | 0 | } |
922 | | /* it was like /.dir ... regurgitate the . */ |
923 | 0 | ah->ups = URIPS_IDLE; |
924 | 0 | if (issue_char(wsi, '.') < 0) |
925 | 0 | return -1; |
926 | 0 | break; |
927 | | |
928 | 0 | case URIPS_SEEN_SLASH_DOT_DOT: |
929 | | |
930 | | /* /../ or /..[End of URI] --> backup to last / */ |
931 | 0 | if (c == '/' || c == '?') { |
932 | | /* |
933 | | * back up one dir level if possible |
934 | | * safe against header fragmentation because |
935 | | * the method URI can only be in 1 fragment |
936 | | */ |
937 | 0 | if (ah->frags[ah->nfrag].len > 2) { |
938 | 0 | ah->pos--; |
939 | 0 | ah->frags[ah->nfrag].len--; |
940 | 0 | do { |
941 | 0 | ah->pos--; |
942 | 0 | ah->frags[ah->nfrag].len--; |
943 | 0 | } while (ah->frags[ah->nfrag].len > 1 && |
944 | 0 | ah->data[ah->pos] != '/'); |
945 | 0 | } |
946 | 0 | ah->ups = URIPS_SEEN_SLASH; |
947 | 0 | if (ah->frags[ah->nfrag].len > 1) |
948 | 0 | break; |
949 | 0 | goto swallow; |
950 | 0 | } |
951 | | |
952 | | /* /..[^/] ... regurgitate and allow */ |
953 | | |
954 | 0 | if (issue_char(wsi, '.') < 0) |
955 | 0 | return -1; |
956 | 0 | if (issue_char(wsi, '.') < 0) |
957 | 0 | return -1; |
958 | 0 | ah->ups = URIPS_IDLE; |
959 | 0 | break; |
960 | 0 | } |
961 | | |
962 | 0 | if (c == '?' && !enc && |
963 | 0 | !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI args */ |
964 | 0 | if (ah->ues != URIES_IDLE) |
965 | 0 | goto forbid; |
966 | | |
967 | | /* seal off uri header */ |
968 | 0 | if (issue_char(wsi, '\0') < 0) |
969 | 0 | return -1; |
970 | | |
971 | | /* don't account for it */ |
972 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len--; |
973 | | |
974 | | /* move to using WSI_TOKEN_HTTP_URI_ARGS */ |
975 | 0 | ah->nfrag++; |
976 | 0 | if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frags)) |
977 | 0 | goto excessive; |
978 | 0 | ah->frags[ah->nfrag].offset = ++ah->pos; |
979 | 0 | ah->frags[ah->nfrag].len = 0; |
980 | 0 | ah->frags[ah->nfrag].nfrag = 0; |
981 | |
|
982 | 0 | ah->post_literal_equal = 0; |
983 | 0 | ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; |
984 | 0 | ah->ups = URIPS_IDLE; |
985 | 0 | goto swallow; |
986 | 0 | } |
987 | | |
988 | 0 | return LPUR_CONTINUE; |
989 | | |
990 | 0 | swallow: |
991 | 0 | return LPUR_SWALLOW; |
992 | | |
993 | 0 | forbid: |
994 | 0 | return LPUR_FORBID; |
995 | | |
996 | 0 | excessive: |
997 | 0 | return LPUR_EXCESSIVE; |
998 | 0 | } |
999 | | |
1000 | | static const unsigned char methods[] = { |
1001 | | WSI_TOKEN_GET_URI, |
1002 | | WSI_TOKEN_POST_URI, |
1003 | | #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) |
1004 | | WSI_TOKEN_OPTIONS_URI, |
1005 | | WSI_TOKEN_PUT_URI, |
1006 | | WSI_TOKEN_PATCH_URI, |
1007 | | WSI_TOKEN_DELETE_URI, |
1008 | | #endif |
1009 | | WSI_TOKEN_CONNECT, |
1010 | | WSI_TOKEN_HEAD_URI, |
1011 | | }; |
1012 | | |
1013 | | /* |
1014 | | * possible returns:, -1 fail, 0 ok or 2, transition to raw |
1015 | | */ |
1016 | | |
1017 | | lws_parser_return_t LWS_WARN_UNUSED_RESULT |
1018 | | lws_parse(struct lws *wsi, unsigned char *buf, int *len) |
1019 | 0 | { |
1020 | 0 | struct allocated_headers *ah = wsi->http.ah; |
1021 | 0 | struct lws_context *context = wsi->a.context; |
1022 | 0 | unsigned int n, m; |
1023 | 0 | unsigned char c; |
1024 | 0 | int r, pos; |
1025 | |
|
1026 | 0 | assert(wsi->http.ah); |
1027 | | |
1028 | 0 | do { |
1029 | 0 | (*len)--; |
1030 | 0 | c = *buf++; |
1031 | |
|
1032 | 0 | switch (ah->parser_state) { |
1033 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1034 | 0 | case WSI_TOKEN_UNKNOWN_VALUE_PART: |
1035 | |
|
1036 | 0 | if (c == '\r') |
1037 | 0 | break; |
1038 | 0 | if (c == '\n') { |
1039 | 0 | lws_ser_wu16be((uint8_t *)&ah->data[ah->unk_pos + 2], |
1040 | 0 | (uint16_t)(ah->pos - ah->unk_value_pos)); |
1041 | 0 | ah->parser_state = WSI_TOKEN_NAME_PART; |
1042 | 0 | ah->unk_pos = 0; |
1043 | 0 | ah->lextable_pos = 0; |
1044 | 0 | break; |
1045 | 0 | } |
1046 | | |
1047 | | /* trim leading whitespace */ |
1048 | 0 | if (ah->pos != ah->unk_value_pos || |
1049 | 0 | (c != ' ' && c != '\t')) { |
1050 | |
|
1051 | 0 | if (lws_pos_in_bounds(wsi)) |
1052 | 0 | return LPR_FAIL; |
1053 | | |
1054 | 0 | ah->data[ah->pos++] = (char)c; |
1055 | 0 | } |
1056 | 0 | pos = ah->lextable_pos; |
1057 | 0 | break; |
1058 | 0 | #endif |
1059 | 0 | default: |
1060 | |
|
1061 | 0 | lwsl_parser("WSI_TOK_(%d) '%c'\n", ah->parser_state, c); |
1062 | | |
1063 | | /* collect into malloc'd buffers */ |
1064 | | /* optional initial space swallow */ |
1065 | 0 | if (!ah->frags[ah->frag_index[ah->parser_state]].len && |
1066 | 0 | c == ' ') |
1067 | 0 | break; |
1068 | | |
1069 | 0 | for (m = 0; m < LWS_ARRAY_SIZE(methods); m++) |
1070 | 0 | if (ah->parser_state == methods[m]) |
1071 | 0 | break; |
1072 | 0 | if (m == LWS_ARRAY_SIZE(methods)) |
1073 | | /* it was not any of the methods */ |
1074 | 0 | goto check_eol; |
1075 | | |
1076 | | /* special URI processing... end at space */ |
1077 | | |
1078 | 0 | if (c == ' ') { |
1079 | | /* enforce starting with / */ |
1080 | 0 | if (!ah->frags[ah->nfrag].len) |
1081 | 0 | if (issue_char(wsi, '/') < 0) |
1082 | 0 | return LPR_FAIL; |
1083 | | |
1084 | 0 | if (ah->ups == URIPS_SEEN_SLASH_DOT_DOT) { |
1085 | | /* |
1086 | | * back up one dir level if possible |
1087 | | * safe against header fragmentation |
1088 | | * because the method URI can only be |
1089 | | * in 1 fragment |
1090 | | */ |
1091 | 0 | if (ah->frags[ah->nfrag].len > 2) { |
1092 | 0 | ah->pos--; |
1093 | 0 | ah->frags[ah->nfrag].len--; |
1094 | 0 | do { |
1095 | 0 | ah->pos--; |
1096 | 0 | ah->frags[ah->nfrag].len--; |
1097 | 0 | } while (ah->frags[ah->nfrag].len > 1 && |
1098 | 0 | ah->data[ah->pos] != '/'); |
1099 | 0 | } |
1100 | 0 | } |
1101 | | |
1102 | | /* begin parsing HTTP version: */ |
1103 | 0 | if (issue_char(wsi, '\0') < 0) |
1104 | 0 | return LPR_FAIL; |
1105 | | /* don't account for it */ |
1106 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len--; |
1107 | 0 | ah->parser_state = WSI_TOKEN_HTTP; |
1108 | 0 | goto start_fragment; |
1109 | 0 | } |
1110 | | |
1111 | 0 | r = lws_parse_urldecode(wsi, &c); |
1112 | 0 | switch (r) { |
1113 | 0 | case LPUR_CONTINUE: |
1114 | 0 | break; |
1115 | 0 | case LPUR_SWALLOW: |
1116 | 0 | goto swallow; |
1117 | 0 | case LPUR_FORBID: |
1118 | 0 | goto forbid; |
1119 | 0 | case LPUR_EXCESSIVE: |
1120 | 0 | goto excessive; |
1121 | 0 | default: |
1122 | 0 | return LPR_FAIL; |
1123 | 0 | } |
1124 | 0 | check_eol: |
1125 | | /* bail at EOL */ |
1126 | 0 | if (ah->parser_state != WSI_TOKEN_CHALLENGE && |
1127 | 0 | (c == '\x0d' || c == '\x0a')) { |
1128 | 0 | if (ah->ues != URIES_IDLE) |
1129 | 0 | goto forbid; |
1130 | | |
1131 | 0 | if (c == '\x0a') { |
1132 | | /* broken peer */ |
1133 | 0 | ah->parser_state = WSI_TOKEN_NAME_PART; |
1134 | 0 | ah->unk_pos = 0; |
1135 | 0 | ah->lextable_pos = 0; |
1136 | 0 | } else |
1137 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; |
1138 | |
|
1139 | 0 | c = '\0'; |
1140 | 0 | lwsl_parser("*\n"); |
1141 | 0 | } |
1142 | | |
1143 | 0 | n = (unsigned int)issue_char(wsi, c); |
1144 | 0 | if ((int)n < 0) |
1145 | 0 | return LPR_FAIL; |
1146 | 0 | if (n > 0) |
1147 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING; |
1148 | 0 | else { |
1149 | | /* |
1150 | | * Explicit zeroes are legal in URI ARGS. |
1151 | | * They can only exist as a safety terminator |
1152 | | * after the valid part of the token contents |
1153 | | * for other types. |
1154 | | */ |
1155 | 0 | if (!c && ah->parser_state != WSI_TOKEN_HTTP_URI_ARGS) |
1156 | | /* don't account for safety terminator */ |
1157 | 0 | wsi->http.ah->frags[wsi->http.ah->nfrag].len--; |
1158 | 0 | } |
1159 | |
|
1160 | 0 | swallow: |
1161 | | /* per-protocol end of headers management */ |
1162 | |
|
1163 | 0 | if (ah->parser_state == WSI_TOKEN_CHALLENGE) |
1164 | 0 | goto set_parsing_complete; |
1165 | 0 | break; |
1166 | | |
1167 | | /* collecting and checking a name part */ |
1168 | 0 | case WSI_TOKEN_NAME_PART: |
1169 | 0 | lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X " |
1170 | 0 | "(role=0x%lx) " |
1171 | 0 | "wsi->lextable_pos=%d\n", c, c, |
1172 | 0 | (unsigned long)lwsi_role(wsi), |
1173 | 0 | ah->lextable_pos); |
1174 | |
|
1175 | 0 | if (!ah->unk_pos && c == '\x0a') |
1176 | | /* broken peer */ |
1177 | 0 | goto set_parsing_complete; |
1178 | | |
1179 | 0 | if (c >= 'A' && c <= 'Z') |
1180 | 0 | c = (unsigned char)(c + 'a' - 'A'); |
1181 | | /* |
1182 | | * ...in case it's an unknown header, speculatively |
1183 | | * store it as the name comes in. If we recognize it as |
1184 | | * a known header, we'll snip this. |
1185 | | */ |
1186 | |
|
1187 | 0 | if (!wsi->mux_substream && !ah->unk_pos) { |
1188 | 0 | ah->unk_pos = ah->pos; |
1189 | |
|
1190 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1191 | | /* |
1192 | | * Prepare new unknown header linked-list entry |
1193 | | * |
1194 | | * - 16-bit BE: name part length |
1195 | | * - 16-bit BE: value part length |
1196 | | * - 32-bit BE: data offset of next, or 0 |
1197 | | */ |
1198 | 0 | for (n = 0; n < 8; n++) |
1199 | 0 | if (!lws_pos_in_bounds(wsi)) |
1200 | 0 | ah->data[ah->pos++] = 0; |
1201 | 0 | #endif |
1202 | 0 | } |
1203 | |
|
1204 | 0 | if (lws_pos_in_bounds(wsi)) |
1205 | 0 | return LPR_FAIL; |
1206 | | |
1207 | 0 | ah->data[ah->pos++] = (char)c; |
1208 | 0 | pos = ah->lextable_pos; |
1209 | |
|
1210 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1211 | 0 | if (!wsi->mux_substream && pos < 0 && c == ':') { |
1212 | 0 | #if defined(_DEBUG) |
1213 | 0 | char dotstar[64]; |
1214 | 0 | int uhlen; |
1215 | 0 | #endif |
1216 | | |
1217 | | /* |
1218 | | * process unknown headers |
1219 | | * |
1220 | | * register us in the unknown hdr ll |
1221 | | */ |
1222 | |
|
1223 | 0 | if (!ah->unk_ll_head) |
1224 | 0 | ah->unk_ll_head = ah->unk_pos; |
1225 | |
|
1226 | 0 | if (ah->unk_ll_tail) |
1227 | 0 | lws_ser_wu32be( |
1228 | 0 | (uint8_t *)&ah->data[ah->unk_ll_tail + UHO_LL], |
1229 | 0 | ah->unk_pos); |
1230 | |
|
1231 | 0 | ah->unk_ll_tail = ah->unk_pos; |
1232 | |
|
1233 | 0 | #if defined(_DEBUG) |
1234 | 0 | uhlen = (int)(ah->pos - (ah->unk_pos + UHO_NAME)); |
1235 | 0 | lws_strnncpy(dotstar, |
1236 | 0 | &ah->data[ah->unk_pos + UHO_NAME], |
1237 | 0 | uhlen, sizeof(dotstar)); |
1238 | 0 | lwsl_debug("%s: unk header %d '%s'\n", |
1239 | 0 | __func__, |
1240 | 0 | ah->pos - (ah->unk_pos + UHO_NAME), |
1241 | 0 | dotstar); |
1242 | 0 | #endif |
1243 | | |
1244 | | /* set the unknown header name part length */ |
1245 | |
|
1246 | 0 | lws_ser_wu16be((uint8_t *)&ah->data[ah->unk_pos], |
1247 | 0 | (uint16_t)((ah->pos - ah->unk_pos) - UHO_NAME)); |
1248 | |
|
1249 | 0 | ah->unk_value_pos = ah->pos; |
1250 | | |
1251 | | /* |
1252 | | * collect whatever's coming for the unknown header |
1253 | | * argument until the next CRLF |
1254 | | */ |
1255 | 0 | ah->parser_state = WSI_TOKEN_UNKNOWN_VALUE_PART; |
1256 | 0 | break; |
1257 | 0 | } |
1258 | 0 | #endif |
1259 | 0 | if (pos < 0) |
1260 | 0 | break; |
1261 | | |
1262 | 0 | while (1) { |
1263 | 0 | if (lextable_h1[pos] & (1 << 7)) { |
1264 | | /* 1-byte, fail on mismatch */ |
1265 | 0 | if ((lextable_h1[pos] & 0x7f) != c) { |
1266 | 0 | nope: |
1267 | 0 | ah->lextable_pos = -1; |
1268 | 0 | break; |
1269 | 0 | } |
1270 | | /* fall thru */ |
1271 | 0 | pos++; |
1272 | 0 | if (lextable_h1[pos] == FAIL_CHAR) |
1273 | 0 | goto nope; |
1274 | | |
1275 | 0 | ah->lextable_pos = (int16_t)pos; |
1276 | 0 | break; |
1277 | 0 | } |
1278 | | |
1279 | 0 | if (lextable_h1[pos] == FAIL_CHAR) |
1280 | 0 | goto nope; |
1281 | | |
1282 | | /* b7 = 0, end or 3-byte */ |
1283 | 0 | if (lextable_h1[pos] < FAIL_CHAR) { |
1284 | 0 | if (!wsi->mux_substream) { |
1285 | | /* |
1286 | | * We hit a terminal marker, so |
1287 | | * we recognized this header... |
1288 | | * drop the speculative name |
1289 | | * part storage |
1290 | | */ |
1291 | 0 | ah->pos = ah->unk_pos; |
1292 | 0 | ah->unk_pos = 0; |
1293 | 0 | } |
1294 | |
|
1295 | 0 | ah->lextable_pos = (int16_t)pos; |
1296 | 0 | break; |
1297 | 0 | } |
1298 | | |
1299 | 0 | if (lextable_h1[pos] == c) { /* goto */ |
1300 | 0 | ah->lextable_pos = (int16_t)(pos + |
1301 | 0 | (lextable_h1[pos + 1]) + |
1302 | 0 | (lextable_h1[pos + 2] << 8)); |
1303 | 0 | break; |
1304 | 0 | } |
1305 | | |
1306 | | /* fall thru goto */ |
1307 | 0 | pos += 3; |
1308 | | /* continue */ |
1309 | 0 | } |
1310 | | |
1311 | | /* |
1312 | | * If it's h1, server needs to be on the look out for |
1313 | | * unknown methods... |
1314 | | */ |
1315 | 0 | if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) && |
1316 | 0 | lwsi_role_server(wsi)) { |
1317 | | /* |
1318 | | * this is not a header we know about... did |
1319 | | * we get a valid method (GET, POST etc) |
1320 | | * already, or is this the bogus method? |
1321 | | */ |
1322 | 0 | for (m = 0; m < LWS_ARRAY_SIZE(methods); m++) |
1323 | 0 | if (ah->frag_index[methods[m]]) { |
1324 | | /* |
1325 | | * already had the method |
1326 | | */ |
1327 | | #if !defined(LWS_WITH_CUSTOM_HEADERS) |
1328 | | ah->parser_state = WSI_TOKEN_SKIPPING; |
1329 | | #endif |
1330 | 0 | if (wsi->mux_substream) |
1331 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING; |
1332 | 0 | break; |
1333 | 0 | } |
1334 | |
|
1335 | 0 | if (m != LWS_ARRAY_SIZE(methods)) { |
1336 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1337 | | /* |
1338 | | * We have the method, this is just an |
1339 | | * unknown header then |
1340 | | */ |
1341 | 0 | if (!wsi->mux_substream) |
1342 | 0 | goto unknown_hdr; |
1343 | 0 | else |
1344 | 0 | break; |
1345 | | #else |
1346 | | break; |
1347 | | #endif |
1348 | 0 | } |
1349 | | /* |
1350 | | * ...it's an unknown http method from a client |
1351 | | * in fact, it cannot be valid http. |
1352 | | * |
1353 | | * Are we set up to transition to another role |
1354 | | * in these cases? |
1355 | | */ |
1356 | 0 | if (lws_check_opt(wsi->a.vhost->options, |
1357 | 0 | LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG)) { |
1358 | 0 | lwsl_notice("%s: http fail fallback\n", |
1359 | 0 | __func__); |
1360 | | /* transition to other role */ |
1361 | 0 | return LPR_DO_FALLBACK; |
1362 | 0 | } |
1363 | | |
1364 | 0 | lwsl_info("Unknown method - dropping\n"); |
1365 | 0 | goto forbid; |
1366 | 0 | } |
1367 | 0 | if (ah->lextable_pos < 0) { |
1368 | | /* |
1369 | | * It's not a header that lws knows about... |
1370 | | */ |
1371 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1372 | 0 | if (!wsi->mux_substream) |
1373 | 0 | goto unknown_hdr; |
1374 | 0 | #endif |
1375 | | /* |
1376 | | * ...otherwise for a client, let him ignore |
1377 | | * unknown headers coming from the server |
1378 | | */ |
1379 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING; |
1380 | 0 | break; |
1381 | 0 | } |
1382 | | |
1383 | 0 | if (lextable_h1[ah->lextable_pos] < FAIL_CHAR) { |
1384 | | /* terminal state */ |
1385 | |
|
1386 | 0 | n = ((unsigned int)lextable_h1[ah->lextable_pos] << 8) | |
1387 | 0 | lextable_h1[ah->lextable_pos + 1]; |
1388 | |
|
1389 | 0 | lwsl_parser("known hdr %d\n", n); |
1390 | 0 | for (m = 0; m < LWS_ARRAY_SIZE(methods); m++) |
1391 | 0 | if (n == methods[m] && |
1392 | 0 | ah->frag_index[methods[m]]) { |
1393 | 0 | lwsl_warn("Duplicated method\n"); |
1394 | 0 | return LPR_FAIL; |
1395 | 0 | } |
1396 | | |
1397 | 0 | if (!wsi->mux_substream) { |
1398 | | /* |
1399 | | * Whether we are collecting unknown names or not, |
1400 | | * if we matched an internal header we can dispense |
1401 | | * with the header name part we were keeping |
1402 | | */ |
1403 | 0 | ah->pos = ah->unk_pos; |
1404 | 0 | ah->unk_pos = 0; |
1405 | 0 | } |
1406 | |
|
1407 | 0 | #if defined(LWS_ROLE_WS) |
1408 | | /* |
1409 | | * WSORIGIN is protocol equiv to ORIGIN, |
1410 | | * JWebSocket likes to send it, map to ORIGIN |
1411 | | */ |
1412 | 0 | if (n == WSI_TOKEN_SWORIGIN) |
1413 | 0 | n = WSI_TOKEN_ORIGIN; |
1414 | 0 | #endif |
1415 | |
|
1416 | 0 | ah->parser_state = (uint8_t) |
1417 | 0 | (WSI_TOKEN_GET_URI + n); |
1418 | 0 | ah->ups = URIPS_IDLE; |
1419 | |
|
1420 | 0 | if (context->token_limits) |
1421 | 0 | ah->current_token_limit = context-> |
1422 | 0 | token_limits->token_limit[ |
1423 | 0 | ah->parser_state]; |
1424 | 0 | else |
1425 | 0 | ah->current_token_limit = |
1426 | 0 | wsi->a.context->max_http_header_data; |
1427 | |
|
1428 | 0 | if (ah->parser_state == WSI_TOKEN_CHALLENGE) |
1429 | 0 | goto set_parsing_complete; |
1430 | | |
1431 | 0 | goto start_fragment; |
1432 | 0 | } |
1433 | 0 | break; |
1434 | | |
1435 | 0 | #if defined(LWS_WITH_CUSTOM_HEADERS) |
1436 | 0 | unknown_hdr: |
1437 | | //ah->parser_state = WSI_TOKEN_SKIPPING; |
1438 | | //break; |
1439 | 0 | if (!wsi->mux_substream) |
1440 | 0 | break; |
1441 | 0 | #endif |
1442 | | |
1443 | 0 | start_fragment: |
1444 | 0 | ah->nfrag++; |
1445 | 0 | excessive: |
1446 | 0 | if (ah->nfrag == LWS_ARRAY_SIZE(ah->frags)) { |
1447 | 0 | lwsl_warn("More hdr frags than we can deal with\n"); |
1448 | 0 | return LPR_FAIL; |
1449 | 0 | } |
1450 | | |
1451 | 0 | ah->frags[ah->nfrag].offset = ah->pos; |
1452 | 0 | ah->frags[ah->nfrag].len = 0; |
1453 | 0 | ah->frags[ah->nfrag].nfrag = 0; |
1454 | 0 | ah->frags[ah->nfrag].flags = 2; |
1455 | |
|
1456 | 0 | n = ah->frag_index[ah->parser_state]; |
1457 | 0 | if (!n) { /* first fragment */ |
1458 | 0 | ah->frag_index[ah->parser_state] = ah->nfrag; |
1459 | 0 | ah->hdr_token_idx = ah->parser_state; |
1460 | 0 | break; |
1461 | 0 | } |
1462 | | /* continuation */ |
1463 | 0 | while (ah->frags[n].nfrag) |
1464 | 0 | n = ah->frags[n].nfrag; |
1465 | 0 | ah->frags[n].nfrag = ah->nfrag; |
1466 | |
|
1467 | 0 | if (issue_char(wsi, ' ') < 0) |
1468 | 0 | return LPR_FAIL; |
1469 | 0 | break; |
1470 | | |
1471 | | /* skipping arg part of a name we didn't recognize */ |
1472 | 0 | case WSI_TOKEN_SKIPPING: |
1473 | 0 | lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c); |
1474 | |
|
1475 | 0 | if (c == '\x0a') { |
1476 | | /* broken peer */ |
1477 | 0 | ah->parser_state = WSI_TOKEN_NAME_PART; |
1478 | 0 | ah->unk_pos = 0; |
1479 | 0 | ah->lextable_pos = 0; |
1480 | 0 | } |
1481 | |
|
1482 | 0 | if (c == '\x0d') |
1483 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; |
1484 | 0 | break; |
1485 | | |
1486 | 0 | case WSI_TOKEN_SKIPPING_SAW_CR: |
1487 | 0 | lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c); |
1488 | 0 | if (ah->ues != URIES_IDLE) |
1489 | 0 | goto forbid; |
1490 | 0 | if (c == '\x0a') { |
1491 | 0 | ah->parser_state = WSI_TOKEN_NAME_PART; |
1492 | 0 | ah->unk_pos = 0; |
1493 | 0 | ah->lextable_pos = 0; |
1494 | 0 | } else |
1495 | 0 | ah->parser_state = WSI_TOKEN_SKIPPING; |
1496 | 0 | break; |
1497 | | /* we're done, ignore anything else */ |
1498 | | |
1499 | 0 | case WSI_PARSING_COMPLETE: |
1500 | 0 | lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c); |
1501 | 0 | break; |
1502 | 0 | } |
1503 | |
|
1504 | 0 | } while (*len); |
1505 | | |
1506 | 0 | return LPR_OK; |
1507 | | |
1508 | 0 | set_parsing_complete: |
1509 | 0 | if (ah->ues != URIES_IDLE) |
1510 | 0 | goto forbid; |
1511 | | |
1512 | 0 | if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { |
1513 | 0 | #if defined(LWS_ROLE_WS) |
1514 | 0 | const char *pv = lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION); |
1515 | 0 | if (pv) |
1516 | 0 | wsi->rx_frame_type = (char)atoi(pv); |
1517 | |
|
1518 | 0 | lwsl_parser("v%02d hdrs done\n", wsi->rx_frame_type); |
1519 | 0 | #endif |
1520 | 0 | } |
1521 | 0 | ah->parser_state = WSI_PARSING_COMPLETE; |
1522 | 0 | wsi->hdr_parsing_completed = 1; |
1523 | |
|
1524 | 0 | return LPR_OK; |
1525 | | |
1526 | 0 | forbid: |
1527 | 0 | lwsl_info(" forbidding on uri sanitation\n"); |
1528 | 0 | #if defined(LWS_WITH_SERVER) |
1529 | 0 | lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); |
1530 | 0 | #endif |
1531 | |
|
1532 | 0 | return LPR_FORBIDDEN; |
1533 | 0 | } |
1534 | | |
1535 | | int |
1536 | | lws_http_cookie_get(struct lws *wsi, const char *name, char *buf, |
1537 | | size_t *max_len) |
1538 | 0 | { |
1539 | 0 | size_t max = *max_len, bl = strlen(name); |
1540 | 0 | char *p, *bo = buf; |
1541 | 0 | int n; |
1542 | |
|
1543 | 0 | n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE); |
1544 | 0 | if ((unsigned int)n < bl + 1) |
1545 | 0 | return 1; |
1546 | | |
1547 | | /* |
1548 | | * This can come to us two ways, in ah fragments (h2) or as a single |
1549 | | * semicolon-delimited string (h1) |
1550 | | */ |
1551 | | |
1552 | 0 | #if defined(LWS_ROLE_H2) |
1553 | 0 | if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_METHOD)) { |
1554 | | |
1555 | | /* |
1556 | | * The h2 way... |
1557 | | */ |
1558 | |
|
1559 | 0 | int f = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_COOKIE]; |
1560 | 0 | size_t fl; |
1561 | |
|
1562 | 0 | while (f) { |
1563 | 0 | p = wsi->http.ah->data + wsi->http.ah->frags[f].offset; |
1564 | 0 | fl = (size_t)wsi->http.ah->frags[f].len; |
1565 | 0 | if (fl >= bl + 1 && |
1566 | 0 | p[bl] == '=' && |
1567 | 0 | !memcmp(p, name, bl)) { |
1568 | 0 | fl -= bl + 1; |
1569 | 0 | if (max - 1 < fl) |
1570 | 0 | fl = max - 1; |
1571 | 0 | if (fl) |
1572 | 0 | memcpy(buf, p + bl + 1, fl); |
1573 | 0 | *max_len = fl; |
1574 | 0 | buf[fl] = '\0'; |
1575 | |
|
1576 | 0 | return 0; |
1577 | 0 | } |
1578 | 0 | f = wsi->http.ah->frags[f].nfrag; |
1579 | 0 | } |
1580 | | |
1581 | 0 | return -1; |
1582 | 0 | } |
1583 | 0 | #endif |
1584 | | |
1585 | | /* |
1586 | | * The h1 way... |
1587 | | */ |
1588 | | |
1589 | 0 | p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE); |
1590 | 0 | if (!p) |
1591 | 0 | return 1; |
1592 | | |
1593 | 0 | p += bl; |
1594 | 0 | n -= (int)bl; |
1595 | 0 | while (n-- > 0) { |
1596 | 0 | if (*p == '=' && !memcmp(p - bl, name, (unsigned int)bl)) { |
1597 | 0 | p++; |
1598 | 0 | while (*p != ';' && n-- && max) { |
1599 | 0 | *buf++ = *p++; |
1600 | 0 | max--; |
1601 | 0 | } |
1602 | 0 | if (!max) |
1603 | 0 | return 2; |
1604 | | |
1605 | 0 | *buf = '\0'; |
1606 | 0 | *max_len = lws_ptr_diff_size_t(buf, bo); |
1607 | |
|
1608 | 0 | return 0; |
1609 | 0 | } |
1610 | 0 | p++; |
1611 | 0 | } |
1612 | | |
1613 | 0 | return 1; |
1614 | 0 | } |
1615 | | |
1616 | | #if defined(LWS_WITH_JOSE) |
1617 | | |
1618 | | #define MAX_JWT_SIZE 1024 |
1619 | | |
1620 | | int |
1621 | | lws_jwt_get_http_cookie_validate_jwt(struct lws *wsi, |
1622 | | struct lws_jwt_sign_set_cookie *i, |
1623 | | char *out, size_t *out_len) |
1624 | | { |
1625 | | char temp[MAX_JWT_SIZE * 2]; |
1626 | | size_t cml = *out_len; |
1627 | | const char *cp; |
1628 | | |
1629 | | /* first use out to hold the encoded JWT */ |
1630 | | |
1631 | | if (lws_http_cookie_get(wsi, i->cookie_name, out, out_len)) { |
1632 | | lwsl_debug("%s: cookie %s not provided\n", __func__, |
1633 | | i->cookie_name); |
1634 | | return 1; |
1635 | | } |
1636 | | |
1637 | | /* decode the JWT into temp */ |
1638 | | |
1639 | | if (lws_jwt_signed_validate(wsi->a.context, i->jwk, i->alg, out, |
1640 | | *out_len, temp, sizeof(temp), out, &cml)) { |
1641 | | lwsl_info("%s: jwt validation failed\n", __func__); |
1642 | | return 1; |
1643 | | } |
1644 | | |
1645 | | /* |
1646 | | * Copy out the decoded JWT payload into out, overwriting the |
1647 | | * original encoded JWT taken from the cookie (that has long ago been |
1648 | | * translated into allocated buffers in the JOSE object) |
1649 | | */ |
1650 | | |
1651 | | if (lws_jwt_token_sanity(out, cml, i->iss, i->aud, i->csrf_in, |
1652 | | i->sub, sizeof(i->sub), |
1653 | | &i->expiry_unix_time)) { |
1654 | | lwsl_notice("%s: jwt sanity failed\n", __func__); |
1655 | | return 1; |
1656 | | } |
1657 | | |
1658 | | /* |
1659 | | * If he's interested in his private JSON part, point him to that in |
1660 | | * the args struct (it's pointing to the data in out |
1661 | | */ |
1662 | | |
1663 | | cp = lws_json_simple_find(out, cml, "\"ext\":", &i->extra_json_len); |
1664 | | if (cp) |
1665 | | i->extra_json = cp; |
1666 | | |
1667 | | if (!cp) |
1668 | | lwsl_notice("%s: no ext JWT payload\n", __func__); |
1669 | | |
1670 | | return 0; |
1671 | | } |
1672 | | |
1673 | | int |
1674 | | lws_jwt_sign_token_set_http_cookie(struct lws *wsi, |
1675 | | const struct lws_jwt_sign_set_cookie *i, |
1676 | | uint8_t **p, uint8_t *end) |
1677 | | { |
1678 | | char plain[MAX_JWT_SIZE + 1], temp[MAX_JWT_SIZE * 2], csrf[17]; |
1679 | | size_t pl = sizeof(plain); |
1680 | | unsigned long long ull; |
1681 | | int n; |
1682 | | |
1683 | | /* |
1684 | | * Create a 16-char random csrf token with the same lifetime as the JWT |
1685 | | */ |
1686 | | |
1687 | | lws_hex_random(wsi->a.context, csrf, sizeof(csrf)); |
1688 | | ull = lws_now_secs(); |
1689 | | if (lws_jwt_sign_compact(wsi->a.context, i->jwk, i->alg, plain, &pl, |
1690 | | temp, sizeof(temp), |
1691 | | "{\"iss\":\"%s\",\"aud\":\"%s\"," |
1692 | | "\"iat\":%llu,\"nbf\":%llu,\"exp\":%llu," |
1693 | | "\"csrf\":\"%s\",\"sub\":\"%s\"%s%s%s}", |
1694 | | i->iss, i->aud, ull, ull - 60, |
1695 | | ull + i->expiry_unix_time, |
1696 | | csrf, i->sub, |
1697 | | i->extra_json ? ",\"ext\":{" : "", |
1698 | | i->extra_json ? i->extra_json : "", |
1699 | | i->extra_json ? "}" : "")) { |
1700 | | lwsl_err("%s: failed to create JWT\n", __func__); |
1701 | | |
1702 | | return 1; |
1703 | | } |
1704 | | |
1705 | | /* |
1706 | | * There's no point the browser holding on to a JWT beyond the JWT's |
1707 | | * expiry time, so set it to be the same. |
1708 | | */ |
1709 | | |
1710 | | n = lws_snprintf(temp, sizeof(temp), "__Host-%s=%s;" |
1711 | | "HttpOnly;" |
1712 | | "Secure;" |
1713 | | "SameSite=strict;" |
1714 | | "Path=/;" |
1715 | | "Max-Age=%lu", |
1716 | | i->cookie_name, plain, i->expiry_unix_time); |
1717 | | |
1718 | | if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SET_COOKIE, |
1719 | | (uint8_t *)temp, n, p, end)) { |
1720 | | lwsl_err("%s: failed to add JWT cookie header\n", __func__); |
1721 | | return 1; |
1722 | | } |
1723 | | |
1724 | | return 0; |
1725 | | } |
1726 | | #endif |