summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/fs/in_memory_fs.rs32
-rw-r--r--ext/fs/interface.rs67
-rw-r--r--ext/fs/lib.rs34
-rw-r--r--ext/fs/ops.rs153
-rw-r--r--ext/fs/std_fs.rs134
-rw-r--r--ext/io/fs.rs15
-rw-r--r--ext/node/ops/require.rs2
-rw-r--r--ext/node/package_json.rs2
8 files changed, 330 insertions, 109 deletions
diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs
index fdd0ad7e7..153327ff6 100644
--- a/ext/fs/in_memory_fs.rs
+++ b/ext/fs/in_memory_fs.rs
@@ -19,6 +19,7 @@ use deno_io::fs::FsError;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;
+use crate::interface::AccessCheckCb;
use crate::interface::FsDirEntry;
use crate::interface::FsFileType;
use crate::FileSystem;
@@ -48,6 +49,7 @@ impl InMemoryFs {
.write_file_sync(
&path,
OpenOptions::write(true, false, false, None),
+ None,
&text.into_bytes(),
)
.unwrap();
@@ -82,15 +84,17 @@ impl FileSystem for InMemoryFs {
&self,
_path: &Path,
_options: OpenOptions,
+ _access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn File>> {
Err(FsError::NotSupported)
}
- async fn open_async(
- &self,
+ async fn open_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<Rc<dyn File>> {
- self.open_sync(&path, options)
+ self.open_sync(&path, options, access_check)
}
fn mkdir_sync(
@@ -350,6 +354,7 @@ impl FileSystem for InMemoryFs {
&self,
path: &Path,
options: OpenOptions,
+ _access_check: Option<AccessCheckCb>,
data: &[u8],
) -> FsResult<()> {
let path = normalize_path(path);
@@ -397,16 +402,21 @@ impl FileSystem for InMemoryFs {
}
}
- async fn write_file_async(
- &self,
+ async fn write_file_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
data: Vec<u8>,
) -> FsResult<()> {
- self.write_file_sync(&path, options, &data)
+ self.write_file_sync(&path, options, access_check, &data)
}
- fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
+ fn read_file_sync(
+ &self,
+ path: &Path,
+ _access_check: Option<AccessCheckCb>,
+ ) -> FsResult<Vec<u8>> {
let entry = self.get_entry(path);
match entry {
Some(entry) => match &*entry {
@@ -419,7 +429,11 @@ impl FileSystem for InMemoryFs {
None => Err(FsError::Io(Error::new(ErrorKind::NotFound, "Not found"))),
}
}
- async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
- self.read_file_sync(&path)
+ async fn read_file_async<'a>(
+ &'a self,
+ path: PathBuf,
+ access_check: Option<AccessCheckCb<'a>>,
+ ) -> FsResult<Vec<u8>> {
+ self.read_file_sync(&path, access_check)
}
}
diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs
index c5a348eb1..70f9fdf63 100644
--- a/ext/fs/interface.rs
+++ b/ext/fs/interface.rs
@@ -80,6 +80,25 @@ pub struct FsDirEntry {
#[allow(clippy::disallowed_types)]
pub type FileSystemRc = crate::sync::MaybeArc<dyn FileSystem>;
+pub trait AccessCheckFn:
+ for<'a> FnMut(
+ bool,
+ &'a Path,
+ &'a OpenOptions,
+) -> FsResult<std::borrow::Cow<'a, Path>>
+{
+}
+impl<T> AccessCheckFn for T where
+ T: for<'a> FnMut(
+ bool,
+ &'a Path,
+ &'a OpenOptions,
+ ) -> FsResult<std::borrow::Cow<'a, Path>>
+{
+}
+
+pub type AccessCheckCb<'a> = &'a mut (dyn AccessCheckFn + 'a);
+
#[async_trait::async_trait(?Send)]
pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
fn cwd(&self) -> FsResult<PathBuf>;
@@ -91,11 +110,13 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
&self,
path: &Path,
options: OpenOptions,
+ access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn File>>;
- async fn open_async(
- &self,
+ async fn open_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<Rc<dyn File>>;
fn mkdir_sync(&self, path: &Path, recursive: bool, mode: u32)
@@ -202,22 +223,24 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
&self,
path: &Path,
options: OpenOptions,
+ access_check: Option<AccessCheckCb>,
data: &[u8],
) -> FsResult<()> {
- let file = self.open_sync(path, options)?;
+ let file = self.open_sync(path, options, access_check)?;
if let Some(mode) = options.mode {
file.clone().chmod_sync(mode)?;
}
file.write_all_sync(data)?;
Ok(())
}
- async fn write_file_async(
- &self,
+ async fn write_file_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
data: Vec<u8>,
) -> FsResult<()> {
- let file = self.open_async(path, options).await?;
+ let file = self.open_async(path, options, access_check).await?;
if let Some(mode) = options.mode {
file.clone().chmod_async(mode).await?;
}
@@ -225,15 +248,23 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
Ok(())
}
- fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
+ fn read_file_sync(
+ &self,
+ path: &Path,
+ access_check: Option<AccessCheckCb>,
+ ) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
- let file = self.open_sync(path, options)?;
+ let file = self.open_sync(path, options, access_check)?;
let buf = file.read_all_sync()?;
Ok(buf)
}
- async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
+ async fn read_file_async<'a>(
+ &'a self,
+ path: PathBuf,
+ access_check: Option<AccessCheckCb<'a>>,
+ ) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
- let file = self.open_async(path, options).await?;
+ let file = self.open_async(path, options, access_check).await?;
let buf = file.read_all_async().await?;
Ok(buf)
}
@@ -253,14 +284,22 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
self.stat_sync(path).is_ok()
}
- fn read_text_file_sync(&self, path: &Path) -> FsResult<String> {
- let buf = self.read_file_sync(path)?;
+ fn read_text_file_sync(
+ &self,
+ path: &Path,
+ access_check: Option<AccessCheckCb>,
+ ) -> FsResult<String> {
+ let buf = self.read_file_sync(path, access_check)?;
String::from_utf8(buf).map_err(|err| {
std::io::Error::new(std::io::ErrorKind::InvalidData, err).into()
})
}
- async fn read_text_file_async(&self, path: PathBuf) -> FsResult<String> {
- let buf = self.read_file_async(path).await?;
+ async fn read_text_file_async<'a>(
+ &'a self,
+ path: PathBuf,
+ access_check: Option<AccessCheckCb<'a>>,
+ ) -> FsResult<String> {
+ let buf = self.read_file_async(path, access_check).await?;
String::from_utf8(buf).map_err(|err| {
std::io::Error::new(std::io::ErrorKind::InvalidData, err).into()
})
diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs
index 05b119e2e..d4e79b75f 100644
--- a/ext/fs/lib.rs
+++ b/ext/fs/lib.rs
@@ -7,6 +7,8 @@ mod std_fs;
pub mod sync;
pub use crate::in_memory_fs::InMemoryFs;
+pub use crate::interface::AccessCheckCb;
+pub use crate::interface::AccessCheckFn;
pub use crate::interface::FileSystem;
pub use crate::interface::FileSystemRc;
pub use crate::interface::FsDirEntry;
@@ -20,9 +22,18 @@ use crate::ops::*;
use deno_core::error::AnyError;
use deno_core::OpState;
+use deno_io::fs::FsError;
use std::path::Path;
-pub trait FsPermissions {
+pub trait FsPermissions: Send + Sync {
+ fn check_open<'a>(
+ &mut self,
+ resolved: bool,
+ read: bool,
+ write: bool,
+ path: &'a Path,
+ api_name: &str,
+ ) -> Result<std::borrow::Cow<'a, Path>, FsError>;
fn check_read(&mut self, path: &Path, api_name: &str)
-> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
@@ -50,19 +61,20 @@ pub trait FsPermissions {
api_name: &str,
) -> Result<(), AnyError>;
- fn check(
+ fn check<'a>(
&mut self,
+ resolved: bool,
open_options: &OpenOptions,
- path: &Path,
+ path: &'a Path,
api_name: &str,
- ) -> Result<(), AnyError> {
- if open_options.read {
- self.check_read(path, api_name)?;
- }
- if open_options.write || open_options.append {
- self.check_write(path, api_name)?;
- }
- Ok(())
+ ) -> Result<std::borrow::Cow<'a, Path>, FsError> {
+ self.check_open(
+ resolved,
+ open_options.read,
+ open_options.write || open_options.append,
+ path,
+ api_name,
+ )
}
}
diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs
index d688f619e..3ff32bd8f 100644
--- a/ext/fs/ops.rs
+++ b/ext/fs/ops.rs
@@ -28,12 +28,55 @@ use rand::Rng;
use serde::Serialize;
use crate::check_unstable;
+use crate::interface::AccessCheckFn;
use crate::interface::FileSystemRc;
use crate::interface::FsDirEntry;
use crate::interface::FsFileType;
use crate::FsPermissions;
use crate::OpenOptions;
+fn sync_permission_check<'a, P: FsPermissions + 'static>(
+ permissions: &'a mut P,
+ api_name: &'static str,
+) -> impl AccessCheckFn + 'a {
+ move |resolved, path, options| {
+ permissions.check(resolved, options, path, api_name)
+ }
+}
+
+fn async_permission_check<P: FsPermissions + 'static>(
+ state: Rc<RefCell<OpState>>,
+ api_name: &'static str,
+) -> impl AccessCheckFn {
+ move |resolved, path, options| {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check(resolved, options, path, api_name)
+ }
+}
+
+fn map_permission_error(
+ operation: &'static str,
+ error: FsError,
+ path: &Path,
+) -> AnyError {
+ match error {
+ FsError::PermissionDenied(err) => {
+ let path = format!("{path:?}");
+ let (path, truncated) = if path.len() > 1024 {
+ (&path[0..1024], "...(truncated)")
+ } else {
+ (path.as_str(), "")
+ };
+ custom_error("PermissionDenied", format!("Requires {err} access to {path}{truncated}, run again with the --allow-{err} flag"))
+ }
+ err => Err::<(), _>(err)
+ .context_path(operation, path)
+ .err()
+ .unwrap(),
+ }
+}
+
#[op2]
#[string]
pub fn op_fs_cwd<P>(state: &mut OpState) -> Result<String, AnyError>
@@ -89,12 +132,14 @@ where
let path = PathBuf::from(path);
let options = options.unwrap_or_else(OpenOptions::read);
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.openSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let file = fs.open_sync(&path, options).context_path("open", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.openSync()");
+ let file = fs
+ .open_sync(&path, options, Some(&mut access_check))
+ .map_err(|error| map_permission_error("open", error, &path))?;
+ drop(access_check);
let rid = state
.resource_table
.add(FileResource::new(file, "fsFile".to_string()));
@@ -114,16 +159,13 @@ where
let path = PathBuf::from(path);
let options = options.unwrap_or_else(OpenOptions::read);
- let fs = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.open()")?;
- state.borrow::<FileSystemRc>().clone()
- };
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.open()");
+ let fs = state.borrow().borrow::<FileSystemRc>().clone();
let file = fs
- .open_async(path.clone(), options)
+ .open_async(path.clone(), options, Some(&mut access_check))
.await
- .context_path("open", &path)?;
+ .map_err(|error| map_permission_error("open", error, &path))?;
let rid = state
.borrow_mut()
@@ -961,11 +1003,10 @@ where
};
let mut rng = thread_rng();
-
const MAX_TRIES: u32 = 10;
for _ in 0..MAX_TRIES {
let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
- match fs.open_sync(&path, open_opts) {
+ match fs.open_sync(&path, open_opts, None) {
Ok(_) => return path_into_string(path.into_os_string()),
Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
continue;
@@ -1007,7 +1048,7 @@ where
const MAX_TRIES: u32 = 10;
for _ in 0..MAX_TRIES {
let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
- match fs.clone().open_async(path.clone(), open_opts).await {
+ match fs.clone().open_async(path.clone(), open_opts, None).await {
Ok(_) => return path_into_string(path.into_os_string()),
Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
continue;
@@ -1150,14 +1191,13 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
let options = OpenOptions::write(create, append, create_new, mode);
- permissions.check(&options, &path, "Deno.writeFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.writeFileSync()");
- fs.write_file_sync(&path, options, &data)
- .context_path("writefile", &path)?;
+ fs.write_file_sync(&path, options, Some(&mut access_check), &data)
+ .map_err(|error| map_permission_error("writefile", error, &path))?;
Ok(())
}
@@ -1181,16 +1221,21 @@ where
let options = OpenOptions::write(create, append, create_new, mode);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.writeFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check(&options, &path, "Deno.writeFile()")?;
+ let state = state.borrow_mut();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.write_file_async(path.clone(), options, data.to_vec());
+ let fut = fs.write_file_async(
+ path.clone(),
+ options,
+ Some(&mut access_check),
+ data.to_vec(),
+ );
if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1201,9 +1246,11 @@ where
}
};
- res?.context_path("writefile", &path)?;
+ res?.map_err(|error| map_permission_error("writefile", error, &path))?;
} else {
- fut.await.context_path("writefile", &path)?;
+ fut
+ .await
+ .map_err(|error| map_permission_error("writefile", error, &path))?;
}
Ok(())
@@ -1220,11 +1267,12 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let buf = fs.read_file_sync(&path).context_path("readfile", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()");
+ let buf = fs
+ .read_file_sync(&path, Some(&mut access_check))
+ .map_err(|error| map_permission_error("readfile", error, &path))?;
Ok(buf.into())
}
@@ -1241,16 +1289,16 @@ where
{
let path = PathBuf::from(path);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.readFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFile()")?;
+ let state = state.borrow();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.read_file_async(path.clone());
+ let fut = fs.read_file_async(path.clone(), Some(&mut access_check));
let buf = if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1261,9 +1309,11 @@ where
}
};
- res?.context_path("readfile", &path)?
+ res?.map_err(|error| map_permission_error("readfile", error, &path))?
} else {
- fut.await.context_path("readfile", &path)?
+ fut
+ .await
+ .map_err(|error| map_permission_error("readfile", error, &path))?
};
Ok(buf.into())
@@ -1280,11 +1330,12 @@ where
{
let path = PathBuf::from(path);
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFileSync()")?;
-
- let fs = state.borrow::<FileSystemRc>();
- let buf = fs.read_file_sync(&path).context_path("readfile", &path)?;
+ let fs = state.borrow::<FileSystemRc>().clone();
+ let mut access_check =
+ sync_permission_check::<P>(state.borrow_mut(), "Deno.readFileSync()");
+ let buf = fs
+ .read_file_sync(&path, Some(&mut access_check))
+ .map_err(|error| map_permission_error("readfile", error, &path))?;
Ok(string_from_utf8_lossy(buf))
}
@@ -1301,16 +1352,16 @@ where
{
let path = PathBuf::from(path);
+ let mut access_check =
+ async_permission_check::<P>(state.clone(), "Deno.readFile()");
let (fs, cancel_handle) = {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.readFile()")?;
+ let state = state.borrow_mut();
let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<FileSystemRc>().clone(), cancel_handle)
};
- let fut = fs.read_file_async(path.clone());
+ let fut = fs.read_file_async(path.clone(), Some(&mut access_check));
let buf = if let Some(cancel_handle) = cancel_handle {
let res = fut.or_cancel(cancel_handle).await;
@@ -1321,9 +1372,11 @@ where
}
};
- res?.context_path("readfile", &path)?
+ res?.map_err(|error| map_permission_error("readfile", error, &path))?
} else {
- fut.await.context_path("readfile", &path)?
+ fut
+ .await
+ .map_err(|error| map_permission_error("readfile", error, &path))?
};
Ok(string_from_utf8_lossy(buf))
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs
index 332866e45..1176697de 100644
--- a/ext/fs/std_fs.rs
+++ b/ext/fs/std_fs.rs
@@ -2,27 +2,29 @@
#![allow(clippy::disallowed_methods)]
+use std::env::current_dir;
use std::fs;
use std::io;
+use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
+use deno_core::normalize_path;
use deno_core::unsync::spawn_blocking;
use deno_io::fs::File;
+use deno_io::fs::FsError;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;
use deno_io::StdFileResourceInner;
+use crate::interface::AccessCheckCb;
use crate::interface::FsDirEntry;
use crate::interface::FsFileType;
use crate::FileSystem;
use crate::OpenOptions;
-#[cfg(not(unix))]
-use deno_io::fs::FsError;
-
#[derive(Debug, Clone)]
pub struct RealFs;
@@ -80,18 +82,18 @@ impl FileSystem for RealFs {
&self,
path: &Path,
options: OpenOptions,
+ access_check: Option<AccessCheckCb>,
) -> FsResult<Rc<dyn File>> {
- let opts = open_options(options);
- let std_file = opts.open(path)?;
+ let std_file = open_with_access_check(options, path, access_check)?;
Ok(Rc::new(StdFileResourceInner::file(std_file)))
}
- async fn open_async(
- &self,
+ async fn open_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
) -> FsResult<Rc<dyn File>> {
- let opts = open_options(options);
- let std_file = spawn_blocking(move || opts.open(path)).await??;
+ let std_file = open_with_access_check(options, &path, access_check)?;
Ok(Rc::new(StdFileResourceInner::file(std_file)))
}
@@ -276,10 +278,10 @@ impl FileSystem for RealFs {
&self,
path: &Path,
options: OpenOptions,
+ access_check: Option<AccessCheckCb>,
data: &[u8],
) -> FsResult<()> {
- let opts = open_options(options);
- let mut file = opts.open(path)?;
+ let mut file = open_with_access_check(options, path, access_check)?;
#[cfg(unix)]
if let Some(mode) = options.mode {
use std::os::unix::fs::PermissionsExt;
@@ -289,15 +291,15 @@ impl FileSystem for RealFs {
Ok(())
}
- async fn write_file_async(
- &self,
+ async fn write_file_async<'a>(
+ &'a self,
path: PathBuf,
options: OpenOptions,
+ access_check: Option<AccessCheckCb<'a>>,
data: Vec<u8>,
) -> FsResult<()> {
+ let mut file = open_with_access_check(options, &path, access_check)?;
spawn_blocking(move || {
- let opts = open_options(options);
- let mut file = opts.open(path)?;
#[cfg(unix)]
if let Some(mode) = options.mode {
use std::os::unix::fs::PermissionsExt;
@@ -309,13 +311,43 @@ impl FileSystem for RealFs {
.await?
}
- fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
- fs::read(path).map_err(Into::into)
- }
- async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
- spawn_blocking(move || fs::read(path))
- .await?
- .map_err(Into::into)
+ fn read_file_sync(
+ &self,
+ path: &Path,
+ access_check: Option<AccessCheckCb>,
+ ) -> FsResult<Vec<u8>> {
+ let mut file = open_with_access_check(
+ OpenOptions {
+ read: true,
+ ..Default::default()
+ },
+ path,
+ access_check,
+ )?;
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok(buf)
+ }
+ async fn read_file_async<'a>(
+ &'a self,
+ path: PathBuf,
+ access_check: Option<AccessCheckCb<'a>>,
+ ) -> FsResult<Vec<u8>> {
+ let mut file = open_with_access_check(
+ OpenOptions {
+ read: true,
+ ..Default::default()
+ },
+ &path,
+ access_check,
+ )?;
+ spawn_blocking(move || {
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok::<_, FsError>(buf)
+ })
+ .await?
+ .map_err(Into::into)
}
}
@@ -410,7 +442,6 @@ fn copy_file(from: &Path, to: &Path) -> FsResult<()> {
use libc::stat;
use libc::unlink;
use std::ffi::CString;
- use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
@@ -845,3 +876,60 @@ fn open_options(options: OpenOptions) -> fs::OpenOptions {
open_options.create_new(options.create_new);
open_options
}
+
+#[inline(always)]
+fn open_with_access_check(
+ options: OpenOptions,
+ path: &Path,
+ access_check: Option<AccessCheckCb>,
+) -> FsResult<std::fs::File> {
+ if let Some(access_check) = access_check {
+ let path = if path.is_absolute() {
+ normalize_path(path)
+ } else {
+ let cwd = current_dir()?;
+ normalize_path(cwd.join(path))
+ };
+ (*access_check)(false, &path, &options)?;
+ // On Linux, /proc may contain magic links that we don't want to resolve
+ let needs_canonicalization =
+ !cfg!(target_os = "linux") || path.starts_with("/proc");
+ let path = if needs_canonicalization {
+ match path.canonicalize() {
+ Ok(path) => path,
+ Err(_) => {
+ if let (Some(parent), Some(filename)) =
+ (path.parent(), path.file_name())
+ {
+ parent.canonicalize()?.join(filename)
+ } else {
+ return Err(std::io::ErrorKind::NotFound.into());
+ }
+ }
+ }
+ } else {
+ path
+ };
+
+ (*access_check)(true, &path, &options)?;
+
+ // For windows
+ #[allow(unused_mut)]
+ let mut opts: fs::OpenOptions = open_options(options);
+ #[cfg(unix)]
+ {
+ // Don't follow symlinks on open -- we must always pass fully-resolved files
+ // with the exception of /proc/ which is too special, and /dev/std* which might point to
+ // proc.
+ use std::os::unix::fs::OpenOptionsExt;
+ if needs_canonicalization {
+ opts.custom_flags(libc::O_NOFOLLOW);
+ }
+ }
+
+ Ok(opts.open(&path)?)
+ } else {
+ let opts = open_options(options);
+ Ok(opts.open(path)?)
+ }
+}
diff --git a/ext/io/fs.rs b/ext/io/fs.rs
index d8f393556..88e4eee47 100644
--- a/ext/io/fs.rs
+++ b/ext/io/fs.rs
@@ -6,6 +6,7 @@ use std::rc::Rc;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
+use deno_core::error::custom_error;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
@@ -21,6 +22,7 @@ pub enum FsError {
Io(io::Error),
FileBusy,
NotSupported,
+ PermissionDenied(&'static str),
}
impl FsError {
@@ -29,6 +31,7 @@ impl FsError {
Self::Io(err) => err.kind(),
Self::FileBusy => io::ErrorKind::Other,
Self::NotSupported => io::ErrorKind::Other,
+ Self::PermissionDenied(_) => io::ErrorKind::PermissionDenied,
}
}
@@ -37,6 +40,9 @@ impl FsError {
FsError::Io(err) => err,
FsError::FileBusy => io::Error::new(self.kind(), "file busy"),
FsError::NotSupported => io::Error::new(self.kind(), "not supported"),
+ FsError::PermissionDenied(err) => {
+ io::Error::new(self.kind(), format!("requires {err} access"))
+ }
}
}
}
@@ -47,12 +53,21 @@ impl From<io::Error> for FsError {
}
}
+impl From<io::ErrorKind> for FsError {
+ fn from(err: io::ErrorKind) -> Self {
+ Self::Io(err.into())
+ }
+}
+
impl From<FsError> for AnyError {
fn from(err: FsError) -> Self {
match err {
FsError::Io(err) => AnyError::from(err),
FsError::FileBusy => resource_unavailable(),
FsError::NotSupported => not_supported(),
+ FsError::PermissionDenied(err) => {
+ custom_error("PermissionDenied", format!("permission denied: {err}"))
+ }
}
}
}
diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs
index 426c41995..176d64e56 100644
--- a/ext/node/ops/require.rs
+++ b/ext/node/ops/require.rs
@@ -452,7 +452,7 @@ where
let file_path = PathBuf::from(file_path);
ensure_read_permission::<P>(state, &file_path)?;
let fs = state.borrow::<FileSystemRc>();
- Ok(fs.read_text_file_sync(&file_path)?)
+ Ok(fs.read_text_file_sync(&file_path, None)?)
}
#[op2]
diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs
index 77352ae1d..d4ffc80d6 100644
--- a/ext/node/package_json.rs
+++ b/ext/node/package_json.rs
@@ -82,7 +82,7 @@ impl PackageJson {
return Ok(CACHE.with(|cache| cache.borrow()[&path].clone()));
}
- let source = match fs.read_text_file_sync(&path) {
+ let source = match fs.read_text_file_sync(&path, None) {
Ok(source) => source,
Err(err) if err.kind() == ErrorKind::NotFound => {
return Ok(Rc::new(PackageJson::empty(path)));