avm2: implement decodeURI

This commit is contained in:
AllinolCP 2023-03-25 16:24:09 +07:00 committed by Mike Welsh
parent 5a18a409f7
commit f0a8e50be1
8 changed files with 308 additions and 1 deletions

View File

@ -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<Value<'gc>, Error<'gc>> {
let class = activation.avm2().classes().urierror;
error_constructor(activation, class, message, code)
}
#[inline(never)]
#[cold]
pub fn error<'gc>(

View File

@ -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),

View File

@ -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<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
decode(activation, args, "", "decodeURIComponent")
}
fn handle_percent<I>(chars: &mut I) -> Option<u8>
where
I: Iterator<Item = Result<char, std::char::DecodeUtf16Error>>,
{
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<Value<'gc>, 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())
}

View File

@ -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("");
}

View File

@ -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")
~!@#$%^&*()_+[]\{}|;',./<>?

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1