diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 391a203d5b5..e6f7492a189 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -221,18 +221,35 @@ void CharString::copy_from(const char *p_cstr) { /* String */ /*************************************************************************/ -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. +Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path, String &r_fragment) const { + // Splits the URL into scheme, host, port, path, fragment. Strip credentials when present. String base = *this; r_scheme = ""; r_host = ""; r_port = 0; r_path = ""; + r_fragment = ""; + 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); + bool is_scheme_valid = true; + for (int i = 0; i < pos; i++) { + if (!is_ascii_alphanumeric_char(base[i]) && base[i] != '+' && base[i] != '-' && base[i] != '.') { + is_scheme_valid = false; + break; + } + } + if (is_scheme_valid) { + r_scheme = base.substr(0, pos + 3).to_lower(); + base = base.substr(pos + 3, base.length() - pos - 3); + } + } + pos = base.find("#"); + // Fragment + if (pos != -1) { + r_fragment = base.substr(pos + 1); + base = base.substr(0, pos); } pos = base.find("/"); // Path diff --git a/core/string/ustring.h b/core/string/ustring.h index 5d4b209c252..aa62c9cb188 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -452,7 +452,7 @@ public: String c_escape_multiline() const; String c_unescape() const; String json_escape() const; - Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const; + Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path, String &r_fragment) const; String property_name_encode() const; diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp index c0efc6a1fc6..9ec60581324 100644 --- a/editor/debugger/editor_debugger_server.cpp +++ b/editor/debugger/editor_debugger_server.cpp @@ -77,8 +77,8 @@ Error EditorDebuggerServerTCP::start(const String &p_uri) { // Optionally override if (!p_uri.is_empty() && p_uri != "tcp://") { - String scheme, path; - Error err = p_uri.parse_url(scheme, bind_host, bind_port, path); + String scheme, path, fragment; + Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment); ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER); } diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index af4b3a1643b..fec8d4b8972 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -993,7 +993,8 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p String url_host; int url_port; String url_path; - Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path); + String url_fragment; + Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path, url_fragment); if (err != OK) { if (is_print_verbose_enabled()) { ERR_PRINT(vformat("Asset Library: Invalid image URL '%s' for asset # %d.", trimmed_url, p_asset_id)); diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp index a28fc53440e..344a0356c5a 100644 --- a/modules/websocket/editor/editor_debugger_server_websocket.cpp +++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp @@ -77,8 +77,8 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) { // Optionally override if (!p_uri.is_empty() && p_uri != "ws://") { - String scheme, path; - Error err = p_uri.parse_url(scheme, bind_host, bind_port, path); + String scheme, path, fragment; + Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment); ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER); } diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 03a530909bf..c5768c9f0b3 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -68,8 +68,9 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref p_tls_option String host; String path; String scheme; + String fragment; int port = 0; - Error err = p_url.parse_url(scheme, host, port, path); + Error err = p_url.parse_url(scheme, host, port, path, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); if (scheme.is_empty()) { diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 0a9a4053e3a..0c0a046805c 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -482,8 +482,9 @@ Error WSLPeer::connect_to_url(const String &p_url, Ref p_options) { String host; String path; String scheme; + String fragment; int port = 0; - Error err = p_url.parse_url(scheme, host, port, path); + Error err = p_url.parse_url(scheme, host, port, path, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); if (scheme.is_empty()) { scheme = "ws://"; diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 3469b806a6e..85266110935 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -49,7 +49,8 @@ Error HTTPRequest::_parse_url(const String &p_url) { redirections = 0; String scheme; - Error err = p_url.parse_url(scheme, url, port, request_string); + String fragment; + Error err = p_url.parse_url(scheme, url, port, request_string, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing URL: '%s'.", p_url)); if (scheme == "https://") { diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 301771a3de1..0131f4c02a6 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -1988,6 +1988,46 @@ TEST_CASE("[String] Variant ptr indexed set") { CHECK_EQ(s, String("azcd")); } +TEST_CASE("[String] parse_url") { + String scheme, host, path, fragment; + int port; + + SUBCASE("Typical URL") { + Error err = String("https://docs.godotengine.org/en/stable/").parse_url(scheme, host, port, path, fragment); + REQUIRE(err == OK); + CHECK_EQ(scheme, "https://"); + CHECK_EQ(host, "docs.godotengine.org"); + CHECK_EQ(port, 0); + CHECK_EQ(path, "/en/stable/"); + CHECK_EQ(fragment, ""); + } + + SUBCASE("All Elements") { + Error err = String("https://www.example.com:8080/path/to/file.html#fragment").parse_url(scheme, host, port, path, fragment); + REQUIRE(err == OK); + CHECK_EQ(scheme, "https://"); + CHECK_EQ(host, "www.example.com"); + CHECK_EQ(port, 8080); + CHECK_EQ(path, "/path/to/file.html"); + CHECK_EQ(fragment, "fragment"); + } + + SUBCASE("Invalid Scheme") { + Error err = String("http_://example.com").parse_url(scheme, host, port, path, fragment); + REQUIRE(err == ERR_INVALID_PARAMETER); // Host being empty is an error. + } + + SUBCASE("Scheme vs Fragment") { + Error err = String("google.com/#goto=http://redirect_url/").parse_url(scheme, host, port, path, fragment); + REQUIRE(err == OK); + CHECK_EQ(scheme, ""); + CHECK_EQ(host, "google.com"); + CHECK_EQ(port, 0); + CHECK_EQ(path, "/"); + CHECK_EQ(fragment, "goto=http://redirect_url/"); + } +} + TEST_CASE("[Stress][String] Empty via ' == String()'") { for (int i = 0; i < 100000; ++i) { String str = "Hello World!";