diff options
-rw-r--r-- | cli/tests/unit/files_test.ts | 16 | ||||
-rw-r--r-- | runtime/ops/fs.rs | 16 |
2 files changed, 30 insertions, 2 deletions
diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts index a509672c0..d15f1f538 100644 --- a/cli/tests/unit/files_test.ts +++ b/cli/tests/unit/files_test.ts @@ -791,3 +791,19 @@ Deno.test( assertEquals(res, "hello world!"); }, ); + +Deno.test( + { permissions: { read: true, write: true } }, + async function readTextFileNonUtf8() { + const path = await Deno.makeTempFile(); + const file = await Deno.open(path, { write: true }); + await file.write(new TextEncoder().encode("hello ")); + await file.write(new Uint8Array([0xC0])); + file.close(); + + const res = await Deno.readTextFile(path); + const resSync = Deno.readTextFileSync(path); + assertEquals(res, resSync); + assertEquals(res, "hello \uFFFD"); + }, +); diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index 501c2ac5f..99b37916d 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -21,6 +21,7 @@ use deno_crypto::rand::Rng; use log::debug; use serde::Deserialize; use serde::Serialize; +use std::borrow::Cow; use std::cell::RefCell; use std::convert::From; use std::env::{current_dir, set_current_dir, temp_dir}; @@ -2032,7 +2033,7 @@ fn op_readfile_text_sync( let permissions = state.borrow_mut::<Permissions>(); let path = Path::new(&path); permissions.read.check(path)?; - Ok(std::fs::read_to_string(path)?) + Ok(string_from_utf8_lossy(std::fs::read(path)?)) } #[op] @@ -2075,7 +2076,7 @@ async fn op_readfile_text_async( } let fut = tokio::task::spawn_blocking(move || { let path = Path::new(&path); - Ok(String::from_utf8(std::fs::read(path)?)?) + Ok(string_from_utf8_lossy(std::fs::read(path)?)) }); if let Some(cancel_rid) = cancel_rid { let cancel_handle = state @@ -2088,3 +2089,14 @@ async fn op_readfile_text_async( } fut.await? } + +// Like String::from_utf8_lossy but operates on owned values +fn string_from_utf8_lossy(buf: Vec<u8>) -> String { + match String::from_utf8_lossy(&buf) { + // buf contained non-utf8 chars than have been patched + Cow::Owned(s) => s, + // SAFETY: if Borrowed then the buf only contains utf8 chars, + // we do this instead of .into_owned() to avoid copying the input buf + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) }, + } +} |