summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorIdan Horowitz <idan.horowitz@gmail.com>2021-04-13 22:50:29 +0300
committerAndreas Kling <kling@serenityos.org>2021-04-14 13:30:10 +0200
commitba77b40808644040f1545eb3adafa97b2517a851 (patch)
treefb98a4b27ed5e30c832b2f8e94e3708d24e4c65c /Userland/Libraries
parent2ab292f3811e3d1b553e4897ebc192de409d9ab9 (diff)
downloadserenity-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')
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h4
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp119
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.h4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/functions/uriEncodeDecode.js59
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");
+ });
+});