From f0a8e50be15da9a241fa2c0391809428ed6c0e27 Mon Sep 17 00:00:00 2001 From: AllinolCP <56921534+AllinolCP@users.noreply.github.com> Date: Sat, 25 Mar 2023 16:24:09 +0700 Subject: [PATCH] avm2: implement decodeURI --- core/src/avm2/error.rs | 11 ++ core/src/avm2/globals.rs | 11 ++ core/src/avm2/globals/toplevel.rs | 133 +++++++++++++++++++- tests/tests/swfs/avm2/decode_uri/Test.as | 81 ++++++++++++ tests/tests/swfs/avm2/decode_uri/output.txt | 72 +++++++++++ tests/tests/swfs/avm2/decode_uri/test.fla | Bin 0 -> 4052 bytes tests/tests/swfs/avm2/decode_uri/test.swf | Bin 0 -> 1659 bytes tests/tests/swfs/avm2/decode_uri/test.toml | 1 + 8 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 tests/tests/swfs/avm2/decode_uri/Test.as create mode 100644 tests/tests/swfs/avm2/decode_uri/output.txt create mode 100644 tests/tests/swfs/avm2/decode_uri/test.fla create mode 100644 tests/tests/swfs/avm2/decode_uri/test.swf create mode 100644 tests/tests/swfs/avm2/decode_uri/test.toml diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index 15ab20826..4fe666929 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -210,6 +210,17 @@ pub fn eof_error<'gc>( error_constructor(activation, class, message, code) } +#[inline(never)] +#[cold] +pub fn uri_error<'gc>( + activation: &mut Activation<'_, 'gc>, + message: &str, + code: u32, +) -> Result, Error<'gc>> { + let class = activation.avm2().classes().urierror; + error_constructor(activation, class, message, code) +} + #[inline(never)] #[cold] pub fn error<'gc>( diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index ee5f5bd68..20a817a26 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -111,6 +111,7 @@ pub struct SystemClasses<'gc> { pub verifyerror: ClassObject<'gc>, pub ioerror: ClassObject<'gc>, pub eoferror: ClassObject<'gc>, + pub urierror: ClassObject<'gc>, pub error: ClassObject<'gc>, pub uncaughterrorevents: ClassObject<'gc>, pub statictext: ClassObject<'gc>, @@ -219,6 +220,7 @@ impl<'gc> SystemClasses<'gc> { verifyerror: object, ioerror: object, eoferror: object, + urierror: object, error: object, uncaughterrorevents: object, statictext: object, @@ -520,6 +522,14 @@ pub fn load_player_globals<'gc>( toplevel::encode_uri_component, script, )?; + function(activation, "", "decodeURI", toplevel::decode_uri, script)?; + function( + activation, + "", + "decodeURIComponent", + toplevel::decode_uri_component, + script, + )?; function(activation, "", "unescape", toplevel::unescape, script)?; avm2_system_class!(vector, activation, vector::create_class(activation), script); @@ -606,6 +616,7 @@ fn load_playerglobal<'gc>( ("", "RegExp", regexp), ("", "ReferenceError", referenceerror), ("", "TypeError", typeerror), + ("", "URIError", urierror), ("", "VerifyError", verifyerror), ("", "XML", xml), ("", "XMLList", xml_list), diff --git a/core/src/avm2/globals/toplevel.rs b/core/src/avm2/globals/toplevel.rs index 893cf90c2..7985f897c 100644 --- a/core/src/avm2/globals/toplevel.rs +++ b/core/src/avm2/globals/toplevel.rs @@ -3,9 +3,9 @@ use ruffle_wstr::Units; use crate::avm2::activation::Activation; +use crate::avm2::error::{uri_error, Error}; use crate::avm2::object::Object; use crate::avm2::value::Value; -use crate::avm2::Error; use crate::string::{AvmString, WStr, WString}; use crate::stub::Stub; use ruffle_wstr::Integer; @@ -379,3 +379,134 @@ fn encode_utf8_with_exclusions<'gc>( Ok(AvmString::new_utf8(activation.context.gc_context, output).into()) } + +pub fn decode_uri<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Option>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + decode( + activation, + args, + // Characters that are reserved, sourced from as3 docs + "#$&+,/:;=?@", + "decodeURI", + ) +} + +pub fn decode_uri_component<'gc>( + activation: &mut Activation<'_, 'gc>, + _this: Option>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + decode(activation, args, "", "decodeURIComponent") +} + +fn handle_percent(chars: &mut I) -> Option +where + I: Iterator>, +{ + let high = chars.next()?.ok()?.to_digit(16)?; + let low = chars.next()?.ok()?.to_digit(16)?; + Some(low as u8 | ((high as u8) << 4)) +} + +// code derived from flash.utils.unescapeMultiByte +// FIXME: support bugzilla #538107 +fn decode<'gc>( + activation: &mut Activation<'_, 'gc>, + args: &[Value<'gc>], + reserved_set: &str, + func_name: &str, +) -> Result, Error<'gc>> { + let value = match args.first() { + None => return Ok("undefined".into()), + Some(Value::Undefined) => return Ok("null".into()), + Some(value) => value.coerce_to_string(activation)?, + }; + + let mut output = WString::new(); + let mut chars = value.chars(); + let mut bytes = Vec::with_capacity(4); + + while let Some(c) = chars.next() { + let Ok(c) = c else { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + }; + + if c != '%' { + output.push_char(c); + continue; + } + + bytes.clear(); + let Some(byte) = handle_percent(&mut chars) else { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + }; + bytes.push(byte); + if (byte & 0x80) != 0 { + let n = byte.leading_ones(); + + if n == 1 || n > 4 { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + } + + for _ in 1..n { + if chars.next() != Some(Ok('%')) { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + }; // consume % + + let Some(byte) = handle_percent(&mut chars) else { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + }; + + if (byte & 0xC0) != 0x80 { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + } + + bytes.push(byte); + } + } + + let Ok(decoded) = std::str::from_utf8(&bytes) else { + return Err(Error::AvmError(uri_error( + activation, + &format!("Error #1052: Invalid URI passed to {func_name} function."), + 1052, + )?)); + }; + if reserved_set.contains(decoded) { + for byte in &bytes { + write!(output, "%{x:02X}", x = byte).unwrap(); + } + } else { + output.push_utf8(decoded); + } + } + + Ok(AvmString::new(activation.context.gc_context, output).into()) +} diff --git a/tests/tests/swfs/avm2/decode_uri/Test.as b/tests/tests/swfs/avm2/decode_uri/Test.as new file mode 100644 index 000000000..1cce5ae07 --- /dev/null +++ b/tests/tests/swfs/avm2/decode_uri/Test.as @@ -0,0 +1,81 @@ +package { + + public class Test { + } +} + +import flash.utils.getDefinitionByName; + +var fns = ["decodeURI", "decodeURIComponent"]; +for each (var fnName in fns) { + var fn = getDefinitionByName(fnName); + trace("// " + fnName + "()"); + trace(fn()); + trace(""); + + trace("// " + fnName + "(undefined)"); + trace(fn(undefined)); + trace(""); + + trace("// typeof(" + fnName + "(undefined))"); + trace(typeof(fn(undefined))); + trace(""); + + trace("// " + fnName + "(null)"); + trace(fn(null)); + trace(""); + + var input = "test"; + trace("// " + fnName + "(\"" + input + "\")"); + trace(fn(input)); + trace(""); + + var input = "%3A"; + trace("// " + fnName + "(\"" + input + "\")"); + trace(fn(input)); + trace(""); + + var input = "%E0%A4%A"; + trace("// " + fnName + "(\"" + input + "\")"); + try { + trace(fn(input)); + } catch (e) { + trace(e); + } + trace(""); + var input = "%FFabcd"; + trace("// " + fnName + "(\"" + input + "\")"); + try { + trace(fn(input)); + } catch (e) { + trace(e); + } + trace(""); + + var src:String = String.fromCharCode(0xD842, 0xDF9F); + var input = encodeURIComponent(src); + trace("// " + fnName + "(\"" + input + "\")"); + try { + trace(fn(input)); + } catch (e) { + trace(e); + } + trace(""); + + + var input = "\x05"; + trace("// " + fnName + "(\"\\x05\")"); + trace(fn(input)); + trace(""); + + var input = "😭"; + trace("// " + fnName + "(\"" + input + "\")"); + trace(fn(input)); + trace(""); + + var input = "~!%40%23%24%25%5E%26*()_%2B%5B%5D%5C%7B%7D%7C%3B'%2C.%2F%3C%3E%3F"; + trace("// " + fnName + "(\"" + input + "\")"); + trace(fn(input)); + trace(""); + +} diff --git a/tests/tests/swfs/avm2/decode_uri/output.txt b/tests/tests/swfs/avm2/decode_uri/output.txt new file mode 100644 index 000000000..882c728c3 --- /dev/null +++ b/tests/tests/swfs/avm2/decode_uri/output.txt @@ -0,0 +1,72 @@ +// decodeURI() +undefined + +// decodeURI(undefined) +null + +// typeof(decodeURI(undefined)) +string + +// decodeURI(null) +null + +// decodeURI("test") +test + +// decodeURI("%3A") +%3A + +// decodeURI("%E0%A4%A") +URIError: Error #1052: Invalid URI passed to decodeURI function. + +// decodeURI("%FFabcd") +URIError: Error #1052: Invalid URI passed to decodeURI function. + +// decodeURI("%F0%A0%AE%9F") +𠮟 + +// decodeURI("\x05") + + +// decodeURI("😭") +😭 + +// decodeURI("~!%40%23%24%25%5E%26*()_%2B%5B%5D%5C%7B%7D%7C%3B'%2C.%2F%3C%3E%3F") +~!%40%23%24%^%26*()_%2B[]\{}|%3B'%2C.%2F<>%3F + +// decodeURIComponent() +undefined + +// decodeURIComponent(undefined) +null + +// typeof(decodeURIComponent(undefined)) +string + +// decodeURIComponent(null) +null + +// decodeURIComponent("test") +test + +// decodeURIComponent("%3A") +: + +// decodeURIComponent("%E0%A4%A") +URIError: Error #1052: Invalid URI passed to decodeURIComponent function. + +// decodeURIComponent("%FFabcd") +URIError: Error #1052: Invalid URI passed to decodeURIComponent function. + +// decodeURIComponent("%F0%A0%AE%9F") +𠮟 + +// decodeURIComponent("\x05") + + +// decodeURIComponent("😭") +😭 + +// decodeURIComponent("~!%40%23%24%25%5E%26*()_%2B%5B%5D%5C%7B%7D%7C%3B'%2C.%2F%3C%3E%3F") +~!@#$%^&*()_+[]\{}|;',./<>? + diff --git a/tests/tests/swfs/avm2/decode_uri/test.fla b/tests/tests/swfs/avm2/decode_uri/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..b27a5e649558410954a4ca6414621bf1a86235a6 GIT binary patch literal 4052 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