Browse Source

Add `uri_file_decode` to handle `+` in file names.

Pāvels Nadtočajevs 4 months ago
parent
commit
9abe2e5294

+ 23 - 0
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");

+ 1 - 0
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;

+ 1 - 0
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());

+ 7 - 0
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.
 			</description>
 		</method>
 		<method name="uri_encode" qualifiers="const">
@@ -1152,6 +1153,12 @@
 				[/codeblocks]
 			</description>
 		</method>
+		<method name="uri_file_decode" qualifiers="const">
+			<return type="String" />
+			<description>
+				Decodes the file path from its URL-encoded format. Unlike [method uri_decode] this method leaves [code]+[/code] as is.
+			</description>
+		</method>
 		<method name="validate_filename" qualifiers="const">
 			<return type="String" />
 			<description>

+ 7 - 0
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.
 			</description>
 		</method>
 		<method name="uri_encode" qualifiers="const">
@@ -1060,6 +1061,12 @@
 				[/codeblocks]
 			</description>
 		</method>
+		<method name="uri_file_decode" qualifiers="const">
+			<return type="String" />
+			<description>
+				Decodes the file path from its URL-encoded format. Unlike [method uri_decode] this method leaves [code]+[/code] as is.
+			</description>
+		</method>
 		<method name="validate_filename" qualifiers="const">
 			<return type="String" />
 			<description>

+ 1 - 1
drivers/unix/dir_access_unix.cpp

@@ -257,7 +257,7 @@ static void _get_drives(List<String> *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);
 					}

+ 2 - 2
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

+ 2 - 2
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;
 }

+ 2 - 2
modules/gltf/gltf_document.cpp

@@ -812,7 +812,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> 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<GLTFState> 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.

+ 1 - 1
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;
 							}

+ 1 - 1
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);

+ 1 - 1
platform/linuxbsd/x11/display_server_x11.cpp

@@ -5321,7 +5321,7 @@ void DisplayServerX11::process_events() {
 					Vector<String> 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()) {

+ 3 - 0
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") {