Unify URI encoding/decoding and add to C#
http_escape and percent_encode have been unified into uri_encode, and http_unescape and percent_decode have been unified into uri_decode.
This commit is contained in:
parent
a3e3bf8227
commit
e829b7aee4
15 changed files with 81 additions and 142 deletions
|
@ -736,14 +736,14 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
|
|||
String query = "";
|
||||
Array keys = p_dict.keys();
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
String encoded_key = String(keys[i]).http_escape();
|
||||
String encoded_key = String(keys[i]).uri_encode();
|
||||
Variant value = p_dict[keys[i]];
|
||||
switch (value.get_type()) {
|
||||
case Variant::ARRAY: {
|
||||
// Repeat the key with every values
|
||||
Array values = value;
|
||||
for (int j = 0; j < values.size(); ++j) {
|
||||
query += "&" + encoded_key + "=" + String(values[j]).http_escape();
|
||||
query += "&" + encoded_key + "=" + String(values[j]).uri_encode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
|
|||
}
|
||||
default: {
|
||||
// Add the key-value pair
|
||||
query += "&" + encoded_key + "=" + String(value).http_escape();
|
||||
query += "&" + encoded_key + "=" + String(value).uri_encode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3752,7 +3752,7 @@ bool String::is_valid_string() const {
|
|||
return valid;
|
||||
}
|
||||
|
||||
String String::http_escape() const {
|
||||
String String::uri_encode() const {
|
||||
const CharString temp = utf8();
|
||||
String res;
|
||||
for (int i = 0; i < temp.length(); ++i) {
|
||||
|
@ -3776,7 +3776,7 @@ String String::http_escape() const {
|
|||
return res;
|
||||
}
|
||||
|
||||
String String::http_unescape() const {
|
||||
String String::uri_decode() const {
|
||||
String res;
|
||||
for (int i = 0; i < length(); ++i) {
|
||||
if (unicode_at(i) == '%' && i + 2 < length()) {
|
||||
|
@ -3791,6 +3791,8 @@ String String::http_unescape() const {
|
|||
} else {
|
||||
res += unicode_at(i);
|
||||
}
|
||||
} else if (unicode_at(i) == '+') {
|
||||
res += ' ';
|
||||
} else {
|
||||
res += unicode_at(i);
|
||||
}
|
||||
|
@ -4342,63 +4344,6 @@ String String::plus_file(const String &p_file) const {
|
|||
return *this + "/" + p_file;
|
||||
}
|
||||
|
||||
String String::percent_encode() const {
|
||||
CharString cs = utf8();
|
||||
String encoded;
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
uint8_t c = cs[i];
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '.') {
|
||||
char p[2] = { (char)c, 0 };
|
||||
encoded += p;
|
||||
} else {
|
||||
char p[4] = { '%', 0, 0, 0 };
|
||||
static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
p[1] = hex[c >> 4];
|
||||
p[2] = hex[c & 0xF];
|
||||
encoded += p;
|
||||
}
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
String String::percent_decode() const {
|
||||
CharString pe;
|
||||
|
||||
CharString cs = utf8();
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
uint8_t c = cs[i];
|
||||
if (c == '%' && i < length() - 2) {
|
||||
uint8_t a = LOWERCASE(cs[i + 1]);
|
||||
uint8_t b = LOWERCASE(cs[i + 2]);
|
||||
|
||||
if (a >= '0' && a <= '9') {
|
||||
c = (a - '0') << 4;
|
||||
} else if (a >= 'a' && a <= 'f') {
|
||||
c = (a - 'a' + 10) << 4;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t d = 0;
|
||||
|
||||
if (b >= '0' && b <= '9') {
|
||||
d = (b - '0');
|
||||
} else if (b >= 'a' && b <= 'f') {
|
||||
d = (b - 'a' + 10);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
c += d;
|
||||
i += 2;
|
||||
}
|
||||
pe += c;
|
||||
}
|
||||
|
||||
return String::utf8(pe.ptr());
|
||||
}
|
||||
|
||||
String String::property_name_encode() const {
|
||||
// Escape and quote strings with extended ASCII or further Unicode characters
|
||||
// as well as '"', '=' or ' ' (32)
|
||||
|
|
|
@ -409,17 +409,14 @@ public:
|
|||
|
||||
String xml_escape(bool p_escape_quotes = false) const;
|
||||
String xml_unescape() const;
|
||||
String http_escape() const;
|
||||
String http_unescape() const;
|
||||
String uri_encode() const;
|
||||
String uri_decode() const;
|
||||
String c_escape() const;
|
||||
String c_escape_multiline() const;
|
||||
String c_unescape() const;
|
||||
String json_escape() const;
|
||||
String word_wrap(int p_chars_per_line) const;
|
||||
|
||||
String percent_encode() const;
|
||||
String percent_decode() const;
|
||||
|
||||
String property_name_encode() const;
|
||||
|
||||
bool is_valid_identifier() const;
|
||||
|
|
|
@ -950,13 +950,11 @@ static void _register_variant_builtin_methods() {
|
|||
bind_method(String, get_file, sarray(), varray());
|
||||
bind_method(String, xml_escape, sarray("escape_quotes"), varray(false));
|
||||
bind_method(String, xml_unescape, sarray(), varray());
|
||||
bind_method(String, http_escape, sarray(), varray());
|
||||
bind_method(String, http_unescape, sarray(), varray());
|
||||
bind_method(String, uri_encode, sarray(), varray());
|
||||
bind_method(String, uri_decode, sarray(), varray());
|
||||
bind_method(String, c_escape, sarray(), varray());
|
||||
bind_method(String, c_unescape, sarray(), varray());
|
||||
bind_method(String, json_escape, sarray(), varray());
|
||||
bind_method(String, percent_encode, sarray(), varray());
|
||||
bind_method(String, percent_decode, sarray(), varray());
|
||||
|
||||
bind_method(String, is_valid_identifier, sarray(), varray());
|
||||
bind_method(String, is_valid_integer, sarray(), varray());
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
var result = new HTTPClient().Request(HTTPClient.Method.Post, "index.php", headers, queryString);
|
||||
[/csharp]
|
||||
[/codeblocks]
|
||||
[b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.http_escape] for an example.
|
||||
[b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.uri_encode] for an example.
|
||||
</description>
|
||||
</method>
|
||||
<method name="request_raw">
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
<description>
|
||||
Creates request on the underlying [HTTPClient]. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request].
|
||||
Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host.
|
||||
[b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.http_escape] for an example.
|
||||
[b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.uri_encode] for an example.
|
||||
</description>
|
||||
</method>
|
||||
<method name="request_raw">
|
||||
|
|
|
@ -244,26 +244,6 @@
|
|||
[/codeblocks]
|
||||
</description>
|
||||
</method>
|
||||
<method name="http_escape">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Escapes (encodes) a string to URL friendly format. Also referred to as 'URL encode'.
|
||||
[codeblock]
|
||||
print("https://example.org/?escaped=" + "Godot Engine:'docs'".http_escape())
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="http_unescape">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Unescapes (decodes) a string in URL encoded format. Also referred to as 'URL decode'.
|
||||
[codeblock]
|
||||
print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".http_unescape())
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="insert">
|
||||
<return type="String">
|
||||
</return>
|
||||
|
@ -578,20 +558,6 @@
|
|||
Formats a number to have an exact number of [code]digits[/code] before the decimal point.
|
||||
</description>
|
||||
</method>
|
||||
<method name="percent_decode">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Decode a percent-encoded string. See [method percent_encode].
|
||||
</description>
|
||||
</method>
|
||||
<method name="percent_encode">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Percent-encodes a string. Encodes parameters in a URL when sending a HTTP GET request (and bodies of form-urlencoded POST requests).
|
||||
</description>
|
||||
</method>
|
||||
<method name="plus_file">
|
||||
<return type="String">
|
||||
</return>
|
||||
|
@ -891,6 +857,36 @@
|
|||
Returns the character code at position [code]at[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="uri_decode">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Decodes a string in URL encoded format. This is meant to decode parameters in a URL when receiving an HTTP request.
|
||||
[codeblocks]
|
||||
[gdscript]
|
||||
print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".uri_decode())
|
||||
[/gdscript]
|
||||
[csharp]
|
||||
GD.Print("https://example.org/?escaped=" + "Godot%20Engine%3a%27Docs%27".URIDecode());
|
||||
[/csharp]
|
||||
[/codeblocks]
|
||||
</description>
|
||||
</method>
|
||||
<method name="uri_encode">
|
||||
<return type="String">
|
||||
</return>
|
||||
<description>
|
||||
Encodes a string to URL friendly format. This is meant to encode parameters in a URL when sending an HTTP request.
|
||||
[codeblocks]
|
||||
[gdscript]
|
||||
print("https://example.org/?escaped=" + "Godot Engine:'docs'".uri_encode())
|
||||
[/gdscript]
|
||||
[csharp]
|
||||
GD.Print("https://example.org/?escaped=" + "Godot Engine:'docs'".URIEncode());
|
||||
[/csharp]
|
||||
[/codeblocks]
|
||||
</description>
|
||||
</method>
|
||||
<method name="xml_escape">
|
||||
<return type="String">
|
||||
</return>
|
||||
|
|
|
@ -232,7 +232,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(string + 7).strip_edges().split_spaces()[0].percent_decode();
|
||||
String fpath = String(string + 7).strip_edges().split_spaces()[0].uri_decode();
|
||||
if (!list->find(fpath)) {
|
||||
list->push_back(fpath);
|
||||
}
|
||||
|
|
|
@ -289,7 +289,7 @@ void Collada::_parse_image(XMLParser &parser) {
|
|||
String path = parser.get_attribute_value("source").strip_edges();
|
||||
if (path.find("://") == -1 && path.is_rel_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().plus_file(path.percent_decode()));
|
||||
image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode()));
|
||||
}
|
||||
} else {
|
||||
while (parser.read() == OK) {
|
||||
|
@ -298,7 +298,7 @@ void Collada::_parse_image(XMLParser &parser) {
|
|||
|
||||
if (name == "init_from") {
|
||||
parser.read();
|
||||
String path = parser.get_node_data().strip_edges().percent_decode();
|
||||
String path = parser.get_node_data().strip_edges().uri_decode();
|
||||
|
||||
if (path.find("://") == -1 && path.is_rel_path()) {
|
||||
// path is relative to file being loaded, so convert to a resource path
|
||||
|
|
|
@ -900,7 +900,7 @@ void EditorAssetLibrary::_search(int p_page) {
|
|||
}
|
||||
|
||||
if (filter->get_text() != String()) {
|
||||
args += "&filter=" + filter->get_text().http_escape();
|
||||
args += "&filter=" + filter->get_text().uri_encode();
|
||||
}
|
||||
|
||||
if (p_page > 0) {
|
||||
|
|
|
@ -350,7 +350,7 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) {
|
|||
String GDScriptWorkspace::get_file_path(const String &p_uri) const {
|
||||
String path = p_uri;
|
||||
path = path.replace(root_uri + "/", "res://");
|
||||
path = path.http_unescape();
|
||||
path = path.uri_decode();
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
|
@ -479,7 +479,7 @@ namespace Godot
|
|||
/// <summary>
|
||||
/// Returns a hexadecimal representation of this byte as a string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte to encode.</param>
|
||||
/// <param name="b">The byte to encode.</param>
|
||||
/// <returns>The hexadecimal representation of this byte.</returns>
|
||||
internal static string HexEncode(this byte b)
|
||||
{
|
||||
|
@ -932,22 +932,6 @@ namespace Godot
|
|||
return s;
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// Decode a percent-encoded string. See [method percent_encode].
|
||||
// </summary>
|
||||
public static string PercentDecode(this string instance)
|
||||
{
|
||||
return Uri.UnescapeDataString(instance);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// Percent-encode a string. This is meant to encode parameters in a URL when sending a HTTP GET request and bodies of form-urlencoded POST request.
|
||||
// </summary>
|
||||
public static string PercentEncode(this string instance)
|
||||
{
|
||||
return Uri.EscapeDataString(instance);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// If the string is a path, this concatenates [code]file[/code] at the end of the string as a subpath. E.g. [code]"this/is".plus_file("path") == "this/is/path"[/code].
|
||||
// </summary>
|
||||
|
@ -1210,6 +1194,33 @@ namespace Godot
|
|||
return Encoding.UTF8.GetBytes(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a string in URL encoded format. This is meant to
|
||||
/// decode parameters in a URL when receiving an HTTP request.
|
||||
/// This mostly wraps around `System.Uri.UnescapeDataString()`,
|
||||
/// but also handles `+`.
|
||||
/// See <see cref="URIEncode"/> for encoding.
|
||||
/// </summary>
|
||||
/// <param name="instance">The string to decode.</param>
|
||||
/// <returns>The unescaped string.</returns>
|
||||
public static string URIDecode(this string instance)
|
||||
{
|
||||
return Uri.UnescapeDataString(instance.Replace("+", "%20"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a string to URL friendly format. This is meant to
|
||||
/// encode parameters in a URL when sending an HTTP request.
|
||||
/// This wraps around `System.Uri.EscapeDataString()`.
|
||||
/// See <see cref="URIDecode"/> for decoding.
|
||||
/// </summary>
|
||||
/// <param name="instance">The string to encode.</param>
|
||||
/// <returns>The escaped string.</returns>
|
||||
public static string URIEncode(this string instance)
|
||||
{
|
||||
return Uri.EscapeDataString(instance);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// Return a copy of the string with special characters escaped using the XML standard.
|
||||
// </summary>
|
||||
|
|
|
@ -3360,7 +3360,7 @@ void DisplayServerX11::process_events() {
|
|||
|
||||
Vector<String> files = String((char *)p.data).split("\n", false);
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges();
|
||||
files.write[i] = files[i].replace("file://", "").uri_decode().strip_edges();
|
||||
}
|
||||
|
||||
if (!windows[window_id].drop_files_callback.is_null()) {
|
||||
|
|
|
@ -410,7 +410,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
|
|||
OS::Time time = OS::get_singleton()->get_time(false);
|
||||
String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, date.month, date.day, time.hour, time.min);
|
||||
timestamp = vformat("%s%02d", timestamp, time.sec); // vformat only supports up to 6 arguments.
|
||||
String trash_info = "[Trash Info]\nPath=" + p_path.http_escape() + "\nDeletionDate=" + timestamp + "\n";
|
||||
String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n";
|
||||
{
|
||||
Error err;
|
||||
FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
|
||||
|
|
|
@ -1152,20 +1152,12 @@ TEST_CASE("[String] hash") {
|
|||
CHECK(a.hash64() != c.hash64());
|
||||
}
|
||||
|
||||
TEST_CASE("[String] http_escape/unescape") {
|
||||
TEST_CASE("[String] uri_encode/unescape") {
|
||||
String s = "Godot Engine:'docs'";
|
||||
String t = "Godot%20Engine%3A%27docs%27";
|
||||
|
||||
CHECK(s.http_escape() == t);
|
||||
CHECK(t.http_unescape() == s);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] percent_encode/decode") { // Note: is it redundant? Seems to be same as http_escape/unescape but in lower case.
|
||||
String s = "Godot Engine:'docs'";
|
||||
String t = "Godot%20Engine%3a%27docs%27";
|
||||
|
||||
CHECK(s.percent_encode() == t);
|
||||
CHECK(t.percent_decode() == s);
|
||||
CHECK(s.uri_encode() == t);
|
||||
CHECK(t.uri_decode() == s);
|
||||
}
|
||||
|
||||
TEST_CASE("[String] xml_escape/unescape") {
|
||||
|
|
Loading…
Reference in a new issue