1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 01:27:43 +00:00

LibJS: Implement the encode/decodeURI(Component) family of functions

These are generally useful and in particular needed for twitter.com
This commit is contained in:
Idan Horowitz 2021-04-13 22:50:29 +03:00 committed by Andreas Kling
parent 2ab292f381
commit ba77b40808
5 changed files with 187 additions and 0 deletions

View file

@ -96,12 +96,16 @@ namespace JS {
P(countReset) \ P(countReset) \
P(create) \ P(create) \
P(debug) \ P(debug) \
P(decodeURI) \
P(decodeURIComponent) \
P(defineProperties) \ P(defineProperties) \
P(defineProperty) \ P(defineProperty) \
P(deleteProperty) \ P(deleteProperty) \
P(description) \ P(description) \
P(done) \ P(done) \
P(dotAll) \ P(dotAll) \
P(encodeURI) \
P(encodeURIComponent) \
P(endsWith) \ P(endsWith) \
P(entries) \ P(entries) \
P(enumerable) \ P(enumerable) \

View file

@ -170,6 +170,7 @@
M(TypedArrayOutOfRangeByteOffset, "Typed array byte offset {} is out of range for buffer with length {}") \ M(TypedArrayOutOfRangeByteOffset, "Typed array byte offset {} is out of range for buffer with length {}") \
M(TypedArrayOutOfRangeByteOffsetOrLength, "Typed array range {}:{} is out of range for buffer with length {}") \ M(TypedArrayOutOfRangeByteOffsetOrLength, "Typed array range {}:{} is out of range for buffer with length {}") \
M(UnknownIdentifier, "'{}' is not defined") \ M(UnknownIdentifier, "'{}' is not defined") \
M(URIMalformed, "URI malformed") \
/* LibWeb bindings */ \ /* LibWeb bindings */ \
M(NotAByteString, "Argument to {}() must be a byte string") \ M(NotAByteString, "Argument to {}() must be a byte string") \
M(BadArgCountOne, "{}() needs one argument") \ M(BadArgCountOne, "{}() needs one argument") \

View file

@ -25,6 +25,8 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <AK/Hex.h>
#include <AK/Platform.h>
#include <AK/TemporaryChange.h> #include <AK/TemporaryChange.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <LibJS/Console.h> #include <LibJS/Console.h>
@ -126,6 +128,10 @@ void GlobalObject::initialize_global_object()
define_native_function(vm.names.parseFloat, parse_float, 1, attr); define_native_function(vm.names.parseFloat, parse_float, 1, attr);
define_native_function(vm.names.parseInt, parse_int, 1, attr); define_native_function(vm.names.parseInt, parse_int, 1, attr);
define_native_function(vm.names.eval, eval, 1, attr); define_native_function(vm.names.eval, eval, 1, attr);
define_native_function(vm.names.encodeURI, encode_uri, 1, attr);
define_native_function(vm.names.decodeURI, decode_uri, 1, attr);
define_native_function(vm.names.encodeURIComponent, encode_uri_component, 1, attr);
define_native_function(vm.names.decodeURIComponent, decode_uri_component, 1, attr);
define_property(vm.names.NaN, js_nan(), 0); define_property(vm.names.NaN, js_nan(), 0);
define_property(vm.names.Infinity, js_infinity(), 0); define_property(vm.names.Infinity, js_infinity(), 0);
@ -340,4 +346,117 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::eval)
return vm.last_value(); return vm.last_value();
} }
// 19.2.6.1.1 Encode ( string, unescapedSet )
static String encode([[maybe_unused]] JS::GlobalObject& global_object, const String& string, StringView unescaped_set)
{
StringBuilder encoded_builder;
for (unsigned char code_unit : string) {
if (unescaped_set.contains(code_unit)) {
encoded_builder.append(code_unit);
continue;
}
// FIXME: check for unpaired surrogates and throw URIError
encoded_builder.appendff("%{:02X}", code_unit);
}
return encoded_builder.build();
}
// 19.2.6.1.2 Decode ( string, reservedSet )
static String decode(JS::GlobalObject& global_object, const String& string, StringView reserved_set)
{
StringBuilder decoded_builder;
auto expected_continuation_bytes = 0;
for (size_t k = 0; k < string.length(); k++) {
auto code_unit = string[k];
if (code_unit != '%') {
if (expected_continuation_bytes > 0) {
global_object.vm().throw_exception<URIError>(global_object, ErrorType::URIMalformed);
return {};
}
decoded_builder.append(code_unit);
continue;
}
if (k + 2 >= string.length()) {
global_object.vm().throw_exception<URIError>(global_object, ErrorType::URIMalformed);
return {};
}
auto first_digit = decode_hex_digit(string[k + 1]);
if (first_digit >= 16) {
global_object.vm().throw_exception<URIError>(global_object, ErrorType::URIMalformed);
return {};
}
auto second_digit = decode_hex_digit(string[k + 2]);
if (second_digit >= 16) {
global_object.vm().throw_exception<URIError>(global_object, ErrorType::URIMalformed);
return {};
}
char decoded_code_unit = (first_digit << 4) | second_digit;
k += 2;
if (expected_continuation_bytes > 0) {
decoded_builder.append(decoded_code_unit);
expected_continuation_bytes--;
continue;
}
if ((decoded_code_unit & 0x80) == 0) {
if (reserved_set.contains(decoded_code_unit))
decoded_builder.append(string.substring_view(k - 2, 3));
else
decoded_builder.append(decoded_code_unit);
continue;
}
auto leading_ones = count_trailing_zeroes_32_safe(~decoded_code_unit) - 24;
if (leading_ones == 1 || leading_ones > 4) {
global_object.vm().throw_exception<URIError>(global_object, ErrorType::URIMalformed);
return {};
}
decoded_builder.append(decoded_code_unit);
expected_continuation_bytes = leading_ones - 1;
}
return decoded_builder.build();
}
JS_DEFINE_NATIVE_FUNCTION(GlobalObject::encode_uri)
{
auto uri_string = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
auto encoded = encode(global_object, uri_string, ";/?:@&=+$,abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()#"sv);
if (vm.exception())
return {};
return js_string(vm, move(encoded));
}
JS_DEFINE_NATIVE_FUNCTION(GlobalObject::decode_uri)
{
auto uri_string = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
auto decoded = decode(global_object, uri_string, ";/?:@&=+$,#"sv);
if (vm.exception())
return {};
return js_string(vm, move(decoded));
}
JS_DEFINE_NATIVE_FUNCTION(GlobalObject::encode_uri_component)
{
auto uri_string = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
auto encoded = encode(global_object, uri_string, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"sv);
if (vm.exception())
return {};
return js_string(vm, move(encoded));
}
JS_DEFINE_NATIVE_FUNCTION(GlobalObject::decode_uri_component)
{
auto uri_string = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
auto decoded = decode(global_object, uri_string, ""sv);
if (vm.exception())
return {};
return js_string(vm, move(decoded));
}
} }

View file

@ -84,6 +84,10 @@ private:
JS_DECLARE_NATIVE_FUNCTION(parse_float); JS_DECLARE_NATIVE_FUNCTION(parse_float);
JS_DECLARE_NATIVE_FUNCTION(parse_int); JS_DECLARE_NATIVE_FUNCTION(parse_int);
JS_DECLARE_NATIVE_FUNCTION(eval); JS_DECLARE_NATIVE_FUNCTION(eval);
JS_DECLARE_NATIVE_FUNCTION(encode_uri);
JS_DECLARE_NATIVE_FUNCTION(decode_uri);
JS_DECLARE_NATIVE_FUNCTION(encode_uri_component);
JS_DECLARE_NATIVE_FUNCTION(decode_uri_component);
NonnullOwnPtr<Console> m_console; NonnullOwnPtr<Console> m_console;

View file

@ -0,0 +1,59 @@
test("encodeURI", () => {
[
["шеллы", "%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"],
[";,/?:@&=+$#", ";,/?:@&=+$#"],
["-_.!~*'()", "-_.!~*'()"],
["ABC abc 123", "ABC%20abc%20123"],
].forEach(test => {
expect(encodeURI(test[0])).toBe(test[1]);
});
});
test("decodeURI", () => {
[
["%D1%88%D0%B5%D0%BB%D0%BB%D1%8B", "шеллы"],
[";,/?:@&=+$#", ";,/?:@&=+$#"],
["-_.!~*'()", "-_.!~*'()"],
["ABC%20abc%20123", "ABC abc 123"],
].forEach(test => {
expect(decodeURI(test[0])).toBe(test[1]);
});
});
test("decodeURI exception", () => {
["%", "%a", "%gh", "%%%"].forEach(test => {
expect(() => {
decodeURI(test);
}).toThrowWithMessage(URIError, "URI malformed");
});
});
test("encodeURIComponent", () => {
[
["шеллы", "%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"],
[";,/?:@&=+$#", "%3B%2C%2F%3F%3A%40%26%3D%2B%24%23"],
["-_.!~*'()", "-_.!~*'()"],
["ABC abc 123", "ABC%20abc%20123"],
].forEach(test => {
expect(encodeURIComponent(test[0])).toBe(test[1]);
});
});
test("decodeURIComponent", () => {
[
["%D1%88%D0%B5%D0%BB%D0%BB%D1%8B", "шеллы"],
["%3B%2C%2F%3F%3A%40%26%3D%2B%24%23", ";,/?:@&=+$#"],
["-_.!~*'()", "-_.!~*'()"],
["ABC%20abc%20123", "ABC abc 123"],
].forEach(test => {
expect(decodeURIComponent(test[0])).toBe(test[1]);
});
});
test("decodeURIComponent exception", () => {
["%", "%a", "%gh", "%%%"].forEach(test => {
expect(() => {
decodeURI(test);
}).toThrowWithMessage(URIError, "URI malformed");
});
});