1 /*************************************************************************/
2 /* http_request.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
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 #include "http_request.h"
31
32 #include "version.h"
33
_redirect_request(const String & p_new_url)34 void HTTPRequest::_redirect_request(const String &p_new_url) {
35 }
36
_request()37 Error HTTPRequest::_request() {
38
39 //print_line("Requesting:\n\tURL: "+url+"\n\tString: "+request_string+"\n\tPort: "+itos(port)+"\n\tSSL: "+itos(use_ssl)+"\n\tValidate SSL: "+itos(validate_ssl));
40 return client->connect(url, port, use_ssl, validate_ssl);
41 }
42
_parse_url(const String & p_url)43 Error HTTPRequest::_parse_url(const String &p_url) {
44
45 url = p_url;
46 use_ssl = false;
47
48 request_string = "";
49 port = 80;
50 request_sent = false;
51 got_response = false;
52 body_len = -1;
53 body.resize(0);
54 downloaded = 0;
55 redirections = 0;
56
57 //print_line("1 url: "+url);
58 if (url.begins_with("http://")) {
59
60 url = url.substr(7, url.length() - 7);
61 //print_line("no SSL");
62
63 } else if (url.begins_with("https://")) {
64 url = url.substr(8, url.length() - 8);
65 use_ssl = true;
66 port = 443;
67 //print_line("yes SSL");
68 } else {
69 ERR_EXPLAIN("Malformed URL");
70 ERR_FAIL_V(ERR_INVALID_PARAMETER);
71 }
72
73 //print_line("2 url: "+url);
74
75 int slash_pos = url.find("/");
76
77 if (slash_pos != -1) {
78 request_string = url.substr(slash_pos, url.length());
79 url = url.substr(0, slash_pos);
80 //print_line("request string: "+request_string);
81 } else {
82 request_string = "/";
83 //print_line("no request");
84 }
85
86 //print_line("3 url: "+url);
87
88 int colon_pos = url.find(":");
89 if (colon_pos != -1) {
90 port = url.substr(colon_pos + 1, url.length()).to_int();
91 url = url.substr(0, colon_pos);
92 ERR_FAIL_COND_V(port < 1 || port > 65535, ERR_INVALID_PARAMETER);
93 }
94
95 //print_line("4 url: "+url);
96
97 return OK;
98 }
99
request(const String & p_url,const Vector<String> & p_custom_headers,bool p_ssl_validate_domain,HTTPClient::Method p_method,const String & p_request_data)100 Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, bool p_ssl_validate_domain, HTTPClient::Method p_method, const String &p_request_data) {
101
102 ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED);
103 if (requesting) {
104 ERR_EXPLAIN("HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one.");
105 ERR_FAIL_V(ERR_BUSY);
106 }
107
108 method = p_method;
109
110 Error err = _parse_url(p_url);
111 if (err)
112 return err;
113
114 validate_ssl = p_ssl_validate_domain;
115
116 bool has_user_agent = false;
117 bool has_accept = false;
118 headers = p_custom_headers;
119
120 request_data = p_request_data;
121
122 for (int i = 0; i < headers.size(); i++) {
123
124 if (headers[i].findn("user-agent:") == 0)
125 has_user_agent = true;
126 if (headers[i].findn("Accept:") == 0)
127 has_accept = true;
128 }
129
130 if (!has_user_agent) {
131 headers.push_back("User-Agent: GodotEngine/" + String(VERSION_MKSTRING) + " (" + OS::get_singleton()->get_name() + ")");
132 }
133
134 if (!has_accept) {
135 headers.push_back("Accept: */*");
136 }
137
138 requesting = true;
139
140 if (use_threads) {
141
142 thread_done = false;
143 thread_request_quit = false;
144 client->set_blocking_mode(true);
145 thread = Thread::create(_thread_func, this);
146 } else {
147 client->set_blocking_mode(false);
148 err = _request();
149 if (err != OK) {
150 call_deferred("_request_done", RESULT_CANT_CONNECT, 0, StringArray(), ByteArray());
151 return ERR_CANT_CONNECT;
152 }
153
154 set_process(true);
155 }
156
157 return OK;
158 }
159
_thread_func(void * p_userdata)160 void HTTPRequest::_thread_func(void *p_userdata) {
161
162 HTTPRequest *hr = (HTTPRequest *)p_userdata;
163
164 Error err = hr->_request();
165
166 if (err != OK) {
167 hr->call_deferred("_request_done", RESULT_CANT_CONNECT, 0, StringArray(), ByteArray());
168 } else {
169 while (!hr->thread_request_quit) {
170
171 bool exit = hr->_update_connection();
172 if (exit)
173 break;
174 OS::get_singleton()->delay_usec(1);
175 }
176 }
177
178 hr->thread_done = true;
179 }
180
cancel_request()181 void HTTPRequest::cancel_request() {
182
183 if (!requesting)
184 return;
185
186 if (!use_threads) {
187 set_process(false);
188 } else {
189 thread_request_quit = true;
190 Thread::wait_to_finish(thread);
191 memdelete(thread);
192 thread = NULL;
193 }
194
195 if (file) {
196 memdelete(file);
197 file = NULL;
198 }
199 client->close();
200 body.resize(0);
201 //downloaded=0;
202 got_response = false;
203 response_code = -1;
204 //body_len=-1;
205 request_sent = false;
206 requesting = false;
207 }
208
_handle_response(bool * ret_value)209 bool HTTPRequest::_handle_response(bool *ret_value) {
210
211 if (!client->has_response()) {
212 call_deferred("_request_done", RESULT_NO_RESPONSE, 0, StringArray(), ByteArray());
213 *ret_value = true;
214 return true;
215 }
216
217 got_response = true;
218 response_code = client->get_response_code();
219 List<String> rheaders;
220 client->get_response_headers(&rheaders);
221 response_headers.resize(0);
222 downloaded = 0;
223 for (List<String>::Element *E = rheaders.front(); E; E = E->next()) {
224 //print_line("HEADER: "+E->get());
225 response_headers.push_back(E->get());
226 }
227
228 if (response_code == 301 || response_code == 302) {
229 //redirect
230 if (max_redirects >= 0 && redirections >= max_redirects) {
231
232 call_deferred("_request_done", RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, ByteArray());
233 *ret_value = true;
234 return true;
235 }
236
237 String new_request;
238
239 for (List<String>::Element *E = rheaders.front(); E; E = E->next()) {
240 if (E->get().findn("Location: ") != -1) {
241 new_request = E->get().substr(9, E->get().length()).strip_edges();
242 }
243 }
244
245 //print_line("NEW LOCATION: "+new_request);
246
247 if (new_request != "") {
248 //process redirect
249 client->close();
250 int new_redirs = redirections + 1; //because _request() will clear it
251 Error err;
252 if (new_request.begins_with("http")) {
253 //new url, request all again
254 err = _parse_url(new_request);
255 } else {
256 request_string = new_request;
257 }
258
259 err = _request();
260
261 //print_line("new connection: "+itos(err));
262 if (err == OK) {
263 request_sent = false;
264 got_response = false;
265 body_len = -1;
266 body.resize(0);
267 downloaded = 0;
268 redirections = new_redirs;
269 *ret_value = false;
270 return true;
271 }
272 }
273 }
274
275 return false;
276 }
277
_update_connection()278 bool HTTPRequest::_update_connection() {
279
280 switch (client->get_status()) {
281 case HTTPClient::STATUS_DISCONNECTED: {
282 call_deferred("_request_done", RESULT_CANT_CONNECT, 0, StringArray(), ByteArray());
283 return true; //end it, since it's doing something
284 } break;
285 case HTTPClient::STATUS_RESOLVING: {
286 client->poll();
287 //must wait
288 return false;
289 } break;
290 case HTTPClient::STATUS_CANT_RESOLVE: {
291 call_deferred("_request_done", RESULT_CANT_RESOLVE, 0, StringArray(), ByteArray());
292 return true;
293
294 } break;
295 case HTTPClient::STATUS_CONNECTING: {
296 client->poll();
297 //must wait
298 return false;
299 } break; //connecting to ip
300 case HTTPClient::STATUS_CANT_CONNECT: {
301
302 call_deferred("_request_done", RESULT_CANT_CONNECT, 0, StringArray(), ByteArray());
303 return true;
304
305 } break;
306 case HTTPClient::STATUS_CONNECTED: {
307
308 if (request_sent) {
309
310 if (!got_response) {
311
312 //no body
313
314 bool ret_value;
315
316 if (_handle_response(&ret_value))
317 return ret_value;
318
319 call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, ByteArray());
320 return true;
321 }
322 if (got_response && body_len < 0) {
323 //chunked transfer is done
324 call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body);
325 return true;
326 }
327
328 call_deferred("_request_done", RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, ByteArray());
329 return true;
330 //request migh have been done
331 } else {
332 //did not request yet, do request
333
334 Error err = client->request(method, request_string, headers, request_data);
335 if (err != OK) {
336 call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, StringArray(), ByteArray());
337 return true;
338 }
339
340 request_sent = true;
341 return false;
342 }
343 } break; //connected: { } break requests only accepted here
344 case HTTPClient::STATUS_REQUESTING: {
345 //must wait, it's requesting
346 client->poll();
347 return false;
348
349 } break; // request in progress
350 case HTTPClient::STATUS_BODY: {
351
352 if (!got_response) {
353
354 bool ret_value;
355
356 if (_handle_response(&ret_value))
357 return ret_value;
358
359 if (!client->is_response_chunked() && client->get_response_body_length() == 0) {
360
361 call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, ByteArray());
362 return true;
363 }
364
365 if (client->is_response_chunked()) {
366 body_len = -1; //no body len because chunked, change your webserver configuration if you want body len
367 } else {
368 body_len = client->get_response_body_length();
369
370 if (body_size_limit >= 0 && body_len > body_size_limit) {
371 call_deferred("_request_done", RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, ByteArray());
372 return true;
373 }
374 }
375
376 if (download_to_file != String()) {
377 file = FileAccess::open(download_to_file, FileAccess::WRITE);
378 if (!file) {
379
380 call_deferred("_request_done", RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, ByteArray());
381 return true;
382 }
383 }
384 }
385
386 //print_line("BODY: "+itos(body.size()));
387 client->poll();
388
389 ByteArray chunk = client->read_response_body_chunk();
390 downloaded += chunk.size();
391
392 if (file) {
393 ByteArray::Read r = chunk.read();
394 file->store_buffer(r.ptr(), chunk.size());
395 if (file->get_error() != OK) {
396 call_deferred("_request_done", RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, ByteArray());
397 return true;
398 }
399 } else {
400 body.append_array(chunk);
401 }
402
403 if (body_size_limit >= 0 && downloaded > body_size_limit) {
404 call_deferred("_request_done", RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, ByteArray());
405 return true;
406 }
407
408 if (body_len >= 0) {
409
410 if (downloaded == body_len) {
411 call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body);
412 return true;
413 }
414 /*if (body.size()>=body_len) {
415 call_deferred("_request_done",RESULT_BODY_SIZE_MISMATCH,response_code,response_headers,ByteArray());
416 return true;
417 }*/
418 }
419
420 return false;
421
422 } break; // request resulted in body: { } break which must be read
423 case HTTPClient::STATUS_CONNECTION_ERROR: {
424 call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, StringArray(), ByteArray());
425 return true;
426 } break;
427 case HTTPClient::STATUS_SSL_HANDSHAKE_ERROR: {
428 call_deferred("_request_done", RESULT_SSL_HANDSHAKE_ERROR, 0, StringArray(), ByteArray());
429 return true;
430 } break;
431 }
432
433 ERR_FAIL_V(false);
434 }
435
_request_done(int p_status,int p_code,const StringArray & headers,const ByteArray & p_data)436 void HTTPRequest::_request_done(int p_status, int p_code, const StringArray &headers, const ByteArray &p_data) {
437
438 cancel_request();
439 emit_signal("request_completed", p_status, p_code, headers, p_data);
440 }
441
_notification(int p_what)442 void HTTPRequest::_notification(int p_what) {
443
444 if (p_what == NOTIFICATION_PROCESS) {
445
446 if (use_threads)
447 return;
448 bool done = _update_connection();
449 if (done) {
450
451 set_process(false);
452 //cancel_request(); called from _request done now
453 }
454 }
455
456 if (p_what == NOTIFICATION_EXIT_TREE) {
457 if (requesting) {
458 cancel_request();
459 }
460 }
461 }
462
set_use_threads(bool p_use)463 void HTTPRequest::set_use_threads(bool p_use) {
464
465 ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED);
466 use_threads = p_use;
467 }
468
is_using_threads() const469 bool HTTPRequest::is_using_threads() const {
470
471 return use_threads;
472 }
473
set_body_size_limit(int p_bytes)474 void HTTPRequest::set_body_size_limit(int p_bytes) {
475
476 ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED);
477
478 body_size_limit = p_bytes;
479 }
480
get_body_size_limit() const481 int HTTPRequest::get_body_size_limit() const {
482
483 return body_size_limit;
484 }
485
set_download_file(const String & p_file)486 void HTTPRequest::set_download_file(const String &p_file) {
487
488 ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED);
489
490 download_to_file = p_file;
491 }
492
get_download_file() const493 String HTTPRequest::get_download_file() const {
494
495 return download_to_file;
496 }
get_http_client_status() const497 HTTPClient::Status HTTPRequest::get_http_client_status() const {
498 return client->get_status();
499 }
500
set_max_redirects(int p_max)501 void HTTPRequest::set_max_redirects(int p_max) {
502
503 max_redirects = p_max;
504 }
505
get_max_redirects() const506 int HTTPRequest::get_max_redirects() const {
507
508 return max_redirects;
509 }
510
get_downloaded_bytes() const511 int HTTPRequest::get_downloaded_bytes() const {
512
513 return downloaded;
514 }
get_body_size() const515 int HTTPRequest::get_body_size() const {
516 return body_len;
517 }
518
_bind_methods()519 void HTTPRequest::_bind_methods() {
520
521 ObjectTypeDB::bind_method(_MD("request", "url", "custom_headers", "ssl_validate_domain", "method", "request_data"), &HTTPRequest::request, DEFVAL(StringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String()));
522 ObjectTypeDB::bind_method(_MD("cancel_request"), &HTTPRequest::cancel_request);
523
524 ObjectTypeDB::bind_method(_MD("get_http_client_status"), &HTTPRequest::get_http_client_status);
525
526 ObjectTypeDB::bind_method(_MD("set_use_threads", "enable"), &HTTPRequest::set_use_threads);
527 ObjectTypeDB::bind_method(_MD("is_using_threads"), &HTTPRequest::is_using_threads);
528
529 ObjectTypeDB::bind_method(_MD("set_body_size_limit", "bytes"), &HTTPRequest::set_body_size_limit);
530 ObjectTypeDB::bind_method(_MD("get_body_size_limit"), &HTTPRequest::get_body_size_limit);
531
532 ObjectTypeDB::bind_method(_MD("set_max_redirects", "amount"), &HTTPRequest::set_max_redirects);
533 ObjectTypeDB::bind_method(_MD("get_max_redirects"), &HTTPRequest::get_max_redirects);
534
535 ObjectTypeDB::bind_method(_MD("set_download_file", "path"), &HTTPRequest::set_download_file);
536 ObjectTypeDB::bind_method(_MD("get_download_file"), &HTTPRequest::get_download_file);
537
538 ObjectTypeDB::bind_method(_MD("get_downloaded_bytes"), &HTTPRequest::get_downloaded_bytes);
539 ObjectTypeDB::bind_method(_MD("get_body_size"), &HTTPRequest::get_body_size);
540
541 ObjectTypeDB::bind_method(_MD("_redirect_request"), &HTTPRequest::_redirect_request);
542 ObjectTypeDB::bind_method(_MD("_request_done"), &HTTPRequest::_request_done);
543
544 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), _SCS("set_use_threads"), _SCS("is_using_threads"));
545 ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000"), _SCS("set_body_size_limit"), _SCS("get_body_size_limit"));
546 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects", PROPERTY_HINT_RANGE, "-1,1024"), _SCS("set_max_redirects"), _SCS("get_max_redirects"));
547
548 ADD_SIGNAL(MethodInfo("request_completed", PropertyInfo(Variant::INT, "result"), PropertyInfo(Variant::INT, "response_code"), PropertyInfo(Variant::STRING_ARRAY, "headers"), PropertyInfo(Variant::RAW_ARRAY, "body")));
549
550 BIND_CONSTANT(RESULT_SUCCESS);
551 //BIND_CONSTANT( RESULT_NO_BODY );
552 BIND_CONSTANT(RESULT_CHUNKED_BODY_SIZE_MISMATCH);
553 BIND_CONSTANT(RESULT_CANT_CONNECT);
554 BIND_CONSTANT(RESULT_CANT_RESOLVE);
555 BIND_CONSTANT(RESULT_CONNECTION_ERROR);
556 BIND_CONSTANT(RESULT_SSL_HANDSHAKE_ERROR);
557 BIND_CONSTANT(RESULT_NO_RESPONSE);
558 BIND_CONSTANT(RESULT_BODY_SIZE_LIMIT_EXCEEDED);
559 BIND_CONSTANT(RESULT_REQUEST_FAILED);
560 BIND_CONSTANT(RESULT_DOWNLOAD_FILE_CANT_OPEN);
561 BIND_CONSTANT(RESULT_DOWNLOAD_FILE_WRITE_ERROR);
562 BIND_CONSTANT(RESULT_REDIRECT_LIMIT_REACHED);
563 }
564
HTTPRequest()565 HTTPRequest::HTTPRequest() {
566
567 thread = NULL;
568
569 port = 80;
570 redirections = 0;
571 max_redirects = 8;
572 body_len = -1;
573 got_response = false;
574 validate_ssl = false;
575 use_ssl = false;
576 response_code = 0;
577 request_sent = false;
578 requesting = false;
579 client.instance();
580 use_threads = false;
581 thread_done = false;
582 body_size_limit = -1;
583 file = NULL;
584 status = HTTPClient::STATUS_DISCONNECTED;
585 }
586
~HTTPRequest()587 HTTPRequest::~HTTPRequest() {
588 if (file)
589 memdelete(file);
590 }
591