1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

fix(compile): inline symlinks as files outside node_modules dir and warn for directories (#19285)

If a symlink within the `node_modules` directory lies outside that
directory, it will now warn and inline the file. For directories, it
will just warn for now.

Probably fixes #19251 (I'm still unable to reproduce).
This commit is contained in:
David Sherret 2023-05-27 10:33:15 -04:00 committed by GitHub
parent be59e93220
commit a96844118c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 229 additions and 39 deletions

View file

@ -24,9 +24,19 @@ use deno_runtime::deno_io::fs::FsResult;
use deno_runtime::deno_io::fs::FsStat; use deno_runtime::deno_io::fs::FsStat;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use thiserror::Error;
use crate::util; use crate::util;
#[derive(Error, Debug)]
#[error(
"Failed to strip prefix '{}' from '{}'", root_path.display(), target.display()
)]
pub struct StripRootError {
root_path: PathBuf,
target: PathBuf,
}
pub struct VfsBuilder { pub struct VfsBuilder {
root_path: PathBuf, root_path: PathBuf,
root_dir: VirtualDirectory, root_dir: VirtualDirectory,
@ -37,6 +47,7 @@ pub struct VfsBuilder {
impl VfsBuilder { impl VfsBuilder {
pub fn new(root_path: PathBuf) -> Self { pub fn new(root_path: PathBuf) -> Self {
log::debug!("Building vfs with root '{}'", root_path.display());
Self { Self {
root_dir: VirtualDirectory { root_dir: VirtualDirectory {
name: root_path name: root_path
@ -58,7 +69,7 @@ impl VfsBuilder {
} }
pub fn add_dir_recursive(&mut self, path: &Path) -> Result<(), AnyError> { pub fn add_dir_recursive(&mut self, path: &Path) -> Result<(), AnyError> {
self.add_dir(path); self.add_dir(path)?;
let read_dir = std::fs::read_dir(path) let read_dir = std::fs::read_dir(path)
.with_context(|| format!("Reading {}", path.display()))?; .with_context(|| format!("Reading {}", path.display()))?;
@ -72,19 +83,44 @@ impl VfsBuilder {
} else if file_type.is_file() { } else if file_type.is_file() {
let file_bytes = std::fs::read(&path) let file_bytes = std::fs::read(&path)
.with_context(|| format!("Reading {}", path.display()))?; .with_context(|| format!("Reading {}", path.display()))?;
self.add_file(&path, file_bytes); self.add_file(&path, file_bytes)?;
} else if file_type.is_symlink() { } else if file_type.is_symlink() {
let target = std::fs::read_link(&path) let target = util::fs::canonicalize_path(&path)
.with_context(|| format!("Reading symlink {}", path.display()))?; .with_context(|| format!("Reading symlink {}", path.display()))?;
self.add_symlink(&path, &target); if let Err(StripRootError { .. }) = self.add_symlink(&path, &target) {
if target.is_file() {
// this may change behavior, so warn the user about it
log::warn!(
"Symlink target is outside '{}'. Inlining symlink at '{}' to '{}' as file.",
self.root_path.display(),
path.display(),
target.display(),
);
// inline the symlink and make the target file
let file_bytes = std::fs::read(&target)
.with_context(|| format!("Reading {}", path.display()))?;
self.add_file(&path, file_bytes)?;
} else {
log::warn!(
"Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'.",
self.root_path.display(),
path.display(),
target.display(),
);
}
}
} }
} }
Ok(()) Ok(())
} }
pub fn add_dir(&mut self, path: &Path) -> &mut VirtualDirectory { pub fn add_dir(
let path = self.path_relative_root(path); &mut self,
path: &Path,
) -> Result<&mut VirtualDirectory, StripRootError> {
log::debug!("Ensuring directory '{}'", path.display());
let path = self.path_relative_root(path)?;
let mut current_dir = &mut self.root_dir; let mut current_dir = &mut self.root_dir;
for component in path.components() { for component in path.components() {
@ -113,10 +149,15 @@ impl VfsBuilder {
}; };
} }
current_dir Ok(current_dir)
} }
pub fn add_file(&mut self, path: &Path, data: Vec<u8>) { pub fn add_file(
&mut self,
path: &Path,
data: Vec<u8>,
) -> Result<(), AnyError> {
log::debug!("Adding file '{}'", path.display());
let checksum = util::checksum::gen(&[&data]); let checksum = util::checksum::gen(&[&data]);
let offset = if let Some(offset) = self.file_offsets.get(&checksum) { let offset = if let Some(offset) = self.file_offsets.get(&checksum) {
// duplicate file, reuse an old offset // duplicate file, reuse an old offset
@ -126,7 +167,7 @@ impl VfsBuilder {
self.current_offset self.current_offset
}; };
let dir = self.add_dir(path.parent().unwrap()); let dir = self.add_dir(path.parent().unwrap())?;
let name = path.file_name().unwrap().to_string_lossy(); let name = path.file_name().unwrap().to_string_lossy();
let data_len = data.len(); let data_len = data.len();
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search_by(|e| e.name().cmp(&name)) {
@ -148,11 +189,22 @@ impl VfsBuilder {
self.files.push(data); self.files.push(data);
self.current_offset += data_len as u64; self.current_offset += data_len as u64;
} }
Ok(())
} }
pub fn add_symlink(&mut self, path: &Path, target: &Path) { pub fn add_symlink(
let dest = self.path_relative_root(target); &mut self,
let dir = self.add_dir(path.parent().unwrap()); path: &Path,
target: &Path,
) -> Result<(), StripRootError> {
log::debug!(
"Adding symlink '{}' to '{}'",
path.display(),
target.display()
);
let dest = self.path_relative_root(target)?;
let dir = self.add_dir(path.parent().unwrap())?;
let name = path.file_name().unwrap().to_string_lossy(); let name = path.file_name().unwrap().to_string_lossy();
match dir.entries.binary_search_by(|e| e.name().cmp(&name)) { match dir.entries.binary_search_by(|e| e.name().cmp(&name)) {
Ok(_) => unreachable!(), Ok(_) => unreachable!(),
@ -169,23 +221,20 @@ impl VfsBuilder {
); );
} }
} }
Ok(())
} }
pub fn into_dir_and_files(self) -> (VirtualDirectory, Vec<Vec<u8>>) { pub fn into_dir_and_files(self) -> (VirtualDirectory, Vec<Vec<u8>>) {
(self.root_dir, self.files) (self.root_dir, self.files)
} }
fn path_relative_root(&self, path: &Path) -> PathBuf { fn path_relative_root(&self, path: &Path) -> Result<PathBuf, StripRootError> {
match path.strip_prefix(&self.root_path) { match path.strip_prefix(&self.root_path) {
Ok(p) => p.to_path_buf(), Ok(p) => Ok(p.to_path_buf()),
Err(err) => { Err(_) => Err(StripRootError {
panic!( root_path: self.root_path.clone(),
"Failed to strip prefix '{}' from '{}': {:#}", target: path.to_path_buf(),
self.root_path.display(), }),
path.display(),
err
)
}
} }
} }
} }
@ -457,7 +506,11 @@ impl FileBackedVfsFile {
} }
SeekFrom::End(offset) => { SeekFrom::End(offset) => {
if offset < 0 && -offset as u64 > self.file.len { if offset < 0 && -offset as u64 > self.file.len {
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "An attempt was made to move the file pointer before the beginning of the file.").into()) let msg = "An attempt was made to move the file pointer before the beginning of the file.";
Err(
std::io::Error::new(std::io::ErrorKind::PermissionDenied, msg)
.into(),
)
} else { } else {
let mut current_pos = self.pos.lock(); let mut current_pos = self.pos.lock();
*current_pos = if offset >= 0 { *current_pos = if offset >= 0 {
@ -790,16 +843,28 @@ mod test {
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
let src_path = temp_dir.path().join("src"); let src_path = temp_dir.path().join("src");
let mut builder = VfsBuilder::new(src_path.clone()); let mut builder = VfsBuilder::new(src_path.clone());
builder.add_file(&src_path.join("a.txt"), "data".into()); builder
builder.add_file(&src_path.join("b.txt"), "data".into()); .add_file(&src_path.join("a.txt"), "data".into())
.unwrap();
builder
.add_file(&src_path.join("b.txt"), "data".into())
.unwrap();
assert_eq!(builder.files.len(), 1); // because duplicate data assert_eq!(builder.files.len(), 1); // because duplicate data
builder.add_file(&src_path.join("c.txt"), "c".into()); builder
builder.add_file(&src_path.join("sub_dir").join("d.txt"), "d".into()); .add_file(&src_path.join("c.txt"), "c".into())
builder.add_file(&src_path.join("e.txt"), "e".into()); .unwrap();
builder.add_symlink( builder
&src_path.join("sub_dir").join("e.txt"), .add_file(&src_path.join("sub_dir").join("d.txt"), "d".into())
&src_path.join("e.txt"), .unwrap();
); builder
.add_file(&src_path.join("e.txt"), "e".into())
.unwrap();
builder
.add_symlink(
&src_path.join("sub_dir").join("e.txt"),
&src_path.join("e.txt"),
)
.unwrap();
// get the virtual fs // get the virtual fs
let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir); let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir);
@ -923,9 +988,15 @@ mod test {
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
let src_path = temp_dir.path().join("src"); let src_path = temp_dir.path().join("src");
let mut builder = VfsBuilder::new(src_path.clone()); let mut builder = VfsBuilder::new(src_path.clone());
builder.add_symlink(&src_path.join("a.txt"), &src_path.join("b.txt")); builder
builder.add_symlink(&src_path.join("b.txt"), &src_path.join("c.txt")); .add_symlink(&src_path.join("a.txt"), &src_path.join("b.txt"))
builder.add_symlink(&src_path.join("c.txt"), &src_path.join("a.txt")); .unwrap();
builder
.add_symlink(&src_path.join("b.txt"), &src_path.join("c.txt"))
.unwrap();
builder
.add_symlink(&src_path.join("c.txt"), &src_path.join("a.txt"))
.unwrap();
let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir); let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir);
assert_eq!( assert_eq!(
virtual_fs virtual_fs
@ -950,10 +1021,12 @@ mod test {
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
let temp_path = temp_dir.path(); let temp_path = temp_dir.path();
let mut builder = VfsBuilder::new(temp_path.to_path_buf()); let mut builder = VfsBuilder::new(temp_path.to_path_buf());
builder.add_file( builder
&temp_path.join("a.txt"), .add_file(
"0123456789".to_string().into_bytes(), &temp_path.join("a.txt"),
); "0123456789".to_string().into_bytes(),
)
.unwrap();
let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir); let (dest_path, virtual_fs) = into_virtual_fs(builder, &temp_dir);
let virtual_fs = Arc::new(virtual_fs); let virtual_fs = Arc::new(virtual_fs);
let file = virtual_fs.open_file(&dest_path.join("a.txt")).unwrap(); let file = virtual_fs.open_file(&dest_path.join("a.txt")).unwrap();

View file

@ -1082,3 +1082,68 @@ fn run_npm_bin_compile_test(opts: RunNpmBinCompileOptions) {
output.assert_matches_file(opts.output_file); output.assert_matches_file(opts.output_file);
output.assert_exit_code(opts.exit_code); output.assert_exit_code(opts.exit_code);
} }
#[test]
fn compile_node_modules_symlink_outside() {
let context = TestContextBuilder::for_npm()
.use_sync_npm_download()
.use_copy_temp_dir("compile/node_modules_symlink_outside")
.cwd("compile/node_modules_symlink_outside")
.build();
let temp_dir = context.temp_dir();
let project_dir = temp_dir
.path()
.join("compile")
.join("node_modules_symlink_outside");
temp_dir.create_dir_all(project_dir.join("node_modules"));
temp_dir.create_dir_all(project_dir.join("some_folder"));
temp_dir.write(project_dir.join("test.txt"), "5");
// create a symlink in the node_modules directory that points to a folder in the cwd
temp_dir.symlink_dir(
project_dir.join("some_folder"),
project_dir.join("node_modules").join("some_folder"),
);
// compile folder
let output = context
.new_command()
.args("compile --allow-read --node-modules-dir --output bin main.ts")
.run();
output.assert_exit_code(0);
output.assert_matches_file(
"compile/node_modules_symlink_outside/main_compile_folder.out",
);
assert!(project_dir.join("node_modules/some_folder").exists());
// Cleanup and remove the folder. The folder test is done separately from
// the file symlink test because different systems would traverse
// the directory items in different order.
temp_dir.remove_dir_all(project_dir.join("node_modules/some_folder"));
// create a symlink in the node_modules directory that points to a file in the cwd
temp_dir.symlink_file(
project_dir.join("test.txt"),
project_dir.join("node_modules").join("test.txt"),
);
assert!(project_dir.join("node_modules/test.txt").exists());
// compile
let output = context
.new_command()
.args("compile --allow-read --node-modules-dir --output bin main.ts")
.run();
output.assert_exit_code(0);
output.assert_matches_file(
"compile/node_modules_symlink_outside/main_compile_file.out",
);
// run
let binary_path =
project_dir.join(if cfg!(windows) { "bin.exe" } else { "bin" });
let output = context
.new_command()
.command_name(binary_path.to_string_lossy())
.run();
output.assert_matches_file("compile/node_modules_symlink_outside/main.out");
}

View file

@ -0,0 +1,2 @@
4
5

View file

@ -0,0 +1,6 @@
import { getValue, setValue } from "npm:@denotest/esm-basic";
setValue(4);
console.log(getValue());
console.log(Deno.readTextFileSync("./node_modules/test.txt"));

View file

@ -0,0 +1,2 @@
Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD]
Symlink target is outside '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules'. Inlining symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]test.txt' to '[WILDCARD]node_modules_symlink_outside[WILDCARD]test.txt' as file.

View file

@ -0,0 +1,6 @@
Download http://localhost:4545/npm/registry/@denotest/esm-basic
Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz
Initialize @denotest/esm-basic@1.0.0
Check file:///[WILDCARD]/node_modules_symlink_outside/main.ts
Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD]
Symlink target is outside '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules'. Excluding symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]some_folder' with target '[WILDCARD]node_modules_symlink_outside[WILDCARD]some_folder'.

View file

@ -80,4 +80,40 @@ impl TempDir {
pub fn write(&self, path: impl AsRef<Path>, text: impl AsRef<str>) { pub fn write(&self, path: impl AsRef<Path>, text: impl AsRef<str>) {
fs::write(self.path().join(path), text.as_ref()).unwrap(); fs::write(self.path().join(path), text.as_ref()).unwrap();
} }
pub fn symlink_dir(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
symlink(self.path().join(oldpath), self.path().join(newpath)).unwrap();
}
#[cfg(not(unix))]
{
use std::os::windows::fs::symlink_dir;
symlink_dir(self.path().join(oldpath), self.path().join(newpath))
.unwrap();
}
}
pub fn symlink_file(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
symlink(self.path().join(oldpath), self.path().join(newpath)).unwrap();
}
#[cfg(not(unix))]
{
use std::os::windows::fs::symlink_file;
symlink_file(self.path().join(oldpath), self.path().join(newpath))
.unwrap();
}
}
} }