| 1 | /**************************************************************************/ |
| 2 | /* http_request.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 | #include "http_request.h" |
| 32 | #include "core/io/compression.h" |
| 33 | #include "scene/main/timer.h" |
| 34 | |
| 35 | Error HTTPRequest::_request() { |
| 36 | return client->connect_to_host(url, port, use_tls ? tls_options : nullptr); |
| 37 | } |
| 38 | |
| 39 | Error HTTPRequest::_parse_url(const String &p_url) { |
| 40 | use_tls = false; |
| 41 | request_string = "" ; |
| 42 | port = 80; |
| 43 | request_sent = false; |
| 44 | got_response = false; |
| 45 | body_len = -1; |
| 46 | body.clear(); |
| 47 | downloaded.set(0); |
| 48 | final_body_size.set(0); |
| 49 | redirections = 0; |
| 50 | |
| 51 | String scheme; |
| 52 | Error err = p_url.parse_url(scheme, url, port, request_string); |
| 53 | ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing URL: " + p_url + "." ); |
| 54 | if (scheme == "https://" ) { |
| 55 | use_tls = true; |
| 56 | } else if (scheme != "http://" ) { |
| 57 | ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid URL scheme: " + scheme + "." ); |
| 58 | } |
| 59 | if (port == 0) { |
| 60 | port = use_tls ? 443 : 80; |
| 61 | } |
| 62 | if (request_string.is_empty()) { |
| 63 | request_string = "/" ; |
| 64 | } |
| 65 | return OK; |
| 66 | } |
| 67 | |
| 68 | bool HTTPRequest::(const PackedStringArray &, const String &) { |
| 69 | bool exists = false; |
| 70 | |
| 71 | String = p_header_name.to_lower(); |
| 72 | for (int i = 0; i < p_headers.size() && !exists; i++) { |
| 73 | String sanitized = p_headers[i].strip_edges().to_lower(); |
| 74 | if (sanitized.begins_with(lower_case_header_name)) { |
| 75 | exists = true; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | return exists; |
| 80 | } |
| 81 | |
| 82 | String HTTPRequest::(const PackedStringArray &, const String &) { |
| 83 | String value = "" ; |
| 84 | |
| 85 | String = p_header_name.to_lower(); |
| 86 | for (int i = 0; i < p_headers.size(); i++) { |
| 87 | if (p_headers[i].find(":" ) > 0) { |
| 88 | Vector<String> parts = p_headers[i].split(":" , false, 1); |
| 89 | if (parts.size() > 1 && parts[0].strip_edges().to_lower() == lowwer_case_header_name) { |
| 90 | value = parts[1].strip_edges(); |
| 91 | break; |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | return value; |
| 97 | } |
| 98 | |
| 99 | Error HTTPRequest::request(const String &p_url, const Vector<String> &, HTTPClient::Method p_method, const String &p_request_data) { |
| 100 | // Copy the string into a raw buffer. |
| 101 | Vector<uint8_t> raw_data; |
| 102 | |
| 103 | CharString charstr = p_request_data.utf8(); |
| 104 | size_t len = charstr.length(); |
| 105 | if (len > 0) { |
| 106 | raw_data.resize(len); |
| 107 | uint8_t *w = raw_data.ptrw(); |
| 108 | memcpy(w, charstr.ptr(), len); |
| 109 | } |
| 110 | |
| 111 | return request_raw(p_url, p_custom_headers, p_method, raw_data); |
| 112 | } |
| 113 | |
| 114 | Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) { |
| 115 | ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED); |
| 116 | ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one." ); |
| 117 | |
| 118 | if (timeout > 0) { |
| 119 | timer->stop(); |
| 120 | timer->start(timeout); |
| 121 | } |
| 122 | |
| 123 | method = p_method; |
| 124 | |
| 125 | Error err = _parse_url(p_url); |
| 126 | if (err) { |
| 127 | return err; |
| 128 | } |
| 129 | |
| 130 | headers = p_custom_headers; |
| 131 | |
| 132 | if (accept_gzip) { |
| 133 | // If the user has specified an Accept-Encoding header, don't overwrite it. |
| 134 | if (!has_header(headers, "Accept-Encoding" )) { |
| 135 | headers.push_back("Accept-Encoding: gzip, deflate" ); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | request_data = p_request_data_raw; |
| 140 | |
| 141 | requesting = true; |
| 142 | |
| 143 | if (use_threads.is_set()) { |
| 144 | thread_done.clear(); |
| 145 | thread_request_quit.clear(); |
| 146 | client->set_blocking_mode(true); |
| 147 | thread.start(_thread_func, this); |
| 148 | } else { |
| 149 | client->set_blocking_mode(false); |
| 150 | err = _request(); |
| 151 | if (err != OK) { |
| 152 | _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); |
| 153 | return ERR_CANT_CONNECT; |
| 154 | } |
| 155 | |
| 156 | set_process_internal(true); |
| 157 | } |
| 158 | |
| 159 | return OK; |
| 160 | } |
| 161 | |
| 162 | void HTTPRequest::_thread_func(void *p_userdata) { |
| 163 | HTTPRequest *hr = static_cast<HTTPRequest *>(p_userdata); |
| 164 | |
| 165 | Error err = hr->_request(); |
| 166 | |
| 167 | if (err != OK) { |
| 168 | hr->_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); |
| 169 | } else { |
| 170 | while (!hr->thread_request_quit.is_set()) { |
| 171 | bool exit = hr->_update_connection(); |
| 172 | if (exit) { |
| 173 | break; |
| 174 | } |
| 175 | OS::get_singleton()->delay_usec(1); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | hr->thread_done.set(); |
| 180 | } |
| 181 | |
| 182 | void HTTPRequest::cancel_request() { |
| 183 | timer->stop(); |
| 184 | |
| 185 | if (!requesting) { |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | if (!use_threads.is_set()) { |
| 190 | set_process_internal(false); |
| 191 | } else { |
| 192 | thread_request_quit.set(); |
| 193 | if (thread.is_started()) { |
| 194 | thread.wait_to_finish(); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | file.unref(); |
| 199 | decompressor.unref(); |
| 200 | client->close(); |
| 201 | body.clear(); |
| 202 | got_response = false; |
| 203 | response_code = -1; |
| 204 | request_sent = false; |
| 205 | requesting = false; |
| 206 | } |
| 207 | |
| 208 | bool HTTPRequest::_handle_response(bool *ret_value) { |
| 209 | if (!client->has_response()) { |
| 210 | _defer_done(RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray()); |
| 211 | *ret_value = true; |
| 212 | return true; |
| 213 | } |
| 214 | |
| 215 | got_response = true; |
| 216 | response_code = client->get_response_code(); |
| 217 | List<String> ; |
| 218 | client->get_response_headers(&rheaders); |
| 219 | response_headers.clear(); |
| 220 | downloaded.set(0); |
| 221 | final_body_size.set(0); |
| 222 | decompressor.unref(); |
| 223 | |
| 224 | for (const String &E : rheaders) { |
| 225 | response_headers.push_back(E); |
| 226 | } |
| 227 | |
| 228 | if (response_code == 301 || response_code == 302) { |
| 229 | // Handle redirect. |
| 230 | |
| 231 | if (max_redirects >= 0 && redirections >= max_redirects) { |
| 232 | _defer_done(RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray()); |
| 233 | *ret_value = true; |
| 234 | return true; |
| 235 | } |
| 236 | |
| 237 | String new_request; |
| 238 | |
| 239 | for (const String &E : rheaders) { |
| 240 | if (E.findn("Location: " ) != -1) { |
| 241 | new_request = E.substr(9, E.length()).strip_edges(); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | if (!new_request.is_empty()) { |
| 246 | // Process redirect. |
| 247 | client->close(); |
| 248 | int new_redirs = redirections + 1; // Because _request() will clear it. |
| 249 | Error err; |
| 250 | if (new_request.begins_with("http" )) { |
| 251 | // New url, new request. |
| 252 | _parse_url(new_request); |
| 253 | } else { |
| 254 | request_string = new_request; |
| 255 | } |
| 256 | |
| 257 | err = _request(); |
| 258 | if (err == OK) { |
| 259 | request_sent = false; |
| 260 | got_response = false; |
| 261 | body_len = -1; |
| 262 | body.clear(); |
| 263 | downloaded.set(0); |
| 264 | final_body_size.set(0); |
| 265 | redirections = new_redirs; |
| 266 | *ret_value = false; |
| 267 | return true; |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | // Check if we need to start streaming decompression. |
| 273 | String content_encoding; |
| 274 | if (accept_gzip) { |
| 275 | content_encoding = get_header_value(response_headers, "Content-Encoding" ).to_lower(); |
| 276 | } |
| 277 | if (content_encoding == "gzip" ) { |
| 278 | decompressor.instantiate(); |
| 279 | decompressor->start_decompression(false, get_download_chunk_size()); |
| 280 | } else if (content_encoding == "deflate" ) { |
| 281 | decompressor.instantiate(); |
| 282 | decompressor->start_decompression(true, get_download_chunk_size()); |
| 283 | } |
| 284 | |
| 285 | return false; |
| 286 | } |
| 287 | |
| 288 | bool HTTPRequest::_update_connection() { |
| 289 | switch (client->get_status()) { |
| 290 | case HTTPClient::STATUS_DISCONNECTED: { |
| 291 | _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); |
| 292 | return true; // End it, since it's disconnected. |
| 293 | } break; |
| 294 | case HTTPClient::STATUS_RESOLVING: { |
| 295 | client->poll(); |
| 296 | // Must wait. |
| 297 | return false; |
| 298 | } break; |
| 299 | case HTTPClient::STATUS_CANT_RESOLVE: { |
| 300 | _defer_done(RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray()); |
| 301 | return true; |
| 302 | |
| 303 | } break; |
| 304 | case HTTPClient::STATUS_CONNECTING: { |
| 305 | client->poll(); |
| 306 | // Must wait. |
| 307 | return false; |
| 308 | } break; // Connecting to IP. |
| 309 | case HTTPClient::STATUS_CANT_CONNECT: { |
| 310 | _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); |
| 311 | return true; |
| 312 | |
| 313 | } break; |
| 314 | case HTTPClient::STATUS_CONNECTED: { |
| 315 | if (request_sent) { |
| 316 | if (!got_response) { |
| 317 | // No body. |
| 318 | |
| 319 | bool ret_value; |
| 320 | |
| 321 | if (_handle_response(&ret_value)) { |
| 322 | return ret_value; |
| 323 | } |
| 324 | |
| 325 | _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); |
| 326 | return true; |
| 327 | } |
| 328 | if (body_len < 0) { |
| 329 | // Chunked transfer is done. |
| 330 | _defer_done(RESULT_SUCCESS, response_code, response_headers, body); |
| 331 | return true; |
| 332 | } |
| 333 | |
| 334 | _defer_done(RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray()); |
| 335 | return true; |
| 336 | // Request might have been done. |
| 337 | } else { |
| 338 | // Did not request yet, do request. |
| 339 | |
| 340 | int size = request_data.size(); |
| 341 | Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size); |
| 342 | if (err != OK) { |
| 343 | _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); |
| 344 | return true; |
| 345 | } |
| 346 | |
| 347 | request_sent = true; |
| 348 | return false; |
| 349 | } |
| 350 | } break; // Connected: break requests only accepted here. |
| 351 | case HTTPClient::STATUS_REQUESTING: { |
| 352 | // Must wait, still requesting. |
| 353 | client->poll(); |
| 354 | return false; |
| 355 | |
| 356 | } break; // Request in progress. |
| 357 | case HTTPClient::STATUS_BODY: { |
| 358 | if (!got_response) { |
| 359 | bool ret_value; |
| 360 | |
| 361 | if (_handle_response(&ret_value)) { |
| 362 | return ret_value; |
| 363 | } |
| 364 | |
| 365 | if (!client->is_response_chunked() && client->get_response_body_length() == 0) { |
| 366 | _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); |
| 367 | return true; |
| 368 | } |
| 369 | |
| 370 | // No body len (-1) if chunked or no content-length header was provided. |
| 371 | // Change your webserver configuration if you want body len. |
| 372 | body_len = client->get_response_body_length(); |
| 373 | |
| 374 | if (body_size_limit >= 0 && body_len > body_size_limit) { |
| 375 | _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); |
| 376 | return true; |
| 377 | } |
| 378 | |
| 379 | if (!download_to_file.is_empty()) { |
| 380 | file = FileAccess::open(download_to_file, FileAccess::WRITE); |
| 381 | if (file.is_null()) { |
| 382 | _defer_done(RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray()); |
| 383 | return true; |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | client->poll(); |
| 389 | if (client->get_status() != HTTPClient::STATUS_BODY) { |
| 390 | return false; |
| 391 | } |
| 392 | |
| 393 | PackedByteArray chunk; |
| 394 | if (decompressor.is_null()) { |
| 395 | // Chunk can be read directly. |
| 396 | chunk = client->read_response_body_chunk(); |
| 397 | downloaded.add(chunk.size()); |
| 398 | } else { |
| 399 | // Chunk is the result of decompression. |
| 400 | PackedByteArray compressed = client->read_response_body_chunk(); |
| 401 | downloaded.add(compressed.size()); |
| 402 | |
| 403 | int pos = 0; |
| 404 | int left = compressed.size(); |
| 405 | while (left) { |
| 406 | int w = 0; |
| 407 | Error err = decompressor->put_partial_data(compressed.ptr() + pos, left, w); |
| 408 | if (err == OK) { |
| 409 | PackedByteArray dc; |
| 410 | dc.resize(decompressor->get_available_bytes()); |
| 411 | err = decompressor->get_data(dc.ptrw(), dc.size()); |
| 412 | chunk.append_array(dc); |
| 413 | } |
| 414 | if (err != OK) { |
| 415 | _defer_done(RESULT_BODY_DECOMPRESS_FAILED, response_code, response_headers, PackedByteArray()); |
| 416 | return true; |
| 417 | } |
| 418 | // We need this check here because a "zip bomb" could result in a chunk of few kilos decompressing into gigabytes of data. |
| 419 | if (body_size_limit >= 0 && final_body_size.get() + chunk.size() > body_size_limit) { |
| 420 | _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); |
| 421 | return true; |
| 422 | } |
| 423 | pos += w; |
| 424 | left -= w; |
| 425 | } |
| 426 | } |
| 427 | final_body_size.add(chunk.size()); |
| 428 | |
| 429 | if (body_size_limit >= 0 && final_body_size.get() > body_size_limit) { |
| 430 | _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); |
| 431 | return true; |
| 432 | } |
| 433 | |
| 434 | if (chunk.size()) { |
| 435 | if (file.is_valid()) { |
| 436 | const uint8_t *r = chunk.ptr(); |
| 437 | file->store_buffer(r, chunk.size()); |
| 438 | if (file->get_error() != OK) { |
| 439 | _defer_done(RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray()); |
| 440 | return true; |
| 441 | } |
| 442 | } else { |
| 443 | body.append_array(chunk); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | if (body_len >= 0) { |
| 448 | if (downloaded.get() == body_len) { |
| 449 | _defer_done(RESULT_SUCCESS, response_code, response_headers, body); |
| 450 | return true; |
| 451 | } |
| 452 | } else if (client->get_status() == HTTPClient::STATUS_DISCONNECTED) { |
| 453 | // We read till EOF, with no errors. Request is done. |
| 454 | _defer_done(RESULT_SUCCESS, response_code, response_headers, body); |
| 455 | return true; |
| 456 | } |
| 457 | |
| 458 | return false; |
| 459 | |
| 460 | } break; // Request resulted in body: break which must be read. |
| 461 | case HTTPClient::STATUS_CONNECTION_ERROR: { |
| 462 | _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); |
| 463 | return true; |
| 464 | } break; |
| 465 | case HTTPClient::STATUS_TLS_HANDSHAKE_ERROR: { |
| 466 | _defer_done(RESULT_TLS_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray()); |
| 467 | return true; |
| 468 | } break; |
| 469 | } |
| 470 | |
| 471 | ERR_FAIL_V(false); |
| 472 | } |
| 473 | |
| 474 | void HTTPRequest::_defer_done(int p_status, int p_code, const PackedStringArray &, const PackedByteArray &p_data) { |
| 475 | call_deferred(SNAME("_request_done" ), p_status, p_code, p_headers, p_data); |
| 476 | } |
| 477 | |
| 478 | void HTTPRequest::_request_done(int p_status, int p_code, const PackedStringArray &, const PackedByteArray &p_data) { |
| 479 | cancel_request(); |
| 480 | |
| 481 | emit_signal(SNAME("request_completed" ), p_status, p_code, p_headers, p_data); |
| 482 | } |
| 483 | |
| 484 | void HTTPRequest::_notification(int p_what) { |
| 485 | switch (p_what) { |
| 486 | case NOTIFICATION_INTERNAL_PROCESS: { |
| 487 | if (use_threads.is_set()) { |
| 488 | return; |
| 489 | } |
| 490 | bool done = _update_connection(); |
| 491 | if (done) { |
| 492 | set_process_internal(false); |
| 493 | } |
| 494 | } break; |
| 495 | |
| 496 | case NOTIFICATION_EXIT_TREE: { |
| 497 | if (requesting) { |
| 498 | cancel_request(); |
| 499 | } |
| 500 | } break; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | void HTTPRequest::set_use_threads(bool p_use) { |
| 505 | ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); |
| 506 | use_threads.set_to(p_use); |
| 507 | } |
| 508 | |
| 509 | bool HTTPRequest::is_using_threads() const { |
| 510 | return use_threads.is_set(); |
| 511 | } |
| 512 | |
| 513 | void HTTPRequest::set_accept_gzip(bool p_gzip) { |
| 514 | accept_gzip = p_gzip; |
| 515 | } |
| 516 | |
| 517 | bool HTTPRequest::is_accepting_gzip() const { |
| 518 | return accept_gzip; |
| 519 | } |
| 520 | |
| 521 | void HTTPRequest::set_body_size_limit(int p_bytes) { |
| 522 | ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); |
| 523 | |
| 524 | body_size_limit = p_bytes; |
| 525 | } |
| 526 | |
| 527 | int HTTPRequest::get_body_size_limit() const { |
| 528 | return body_size_limit; |
| 529 | } |
| 530 | |
| 531 | void HTTPRequest::set_download_file(const String &p_file) { |
| 532 | ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); |
| 533 | |
| 534 | download_to_file = p_file; |
| 535 | } |
| 536 | |
| 537 | String HTTPRequest::get_download_file() const { |
| 538 | return download_to_file; |
| 539 | } |
| 540 | |
| 541 | void HTTPRequest::set_download_chunk_size(int p_chunk_size) { |
| 542 | ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); |
| 543 | |
| 544 | client->set_read_chunk_size(p_chunk_size); |
| 545 | } |
| 546 | |
| 547 | int HTTPRequest::get_download_chunk_size() const { |
| 548 | return client->get_read_chunk_size(); |
| 549 | } |
| 550 | |
| 551 | HTTPClient::Status HTTPRequest::get_http_client_status() const { |
| 552 | return client->get_status(); |
| 553 | } |
| 554 | |
| 555 | void HTTPRequest::set_max_redirects(int p_max) { |
| 556 | max_redirects = p_max; |
| 557 | } |
| 558 | |
| 559 | int HTTPRequest::get_max_redirects() const { |
| 560 | return max_redirects; |
| 561 | } |
| 562 | |
| 563 | int HTTPRequest::get_downloaded_bytes() const { |
| 564 | return downloaded.get(); |
| 565 | } |
| 566 | |
| 567 | int HTTPRequest::get_body_size() const { |
| 568 | return body_len; |
| 569 | } |
| 570 | |
| 571 | void HTTPRequest::set_http_proxy(const String &p_host, int p_port) { |
| 572 | client->set_http_proxy(p_host, p_port); |
| 573 | } |
| 574 | |
| 575 | void HTTPRequest::set_https_proxy(const String &p_host, int p_port) { |
| 576 | client->set_https_proxy(p_host, p_port); |
| 577 | } |
| 578 | |
| 579 | void HTTPRequest::set_timeout(double p_timeout) { |
| 580 | ERR_FAIL_COND(p_timeout < 0); |
| 581 | timeout = p_timeout; |
| 582 | } |
| 583 | |
| 584 | double HTTPRequest::get_timeout() { |
| 585 | return timeout; |
| 586 | } |
| 587 | |
| 588 | void HTTPRequest::_timeout() { |
| 589 | cancel_request(); |
| 590 | _defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); |
| 591 | } |
| 592 | |
| 593 | void HTTPRequest::set_tls_options(const Ref<TLSOptions> &p_options) { |
| 594 | ERR_FAIL_COND(p_options.is_null() || p_options->is_server()); |
| 595 | tls_options = p_options; |
| 596 | } |
| 597 | |
| 598 | void HTTPRequest::_bind_methods() { |
| 599 | ClassDB::bind_method(D_METHOD("request" , "url" , "custom_headers" , "method" , "request_data" ), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String())); |
| 600 | ClassDB::bind_method(D_METHOD("request_raw" , "url" , "custom_headers" , "method" , "request_data_raw" ), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray())); |
| 601 | ClassDB::bind_method(D_METHOD("cancel_request" ), &HTTPRequest::cancel_request); |
| 602 | ClassDB::bind_method(D_METHOD("set_tls_options" , "client_options" ), &HTTPRequest::set_tls_options); |
| 603 | |
| 604 | ClassDB::bind_method(D_METHOD("get_http_client_status" ), &HTTPRequest::get_http_client_status); |
| 605 | |
| 606 | ClassDB::bind_method(D_METHOD("set_use_threads" , "enable" ), &HTTPRequest::set_use_threads); |
| 607 | ClassDB::bind_method(D_METHOD("is_using_threads" ), &HTTPRequest::is_using_threads); |
| 608 | |
| 609 | ClassDB::bind_method(D_METHOD("set_accept_gzip" , "enable" ), &HTTPRequest::set_accept_gzip); |
| 610 | ClassDB::bind_method(D_METHOD("is_accepting_gzip" ), &HTTPRequest::is_accepting_gzip); |
| 611 | |
| 612 | ClassDB::bind_method(D_METHOD("set_body_size_limit" , "bytes" ), &HTTPRequest::set_body_size_limit); |
| 613 | ClassDB::bind_method(D_METHOD("get_body_size_limit" ), &HTTPRequest::get_body_size_limit); |
| 614 | |
| 615 | ClassDB::bind_method(D_METHOD("set_max_redirects" , "amount" ), &HTTPRequest::set_max_redirects); |
| 616 | ClassDB::bind_method(D_METHOD("get_max_redirects" ), &HTTPRequest::get_max_redirects); |
| 617 | |
| 618 | ClassDB::bind_method(D_METHOD("set_download_file" , "path" ), &HTTPRequest::set_download_file); |
| 619 | ClassDB::bind_method(D_METHOD("get_download_file" ), &HTTPRequest::get_download_file); |
| 620 | |
| 621 | ClassDB::bind_method(D_METHOD("get_downloaded_bytes" ), &HTTPRequest::get_downloaded_bytes); |
| 622 | ClassDB::bind_method(D_METHOD("get_body_size" ), &HTTPRequest::get_body_size); |
| 623 | |
| 624 | ClassDB::bind_method(D_METHOD("_request_done" ), &HTTPRequest::_request_done); |
| 625 | |
| 626 | ClassDB::bind_method(D_METHOD("set_timeout" , "timeout" ), &HTTPRequest::set_timeout); |
| 627 | ClassDB::bind_method(D_METHOD("get_timeout" ), &HTTPRequest::get_timeout); |
| 628 | |
| 629 | ClassDB::bind_method(D_METHOD("set_download_chunk_size" , "chunk_size" ), &HTTPRequest::set_download_chunk_size); |
| 630 | ClassDB::bind_method(D_METHOD("get_download_chunk_size" ), &HTTPRequest::get_download_chunk_size); |
| 631 | |
| 632 | ClassDB::bind_method(D_METHOD("set_http_proxy" , "host" , "port" ), &HTTPRequest::set_http_proxy); |
| 633 | ClassDB::bind_method(D_METHOD("set_https_proxy" , "host" , "port" ), &HTTPRequest::set_https_proxy); |
| 634 | |
| 635 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "download_file" , PROPERTY_HINT_FILE), "set_download_file" , "get_download_file" ); |
| 636 | ADD_PROPERTY(PropertyInfo(Variant::INT, "download_chunk_size" , PROPERTY_HINT_RANGE, "256,16777216,suffix:B" ), "set_download_chunk_size" , "get_download_chunk_size" ); |
| 637 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads" ), "set_use_threads" , "is_using_threads" ); |
| 638 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "accept_gzip" ), "set_accept_gzip" , "is_accepting_gzip" ); |
| 639 | ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit" , PROPERTY_HINT_RANGE, "-1,2000000000,suffix:B" ), "set_body_size_limit" , "get_body_size_limit" ); |
| 640 | ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects" , PROPERTY_HINT_RANGE, "-1,64" ), "set_max_redirects" , "get_max_redirects" ); |
| 641 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeout" , PROPERTY_HINT_RANGE, "0,3600,0.1,or_greater,suffix:s" ), "set_timeout" , "get_timeout" ); |
| 642 | |
| 643 | ADD_SIGNAL(MethodInfo("request_completed" , PropertyInfo(Variant::INT, "result" ), PropertyInfo(Variant::INT, "response_code" ), PropertyInfo(Variant::PACKED_STRING_ARRAY, "headers" ), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "body" ))); |
| 644 | |
| 645 | BIND_ENUM_CONSTANT(RESULT_SUCCESS); |
| 646 | BIND_ENUM_CONSTANT(RESULT_CHUNKED_BODY_SIZE_MISMATCH); |
| 647 | BIND_ENUM_CONSTANT(RESULT_CANT_CONNECT); |
| 648 | BIND_ENUM_CONSTANT(RESULT_CANT_RESOLVE); |
| 649 | BIND_ENUM_CONSTANT(RESULT_CONNECTION_ERROR); |
| 650 | BIND_ENUM_CONSTANT(RESULT_TLS_HANDSHAKE_ERROR); |
| 651 | BIND_ENUM_CONSTANT(RESULT_NO_RESPONSE); |
| 652 | BIND_ENUM_CONSTANT(RESULT_BODY_SIZE_LIMIT_EXCEEDED); |
| 653 | BIND_ENUM_CONSTANT(RESULT_BODY_DECOMPRESS_FAILED); |
| 654 | BIND_ENUM_CONSTANT(RESULT_REQUEST_FAILED); |
| 655 | BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_CANT_OPEN); |
| 656 | BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_WRITE_ERROR); |
| 657 | BIND_ENUM_CONSTANT(RESULT_REDIRECT_LIMIT_REACHED); |
| 658 | BIND_ENUM_CONSTANT(RESULT_TIMEOUT); |
| 659 | } |
| 660 | |
| 661 | HTTPRequest::HTTPRequest() { |
| 662 | client = Ref<HTTPClient>(HTTPClient::create()); |
| 663 | tls_options = TLSOptions::client(); |
| 664 | timer = memnew(Timer); |
| 665 | timer->set_one_shot(true); |
| 666 | timer->connect("timeout" , callable_mp(this, &HTTPRequest::_timeout)); |
| 667 | add_child(timer); |
| 668 | } |
| 669 | |