diff options
author | Aaron O'Mullan <aaron.omullan@gmail.com> | 2022-03-05 20:12:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-05 20:12:30 +0100 |
commit | 72d593fc5c662934ddce1ee339bfc00b68b7c4b4 (patch) | |
tree | cbd5ab84761f5a482ffd69a0660c7c36a3f46f12 /ext/web/lib.rs | |
parent | 96dc7421ae1dccb90e0645b665f1f7df75f41fe4 (diff) |
perf(ext/web): optimize atob/btoa (#13841)
Follow up to #13839, optimizing `base64_roundtrip` ~20x (~125ms => ~6.5ms)
Diffstat (limited to 'ext/web/lib.rs')
-rw-r--r-- | ext/web/lib.rs | 83 |
1 files changed, 56 insertions, 27 deletions
diff --git a/ext/web/lib.rs b/ext/web/lib.rs index b10cb972d..b8f948159 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -12,6 +12,7 @@ use deno_core::include_js_files; use deno_core::op_async; use deno_core::op_sync; use deno_core::url::Url; +use deno_core::ByteString; use deno_core::Extension; use deno_core::OpState; use deno_core::Resource; @@ -85,6 +86,8 @@ pub fn init<P: TimersPermission + 'static>( .ops(vec![ ("op_base64_decode", op_sync(op_base64_decode)), ("op_base64_encode", op_sync(op_base64_encode)), + ("op_base64_atob", op_sync(op_base64_atob)), + ("op_base64_btoa", op_sync(op_base64_btoa)), ( "op_encoding_normalize_label", op_sync(op_encoding_normalize_label), @@ -146,21 +149,42 @@ pub fn init<P: TimersPermission + 'static>( } fn op_base64_decode( - _state: &mut OpState, + _: &mut OpState, input: String, _: (), ) -> Result<ZeroCopyBuf, AnyError> { - let mut input: &str = &input.replace(|c| char::is_ascii_whitespace(&c), ""); + let mut input = input.into_bytes(); + input.retain(|c| !c.is_ascii_whitespace()); + Ok(b64_decode(&input)?.into()) +} + +fn op_base64_atob( + _: &mut OpState, + s: ByteString, + _: (), +) -> Result<ByteString, AnyError> { + let mut s = s.0; + s.retain(|c| !c.is_ascii_whitespace()); + + // If padding is expected, fail if not 4-byte aligned + if s.len() % 4 != 0 && (s.ends_with(b"==") || s.ends_with(b"=")) { + return Err( + DomExceptionInvalidCharacterError::new("Failed to decode base64.").into(), + ); + } + + Ok(ByteString(b64_decode(&s)?)) +} + +fn b64_decode(input: &[u8]) -> Result<Vec<u8>, AnyError> { // "If the length of input divides by 4 leaving no remainder, then: // if input ends with one or two U+003D EQUALS SIGN (=) characters, // remove them from input." - if input.len() % 4 == 0 { - if input.ends_with("==") { - input = &input[..input.len() - 2] - } else if input.ends_with('=') { - input = &input[..input.len() - 1] - } - } + let input = match input.len() % 4 == 0 { + true if input.ends_with(b"==") => &input[..input.len() - 2], + true if input.ends_with(b"=") => &input[..input.len() - 1], + _ => input, + }; // "If the length of input divides by 4 leaving a remainder of 1, // throw an InvalidCharacterError exception and abort these steps." @@ -170,38 +194,43 @@ fn op_base64_decode( ); } - if input - .chars() - .any(|c| c != '+' && c != '/' && !c.is_alphanumeric()) - { - return Err( + let cfg = base64::Config::new(base64::CharacterSet::Standard, true) + .decode_allow_trailing_bits(true); + let out = base64::decode_config(input, cfg).map_err(|err| match err { + base64::DecodeError::InvalidByte(_, _) => { DomExceptionInvalidCharacterError::new( "Failed to decode base64: invalid character", ) - .into(), - ); - } - - let cfg = base64::Config::new(base64::CharacterSet::Standard, true) - .decode_allow_trailing_bits(true); - let out = base64::decode_config(&input, cfg).map_err(|err| { - DomExceptionInvalidCharacterError::new(&format!( + } + _ => DomExceptionInvalidCharacterError::new(&format!( "Failed to decode base64: {:?}", err - )) + )), })?; - Ok(ZeroCopyBuf::from(out)) + + Ok(out) } fn op_base64_encode( - _state: &mut OpState, + _: &mut OpState, s: ZeroCopyBuf, _: (), ) -> Result<String, AnyError> { + Ok(b64_encode(&s)) +} + +fn op_base64_btoa( + _: &mut OpState, + s: ByteString, + _: (), +) -> Result<String, AnyError> { + Ok(b64_encode(&s)) +} + +fn b64_encode(s: impl AsRef<[u8]>) -> String { let cfg = base64::Config::new(base64::CharacterSet::Standard, true) .decode_allow_trailing_bits(true); - let out = base64::encode_config(&s, cfg); - Ok(out) + base64::encode_config(s.as_ref(), cfg) } #[derive(Deserialize)] |