| 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 | |