Skip to content

Commit

Permalink
[Net] Implement String::parse_url for parsing URLs.
Browse files Browse the repository at this point in the history
Splits the URL into (scheme, host, port, path).
Supports both literal IPv4 and IPv6.
Strip credentials when present (e.g. http://user:pass@example.com/).

Use that function in both HTTPRequest and WebSocketClient.
  • Loading branch information
Faless committed Apr 26, 2021
1 parent 15a85fe commit 3bb4066
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 51 deletions.
65 changes: 65 additions & 0 deletions core/string/ustring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,71 @@ String String::word_wrap(int p_chars_per_line) const {
return ret;
}

Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const {
// Splits the URL into scheme, host, port, path. Strip credentials when present.
String base = *this;
r_scheme = "";
r_host = "";
r_port = 0;
r_path = "";
int pos = base.find("://");
// Scheme
if (pos != -1) {
r_scheme = base.substr(0, pos + 3).to_lower();
base = base.substr(pos + 3, base.length() - pos - 3);
}
pos = base.find("/");
// Path
if (pos != -1) {
r_path = base.substr(pos, base.length() - pos);
base = base.substr(0, pos);
}
// Host
pos = base.find("@");
if (pos != -1) {
// Strip credentials
base = base.substr(pos + 1, base.length() - pos - 1);
}
if (base.begins_with("[")) {
// Literal IPv6
pos = base.rfind("]");
if (pos == -1) {
return ERR_INVALID_PARAMETER;
}
r_host = base.substr(1, pos - 1);
base = base.substr(pos + 1, base.length() - pos - 1);
} else {
// Anything else
if (base.get_slice_count(":") > 1) {
return ERR_INVALID_PARAMETER;
}
pos = base.rfind(":");
if (pos == -1) {
r_host = base;
base = "";
} else {
r_host = base.substr(0, pos);
base = base.substr(pos, base.length() - pos);
}
}
if (r_host.is_empty()) {
return ERR_INVALID_PARAMETER;
}
r_host = r_host.to_lower();
// Port
if (base.begins_with(":")) {
base = base.substr(1, base.length() - 1);
if (!base.is_valid_integer()) {
return ERR_INVALID_PARAMETER;
}
r_port = base.to_int();
if (r_port < 1 || r_port > 65535) {
return ERR_INVALID_PARAMETER;
}
}
return OK;
}

void String::copy_from(const char *p_cstr) {
// copy Latin-1 encoded c-string directly
if (!p_cstr) {
Expand Down
1 change: 1 addition & 0 deletions core/string/ustring.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ class String {
String c_unescape() const;
String json_escape() const;
String word_wrap(int p_chars_per_line) const;
Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const;

String property_name_encode() const;

Expand Down
32 changes: 8 additions & 24 deletions modules/websocket/websocket_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,18 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_proto

String host = p_url;
String path = "/";
int p_len = -1;
String scheme = "";
int port = 80;
bool ssl = false;
if (host.begins_with("wss://")) {
ssl = true; // we should implement this
host = host.substr(6, host.length() - 6);
port = 443;
} else {
ssl = false;
if (host.begins_with("ws://")) {
host = host.substr(5, host.length() - 5);
}
}
Error err = p_url.parse_url(scheme, host, port, path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);

// Path
p_len = host.find("/");
if (p_len != -1) {
path = host.substr(p_len, host.length() - p_len);
host = host.substr(0, p_len);
bool ssl = false;
if (scheme == "wss://") {
ssl = true;
}

// Port
p_len = host.rfind(":");
if (p_len != -1 && p_len == host.find(":")) {
port = host.substr(p_len, host.length() - p_len).to_int();
host = host.substr(0, p_len);
if (port == 0) {
port = ssl ? 443 : 80;
}

return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers);
}

Expand Down
37 changes: 10 additions & 27 deletions scene/main/http_request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ Error HTTPRequest::_request() {
}

Error HTTPRequest::_parse_url(const String &p_url) {
url = p_url;
use_ssl = false;

request_string = "";
port = 80;
request_sent = false;
Expand All @@ -52,35 +50,20 @@ Error HTTPRequest::_parse_url(const String &p_url) {
downloaded.set(0);
redirections = 0;

String url_lower = url.to_lower();
if (url_lower.begins_with("http://")) {
url = url.substr(7, url.length() - 7);
} else if (url_lower.begins_with("https://")) {
url = url.substr(8, url.length() - 8);
String scheme;
Error err = p_url.parse_url(scheme, url, port, request_string);
ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing URL: " + p_url + ".");
if (scheme == "https://") {
use_ssl = true;
port = 443;
} else {
ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Malformed URL: " + url + ".");
} else if (scheme != "http://") {
ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid URL scheme: " + scheme + ".");
}

ERR_FAIL_COND_V_MSG(url.length() < 1, ERR_INVALID_PARAMETER, "URL too short: " + url + ".");

int slash_pos = url.find("/");

if (slash_pos != -1) {
request_string = url.substr(slash_pos, url.length());
url = url.substr(0, slash_pos);
} else {
request_string = "/";
if (port == 0) {
port = use_ssl ? 443 : 80;
}

int colon_pos = url.find(":");
if (colon_pos != -1) {
port = url.substr(colon_pos + 1, url.length()).to_int();
url = url.substr(0, colon_pos);
ERR_FAIL_COND_V(port < 1 || port > 65535, ERR_INVALID_PARAMETER);
if (request_string.is_empty()) {
request_string = "/";
}

return OK;
}

Expand Down

0 comments on commit 3bb4066

Please sign in to comment.