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