diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 2f97beebb89..6a23ea170d5 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -99,6 +99,7 @@ namespace JS { P(entries) \ P(enumerable) \ P(error) \ + P(escape) \ P(eval) \ P(every) \ P(exec) \ @@ -263,6 +264,7 @@ namespace JS { P(trimStart) \ P(trunc) \ P(undefined) \ + P(unescape) \ P(unicode) \ P(unshift) \ P(value) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index a38489aae5b..286ba658157 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -112,6 +112,8 @@ void GlobalObject::initialize_global_object() 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_native_function(vm.names.escape, escape, 1, attr); + define_native_function(vm.names.unescape, unescape, 1, attr); define_property(vm.names.NaN, js_nan(), 0); define_property(vm.names.Infinity, js_infinity(), 0); @@ -433,4 +435,46 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::decode_uri_component) return js_string(vm, move(decoded)); } +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::escape) +{ + auto string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + StringBuilder escaped; + for (auto code_point : Utf8View(string)) { + if (code_point < 256) { + if ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./"sv.contains(code_point)) + escaped.append(code_point); + else + escaped.appendff("%{:02X}", code_point); + continue; + } + escaped.appendff("%u{:04X}", code_point); // FIXME: Handle utf-16 surrogate pairs + } + return js_string(vm, escaped.build()); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::unescape) +{ + auto string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + ssize_t length = string.length(); + StringBuilder unescaped(length); + for (auto k = 0; k < length; ++k) { + u32 code_point = string[k]; + if (code_point == '%') { + if (k <= length - 6 && string[k + 1] == 'u' && is_ascii_hex_digit(string[k + 2]) && is_ascii_hex_digit(string[k + 3]) && is_ascii_hex_digit(string[k + 4]) && is_ascii_hex_digit(string[k + 5])) { + code_point = (parse_ascii_hex_digit(string[k + 2]) << 12) | (parse_ascii_hex_digit(string[k + 3]) << 8) | (parse_ascii_hex_digit(string[k + 4]) << 4) | parse_ascii_hex_digit(string[k + 5]); + k += 5; + } else if (k <= length - 3 && is_ascii_hex_digit(string[k + 1]) && is_ascii_hex_digit(string[k + 2])) { + code_point = (parse_ascii_hex_digit(string[k + 1]) << 4) | parse_ascii_hex_digit(string[k + 2]); + k += 2; + } + } + unescaped.append_code_point(code_point); + } + return js_string(vm, unescaped.build()); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.h b/Userland/Libraries/LibJS/Runtime/GlobalObject.h index dc950c6604b..ddcb0dbc139 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.h @@ -68,6 +68,8 @@ private: JS_DECLARE_NATIVE_FUNCTION(decode_uri); JS_DECLARE_NATIVE_FUNCTION(encode_uri_component); JS_DECLARE_NATIVE_FUNCTION(decode_uri_component); + JS_DECLARE_NATIVE_FUNCTION(escape); + JS_DECLARE_NATIVE_FUNCTION(unescape); NonnullOwnPtr m_console; diff --git a/Userland/Libraries/LibJS/Tests/builtins/functions/escapeUnescape.js b/Userland/Libraries/LibJS/Tests/builtins/functions/escapeUnescape.js new file mode 100644 index 00000000000..c8e29a4a899 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/functions/escapeUnescape.js @@ -0,0 +1,21 @@ +test("escape", () => { + [ + ["abc123", "abc123"], + ["äöü", "%E4%F6%FC"], + ["ć", "%u0107"], + ["@*_+-./", "@*_+-./"], + ].forEach(test => { + expect(escape(test[0])).toBe(test[1]); + }); +}); + +test("unescape", () => { + [ + ["abc123", "abc123"], + ["%E4%F6%FC", "äöü"], + ["%u0107", "ć"], + ["@*_+-./", "@*_+-./"], + ].forEach(test => { + expect(unescape(test[0])).toBe(test[1]); + }); +});