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
38HTTPClient *HTTPClientTCP::_create_func() {
39 return memnew(HTTPClientTCP);
40}
41
42Error 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
110void 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
127Ref<StreamPeer> HTTPClientTCP::get_connection() const {
128 return connection;
129}
130
131static 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
150Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &p_headers, 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
220bool HTTPClientTCP::has_response() const {
221 return response_headers.size() != 0;
222}
223
224bool HTTPClientTCP::is_response_chunked() const {
225 return chunked;
226}
227
228int HTTPClientTCP::get_response_code() const {
229 return response_num;
230}
231
232Error HTTPClientTCP::get_response_headers(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
246void 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
273Error 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> headers;
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 header = 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
567int64_t HTTPClientTCP::get_response_body_length() const {
568 return body_size;
569}
570
571PackedByteArray 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
723HTTPClientTCP::Status HTTPClientTCP::get_status() const {
724 return status;
725}
726
727void HTTPClientTCP::set_blocking_mode(bool p_enable) {
728 blocking = p_enable;
729}
730
731bool HTTPClientTCP::is_blocking_mode_enabled() const {
732 return blocking;
733}
734
735Error 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
761void 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
766int HTTPClientTCP::get_read_chunk_size() const {
767 return read_chunk_size;
768}
769
770void 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
780void 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
790HTTPClientTCP::HTTPClientTCP() {
791 tcp_connection.instantiate();
792 request_buffer.instantiate();
793}
794
795HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func;
796
797#endif // WEB_ENABLED
798