Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/io/http_client_tcp.cpp
9973 views
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(bool p_notify_postinitialize) {
39
return static_cast<HTTPClient *>(ClassDB::creator<HTTPClientTCP>(p_notify_postinitialize));
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);
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);
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> &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(GODOT_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::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
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> 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 = String::utf8((const char *)response_str.ptr(), response_str.size());
487
Vector<String> responses = response.split("\n");
488
body_size = -1;
489
chunked = false;
490
body_left = 0;
491
chunk_left = 0;
492
chunk_trailer_part = false;
493
read_until_eof = false;
494
response_str.clear();
495
response_headers.clear();
496
response_num = RESPONSE_OK;
497
498
// Per the HTTP 1.1 spec, keep-alive is the default.
499
// Not following that specification breaks standard implementations.
500
// Broken web servers should be fixed.
501
bool keep_alive = true;
502
503
for (int i = 0; i < responses.size(); i++) {
504
String header = responses[i].strip_edges();
505
String s = header.to_lower();
506
if (s.length() == 0) {
507
continue;
508
}
509
if (s.begins_with("content-length:")) {
510
body_size = s.substr(s.find_char(':') + 1).strip_edges().to_int();
511
body_left = body_size;
512
513
} else if (s.begins_with("transfer-encoding:")) {
514
String encoding = header.substr(header.find_char(':') + 1).strip_edges();
515
if (encoding == "chunked") {
516
chunked = true;
517
}
518
} else if (s.begins_with("connection: close")) {
519
keep_alive = false;
520
}
521
522
if (i == 0 && responses[i].begins_with("HTTP")) {
523
String num = responses[i].get_slicec(' ', 1);
524
response_num = num.to_int();
525
} else {
526
response_headers.push_back(header);
527
}
528
}
529
530
// This is a HEAD request, we won't receive anything.
531
if (head_request) {
532
body_size = 0;
533
body_left = 0;
534
}
535
536
if (body_size != -1 || chunked) {
537
status = STATUS_BODY;
538
} else if (!keep_alive) {
539
read_until_eof = true;
540
status = STATUS_BODY;
541
} else {
542
status = STATUS_CONNECTED;
543
}
544
return OK;
545
}
546
}
547
} break;
548
case STATUS_DISCONNECTED: {
549
return ERR_UNCONFIGURED;
550
} break;
551
case STATUS_CONNECTION_ERROR:
552
case STATUS_TLS_HANDSHAKE_ERROR: {
553
return ERR_CONNECTION_ERROR;
554
} break;
555
case STATUS_CANT_CONNECT: {
556
return ERR_CANT_CONNECT;
557
} break;
558
case STATUS_CANT_RESOLVE: {
559
return ERR_CANT_RESOLVE;
560
} break;
561
}
562
563
return OK;
564
}
565
566
int64_t HTTPClientTCP::get_response_body_length() const {
567
return body_size;
568
}
569
570
PackedByteArray HTTPClientTCP::read_response_body_chunk() {
571
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
572
573
PackedByteArray ret;
574
Error err = OK;
575
576
if (chunked) {
577
while (true) {
578
if (chunk_trailer_part) {
579
// We need to consume the trailer part too or keep-alive will break.
580
uint8_t b;
581
int rec = 0;
582
err = _get_http_data(&b, 1, rec);
583
584
if (rec == 0) {
585
break;
586
}
587
588
chunk.push_back(b);
589
int cs = chunk.size();
590
if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) {
591
if (cs == 2) {
592
// Finally over.
593
chunk_trailer_part = false;
594
status = STATUS_CONNECTED;
595
chunk.clear();
596
break;
597
} else {
598
// We do not process nor return the trailer data.
599
chunk.clear();
600
}
601
}
602
} else if (chunk_left == 0) {
603
// Reading length.
604
uint8_t b;
605
int rec = 0;
606
err = _get_http_data(&b, 1, rec);
607
608
if (rec == 0) {
609
break;
610
}
611
612
chunk.push_back(b);
613
614
if (chunk.size() > 32) {
615
ERR_PRINT("HTTP Invalid chunk hex len");
616
status = STATUS_CONNECTION_ERROR;
617
break;
618
}
619
620
if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') {
621
int len = 0;
622
for (int i = 0; i < chunk.size() - 2; i++) {
623
char c = chunk[i];
624
int v = 0;
625
if (is_digit(c)) {
626
v = c - '0';
627
} else if (c >= 'a' && c <= 'f') {
628
v = c - 'a' + 10;
629
} else if (c >= 'A' && c <= 'F') {
630
v = c - 'A' + 10;
631
} else {
632
ERR_PRINT("HTTP Chunk len not in hex!!");
633
status = STATUS_CONNECTION_ERROR;
634
break;
635
}
636
len <<= 4;
637
len |= v;
638
if (len > (1 << 24)) {
639
ERR_PRINT("HTTP Chunk too big!! >16mb");
640
status = STATUS_CONNECTION_ERROR;
641
break;
642
}
643
}
644
645
if (len == 0) {
646
// End reached!
647
chunk_trailer_part = true;
648
chunk.clear();
649
break;
650
}
651
652
chunk_left = len + 2;
653
chunk.resize(chunk_left);
654
}
655
} else {
656
int rec = 0;
657
err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec);
658
if (rec == 0) {
659
break;
660
}
661
chunk_left -= rec;
662
663
if (chunk_left == 0) {
664
const int chunk_size = chunk.size();
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)(bool p_notify_postinitialize) = HTTPClientTCP::_create_func;
796
797
#endif // WEB_ENABLED
798
799