diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js index a250dd69b93e59..64d40f9db4e16b 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -233,7 +233,7 @@ } /** - * @param {Uint8Array} data + * @param {string} data * @returns {string} */ function forgivingBase64Encode(data) { @@ -242,7 +242,7 @@ /** * @param {string} data - * @returns {Uint8Array} + * @returns {string} */ function forgivingBase64Decode(data) { return core.opSync("op_base64_decode", data); diff --git a/ext/web/05_base64.js b/ext/web/05_base64.js index 1244ecfd5cf36b..388c0a5827ce80 100644 --- a/ext/web/05_base64.js +++ b/ext/web/05_base64.js @@ -37,12 +37,7 @@ context: "Argument 1", }); - const uint8Array = forgivingBase64Decode(data); - const result = ArrayPrototypeMap( - [...new SafeArrayIterator(uint8Array)], - (byte) => StringFromCharCode(byte), - ); - return ArrayPrototypeJoin(result, ""); + return forgivingBase64Decode(data); } /** @@ -56,20 +51,7 @@ prefix, context: "Argument 1", }); - const byteArray = ArrayPrototypeMap( - [...new SafeArrayIterator(data)], - (char) => { - const charCode = StringPrototypeCharCodeAt(char, 0); - if (charCode > 0xff) { - throw new DOMException( - "The string to be encoded contains characters outside of the Latin1 range.", - "InvalidCharacterError", - ); - } - return charCode; - }, - ); - return forgivingBase64Encode(TypedArrayFrom(Uint8Array, byteArray)); + return forgivingBase64Encode(data); } window.__bootstrap.base64 = { diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index 41d4c9b325cd22..f0b7ec12d420ef 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -43,8 +43,8 @@ declare namespace globalThis { result: string; position: number; }; - forgivingBase64Encode(data: Uint8Array): string; - forgivingBase64Decode(data: string): Uint8Array; + forgivingBase64Encode(data: string): string; + forgivingBase64Decode(data: string): string; }; declare var domException: { diff --git a/ext/web/lib.rs b/ext/web/lib.rs index b10cb972d1ef2a..64832debc5a267 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -149,8 +149,25 @@ fn op_base64_decode( _state: &mut OpState, input: String, _: (), -) -> Result { - let mut input: &str = &input.replace(|c| char::is_ascii_whitespace(&c), ""); +) -> Result { + let mut ws = false; + let len = input.len(); + let invalid = input.char_indices().any(|(i, c)| { + if char::is_ascii_whitespace(&c) { + ws = true; + } else if c == '=' { + if i != len - 1 && i != len - 2 { + return true; + } + } else if c != '+' && c != '/' && !c.is_alphanumeric() { + return true; + } + false + }); + + // "Remove all ASCII whitespace from data" + let input = if ws { input.replace(|c| char::is_ascii_whitespace(&c), "") } else { input }; + let mut input: &str = &input; // "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." @@ -170,10 +187,8 @@ fn op_base64_decode( ); } - if input - .chars() - .any(|c| c != '+' && c != '/' && !c.is_alphanumeric()) - { + // "If data contains a code point that is not '+', '/', or ASCII alphanumeric then return failure." + if invalid { return Err( DomExceptionInvalidCharacterError::new( "Failed to decode base64: invalid character", @@ -190,14 +205,21 @@ fn op_base64_decode( err )) })?; - Ok(ZeroCopyBuf::from(out)) + Ok(String::from_utf8(out).unwrap()) } fn op_base64_encode( _state: &mut OpState, - s: ZeroCopyBuf, + s: String, _: (), ) -> Result { + let char_count = s.chars().count(); + let s = s.into_bytes(); + if s.len() != char_count { + // If the byte vec length is longer than the string length, it means that some characters were not respresentable as a single + // byte, ie. some characters were not Latin1. + return Err(DomExceptionInvalidCharacterError::new("The string to be encoded contains characters outside of the Latin1 range.").into()); + } let cfg = base64::Config::new(base64::CharacterSet::Standard, true) .decode_allow_trailing_bits(true); let out = base64::encode_config(&s, cfg);