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