Add uri_file_decode to handle + in file names.

This commit is contained in:
Pāvels Nadtočajevs 2025-04-07 22:49:57 +03:00
parent af2c713971
commit 9abe2e5294
No known key found for this signature in database
GPG key ID: 8413210218EF35D2
13 changed files with 52 additions and 10 deletions

View file

@ -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");

View file

@ -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;

View file

@ -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());

View file

@ -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>

View file

@ -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>

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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);

View file

@ -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()) {

View file

@ -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") {