1 /*************************************************************************/
2 /*  http_client.cpp                                                      */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 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 
31 #include "http_client.h"
32 
33 #include "core/io/stream_peer_ssl.h"
34 #include "core/version.h"
35 
36 const char *HTTPClient::_methods[METHOD_MAX] = {
37 	"GET",
38 	"HEAD",
39 	"POST",
40 	"PUT",
41 	"DELETE",
42 	"OPTIONS",
43 	"TRACE",
44 	"CONNECT",
45 	"PATCH"
46 };
47 
48 #ifndef JAVASCRIPT_ENABLED
connect_to_host(const String & p_host,int p_port,bool p_ssl,bool p_verify_host)49 Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
50 
51 	close();
52 
53 	conn_port = p_port;
54 	conn_host = p_host;
55 
56 	ssl = p_ssl;
57 	ssl_verify_host = p_verify_host;
58 
59 	String host_lower = conn_host.to_lower();
60 	if (host_lower.begins_with("http://")) {
61 
62 		conn_host = conn_host.substr(7, conn_host.length() - 7);
63 	} else if (host_lower.begins_with("https://")) {
64 
65 		ssl = true;
66 		conn_host = conn_host.substr(8, conn_host.length() - 8);
67 	}
68 
69 	ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
70 
71 	if (conn_port < 0) {
72 		if (ssl) {
73 			conn_port = PORT_HTTPS;
74 		} else {
75 			conn_port = PORT_HTTP;
76 		}
77 	}
78 
79 	connection = tcp_connection;
80 
81 	if (conn_host.is_valid_ip_address()) {
82 		// Host contains valid IP
83 		Error err = tcp_connection->connect_to_host(IP_Address(conn_host), p_port);
84 		if (err) {
85 			status = STATUS_CANT_CONNECT;
86 			return err;
87 		}
88 
89 		status = STATUS_CONNECTING;
90 	} else {
91 		// Host contains hostname and needs to be resolved to IP
92 		resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host);
93 		status = STATUS_RESOLVING;
94 	}
95 
96 	return OK;
97 }
98 
set_connection(const Ref<StreamPeer> & p_connection)99 void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) {
100 
101 	ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object.");
102 
103 	close();
104 	connection = p_connection;
105 	status = STATUS_CONNECTED;
106 }
107 
get_connection() const108 Ref<StreamPeer> HTTPClient::get_connection() const {
109 
110 	return connection;
111 }
112 
request_raw(Method p_method,const String & p_url,const Vector<String> & p_headers,const PoolVector<uint8_t> & p_body)113 Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const PoolVector<uint8_t> &p_body) {
114 
115 	ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
116 	ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
117 	ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
118 	ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
119 
120 	String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
121 	if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
122 		// Don't append the standard ports
123 		request += "Host: " + conn_host + "\r\n";
124 	} else {
125 		request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
126 	}
127 	bool add_clen = p_body.size() > 0;
128 	bool add_uagent = true;
129 	bool add_accept = true;
130 	for (int i = 0; i < p_headers.size(); i++) {
131 		request += p_headers[i] + "\r\n";
132 		if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
133 			add_clen = false;
134 		}
135 		if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
136 			add_uagent = false;
137 		}
138 		if (add_accept && p_headers[i].findn("Accept:") == 0) {
139 			add_accept = false;
140 		}
141 	}
142 	if (add_clen) {
143 		request += "Content-Length: " + itos(p_body.size()) + "\r\n";
144 		// Should it add utf8 encoding?
145 	}
146 	if (add_uagent) {
147 		request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
148 	}
149 	if (add_accept) {
150 		request += "Accept: */*\r\n";
151 	}
152 	request += "\r\n";
153 	CharString cs = request.utf8();
154 
155 	PoolVector<uint8_t> data;
156 	data.resize(cs.length());
157 	{
158 		PoolVector<uint8_t>::Write data_write = data.write();
159 		for (int i = 0; i < cs.length(); i++) {
160 			data_write[i] = cs[i];
161 		}
162 	}
163 
164 	data.append_array(p_body);
165 
166 	PoolVector<uint8_t>::Read r = data.read();
167 	Error err = connection->put_data(&r[0], data.size());
168 
169 	if (err) {
170 		close();
171 		status = STATUS_CONNECTION_ERROR;
172 		return err;
173 	}
174 
175 	status = STATUS_REQUESTING;
176 	head_request = p_method == METHOD_HEAD;
177 
178 	return OK;
179 }
180 
request(Method p_method,const String & p_url,const Vector<String> & p_headers,const String & p_body)181 Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
182 
183 	ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
184 	ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
185 	ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
186 	ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
187 
188 	String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
189 	if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
190 		// Don't append the standard ports
191 		request += "Host: " + conn_host + "\r\n";
192 	} else {
193 		request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
194 	}
195 	bool add_uagent = true;
196 	bool add_accept = true;
197 	bool add_clen = p_body.length() > 0;
198 	for (int i = 0; i < p_headers.size(); i++) {
199 		request += p_headers[i] + "\r\n";
200 		if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
201 			add_clen = false;
202 		}
203 		if (add_uagent && p_headers[i].findn("User-Agent:") == 0) {
204 			add_uagent = false;
205 		}
206 		if (add_accept && p_headers[i].findn("Accept:") == 0) {
207 			add_accept = false;
208 		}
209 	}
210 	if (add_clen) {
211 		request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n";
212 		// Should it add utf8 encoding?
213 	}
214 	if (add_uagent) {
215 		request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n";
216 	}
217 	if (add_accept) {
218 		request += "Accept: */*\r\n";
219 	}
220 	request += "\r\n";
221 	request += p_body;
222 
223 	CharString cs = request.utf8();
224 	Error err = connection->put_data((const uint8_t *)cs.ptr(), cs.length());
225 	if (err) {
226 		close();
227 		status = STATUS_CONNECTION_ERROR;
228 		return err;
229 	}
230 
231 	status = STATUS_REQUESTING;
232 	head_request = p_method == METHOD_HEAD;
233 
234 	return OK;
235 }
236 
has_response() const237 bool HTTPClient::has_response() const {
238 
239 	return response_headers.size() != 0;
240 }
241 
is_response_chunked() const242 bool HTTPClient::is_response_chunked() const {
243 
244 	return chunked;
245 }
246 
get_response_code() const247 int HTTPClient::get_response_code() const {
248 
249 	return response_num;
250 }
251 
get_response_headers(List<String> * r_response)252 Error HTTPClient::get_response_headers(List<String> *r_response) {
253 
254 	if (!response_headers.size())
255 		return ERR_INVALID_PARAMETER;
256 
257 	for (int i = 0; i < response_headers.size(); i++) {
258 
259 		r_response->push_back(response_headers[i]);
260 	}
261 
262 	response_headers.clear();
263 
264 	return OK;
265 }
266 
close()267 void HTTPClient::close() {
268 
269 	if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE)
270 		tcp_connection->disconnect_from_host();
271 
272 	connection.unref();
273 	status = STATUS_DISCONNECTED;
274 	head_request = false;
275 	if (resolving != IP::RESOLVER_INVALID_ID) {
276 
277 		IP::get_singleton()->erase_resolve_item(resolving);
278 		resolving = IP::RESOLVER_INVALID_ID;
279 	}
280 
281 	response_headers.clear();
282 	response_str.clear();
283 	body_size = -1;
284 	body_left = 0;
285 	chunk_left = 0;
286 	chunk_trailer_part = 0;
287 	read_until_eof = false;
288 	response_num = 0;
289 	handshaking = false;
290 }
291 
poll()292 Error HTTPClient::poll() {
293 
294 	switch (status) {
295 
296 		case STATUS_RESOLVING: {
297 			ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG);
298 
299 			IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving);
300 			switch (rstatus) {
301 				case IP::RESOLVER_STATUS_WAITING:
302 					return OK; // Still resolving
303 
304 				case IP::RESOLVER_STATUS_DONE: {
305 
306 					IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving);
307 					Error err = tcp_connection->connect_to_host(host, conn_port);
308 					IP::get_singleton()->erase_resolve_item(resolving);
309 					resolving = IP::RESOLVER_INVALID_ID;
310 					if (err) {
311 						status = STATUS_CANT_CONNECT;
312 						return err;
313 					}
314 
315 					status = STATUS_CONNECTING;
316 				} break;
317 				case IP::RESOLVER_STATUS_NONE:
318 				case IP::RESOLVER_STATUS_ERROR: {
319 
320 					IP::get_singleton()->erase_resolve_item(resolving);
321 					resolving = IP::RESOLVER_INVALID_ID;
322 					close();
323 					status = STATUS_CANT_RESOLVE;
324 					return ERR_CANT_RESOLVE;
325 				} break;
326 			}
327 		} break;
328 		case STATUS_CONNECTING: {
329 
330 			StreamPeerTCP::Status s = tcp_connection->get_status();
331 			switch (s) {
332 
333 				case StreamPeerTCP::STATUS_CONNECTING: {
334 					return OK;
335 				} break;
336 				case StreamPeerTCP::STATUS_CONNECTED: {
337 					if (ssl) {
338 						Ref<StreamPeerSSL> ssl;
339 						if (!handshaking) {
340 							// Connect the StreamPeerSSL and start handshaking
341 							ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
342 							ssl->set_blocking_handshake_enabled(false);
343 							Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host);
344 							if (err != OK) {
345 								close();
346 								status = STATUS_SSL_HANDSHAKE_ERROR;
347 								return ERR_CANT_CONNECT;
348 							}
349 							connection = ssl;
350 							handshaking = true;
351 						} else {
352 							// We are already handshaking, which means we can use your already active SSL connection
353 							ssl = static_cast<Ref<StreamPeerSSL> >(connection);
354 							if (ssl.is_null()) {
355 								close();
356 								status = STATUS_SSL_HANDSHAKE_ERROR;
357 								return ERR_CANT_CONNECT;
358 							}
359 
360 							ssl->poll(); // Try to finish the handshake
361 						}
362 
363 						if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
364 							// Handshake has been successful
365 							handshaking = false;
366 							status = STATUS_CONNECTED;
367 							return OK;
368 						} else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
369 							// Handshake has failed
370 							close();
371 							status = STATUS_SSL_HANDSHAKE_ERROR;
372 							return ERR_CANT_CONNECT;
373 						}
374 						// ... we will need to poll more for handshake to finish
375 					} else {
376 						status = STATUS_CONNECTED;
377 					}
378 					return OK;
379 				} break;
380 				case StreamPeerTCP::STATUS_ERROR:
381 				case StreamPeerTCP::STATUS_NONE: {
382 
383 					close();
384 					status = STATUS_CANT_CONNECT;
385 					return ERR_CANT_CONNECT;
386 				} break;
387 			}
388 		} break;
389 		case STATUS_BODY:
390 		case STATUS_CONNECTED: {
391 			// Check if we are still connected
392 			if (ssl) {
393 				Ref<StreamPeerSSL> tmp = connection;
394 				tmp->poll();
395 				if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) {
396 					status = STATUS_CONNECTION_ERROR;
397 					return ERR_CONNECTION_ERROR;
398 				}
399 			} else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
400 				status = STATUS_CONNECTION_ERROR;
401 				return ERR_CONNECTION_ERROR;
402 			}
403 			// Connection established, requests can now be made
404 			return OK;
405 		} break;
406 		case STATUS_REQUESTING: {
407 
408 			while (true) {
409 				uint8_t byte;
410 				int rec = 0;
411 				Error err = _get_http_data(&byte, 1, rec);
412 				if (err != OK) {
413 					close();
414 					status = STATUS_CONNECTION_ERROR;
415 					return ERR_CONNECTION_ERROR;
416 				}
417 
418 				if (rec == 0)
419 					return OK; // Still requesting, keep trying!
420 
421 				response_str.push_back(byte);
422 				int rs = response_str.size();
423 				if (
424 						(rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') ||
425 						(rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) {
426 
427 					// End of response, parse.
428 					response_str.push_back(0);
429 					String response;
430 					response.parse_utf8((const char *)response_str.ptr());
431 					Vector<String> responses = response.split("\n");
432 					body_size = -1;
433 					chunked = false;
434 					body_left = 0;
435 					chunk_left = 0;
436 					chunk_trailer_part = false;
437 					read_until_eof = false;
438 					response_str.clear();
439 					response_headers.clear();
440 					response_num = RESPONSE_OK;
441 
442 					// Per the HTTP 1.1 spec, keep-alive is the default.
443 					// Not following that specification breaks standard implementations.
444 					// Broken web servers should be fixed.
445 					bool keep_alive = true;
446 
447 					for (int i = 0; i < responses.size(); i++) {
448 
449 						String header = responses[i].strip_edges();
450 						String s = header.to_lower();
451 						if (s.length() == 0)
452 							continue;
453 						if (s.begins_with("content-length:")) {
454 							body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int();
455 							body_left = body_size;
456 
457 						} else if (s.begins_with("transfer-encoding:")) {
458 							String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges();
459 							if (encoding == "chunked") {
460 								chunked = true;
461 							}
462 						} else if (s.begins_with("connection: close")) {
463 							keep_alive = false;
464 						}
465 
466 						if (i == 0 && responses[i].begins_with("HTTP")) {
467 
468 							String num = responses[i].get_slicec(' ', 1);
469 							response_num = num.to_int();
470 						} else {
471 
472 							response_headers.push_back(header);
473 						}
474 					}
475 
476 					// This is a HEAD request, we wont receive anything.
477 					if (head_request) {
478 						body_size = 0;
479 						body_left = 0;
480 					}
481 
482 					if (body_size != -1 || chunked) {
483 
484 						status = STATUS_BODY;
485 					} else if (!keep_alive) {
486 
487 						read_until_eof = true;
488 						status = STATUS_BODY;
489 					} else {
490 
491 						status = STATUS_CONNECTED;
492 					}
493 					return OK;
494 				}
495 			}
496 		} break;
497 		case STATUS_DISCONNECTED: {
498 			return ERR_UNCONFIGURED;
499 		} break;
500 		case STATUS_CONNECTION_ERROR:
501 		case STATUS_SSL_HANDSHAKE_ERROR: {
502 			return ERR_CONNECTION_ERROR;
503 		} break;
504 		case STATUS_CANT_CONNECT: {
505 			return ERR_CANT_CONNECT;
506 		} break;
507 		case STATUS_CANT_RESOLVE: {
508 			return ERR_CANT_RESOLVE;
509 		} break;
510 	}
511 
512 	return OK;
513 }
514 
get_response_body_length() const515 int HTTPClient::get_response_body_length() const {
516 
517 	return body_size;
518 }
519 
read_response_body_chunk()520 PoolByteArray HTTPClient::read_response_body_chunk() {
521 
522 	ERR_FAIL_COND_V(status != STATUS_BODY, PoolByteArray());
523 
524 	PoolByteArray ret;
525 	Error err = OK;
526 
527 	if (chunked) {
528 
529 		while (true) {
530 
531 			if (chunk_trailer_part) {
532 				// We need to consume the trailer part too or keep-alive will break
533 				uint8_t b;
534 				int rec = 0;
535 				err = _get_http_data(&b, 1, rec);
536 
537 				if (rec == 0)
538 					break;
539 
540 				chunk.push_back(b);
541 				int cs = chunk.size();
542 				if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) {
543 					if (cs == 2) {
544 						// Finally over
545 						chunk_trailer_part = false;
546 						status = STATUS_CONNECTED;
547 						chunk.clear();
548 						break;
549 					} else {
550 						// We do not process nor return the trailer data
551 						chunk.clear();
552 					}
553 				}
554 			} else if (chunk_left == 0) {
555 				// Reading length
556 				uint8_t b;
557 				int rec = 0;
558 				err = _get_http_data(&b, 1, rec);
559 
560 				if (rec == 0)
561 					break;
562 
563 				chunk.push_back(b);
564 
565 				if (chunk.size() > 32) {
566 					ERR_PRINT("HTTP Invalid chunk hex len");
567 					status = STATUS_CONNECTION_ERROR;
568 					break;
569 				}
570 
571 				if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') {
572 
573 					int len = 0;
574 					for (int i = 0; i < chunk.size() - 2; i++) {
575 						char c = chunk[i];
576 						int v = 0;
577 						if (c >= '0' && c <= '9')
578 							v = c - '0';
579 						else if (c >= 'a' && c <= 'f')
580 							v = c - 'a' + 10;
581 						else if (c >= 'A' && c <= 'F')
582 							v = c - 'A' + 10;
583 						else {
584 							ERR_PRINT("HTTP Chunk len not in hex!!");
585 							status = STATUS_CONNECTION_ERROR;
586 							break;
587 						}
588 						len <<= 4;
589 						len |= v;
590 						if (len > (1 << 24)) {
591 							ERR_PRINT("HTTP Chunk too big!! >16mb");
592 							status = STATUS_CONNECTION_ERROR;
593 							break;
594 						}
595 					}
596 
597 					if (len == 0) {
598 						// End reached!
599 						chunk_trailer_part = true;
600 						chunk.clear();
601 						break;
602 					}
603 
604 					chunk_left = len + 2;
605 					chunk.resize(chunk_left);
606 				}
607 			} else {
608 
609 				int rec = 0;
610 				err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec);
611 				if (rec == 0) {
612 					break;
613 				}
614 				chunk_left -= rec;
615 
616 				if (chunk_left == 0) {
617 
618 					if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
619 						ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
620 						status = STATUS_CONNECTION_ERROR;
621 						break;
622 					}
623 
624 					ret.resize(chunk.size() - 2);
625 					PoolByteArray::Write w = ret.write();
626 					copymem(w.ptr(), chunk.ptr(), chunk.size() - 2);
627 					chunk.clear();
628 				}
629 
630 				break;
631 			}
632 		}
633 
634 	} else {
635 
636 		int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size;
637 		ret.resize(to_read);
638 		int _offset = 0;
639 		while (to_read > 0) {
640 			int rec = 0;
641 			{
642 				PoolByteArray::Write w = ret.write();
643 				err = _get_http_data(w.ptr() + _offset, to_read, rec);
644 			}
645 			if (rec <= 0) { // Ended up reading less
646 				ret.resize(_offset);
647 				break;
648 			} else {
649 				_offset += rec;
650 				to_read -= rec;
651 				if (!read_until_eof) {
652 					body_left -= rec;
653 				}
654 			}
655 			if (err != OK)
656 				break;
657 		}
658 	}
659 
660 	if (err != OK) {
661 
662 		close();
663 
664 		if (err == ERR_FILE_EOF) {
665 
666 			status = STATUS_DISCONNECTED; // Server disconnected
667 		} else {
668 
669 			status = STATUS_CONNECTION_ERROR;
670 		}
671 	} else if (body_left == 0 && !chunked && !read_until_eof) {
672 
673 		status = STATUS_CONNECTED;
674 	}
675 
676 	return ret;
677 }
678 
get_status() const679 HTTPClient::Status HTTPClient::get_status() const {
680 
681 	return status;
682 }
683 
set_blocking_mode(bool p_enable)684 void HTTPClient::set_blocking_mode(bool p_enable) {
685 
686 	blocking = p_enable;
687 }
688 
is_blocking_mode_enabled() const689 bool HTTPClient::is_blocking_mode_enabled() const {
690 
691 	return blocking;
692 }
693 
_get_http_data(uint8_t * p_buffer,int p_bytes,int & r_received)694 Error HTTPClient::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
695 
696 	if (blocking) {
697 
698 		// We can't use StreamPeer.get_data, since when reaching EOF we will get an
699 		// error without knowing how many bytes we received.
700 		Error err = ERR_FILE_EOF;
701 		int read = 0;
702 		int left = p_bytes;
703 		r_received = 0;
704 		while (left > 0) {
705 			err = connection->get_partial_data(p_buffer + r_received, left, read);
706 			if (err == OK) {
707 				r_received += read;
708 			} else if (err == ERR_FILE_EOF) {
709 				r_received += read;
710 				return err;
711 			} else {
712 				return err;
713 			}
714 			left -= read;
715 		}
716 		return err;
717 	} else {
718 		return connection->get_partial_data(p_buffer, p_bytes, r_received);
719 	}
720 }
721 
set_read_chunk_size(int p_size)722 void HTTPClient::set_read_chunk_size(int p_size) {
723 	ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24));
724 	read_chunk_size = p_size;
725 }
726 
get_read_chunk_size() const727 int HTTPClient::get_read_chunk_size() const {
728 	return read_chunk_size;
729 }
730 
HTTPClient()731 HTTPClient::HTTPClient() {
732 
733 	tcp_connection.instance();
734 	resolving = IP::RESOLVER_INVALID_ID;
735 	status = STATUS_DISCONNECTED;
736 	head_request = false;
737 	conn_port = -1;
738 	body_size = -1;
739 	chunked = false;
740 	body_left = 0;
741 	read_until_eof = false;
742 	chunk_left = 0;
743 	chunk_trailer_part = false;
744 	response_num = 0;
745 	ssl = false;
746 	blocking = false;
747 	handshaking = false;
748 	read_chunk_size = 4096;
749 }
750 
~HTTPClient()751 HTTPClient::~HTTPClient() {
752 }
753 
754 #endif // #ifndef JAVASCRIPT_ENABLED
755 
query_string_from_dict(const Dictionary & p_dict)756 String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
757 	String query = "";
758 	Array keys = p_dict.keys();
759 	for (int i = 0; i < keys.size(); ++i) {
760 		String encoded_key = String(keys[i]).http_escape();
761 		Variant value = p_dict[keys[i]];
762 		switch (value.get_type()) {
763 			case Variant::ARRAY: {
764 				// Repeat the key with every values
765 				Array values = value;
766 				for (int j = 0; j < values.size(); ++j) {
767 					query += "&" + encoded_key + "=" + String(values[j]).http_escape();
768 				}
769 				break;
770 			}
771 			case Variant::NIL: {
772 				// Add the key with no value
773 				query += "&" + encoded_key;
774 				break;
775 			}
776 			default: {
777 				// Add the key-value pair
778 				query += "&" + encoded_key + "=" + String(value).http_escape();
779 			}
780 		}
781 	}
782 	query.erase(0, 1);
783 	return query;
784 }
785 
_get_response_headers_as_dictionary()786 Dictionary HTTPClient::_get_response_headers_as_dictionary() {
787 
788 	List<String> rh;
789 	get_response_headers(&rh);
790 	Dictionary ret;
791 	for (const List<String>::Element *E = rh.front(); E; E = E->next()) {
792 		const String &s = E->get();
793 		int sp = s.find(":");
794 		if (sp == -1)
795 			continue;
796 		String key = s.substr(0, sp).strip_edges();
797 		String value = s.substr(sp + 1, s.length()).strip_edges();
798 		ret[key] = value;
799 	}
800 
801 	return ret;
802 }
803 
_get_response_headers()804 PoolStringArray HTTPClient::_get_response_headers() {
805 
806 	List<String> rh;
807 	get_response_headers(&rh);
808 	PoolStringArray ret;
809 	ret.resize(rh.size());
810 	int idx = 0;
811 	for (const List<String>::Element *E = rh.front(); E; E = E->next()) {
812 		ret.set(idx++, E->get());
813 	}
814 
815 	return ret;
816 }
817 
_bind_methods()818 void HTTPClient::_bind_methods() {
819 
820 	ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_ssl", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true));
821 	ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection);
822 	ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection);
823 	ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::request_raw);
824 	ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::request, DEFVAL(String()));
825 	ClassDB::bind_method(D_METHOD("close"), &HTTPClient::close);
826 
827 	ClassDB::bind_method(D_METHOD("has_response"), &HTTPClient::has_response);
828 	ClassDB::bind_method(D_METHOD("is_response_chunked"), &HTTPClient::is_response_chunked);
829 	ClassDB::bind_method(D_METHOD("get_response_code"), &HTTPClient::get_response_code);
830 	ClassDB::bind_method(D_METHOD("get_response_headers"), &HTTPClient::_get_response_headers);
831 	ClassDB::bind_method(D_METHOD("get_response_headers_as_dictionary"), &HTTPClient::_get_response_headers_as_dictionary);
832 	ClassDB::bind_method(D_METHOD("get_response_body_length"), &HTTPClient::get_response_body_length);
833 	ClassDB::bind_method(D_METHOD("read_response_body_chunk"), &HTTPClient::read_response_body_chunk);
834 	ClassDB::bind_method(D_METHOD("set_read_chunk_size", "bytes"), &HTTPClient::set_read_chunk_size);
835 	ClassDB::bind_method(D_METHOD("get_read_chunk_size"), &HTTPClient::get_read_chunk_size);
836 
837 	ClassDB::bind_method(D_METHOD("set_blocking_mode", "enabled"), &HTTPClient::set_blocking_mode);
838 	ClassDB::bind_method(D_METHOD("is_blocking_mode_enabled"), &HTTPClient::is_blocking_mode_enabled);
839 
840 	ClassDB::bind_method(D_METHOD("get_status"), &HTTPClient::get_status);
841 	ClassDB::bind_method(D_METHOD("poll"), &HTTPClient::poll);
842 
843 	ClassDB::bind_method(D_METHOD("query_string_from_dict", "fields"), &HTTPClient::query_string_from_dict);
844 
845 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_mode_enabled"), "set_blocking_mode", "is_blocking_mode_enabled");
846 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "connection", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", 0), "set_connection", "get_connection");
847 	ADD_PROPERTY(PropertyInfo(Variant::INT, "read_chunk_size", PROPERTY_HINT_RANGE, "256,16777216"), "set_read_chunk_size", "get_read_chunk_size");
848 
849 	BIND_ENUM_CONSTANT(METHOD_GET);
850 	BIND_ENUM_CONSTANT(METHOD_HEAD);
851 	BIND_ENUM_CONSTANT(METHOD_POST);
852 	BIND_ENUM_CONSTANT(METHOD_PUT);
853 	BIND_ENUM_CONSTANT(METHOD_DELETE);
854 	BIND_ENUM_CONSTANT(METHOD_OPTIONS);
855 	BIND_ENUM_CONSTANT(METHOD_TRACE);
856 	BIND_ENUM_CONSTANT(METHOD_CONNECT);
857 	BIND_ENUM_CONSTANT(METHOD_PATCH);
858 	BIND_ENUM_CONSTANT(METHOD_MAX);
859 
860 	BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
861 	BIND_ENUM_CONSTANT(STATUS_RESOLVING); // Resolving hostname (if hostname was passed in)
862 	BIND_ENUM_CONSTANT(STATUS_CANT_RESOLVE);
863 	BIND_ENUM_CONSTANT(STATUS_CONNECTING); // Connecting to IP
864 	BIND_ENUM_CONSTANT(STATUS_CANT_CONNECT);
865 	BIND_ENUM_CONSTANT(STATUS_CONNECTED); // Connected, now accepting requests
866 	BIND_ENUM_CONSTANT(STATUS_REQUESTING); // Request in progress
867 	BIND_ENUM_CONSTANT(STATUS_BODY); // Request resulted in body which must be read
868 	BIND_ENUM_CONSTANT(STATUS_CONNECTION_ERROR);
869 	BIND_ENUM_CONSTANT(STATUS_SSL_HANDSHAKE_ERROR);
870 
871 	BIND_ENUM_CONSTANT(RESPONSE_CONTINUE);
872 	BIND_ENUM_CONSTANT(RESPONSE_SWITCHING_PROTOCOLS);
873 	BIND_ENUM_CONSTANT(RESPONSE_PROCESSING);
874 
875 	// 2xx successful
876 	BIND_ENUM_CONSTANT(RESPONSE_OK);
877 	BIND_ENUM_CONSTANT(RESPONSE_CREATED);
878 	BIND_ENUM_CONSTANT(RESPONSE_ACCEPTED);
879 	BIND_ENUM_CONSTANT(RESPONSE_NON_AUTHORITATIVE_INFORMATION);
880 	BIND_ENUM_CONSTANT(RESPONSE_NO_CONTENT);
881 	BIND_ENUM_CONSTANT(RESPONSE_RESET_CONTENT);
882 	BIND_ENUM_CONSTANT(RESPONSE_PARTIAL_CONTENT);
883 	BIND_ENUM_CONSTANT(RESPONSE_MULTI_STATUS);
884 	BIND_ENUM_CONSTANT(RESPONSE_ALREADY_REPORTED);
885 	BIND_ENUM_CONSTANT(RESPONSE_IM_USED);
886 
887 	// 3xx redirection
888 	BIND_ENUM_CONSTANT(RESPONSE_MULTIPLE_CHOICES);
889 	BIND_ENUM_CONSTANT(RESPONSE_MOVED_PERMANENTLY);
890 	BIND_ENUM_CONSTANT(RESPONSE_FOUND);
891 	BIND_ENUM_CONSTANT(RESPONSE_SEE_OTHER);
892 	BIND_ENUM_CONSTANT(RESPONSE_NOT_MODIFIED);
893 	BIND_ENUM_CONSTANT(RESPONSE_USE_PROXY);
894 	BIND_ENUM_CONSTANT(RESPONSE_SWITCH_PROXY);
895 	BIND_ENUM_CONSTANT(RESPONSE_TEMPORARY_REDIRECT);
896 	BIND_ENUM_CONSTANT(RESPONSE_PERMANENT_REDIRECT);
897 
898 	// 4xx client error
899 	BIND_ENUM_CONSTANT(RESPONSE_BAD_REQUEST);
900 	BIND_ENUM_CONSTANT(RESPONSE_UNAUTHORIZED);
901 	BIND_ENUM_CONSTANT(RESPONSE_PAYMENT_REQUIRED);
902 	BIND_ENUM_CONSTANT(RESPONSE_FORBIDDEN);
903 	BIND_ENUM_CONSTANT(RESPONSE_NOT_FOUND);
904 	BIND_ENUM_CONSTANT(RESPONSE_METHOD_NOT_ALLOWED);
905 	BIND_ENUM_CONSTANT(RESPONSE_NOT_ACCEPTABLE);
906 	BIND_ENUM_CONSTANT(RESPONSE_PROXY_AUTHENTICATION_REQUIRED);
907 	BIND_ENUM_CONSTANT(RESPONSE_REQUEST_TIMEOUT);
908 	BIND_ENUM_CONSTANT(RESPONSE_CONFLICT);
909 	BIND_ENUM_CONSTANT(RESPONSE_GONE);
910 	BIND_ENUM_CONSTANT(RESPONSE_LENGTH_REQUIRED);
911 	BIND_ENUM_CONSTANT(RESPONSE_PRECONDITION_FAILED);
912 	BIND_ENUM_CONSTANT(RESPONSE_REQUEST_ENTITY_TOO_LARGE);
913 	BIND_ENUM_CONSTANT(RESPONSE_REQUEST_URI_TOO_LONG);
914 	BIND_ENUM_CONSTANT(RESPONSE_UNSUPPORTED_MEDIA_TYPE);
915 	BIND_ENUM_CONSTANT(RESPONSE_REQUESTED_RANGE_NOT_SATISFIABLE);
916 	BIND_ENUM_CONSTANT(RESPONSE_EXPECTATION_FAILED);
917 	BIND_ENUM_CONSTANT(RESPONSE_IM_A_TEAPOT);
918 	BIND_ENUM_CONSTANT(RESPONSE_MISDIRECTED_REQUEST);
919 	BIND_ENUM_CONSTANT(RESPONSE_UNPROCESSABLE_ENTITY);
920 	BIND_ENUM_CONSTANT(RESPONSE_LOCKED);
921 	BIND_ENUM_CONSTANT(RESPONSE_FAILED_DEPENDENCY);
922 	BIND_ENUM_CONSTANT(RESPONSE_UPGRADE_REQUIRED);
923 	BIND_ENUM_CONSTANT(RESPONSE_PRECONDITION_REQUIRED);
924 	BIND_ENUM_CONSTANT(RESPONSE_TOO_MANY_REQUESTS);
925 	BIND_ENUM_CONSTANT(RESPONSE_REQUEST_HEADER_FIELDS_TOO_LARGE);
926 	BIND_ENUM_CONSTANT(RESPONSE_UNAVAILABLE_FOR_LEGAL_REASONS);
927 
928 	// 5xx server error
929 	BIND_ENUM_CONSTANT(RESPONSE_INTERNAL_SERVER_ERROR);
930 	BIND_ENUM_CONSTANT(RESPONSE_NOT_IMPLEMENTED);
931 	BIND_ENUM_CONSTANT(RESPONSE_BAD_GATEWAY);
932 	BIND_ENUM_CONSTANT(RESPONSE_SERVICE_UNAVAILABLE);
933 	BIND_ENUM_CONSTANT(RESPONSE_GATEWAY_TIMEOUT);
934 	BIND_ENUM_CONSTANT(RESPONSE_HTTP_VERSION_NOT_SUPPORTED);
935 	BIND_ENUM_CONSTANT(RESPONSE_VARIANT_ALSO_NEGOTIATES);
936 	BIND_ENUM_CONSTANT(RESPONSE_INSUFFICIENT_STORAGE);
937 	BIND_ENUM_CONSTANT(RESPONSE_LOOP_DETECTED);
938 	BIND_ENUM_CONSTANT(RESPONSE_NOT_EXTENDED);
939 	BIND_ENUM_CONSTANT(RESPONSE_NETWORK_AUTH_REQUIRED);
940 }
941