Skip to content

Commit

Permalink
Merge pull request #43978 from aaronfranke/cs-string
Browse files Browse the repository at this point in the history
Unify URI encoding/decoding, handle spaces-are-pluses, and handle hex/bin prefix automatically
  • Loading branch information
akien-mga authored Jan 28, 2021
2 parents 726967f + e829b7a commit e50422d
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 179 deletions.
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) {
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

0 comments on commit e50422d

Please sign in to comment.