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