diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-04-13 22:50:29 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-04-14 13:30:10 +0200 |
commit | ba77b40808644040f1545eb3adafa97b2517a851 (patch) | |
tree | fb98a4b27ed5e30c832b2f8e94e3708d24e4c65c /Userland/Libraries | |
parent | 2ab292f3811e3d1b553e4897ebc192de409d9ab9 (diff) | |
download | serenity-ba77b40808644040f1545eb3adafa97b2517a851.zip |
LibJS: Implement the encode/decodeURI(Component) family of functions
These are generally useful and in particular needed for twitter.com
Diffstat (limited to 'Userland/Libraries')
5 files changed, 187 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index d5106d81be..ce4c3807fa 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -96,12 +96,16 @@ namespace JS { P(countReset) \ P(create) \ P(debug) \ + P(decodeURI) \ + P(decodeURIComponent) \ P(defineProperties) \ P(defineProperty) \ P(deleteProperty) \ P(description) \ P(done) \ P(dotAll) \ + P(encodeURI) \ + P(encodeURIComponent) \ P(endsWith) \ P(entries) \ P(enumerable) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index fc91c2f917..b9043ec771 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -170,6 +170,7 @@ 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(UnknownIdentifier, "'{}' is not defined") \ + M(URIMalformed, "URI malformed") \ /* LibWeb bindings */ \ M(NotAByteString, "Argument to {}() must be a byte string") \ M(BadArgCountOne, "{}() needs one argument") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 25a3509716..e81bed38ef 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -25,6 +25,8 @@ * 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/Utf8View.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.parseInt, parse_int, 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.Infinity, js_infinity(), 0); @@ -340,4 +346,117 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::eval) 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)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.h b/Userland/Libraries/LibJS/Runtime/GlobalObject.h index 1062a3f75d..a02b7f50fa 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.h @@ -84,6 +84,10 @@ private: JS_DECLARE_NATIVE_FUNCTION(parse_float); JS_DECLARE_NATIVE_FUNCTION(parse_int); 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; diff --git a/Userland/Libraries/LibJS/Tests/builtins/functions/uriEncodeDecode.js b/Userland/Libraries/LibJS/Tests/builtins/functions/uriEncodeDecode.js new file mode 100644 index 0000000000..3fb504ce4a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/functions/uriEncodeDecode.js @@ -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"); + }); +}); |