Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify URI encoding/decoding, handle spaces-are-pluses, and handle hex/bin prefix automatically #43978

Merged
merged 2 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/io/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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();
}
}
}
Expand Down
85 changes: 13 additions & 72 deletions core/string/ustring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2097,8 +2097,9 @@ String::String(const StrRange &p_range) {
copy_from(p_range.c_str, p_range.len);
}

int64_t String::hex_to_int(bool p_with_prefix) const {
if (p_with_prefix && length() < 3) {
aaronfranke marked this conversation as resolved.
Show resolved Hide resolved
int64_t String::hex_to_int() const {
int len = length();
if (len == 0) {
return 0;
}

Expand All @@ -2110,10 +2111,7 @@ int64_t String::hex_to_int(bool p_with_prefix) const {
s++;
}

if (p_with_prefix) {
if (s[0] != '0' || s[1] != 'x') {
return 0;
}
if (len > 2 && s[0] == '0' && s[1] == 'x') {
s += 2;
}

Expand All @@ -2140,8 +2138,9 @@ int64_t String::hex_to_int(bool p_with_prefix) const {
return hex * sign;
}

int64_t String::bin_to_int(bool p_with_prefix) const {
if (p_with_prefix && length() < 3) {
int64_t String::bin_to_int() const {
int len = length();
if (len == 0) {
return 0;
}

Expand All @@ -2153,10 +2152,7 @@ int64_t String::bin_to_int(bool p_with_prefix) const {
s++;
}

if (p_with_prefix) {
if (s[0] != '0' || s[1] != 'b') {
return 0;
}
if (len > 2 && s[0] == '0' && s[1] == 'b') {
s += 2;
}

Expand Down Expand Up @@ -3756,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) {
Expand All @@ -3780,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()) {
Expand All @@ -3795,6 +3791,8 @@ String String::http_unescape() const {
} else {
res += unicode_at(i);
}
} else if (unicode_at(i) == '+') {
res += ' ';
} else {
res += unicode_at(i);
}
Expand Down Expand Up @@ -4251,7 +4249,7 @@ bool String::is_valid_ip_address() const {
continue;
}
if (n.is_valid_hex_number(false)) {
int64_t nint = n.hex_to_int(false);
int64_t nint = n.hex_to_int();
if (nint < 0 || nint > 0xffff) {
return false;
}
Expand Down Expand Up @@ -4346,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)
Expand Down
11 changes: 4 additions & 7 deletions core/string/ustring.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ class String {
bool is_numeric() const;

double to_float() const;
int64_t hex_to_int(bool p_with_prefix = true) const;
int64_t bin_to_int(bool p_with_prefix = true) const;
int64_t hex_to_int() const;
int64_t bin_to_int() const;
int64_t to_int() const;

static int64_t to_int(const char *p_str, int p_len = -1);
Expand Down Expand Up @@ -409,17 +409,14 @@ class String {

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;
Expand Down
10 changes: 4 additions & 6 deletions core/variant/variant_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -968,8 +966,8 @@ static void _register_variant_builtin_methods() {

bind_method(String, to_int, sarray(), varray());
bind_method(String, to_float, sarray(), varray());
bind_method(String, hex_to_int, sarray("with_prefix"), varray(true));
bind_method(String, bin_to_int, sarray("with_prefix"), varray(true));
bind_method(String, hex_to_int, sarray(), varray());
bind_method(String, bin_to_int, sarray(), varray());

bind_method(String, lpad, sarray("min_length", "character"), varray(" "));
bind_method(String, rpad, sarray("min_length", "character"), varray(" "));
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/HTTPClient.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/HTTPRequest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
95 changes: 52 additions & 43 deletions doc/classes/String.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,18 @@
<method name="bin_to_int">
<return type="int">
</return>
<argument index="0" name="with_prefix" type="bool" default="true">
</argument>
<description>
Converts a string containing a binary number into an integer. Binary strings can either be prefixed with [code]0b[/code] or not, and they can also start with a [code]-[/code] before the optional prefix.
[codeblocks]
[gdscript]
print("0x101".bin_to_int()) # Prints "5".
print("101".bin_to_int()) # Prints "5".
[/gdscript]
[csharp]
GD.Print("0x101".BinToInt()); // Prints "5".
GD.Print("101".BinToInt()); // Prints "5".
[/csharp]
[/codeblocks]
</description>
</method>
<method name="c_escape">
Expand Down Expand Up @@ -221,34 +230,18 @@
<method name="hex_to_int">
<return type="int">
</return>
<argument index="0" name="with_prefix" type="bool" default="true">
</argument>
<description>
Converts a string containing a hexadecimal number into a decimal integer. If [code]with_prefix[/code] is [code]true[/code], the hexadecimal string should start with the [code]0x[/code] prefix, otherwise [code]0[/code] is returned.
[codeblock]
print("0xff".hex_to_int()) # Print "255"
print("ab".hex_to_int(false)) # Print "171"
[/codeblock]
</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]
Converts a string containing a hexadecimal number into an integer. Hexadecimal strings can either be prefixed with [code]0x[/code] or not, and they can also start with a [code]-[/code] before the optional prefix.
[codeblocks]
[gdscript]
print("0xff".hex_to_int()) # Prints "255".
print("ab".hex_to_int()) # Prints "171".
[/gdscript]
[csharp]
GD.Print("0xff".HexToInt()); // Prints "255".
GD.Print("ab".HexToInt()); // Prints "171".
[/csharp]
[/codeblocks]
</description>
</method>
<method name="insert">
Expand Down Expand Up @@ -565,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>
Expand Down Expand Up @@ -878,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>
Expand Down
2 changes: 1 addition & 1 deletion drivers/unix/dir_access_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading