1 | /**************************************************************************/ |
2 | /* http_client_tcp.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #ifndef WEB_ENABLED |
32 | |
33 | #include "http_client_tcp.h" |
34 | |
35 | #include "core/io/stream_peer_tls.h" |
36 | #include "core/version.h" |
37 | |
38 | HTTPClient *HTTPClientTCP::_create_func() { |
39 | return memnew(HTTPClientTCP); |
40 | } |
41 | |
42 | Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_options) { |
43 | close(); |
44 | |
45 | conn_port = p_port; |
46 | conn_host = p_host; |
47 | tls_options = p_options; |
48 | |
49 | ip_candidates.clear(); |
50 | |
51 | String host_lower = conn_host.to_lower(); |
52 | if (host_lower.begins_with("http://" )) { |
53 | conn_host = conn_host.substr(7, conn_host.length() - 7); |
54 | tls_options.unref(); |
55 | } else if (host_lower.begins_with("https://" )) { |
56 | if (tls_options.is_null()) { |
57 | tls_options = TLSOptions::client(); |
58 | } |
59 | conn_host = conn_host.substr(8, conn_host.length() - 8); |
60 | } |
61 | |
62 | ERR_FAIL_COND_V(tls_options.is_valid() && tls_options->is_server(), ERR_INVALID_PARAMETER); |
63 | ERR_FAIL_COND_V_MSG(tls_options.is_valid() && !StreamPeerTLS::is_available(), ERR_UNAVAILABLE, "HTTPS is not available in this build." ); |
64 | ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); |
65 | |
66 | if (conn_port < 0) { |
67 | if (tls_options.is_valid()) { |
68 | conn_port = PORT_HTTPS; |
69 | } else { |
70 | conn_port = PORT_HTTP; |
71 | } |
72 | } |
73 | |
74 | connection = tcp_connection; |
75 | |
76 | if (tls_options.is_valid() && https_proxy_port != -1) { |
77 | proxy_client.instantiate(); // Needs proxy negotiation. |
78 | server_host = https_proxy_host; |
79 | server_port = https_proxy_port; |
80 | } else if (tls_options.is_null() && http_proxy_port != -1) { |
81 | server_host = http_proxy_host; |
82 | server_port = http_proxy_port; |
83 | } else { |
84 | server_host = conn_host; |
85 | server_port = conn_port; |
86 | } |
87 | |
88 | if (server_host.is_valid_ip_address()) { |
89 | // Host contains valid IP. |
90 | Error err = tcp_connection->connect_to_host(IPAddress(server_host), server_port); |
91 | if (err) { |
92 | status = STATUS_CANT_CONNECT; |
93 | return err; |
94 | } |
95 | |
96 | status = STATUS_CONNECTING; |
97 | } else { |
98 | // Host contains hostname and needs to be resolved to IP. |
99 | resolving = IP::get_singleton()->resolve_hostname_queue_item(server_host); |
100 | if (resolving == IP::RESOLVER_INVALID_ID) { |
101 | status = STATUS_CANT_RESOLVE; |
102 | return ERR_CANT_RESOLVE; |
103 | } |
104 | status = STATUS_RESOLVING; |
105 | } |
106 | |
107 | return OK; |
108 | } |
109 | |
110 | void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) { |
111 | ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object." ); |
112 | |
113 | if (tls_options.is_valid()) { |
114 | ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerTLS>(p_connection.ptr()), |
115 | "Connection is not a reference to a valid StreamPeerTLS object." ); |
116 | } |
117 | |
118 | if (connection == p_connection) { |
119 | return; |
120 | } |
121 | |
122 | close(); |
123 | connection = p_connection; |
124 | status = STATUS_CONNECTED; |
125 | } |
126 | |
127 | Ref<StreamPeer> HTTPClientTCP::get_connection() const { |
128 | return connection; |
129 | } |
130 | |
131 | static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) { |
132 | switch (p_method) { |
133 | case HTTPClientTCP::METHOD_CONNECT: { |
134 | // Authority in host:port format, as in RFC7231. |
135 | int pos = p_url.find_char(':'); |
136 | return 0 < pos && pos < p_url.length() - 1; |
137 | } |
138 | case HTTPClientTCP::METHOD_OPTIONS: { |
139 | if (p_url == "*" ) { |
140 | return true; |
141 | } |
142 | [[fallthrough]]; |
143 | } |
144 | default: |
145 | // Absolute path or absolute URL. |
146 | return p_url.begins_with("/" ) || p_url.begins_with("http://" ) || p_url.begins_with("https://" ); |
147 | } |
148 | } |
149 | |
150 | Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &, const uint8_t *p_body, int p_body_size) { |
151 | ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); |
152 | ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER); |
153 | ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); |
154 | ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); |
155 | |
156 | Error err = verify_headers(p_headers); |
157 | if (err) { |
158 | return err; |
159 | } |
160 | |
161 | String uri = p_url; |
162 | if (tls_options.is_null() && http_proxy_port != -1) { |
163 | uri = vformat("http://%s:%d%s" , conn_host, conn_port, p_url); |
164 | } |
165 | |
166 | String request = String(_methods[p_method]) + " " + uri + " HTTP/1.1\r\n" ; |
167 | bool add_host = true; |
168 | bool add_clen = p_body_size > 0; |
169 | bool add_uagent = true; |
170 | bool add_accept = true; |
171 | for (int i = 0; i < p_headers.size(); i++) { |
172 | request += p_headers[i] + "\r\n" ; |
173 | if (add_host && p_headers[i].findn("Host:" ) == 0) { |
174 | add_host = false; |
175 | } |
176 | if (add_clen && p_headers[i].findn("Content-Length:" ) == 0) { |
177 | add_clen = false; |
178 | } |
179 | if (add_uagent && p_headers[i].findn("User-Agent:" ) == 0) { |
180 | add_uagent = false; |
181 | } |
182 | if (add_accept && p_headers[i].findn("Accept:" ) == 0) { |
183 | add_accept = false; |
184 | } |
185 | } |
186 | if (add_host) { |
187 | if ((tls_options.is_valid() && conn_port == PORT_HTTPS) || (tls_options.is_null() && conn_port == PORT_HTTP)) { |
188 | // Don't append the standard ports. |
189 | request += "Host: " + conn_host + "\r\n" ; |
190 | } else { |
191 | request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n" ; |
192 | } |
193 | } |
194 | if (add_clen) { |
195 | request += "Content-Length: " + itos(p_body_size) + "\r\n" ; |
196 | // Should it add utf8 encoding? |
197 | } |
198 | if (add_uagent) { |
199 | request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n" ; |
200 | } |
201 | if (add_accept) { |
202 | request += "Accept: */*\r\n" ; |
203 | } |
204 | request += "\r\n" ; |
205 | CharString cs = request.utf8(); |
206 | |
207 | request_buffer->clear(); |
208 | request_buffer->put_data((const uint8_t *)cs.get_data(), cs.length()); |
209 | if (p_body_size > 0) { |
210 | request_buffer->put_data(p_body, p_body_size); |
211 | } |
212 | request_buffer->seek(0); |
213 | |
214 | status = STATUS_REQUESTING; |
215 | head_request = p_method == METHOD_HEAD; |
216 | |
217 | return OK; |
218 | } |
219 | |
220 | bool HTTPClientTCP::has_response() const { |
221 | return response_headers.size() != 0; |
222 | } |
223 | |
224 | bool HTTPClientTCP::is_response_chunked() const { |
225 | return chunked; |
226 | } |
227 | |
228 | int HTTPClientTCP::get_response_code() const { |
229 | return response_num; |
230 | } |
231 | |
232 | Error HTTPClientTCP::(List<String> *r_response) { |
233 | if (!response_headers.size()) { |
234 | return ERR_INVALID_PARAMETER; |
235 | } |
236 | |
237 | for (int i = 0; i < response_headers.size(); i++) { |
238 | r_response->push_back(response_headers[i]); |
239 | } |
240 | |
241 | response_headers.clear(); |
242 | |
243 | return OK; |
244 | } |
245 | |
246 | void HTTPClientTCP::close() { |
247 | if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) { |
248 | tcp_connection->disconnect_from_host(); |
249 | } |
250 | |
251 | connection.unref(); |
252 | proxy_client.unref(); |
253 | status = STATUS_DISCONNECTED; |
254 | head_request = false; |
255 | if (resolving != IP::RESOLVER_INVALID_ID) { |
256 | IP::get_singleton()->erase_resolve_item(resolving); |
257 | resolving = IP::RESOLVER_INVALID_ID; |
258 | } |
259 | |
260 | ip_candidates.clear(); |
261 | response_headers.clear(); |
262 | response_str.clear(); |
263 | request_buffer->clear(); |
264 | body_size = -1; |
265 | body_left = 0; |
266 | chunk_left = 0; |
267 | chunk_trailer_part = false; |
268 | read_until_eof = false; |
269 | response_num = 0; |
270 | handshaking = false; |
271 | } |
272 | |
273 | Error HTTPClientTCP::poll() { |
274 | if (tcp_connection.is_valid()) { |
275 | tcp_connection->poll(); |
276 | } |
277 | switch (status) { |
278 | case STATUS_RESOLVING: { |
279 | ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); |
280 | |
281 | IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); |
282 | switch (rstatus) { |
283 | case IP::RESOLVER_STATUS_WAITING: |
284 | return OK; // Still resolving. |
285 | |
286 | case IP::RESOLVER_STATUS_DONE: { |
287 | ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); |
288 | IP::get_singleton()->erase_resolve_item(resolving); |
289 | resolving = IP::RESOLVER_INVALID_ID; |
290 | |
291 | Error err = ERR_BUG; // Should be at least one entry. |
292 | while (ip_candidates.size() > 0) { |
293 | err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port); |
294 | if (err == OK) { |
295 | break; |
296 | } |
297 | } |
298 | if (err) { |
299 | status = STATUS_CANT_CONNECT; |
300 | return err; |
301 | } |
302 | |
303 | status = STATUS_CONNECTING; |
304 | } break; |
305 | case IP::RESOLVER_STATUS_NONE: |
306 | case IP::RESOLVER_STATUS_ERROR: { |
307 | IP::get_singleton()->erase_resolve_item(resolving); |
308 | resolving = IP::RESOLVER_INVALID_ID; |
309 | close(); |
310 | status = STATUS_CANT_RESOLVE; |
311 | return ERR_CANT_RESOLVE; |
312 | } break; |
313 | } |
314 | } break; |
315 | case STATUS_CONNECTING: { |
316 | StreamPeerTCP::Status s = tcp_connection->get_status(); |
317 | switch (s) { |
318 | case StreamPeerTCP::STATUS_CONNECTING: { |
319 | return OK; |
320 | } break; |
321 | case StreamPeerTCP::STATUS_CONNECTED: { |
322 | if (tls_options.is_valid() && proxy_client.is_valid()) { |
323 | Error err = proxy_client->poll(); |
324 | if (err == ERR_UNCONFIGURED) { |
325 | proxy_client->set_connection(tcp_connection); |
326 | const Vector<String> ; |
327 | err = proxy_client->request(METHOD_CONNECT, vformat("%s:%d" , conn_host, conn_port), headers, nullptr, 0); |
328 | if (err != OK) { |
329 | status = STATUS_CANT_CONNECT; |
330 | return err; |
331 | } |
332 | } else if (err != OK) { |
333 | status = STATUS_CANT_CONNECT; |
334 | return err; |
335 | } |
336 | switch (proxy_client->get_status()) { |
337 | case STATUS_REQUESTING: { |
338 | return OK; |
339 | } break; |
340 | case STATUS_BODY: { |
341 | proxy_client->read_response_body_chunk(); |
342 | return OK; |
343 | } break; |
344 | case STATUS_CONNECTED: { |
345 | if (proxy_client->get_response_code() != RESPONSE_OK) { |
346 | status = STATUS_CANT_CONNECT; |
347 | return ERR_CANT_CONNECT; |
348 | } |
349 | proxy_client.unref(); |
350 | return OK; |
351 | } |
352 | case STATUS_DISCONNECTED: |
353 | case STATUS_RESOLVING: |
354 | case STATUS_CONNECTING: { |
355 | status = STATUS_CANT_CONNECT; |
356 | ERR_FAIL_V(ERR_BUG); |
357 | } break; |
358 | default: { |
359 | status = STATUS_CANT_CONNECT; |
360 | return ERR_CANT_CONNECT; |
361 | } break; |
362 | } |
363 | } else if (tls_options.is_valid()) { |
364 | Ref<StreamPeerTLS> tls_conn; |
365 | if (!handshaking) { |
366 | // Connect the StreamPeerTLS and start handshaking. |
367 | tls_conn = Ref<StreamPeerTLS>(StreamPeerTLS::create()); |
368 | Error err = tls_conn->connect_to_stream(tcp_connection, conn_host, tls_options); |
369 | if (err != OK) { |
370 | close(); |
371 | status = STATUS_TLS_HANDSHAKE_ERROR; |
372 | return ERR_CANT_CONNECT; |
373 | } |
374 | connection = tls_conn; |
375 | handshaking = true; |
376 | } else { |
377 | // We are already handshaking, which means we can use your already active TLS connection. |
378 | tls_conn = static_cast<Ref<StreamPeerTLS>>(connection); |
379 | if (tls_conn.is_null()) { |
380 | close(); |
381 | status = STATUS_TLS_HANDSHAKE_ERROR; |
382 | return ERR_CANT_CONNECT; |
383 | } |
384 | |
385 | tls_conn->poll(); // Try to finish the handshake. |
386 | } |
387 | |
388 | if (tls_conn->get_status() == StreamPeerTLS::STATUS_CONNECTED) { |
389 | // Handshake has been successful. |
390 | handshaking = false; |
391 | ip_candidates.clear(); |
392 | status = STATUS_CONNECTED; |
393 | return OK; |
394 | } else if (tls_conn->get_status() != StreamPeerTLS::STATUS_HANDSHAKING) { |
395 | // Handshake has failed. |
396 | close(); |
397 | status = STATUS_TLS_HANDSHAKE_ERROR; |
398 | return ERR_CANT_CONNECT; |
399 | } |
400 | // ... we will need to poll more for handshake to finish. |
401 | } else { |
402 | ip_candidates.clear(); |
403 | status = STATUS_CONNECTED; |
404 | } |
405 | return OK; |
406 | } break; |
407 | case StreamPeerTCP::STATUS_ERROR: |
408 | case StreamPeerTCP::STATUS_NONE: { |
409 | Error err = ERR_CANT_CONNECT; |
410 | while (ip_candidates.size() > 0) { |
411 | tcp_connection->disconnect_from_host(); |
412 | err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port); |
413 | if (err == OK) { |
414 | return OK; |
415 | } |
416 | } |
417 | close(); |
418 | status = STATUS_CANT_CONNECT; |
419 | return err; |
420 | } break; |
421 | } |
422 | } break; |
423 | case STATUS_BODY: |
424 | case STATUS_CONNECTED: { |
425 | // Check if we are still connected. |
426 | if (tls_options.is_valid()) { |
427 | Ref<StreamPeerTLS> tmp = connection; |
428 | tmp->poll(); |
429 | if (tmp->get_status() != StreamPeerTLS::STATUS_CONNECTED) { |
430 | status = STATUS_CONNECTION_ERROR; |
431 | return ERR_CONNECTION_ERROR; |
432 | } |
433 | } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { |
434 | status = STATUS_CONNECTION_ERROR; |
435 | return ERR_CONNECTION_ERROR; |
436 | } |
437 | // Connection established, requests can now be made. |
438 | return OK; |
439 | } break; |
440 | case STATUS_REQUESTING: { |
441 | if (request_buffer->get_available_bytes()) { |
442 | int avail = request_buffer->get_available_bytes(); |
443 | int pos = request_buffer->get_position(); |
444 | const Vector<uint8_t> data = request_buffer->get_data_array(); |
445 | int wrote = 0; |
446 | Error err; |
447 | if (blocking) { |
448 | err = connection->put_data(data.ptr() + pos, avail); |
449 | wrote += avail; |
450 | } else { |
451 | err = connection->put_partial_data(data.ptr() + pos, avail, wrote); |
452 | } |
453 | if (err != OK) { |
454 | close(); |
455 | status = STATUS_CONNECTION_ERROR; |
456 | return ERR_CONNECTION_ERROR; |
457 | } |
458 | pos += wrote; |
459 | request_buffer->seek(pos); |
460 | if (avail - wrote > 0) { |
461 | return OK; |
462 | } |
463 | request_buffer->clear(); |
464 | } |
465 | while (true) { |
466 | uint8_t byte; |
467 | int rec = 0; |
468 | Error err = _get_http_data(&byte, 1, rec); |
469 | if (err != OK) { |
470 | close(); |
471 | status = STATUS_CONNECTION_ERROR; |
472 | return ERR_CONNECTION_ERROR; |
473 | } |
474 | |
475 | if (rec == 0) { |
476 | return OK; // Still requesting, keep trying! |
477 | } |
478 | |
479 | response_str.push_back(byte); |
480 | int rs = response_str.size(); |
481 | if ( |
482 | (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || |
483 | (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { |
484 | // End of response, parse. |
485 | response_str.push_back(0); |
486 | String response; |
487 | response.parse_utf8((const char *)response_str.ptr()); |
488 | Vector<String> responses = response.split("\n" ); |
489 | body_size = -1; |
490 | chunked = false; |
491 | body_left = 0; |
492 | chunk_left = 0; |
493 | chunk_trailer_part = false; |
494 | read_until_eof = false; |
495 | response_str.clear(); |
496 | response_headers.clear(); |
497 | response_num = RESPONSE_OK; |
498 | |
499 | // Per the HTTP 1.1 spec, keep-alive is the default. |
500 | // Not following that specification breaks standard implementations. |
501 | // Broken web servers should be fixed. |
502 | bool keep_alive = true; |
503 | |
504 | for (int i = 0; i < responses.size(); i++) { |
505 | String = responses[i].strip_edges(); |
506 | String s = header.to_lower(); |
507 | if (s.length() == 0) { |
508 | continue; |
509 | } |
510 | if (s.begins_with("content-length:" )) { |
511 | body_size = s.substr(s.find(":" ) + 1, s.length()).strip_edges().to_int(); |
512 | body_left = body_size; |
513 | |
514 | } else if (s.begins_with("transfer-encoding:" )) { |
515 | String encoding = header.substr(header.find(":" ) + 1, header.length()).strip_edges(); |
516 | if (encoding == "chunked" ) { |
517 | chunked = true; |
518 | } |
519 | } else if (s.begins_with("connection: close" )) { |
520 | keep_alive = false; |
521 | } |
522 | |
523 | if (i == 0 && responses[i].begins_with("HTTP" )) { |
524 | String num = responses[i].get_slicec(' ', 1); |
525 | response_num = num.to_int(); |
526 | } else { |
527 | response_headers.push_back(header); |
528 | } |
529 | } |
530 | |
531 | // This is a HEAD request, we won't receive anything. |
532 | if (head_request) { |
533 | body_size = 0; |
534 | body_left = 0; |
535 | } |
536 | |
537 | if (body_size != -1 || chunked) { |
538 | status = STATUS_BODY; |
539 | } else if (!keep_alive) { |
540 | read_until_eof = true; |
541 | status = STATUS_BODY; |
542 | } else { |
543 | status = STATUS_CONNECTED; |
544 | } |
545 | return OK; |
546 | } |
547 | } |
548 | } break; |
549 | case STATUS_DISCONNECTED: { |
550 | return ERR_UNCONFIGURED; |
551 | } break; |
552 | case STATUS_CONNECTION_ERROR: |
553 | case STATUS_TLS_HANDSHAKE_ERROR: { |
554 | return ERR_CONNECTION_ERROR; |
555 | } break; |
556 | case STATUS_CANT_CONNECT: { |
557 | return ERR_CANT_CONNECT; |
558 | } break; |
559 | case STATUS_CANT_RESOLVE: { |
560 | return ERR_CANT_RESOLVE; |
561 | } break; |
562 | } |
563 | |
564 | return OK; |
565 | } |
566 | |
567 | int64_t HTTPClientTCP::get_response_body_length() const { |
568 | return body_size; |
569 | } |
570 | |
571 | PackedByteArray HTTPClientTCP::read_response_body_chunk() { |
572 | ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); |
573 | |
574 | PackedByteArray ret; |
575 | Error err = OK; |
576 | |
577 | if (chunked) { |
578 | while (true) { |
579 | if (chunk_trailer_part) { |
580 | // We need to consume the trailer part too or keep-alive will break. |
581 | uint8_t b; |
582 | int rec = 0; |
583 | err = _get_http_data(&b, 1, rec); |
584 | |
585 | if (rec == 0) { |
586 | break; |
587 | } |
588 | |
589 | chunk.push_back(b); |
590 | int cs = chunk.size(); |
591 | if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { |
592 | if (cs == 2) { |
593 | // Finally over. |
594 | chunk_trailer_part = false; |
595 | status = STATUS_CONNECTED; |
596 | chunk.clear(); |
597 | break; |
598 | } else { |
599 | // We do not process nor return the trailer data. |
600 | chunk.clear(); |
601 | } |
602 | } |
603 | } else if (chunk_left == 0) { |
604 | // Reading length. |
605 | uint8_t b; |
606 | int rec = 0; |
607 | err = _get_http_data(&b, 1, rec); |
608 | |
609 | if (rec == 0) { |
610 | break; |
611 | } |
612 | |
613 | chunk.push_back(b); |
614 | |
615 | if (chunk.size() > 32) { |
616 | ERR_PRINT("HTTP Invalid chunk hex len" ); |
617 | status = STATUS_CONNECTION_ERROR; |
618 | break; |
619 | } |
620 | |
621 | if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { |
622 | int len = 0; |
623 | for (int i = 0; i < chunk.size() - 2; i++) { |
624 | char c = chunk[i]; |
625 | int v = 0; |
626 | if (is_digit(c)) { |
627 | v = c - '0'; |
628 | } else if (c >= 'a' && c <= 'f') { |
629 | v = c - 'a' + 10; |
630 | } else if (c >= 'A' && c <= 'F') { |
631 | v = c - 'A' + 10; |
632 | } else { |
633 | ERR_PRINT("HTTP Chunk len not in hex!!" ); |
634 | status = STATUS_CONNECTION_ERROR; |
635 | break; |
636 | } |
637 | len <<= 4; |
638 | len |= v; |
639 | if (len > (1 << 24)) { |
640 | ERR_PRINT("HTTP Chunk too big!! >16mb" ); |
641 | status = STATUS_CONNECTION_ERROR; |
642 | break; |
643 | } |
644 | } |
645 | |
646 | if (len == 0) { |
647 | // End reached! |
648 | chunk_trailer_part = true; |
649 | chunk.clear(); |
650 | break; |
651 | } |
652 | |
653 | chunk_left = len + 2; |
654 | chunk.resize(chunk_left); |
655 | } |
656 | } else { |
657 | int rec = 0; |
658 | err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); |
659 | if (rec == 0) { |
660 | break; |
661 | } |
662 | chunk_left -= rec; |
663 | |
664 | if (chunk_left == 0) { |
665 | if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { |
666 | ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)" ); |
667 | status = STATUS_CONNECTION_ERROR; |
668 | break; |
669 | } |
670 | |
671 | ret.resize(chunk.size() - 2); |
672 | uint8_t *w = ret.ptrw(); |
673 | memcpy(w, chunk.ptr(), chunk.size() - 2); |
674 | chunk.clear(); |
675 | } |
676 | |
677 | break; |
678 | } |
679 | } |
680 | |
681 | } else { |
682 | int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; |
683 | ret.resize(to_read); |
684 | int _offset = 0; |
685 | while (to_read > 0) { |
686 | int rec = 0; |
687 | { |
688 | uint8_t *w = ret.ptrw(); |
689 | err = _get_http_data(w + _offset, to_read, rec); |
690 | } |
691 | if (rec <= 0) { // Ended up reading less. |
692 | ret.resize(_offset); |
693 | break; |
694 | } else { |
695 | _offset += rec; |
696 | to_read -= rec; |
697 | if (!read_until_eof) { |
698 | body_left -= rec; |
699 | } |
700 | } |
701 | if (err != OK) { |
702 | ret.resize(_offset); |
703 | break; |
704 | } |
705 | } |
706 | } |
707 | |
708 | if (err != OK) { |
709 | close(); |
710 | |
711 | if (err == ERR_FILE_EOF) { |
712 | status = STATUS_DISCONNECTED; // Server disconnected. |
713 | } else { |
714 | status = STATUS_CONNECTION_ERROR; |
715 | } |
716 | } else if (body_left == 0 && !chunked && !read_until_eof) { |
717 | status = STATUS_CONNECTED; |
718 | } |
719 | |
720 | return ret; |
721 | } |
722 | |
723 | HTTPClientTCP::Status HTTPClientTCP::get_status() const { |
724 | return status; |
725 | } |
726 | |
727 | void HTTPClientTCP::set_blocking_mode(bool p_enable) { |
728 | blocking = p_enable; |
729 | } |
730 | |
731 | bool HTTPClientTCP::is_blocking_mode_enabled() const { |
732 | return blocking; |
733 | } |
734 | |
735 | Error HTTPClientTCP::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) { |
736 | if (blocking) { |
737 | // We can't use StreamPeer.get_data, since when reaching EOF we will get an |
738 | // error without knowing how many bytes we received. |
739 | Error err = ERR_FILE_EOF; |
740 | int read = 0; |
741 | int left = p_bytes; |
742 | r_received = 0; |
743 | while (left > 0) { |
744 | err = connection->get_partial_data(p_buffer + r_received, left, read); |
745 | if (err == OK) { |
746 | r_received += read; |
747 | } else if (err == ERR_FILE_EOF) { |
748 | r_received += read; |
749 | return err; |
750 | } else { |
751 | return err; |
752 | } |
753 | left -= read; |
754 | } |
755 | return err; |
756 | } else { |
757 | return connection->get_partial_data(p_buffer, p_bytes, r_received); |
758 | } |
759 | } |
760 | |
761 | void HTTPClientTCP::set_read_chunk_size(int p_size) { |
762 | ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); |
763 | read_chunk_size = p_size; |
764 | } |
765 | |
766 | int HTTPClientTCP::get_read_chunk_size() const { |
767 | return read_chunk_size; |
768 | } |
769 | |
770 | void HTTPClientTCP::set_http_proxy(const String &p_host, int p_port) { |
771 | if (p_host.is_empty() || p_port == -1) { |
772 | http_proxy_host = "" ; |
773 | http_proxy_port = -1; |
774 | } else { |
775 | http_proxy_host = p_host; |
776 | http_proxy_port = p_port; |
777 | } |
778 | } |
779 | |
780 | void HTTPClientTCP::set_https_proxy(const String &p_host, int p_port) { |
781 | if (p_host.is_empty() || p_port == -1) { |
782 | https_proxy_host = "" ; |
783 | https_proxy_port = -1; |
784 | } else { |
785 | https_proxy_host = p_host; |
786 | https_proxy_port = p_port; |
787 | } |
788 | } |
789 | |
790 | HTTPClientTCP::HTTPClientTCP() { |
791 | tcp_connection.instantiate(); |
792 | request_buffer.instantiate(); |
793 | } |
794 | |
795 | HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; |
796 | |
797 | #endif // WEB_ENABLED |
798 | |