diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 854d32af967..e8c1ea42edd 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4707,6 +4707,29 @@ String String::uri_decode() const { return String::utf8(res); } +String String::uri_file_decode() const { + CharString src = utf8(); + CharString res; + for (int i = 0; i < src.length(); ++i) { + if (src[i] == '%' && i + 2 < src.length()) { + char ord1 = src[i + 1]; + if (is_digit(ord1) || is_ascii_upper_case(ord1)) { + char ord2 = src[i + 2]; + if (is_digit(ord2) || is_ascii_upper_case(ord2)) { + char bytes[3] = { (char)ord1, (char)ord2, 0 }; + res += (char)strtol(bytes, nullptr, 16); + i += 2; + } + } else { + res += src[i]; + } + } else { + res += src[i]; + } + } + return String::utf8(res); +} + String String::c_unescape() const { String escaped = *this; escaped = escaped.replace("\\a", "\a"); diff --git a/core/string/ustring.h b/core/string/ustring.h index e828456df12..75c12ff6650 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -572,6 +572,7 @@ public: String xml_unescape() const; String uri_encode() const; String uri_decode() const; + String uri_file_decode() const; String c_escape() const; String c_escape_multiline() const; String c_unescape() const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 212091dec38..a6a68f5ed6a 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1790,6 +1790,7 @@ static void _register_variant_builtin_methods_string() { bind_string_method(xml_unescape, sarray(), varray()); bind_string_method(uri_encode, sarray(), varray()); bind_string_method(uri_decode, sarray(), varray()); + bind_string_method(uri_file_decode, sarray(), varray()); bind_string_method(c_escape, sarray(), varray()); bind_string_method(c_unescape, sarray(), varray()); bind_string_method(json_escape, sarray(), varray()); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index a3cf6c21490..f3d23ed3e2b 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -1130,6 +1130,7 @@ GD.Print(url.URIDecode()) // Prints "$DOCS_URL/?highlight=Godot Engine:docs" [/csharp] [/codeblocks] + [b]Note:[/b] This method decodes [code]+[/code] as space. @@ -1152,6 +1153,12 @@ [/codeblocks] + + + + Decodes the file path from its URL-encoded format. Unlike [method uri_decode] this method leaves [code]+[/code] as is. + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index e0c84bd6708..f3eebcffdb7 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -1038,6 +1038,7 @@ GD.Print(url.URIDecode()) // Prints "$DOCS_URL/?highlight=Godot Engine:docs" [/csharp] [/codeblocks] + [b]Note:[/b] This method decodes [code]+[/code] as space. @@ -1060,6 +1061,12 @@ [/codeblocks] + + + + Decodes the file path from its URL-encoded format. Unlike [method uri_decode] this method leaves [code]+[/code] as is. + + diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index 1b3757e0b00..93491a96b14 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -257,7 +257,7 @@ static void _get_drives(List *list) { // Parse only file:// links if (strncmp(string, "file://", 7) == 0) { // Strip any unwanted edges on the strings and push_back if it's not a duplicate. - String fpath = String::utf8(string + 7).strip_edges().split_spaces()[0].uri_decode(); + String fpath = String::utf8(string + 7).strip_edges().split_spaces()[0].uri_file_decode(); if (!list->find(fpath)) { list->push_back(fpath); } diff --git a/editor/import/3d/collada.cpp b/editor/import/3d/collada.cpp index b403c53ef18..9aa0a72f03b 100644 --- a/editor/import/3d/collada.cpp +++ b/editor/import/3d/collada.cpp @@ -288,7 +288,7 @@ void Collada::_parse_image(XMLParser &p_parser) { String path = p_parser.get_named_attribute_value("source").strip_edges(); if (!path.contains("://") && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path - image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().path_join(path.uri_decode())); + image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().path_join(path.uri_file_decode())); } } else { while (p_parser.read() == OK) { @@ -297,7 +297,7 @@ void Collada::_parse_image(XMLParser &p_parser) { if (name == "init_from") { p_parser.read(); - String path = p_parser.get_node_data().strip_edges().uri_decode(); + String path = p_parser.get_node_data().strip_edges().uri_file_decode(); if (!path.contains("://") && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 6806c801611..b891c42eaaa 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -559,8 +559,8 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) { } String GDScriptWorkspace::get_file_path(const String &p_uri) const { - String path = p_uri.uri_decode(); - String base_uri = root_uri.uri_decode(); + String path = p_uri.uri_file_decode(); + String base_uri = root_uri.uri_file_decode(); path = path.replacen(base_uri + "/", "res://"); return path; } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 14b4f96c8df..59e61eb9e0f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -812,7 +812,7 @@ Error GLTFDocument::_parse_buffers(Ref p_state, const String &p_base_ buffer_data = _parse_base64_uri(uri); } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); - uri = uri.uri_decode(); + uri = uri.uri_file_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri); buffer_data = FileAccess::get_file_as_bytes(uri); @@ -4123,7 +4123,7 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p } } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); - uri = uri.uri_decode(); + uri = uri.uri_file_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. resource_uri = uri.simplify_path(); // ResourceLoader will rely on the file extension to use the relevant loader. diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index cafba278336..21e18b305bc 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -473,7 +473,7 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) { const char *value; dbus_message_iter_get_basic(&uri_iter, &value); - r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_decode()); + r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_file_decode()); if (!dbus_message_iter_next(&uri_iter)) { break; } diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 0e02656941b..30b90b7b4af 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -2147,7 +2147,7 @@ void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *w msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false); for (int i = 0; i < msg->files.size(); i++) { - msg->files.write[i] = msg->files[i].replace("file://", "").uri_decode(); + msg->files.write[i] = msg->files[i].replace("file://", "").uri_file_decode(); } wayland_thread->push_message(msg); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 5f2850c83c5..c0717db29dc 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -5321,7 +5321,7 @@ void DisplayServerX11::process_events() { Vector files = String((char *)p.data).split("\r\n", false); XFree(p.data); for (int i = 0; i < files.size(); i++) { - files.write[i] = files[i].replace("file://", "").uri_decode(); + files.write[i] = files[i].replace("file://", "").uri_file_decode(); } if (windows[window_id].drop_files_callback.is_valid()) { diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 4e08e6ac09c..314c926e1f1 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -1774,6 +1774,7 @@ TEST_CASE("[String] uri_encode/unescape") { static const uint8_t u8str[] = { 0x54, 0xC4, 0x93, 0xC5, 0xA1, 0x74, 0x00 }; String x2 = String::utf8((const char *)u8str); String x3 = U"Tēšt"; + String x4 = U"file+name"; CHECK(x1.uri_decode() == x2); CHECK(x1.uri_decode() == x3); @@ -1783,6 +1784,8 @@ TEST_CASE("[String] uri_encode/unescape") { CHECK(s.uri_encode() == t); CHECK(t.uri_decode() == s); + CHECK(x4.uri_file_decode() == x4); + CHECK(x4.uri_decode() == U"file name"); } TEST_CASE("[String] xml_escape/unescape") {