From c77c9b29581b76e00caa56f654ba4326d297f355 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 18 Oct 2024 13:12:34 +0200 Subject: [PATCH 01/16] fix(help): missing package specifier (#26380) Was notified of one more occurance where we were missing an explicit specifier for a `deno add` call. See https://github.com/denoland/deno/issues/26295#issuecomment-2421637401 --- cli/args/flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index b92e5dd945..cb3d55e50d 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -1178,7 +1178,7 @@ static DENO_HELP: &str = cstr!( Dependency management: add Add dependencies - deno add @std/assert | deno add npm:express + deno add jsr:@std/assert | deno add npm:express install Install script as an executable uninstall Uninstall a script previously installed with deno install remove Remove dependencies from the configuration file From 85a99eb405ef3ec5f8e478d93b2c866afbc53f95 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 06:38:17 -0700 Subject: [PATCH 02/16] refactor(ext/fs): use concrete error types (#26317) --- Cargo.lock | 13 +- Cargo.toml | 2 +- ext/fs/Cargo.toml | 1 + ext/fs/lib.rs | 2 + ext/fs/ops.rs | 575 ++++++++++++++++++++++++++++++---------------- runtime/errors.rs | 31 +++ 6 files changed, 425 insertions(+), 199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcbbaaae03..9b2baf79c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1423,9 +1423,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.314.1" +version = "0.314.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcd11ab87426c611b7170138a768dad7170c8fb66d8095b773d25e58fd254ea" +checksum = "83138917579676069b423c3eb9be3c1e579f60dc022d85f6ded4c792456255ff" dependencies = [ "anyhow", "bincode", @@ -1600,6 +1600,7 @@ dependencies = [ "rand", "rayon", "serde", + "thiserror", "winapi", "windows-sys 0.52.0", ] @@ -1916,9 +1917,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.190.0" +version = "0.190.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48a3e06cace18a2c49e148da067678c6af80e70757a8c3991301397cf6b9919" +checksum = "26f46d4e4f52f26c882b74a9b58810ea75252b807cf0966166ec333077cdfd85" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -6215,9 +6216,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.223.0" +version = "0.223.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127bb9f2024433d06789b242477c808fd7f7dc4c3278576dd5bc99c4e5c75ff" +checksum = "9cf3d859dda87ee96423c01244f10af864fa6d6a9fcdc2b77e0595078ea0ea11" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3bf5342b8e..e2f439e37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ repository = "https://github.com/denoland/deno" [workspace.dependencies] deno_ast = { version = "=0.42.2", features = ["transpiling"] } -deno_core = { version = "0.314.1" } +deno_core = { version = "0.314.2" } deno_bench_util = { version = "0.167.0", path = "./bench_util" } deno_lockfile = "=0.23.1" diff --git a/ext/fs/Cargo.toml b/ext/fs/Cargo.toml index a33347f9c7..313c84fdb6 100644 --- a/ext/fs/Cargo.toml +++ b/ext/fs/Cargo.toml @@ -28,6 +28,7 @@ libc.workspace = true rand.workspace = true rayon = "1.8.0" serde.workspace = true +thiserror.workspace = true [target.'cfg(unix)'.dependencies] nix.workspace = true diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index bd49078b2e..cd2baf22a9 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -14,6 +14,8 @@ pub use crate::interface::FileSystemRc; pub use crate::interface::FsDirEntry; pub use crate::interface::FsFileType; pub use crate::interface::OpenOptions; +pub use crate::ops::FsOpsError; +pub use crate::ops::OperationError; pub use crate::std_fs::RealFs; pub use crate::sync::MaybeSend; pub use crate::sync::MaybeSync; diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index b13d3a7d1a..a3f59da4ea 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -1,6 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::cell::RefCell; +use std::error::Error; +use std::fmt::Formatter; use std::io; use std::io::SeekFrom; use std::path::Path; @@ -8,10 +10,6 @@ use std::path::PathBuf; use std::path::StripPrefixError; use std::rc::Rc; -use deno_core::anyhow::bail; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::CancelFuture; use deno_core::CancelHandle; @@ -34,6 +32,67 @@ use crate::interface::FsFileType; use crate::FsPermissions; use crate::OpenOptions; +#[derive(Debug, thiserror::Error)] +pub enum FsOpsError { + #[error("{0}")] + Io(#[source] std::io::Error), + #[error("{0}")] + OperationError(#[source] OperationError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("File name or path {0:?} is not valid UTF-8")] + InvalidUtf8(std::ffi::OsString), + #[error("{0}")] + StripPrefix(#[from] StripPrefixError), + #[error("{0}")] + Canceled(#[from] deno_core::Canceled), + #[error("Invalid seek mode: {0}")] + InvalidSeekMode(i32), + #[error("Invalid control character in prefix or suffix: {0:?}")] + InvalidControlCharacter(String), + #[error("Invalid character in prefix or suffix: {0:?}")] + InvalidCharacter(String), + #[cfg(windows)] + #[error("Invalid trailing character in suffix")] + InvalidTrailingCharacter, + #[error("Requires {err} access to {path}, {}", print_not_capable_info(*.standalone, .err))] + NotCapableAccess { + // NotCapable + standalone: bool, + err: &'static str, + path: String, + }, + #[error("permission denied: {0}")] + NotCapable(&'static str), // NotCapable + #[error(transparent)] + Other(deno_core::error::AnyError), +} + +impl From for FsOpsError { + fn from(err: FsError) -> Self { + match err { + FsError::Io(err) => FsOpsError::Io(err), + FsError::FileBusy => { + FsOpsError::Other(deno_core::error::resource_unavailable()) + } + FsError::NotSupported => { + FsOpsError::Other(deno_core::error::not_supported()) + } + FsError::NotCapable(err) => FsOpsError::NotCapable(err), + } + } +} + +fn print_not_capable_info(standalone: bool, err: &'static str) -> String { + if standalone { + format!("specify the required permissions during compilation using `deno compile --allow-{err}`") + } else { + format!("run again with the --allow-{err} flag") + } +} + fn sync_permission_check<'a, P: FsPermissions + 'static>( permissions: &'a mut P, api_name: &'static str, @@ -58,7 +117,7 @@ fn map_permission_error( operation: &'static str, error: FsError, path: &Path, -) -> AnyError { +) -> FsOpsError { match error { FsError::NotCapable(err) => { let path = format!("{path:?}"); @@ -67,14 +126,12 @@ fn map_permission_error( } else { (path.as_str(), "") }; - let msg = if deno_permissions::is_standalone() { - format!( - "Requires {err} access to {path}{truncated}, specify the required permissions during compilation using `deno compile --allow-{err}`") - } else { - format!( - "Requires {err} access to {path}{truncated}, run again with the --allow-{err} flag") - }; - custom_error("NotCapable", msg) + + FsOpsError::NotCapableAccess { + standalone: deno_permissions::is_standalone(), + err, + path: format!("{path}{truncated}"), + } } err => Err::<(), _>(err) .context_path(operation, path) @@ -85,7 +142,7 @@ fn map_permission_error( #[op2] #[string] -pub fn op_fs_cwd

(state: &mut OpState) -> Result +pub fn op_fs_cwd

(state: &mut OpState) -> Result where P: FsPermissions + 'static, { @@ -93,7 +150,8 @@ where let path = fs.cwd()?; state .borrow_mut::

() - .check_read_blind(&path, "CWD", "Deno.cwd()")?; + .check_read_blind(&path, "CWD", "Deno.cwd()") + .map_err(FsOpsError::Permission)?; let path_str = path_into_string(path.into_os_string())?; Ok(path_str) } @@ -102,13 +160,14 @@ where pub fn op_fs_chdir

( state: &mut OpState, #[string] directory: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let d = state .borrow_mut::

() - .check_read(directory, "Deno.chdir()")?; + .check_read(directory, "Deno.chdir()") + .map_err(FsOpsError::Permission)?; state .borrow::() .chdir(&d) @@ -119,7 +178,7 @@ where pub fn op_fs_umask( state: &mut OpState, mask: Option, -) -> Result +) -> Result where { state.borrow::().umask(mask).context("umask") @@ -131,7 +190,7 @@ pub fn op_fs_open_sync

( state: &mut OpState, #[string] path: String, #[serde] options: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -158,7 +217,7 @@ pub async fn op_fs_open_async

( state: Rc>, #[string] path: String, #[serde] options: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -186,7 +245,7 @@ pub fn op_fs_mkdir_sync

( #[string] path: String, recursive: bool, mode: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -194,7 +253,8 @@ where let path = state .borrow_mut::

() - .check_write(&path, "Deno.mkdirSync()")?; + .check_write(&path, "Deno.mkdirSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.mkdir_sync(&path, recursive, Some(mode)) @@ -209,7 +269,7 @@ pub async fn op_fs_mkdir_async

( #[string] path: String, recursive: bool, mode: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -217,7 +277,10 @@ where let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.mkdir()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.mkdir()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -233,13 +296,14 @@ pub fn op_fs_chmod_sync

( state: &mut OpState, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(&path, "Deno.chmodSync()")?; + .check_write(&path, "Deno.chmodSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.chmod_sync(&path, mode).context_path("chmod", &path)?; Ok(()) @@ -250,13 +314,16 @@ pub async fn op_fs_chmod_async

( state: Rc>, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.chmod()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.chmod()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; fs.chmod_async(path.clone(), mode) @@ -271,13 +338,14 @@ pub fn op_fs_chown_sync

( #[string] path: String, uid: Option, gid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(&path, "Deno.chownSync()")?; + .check_write(&path, "Deno.chownSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.chown_sync(&path, uid, gid) .context_path("chown", &path)?; @@ -290,13 +358,16 @@ pub async fn op_fs_chown_async

( #[string] path: String, uid: Option, gid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.chown()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.chown()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; fs.chown_async(path.clone(), uid, gid) @@ -310,13 +381,14 @@ pub fn op_fs_remove_sync

( state: &mut OpState, #[string] path: &str, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(path, "Deno.removeSync()")?; + .check_write(path, "Deno.removeSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.remove_sync(&path, recursive) @@ -330,7 +402,7 @@ pub async fn op_fs_remove_async

( state: Rc>, #[string] path: String, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -339,11 +411,13 @@ where let path = if recursive { state .borrow_mut::

() - .check_write(&path, "Deno.remove()")? + .check_write(&path, "Deno.remove()") + .map_err(FsOpsError::Permission)? } else { state .borrow_mut::

() - .check_write_partial(&path, "Deno.remove()")? + .check_write_partial(&path, "Deno.remove()") + .map_err(FsOpsError::Permission)? }; (state.borrow::().clone(), path) @@ -361,13 +435,17 @@ pub fn op_fs_copy_file_sync

( state: &mut OpState, #[string] from: &str, #[string] to: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - let from = permissions.check_read(from, "Deno.copyFileSync()")?; - let to = permissions.check_write(to, "Deno.copyFileSync()")?; + let from = permissions + .check_read(from, "Deno.copyFileSync()") + .map_err(FsOpsError::Permission)?; + let to = permissions + .check_write(to, "Deno.copyFileSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.copy_file_sync(&from, &to) @@ -381,15 +459,19 @@ pub async fn op_fs_copy_file_async

( state: Rc>, #[string] from: String, #[string] to: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, from, to) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let from = permissions.check_read(&from, "Deno.copyFile()")?; - let to = permissions.check_write(&to, "Deno.copyFile()")?; + let from = permissions + .check_read(&from, "Deno.copyFile()") + .map_err(FsOpsError::Permission)?; + let to = permissions + .check_write(&to, "Deno.copyFile()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), from, to) }; @@ -405,13 +487,14 @@ pub fn op_fs_stat_sync

( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.statSync()")?; + .check_read(&path, "Deno.statSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let stat = fs.stat_sync(&path).context_path("stat", &path)?; let serializable_stat = SerializableStat::from(stat); @@ -424,14 +507,16 @@ where pub async fn op_fs_stat_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.stat()")?; + let path = permissions + .check_read(&path, "Deno.stat()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; let stat = fs @@ -446,13 +531,14 @@ pub fn op_fs_lstat_sync

( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.lstatSync()")?; + .check_read(&path, "Deno.lstatSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let stat = fs.lstat_sync(&path).context_path("lstat", &path)?; let serializable_stat = SerializableStat::from(stat); @@ -465,14 +551,16 @@ where pub async fn op_fs_lstat_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.lstat()")?; + let path = permissions + .check_read(&path, "Deno.lstat()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; let stat = fs @@ -487,15 +575,19 @@ where pub fn op_fs_realpath_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let fs = state.borrow::().clone(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.realPathSync()")?; + let path = permissions + .check_read(&path, "Deno.realPathSync()") + .map_err(FsOpsError::Permission)?; if path.is_relative() { - permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()")?; + permissions + .check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()") + .map_err(FsOpsError::Permission)?; } let resolved_path = @@ -510,7 +602,7 @@ where pub async fn op_fs_realpath_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -518,9 +610,13 @@ where let mut state = state.borrow_mut(); let fs = state.borrow::().clone(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.realPath()")?; + let path = permissions + .check_read(&path, "Deno.realPath()") + .map_err(FsOpsError::Permission)?; if path.is_relative() { - permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()")?; + permissions + .check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()") + .map_err(FsOpsError::Permission)?; } (fs, path) }; @@ -538,13 +634,14 @@ where pub fn op_fs_read_dir_sync

( state: &mut OpState, #[string] path: String, -) -> Result, AnyError> +) -> Result, FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.readDirSync()")?; + .check_read(&path, "Deno.readDirSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let entries = fs.read_dir_sync(&path).context_path("readdir", &path)?; @@ -557,7 +654,7 @@ where pub async fn op_fs_read_dir_async

( state: Rc>, #[string] path: String, -) -> Result, AnyError> +) -> Result, FsOpsError> where P: FsPermissions + 'static, { @@ -565,7 +662,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_read(&path, "Deno.readDir()")?; + .check_read(&path, "Deno.readDir()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -582,14 +680,20 @@ pub fn op_fs_rename_sync

( state: &mut OpState, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - let _ = permissions.check_read(&oldpath, "Deno.renameSync()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.renameSync()")?; - let newpath = permissions.check_write(&newpath, "Deno.renameSync()")?; + let _ = permissions + .check_read(&oldpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.rename_sync(&oldpath, &newpath) @@ -603,16 +707,22 @@ pub async fn op_fs_rename_async

( state: Rc>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - _ = permissions.check_read(&oldpath, "Deno.rename()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.rename()")?; - let newpath = permissions.check_write(&newpath, "Deno.rename()")?; + _ = permissions + .check_read(&oldpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), oldpath, newpath) }; @@ -628,15 +738,23 @@ pub fn op_fs_link_sync

( state: &mut OpState, #[string] oldpath: &str, #[string] newpath: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - _ = permissions.check_read(oldpath, "Deno.linkSync()")?; - let oldpath = permissions.check_write(oldpath, "Deno.linkSync()")?; - _ = permissions.check_read(newpath, "Deno.linkSync()")?; - let newpath = permissions.check_write(newpath, "Deno.linkSync()")?; + _ = permissions + .check_read(oldpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(oldpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + _ = permissions + .check_read(newpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(newpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.link_sync(&oldpath, &newpath) @@ -650,17 +768,25 @@ pub async fn op_fs_link_async

( state: Rc>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - _ = permissions.check_read(&oldpath, "Deno.link()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.link()")?; - _ = permissions.check_read(&newpath, "Deno.link()")?; - let newpath = permissions.check_write(&newpath, "Deno.link()")?; + _ = permissions + .check_read(&oldpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + _ = permissions + .check_read(&newpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), oldpath, newpath) }; @@ -677,7 +803,7 @@ pub fn op_fs_symlink_sync

( #[string] oldpath: &str, #[string] newpath: &str, #[serde] file_type: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -685,8 +811,12 @@ where let newpath = PathBuf::from(newpath); let permissions = state.borrow_mut::

(); - permissions.check_write_all("Deno.symlinkSync()")?; - permissions.check_read_all("Deno.symlinkSync()")?; + permissions + .check_write_all("Deno.symlinkSync()") + .map_err(FsOpsError::Permission)?; + permissions + .check_read_all("Deno.symlinkSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.symlink_sync(&oldpath, &newpath, file_type) @@ -701,7 +831,7 @@ pub async fn op_fs_symlink_async

( #[string] oldpath: String, #[string] newpath: String, #[serde] file_type: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -711,8 +841,12 @@ where let fs = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - permissions.check_write_all("Deno.symlink()")?; - permissions.check_read_all("Deno.symlink()")?; + permissions + .check_write_all("Deno.symlink()") + .map_err(FsOpsError::Permission)?; + permissions + .check_read_all("Deno.symlink()") + .map_err(FsOpsError::Permission)?; state.borrow::().clone() }; @@ -728,13 +862,14 @@ where pub fn op_fs_read_link_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.readLink()")?; + .check_read(&path, "Deno.readLink()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); @@ -748,7 +883,7 @@ where pub async fn op_fs_read_link_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -756,7 +891,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_read(&path, "Deno.readLink()")?; + .check_read(&path, "Deno.readLink()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -773,13 +909,14 @@ pub fn op_fs_truncate_sync

( state: &mut OpState, #[string] path: &str, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(path, "Deno.truncateSync()")?; + .check_write(path, "Deno.truncateSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.truncate_sync(&path, len) @@ -793,7 +930,7 @@ pub async fn op_fs_truncate_async

( state: Rc>, #[string] path: String, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -801,7 +938,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_write(&path, "Deno.truncate()")?; + .check_write(&path, "Deno.truncate()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -820,11 +958,14 @@ pub fn op_fs_utime_sync

( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { - let path = state.borrow_mut::

().check_write(path, "Deno.utime()")?; + let path = state + .borrow_mut::

() + .check_write(path, "Deno.utime()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) @@ -841,13 +982,16 @@ pub async fn op_fs_utime_async

( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.utime()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.utime()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -871,7 +1015,7 @@ pub fn op_fs_make_temp_dir_sync

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -913,7 +1057,7 @@ pub async fn op_fs_make_temp_dir_async

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -959,7 +1103,7 @@ pub fn op_fs_make_temp_file_sync

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1007,7 +1151,7 @@ pub async fn op_fs_make_temp_file_async

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1069,18 +1213,22 @@ fn make_temp_check_sync

( state: &mut OpState, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { let fs = state.borrow::().clone(); let dir = match dir { - Some(dir) => state.borrow_mut::

().check_write(dir, api_name)?, + Some(dir) => state + .borrow_mut::

() + .check_write(dir, api_name) + .map_err(FsOpsError::Permission)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state .borrow_mut::

() - .check_write_blind(&dir, "TMP", api_name)?; + .check_write_blind(&dir, "TMP", api_name) + .map_err(FsOpsError::Permission)?; dir } }; @@ -1091,19 +1239,23 @@ fn make_temp_check_async

( state: Rc>, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { let mut state = state.borrow_mut(); let fs = state.borrow::().clone(); let dir = match dir { - Some(dir) => state.borrow_mut::

().check_write(dir, api_name)?, + Some(dir) => state + .borrow_mut::

() + .check_write(dir, api_name) + .map_err(FsOpsError::Permission)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state .borrow_mut::

() - .check_write_blind(&dir, "TMP", api_name)?; + .check_write_blind(&dir, "TMP", api_name) + .map_err(FsOpsError::Permission)?; dir } }; @@ -1116,10 +1268,10 @@ where fn validate_temporary_filename_component( component: &str, #[allow(unused_variables)] suffix: bool, -) -> Result<(), AnyError> { +) -> Result<(), FsOpsError> { // Ban ASCII and Unicode control characters: these will often fail if let Some(c) = component.matches(|c: char| c.is_control()).next() { - bail!("Invalid control character in prefix or suffix: {:?}", c); + return Err(FsOpsError::InvalidControlCharacter(c.to_string())); } // Windows has the most restrictive filenames. As temp files aren't normal files, we just // use this set of banned characters for all platforms because wildcard-like files can also @@ -1135,13 +1287,13 @@ fn validate_temporary_filename_component( .matches(|c: char| "<>:\"/\\|?*".contains(c)) .next() { - bail!("Invalid character in prefix or suffix: {:?}", c); + return Err(FsOpsError::InvalidCharacter(c.to_string())); } // This check is only for Windows #[cfg(windows)] if suffix && component.ends_with(|c: char| ". ".contains(c)) { - bail!("Invalid trailing character in suffix"); + return Err(FsOpsError::InvalidTrailingCharacter); } Ok(()) @@ -1152,7 +1304,7 @@ fn tmp_name( dir: &Path, prefix: Option<&str>, suffix: Option<&str>, -) -> Result { +) -> Result { let prefix = prefix.unwrap_or(""); validate_temporary_filename_component(prefix, false)?; let suffix = suffix.unwrap_or(""); @@ -1179,7 +1331,7 @@ pub fn op_fs_write_file_sync

( create: bool, create_new: bool, #[buffer] data: JsBuffer, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1207,7 +1359,7 @@ pub async fn op_fs_write_file_async

( create_new: bool, #[buffer] data: JsBuffer, #[smi] cancel_rid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1255,7 +1407,7 @@ where pub fn op_fs_read_file_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1277,7 +1429,7 @@ pub async fn op_fs_read_file_async

( state: Rc>, #[string] path: String, #[smi] cancel_rid: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1318,7 +1470,7 @@ where pub fn op_fs_read_file_text_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1340,7 +1492,7 @@ pub async fn op_fs_read_file_text_async

( state: Rc>, #[string] path: String, #[smi] cancel_rid: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1377,13 +1529,13 @@ where Ok(str) } -fn to_seek_from(offset: i64, whence: i32) -> Result { +fn to_seek_from(offset: i64, whence: i32) -> Result { let seek_from = match whence { 0 => SeekFrom::Start(offset as u64), 1 => SeekFrom::Current(offset), 2 => SeekFrom::End(offset), _ => { - return Err(type_error(format!("Invalid seek mode: {whence}"))); + return Err(FsOpsError::InvalidSeekMode(whence)); } }; Ok(seek_from) @@ -1396,9 +1548,10 @@ pub fn op_fs_seek_sync( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result { +) -> Result { let pos = to_seek_from(offset, whence)?; - let file = FileResource::get_file(state, rid)?; + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; let cursor = file.seek_sync(pos)?; Ok(cursor) } @@ -1410,9 +1563,10 @@ pub async fn op_fs_seek_async( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result { +) -> Result { let pos = to_seek_from(offset, whence)?; - let file = FileResource::get_file(&state.borrow(), rid)?; + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; let cursor = file.seek_async(pos).await?; Ok(cursor) } @@ -1421,8 +1575,9 @@ pub async fn op_fs_seek_async( pub fn op_fs_file_sync_data_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.datasync_sync()?; Ok(()) } @@ -1431,8 +1586,9 @@ pub fn op_fs_file_sync_data_sync( pub async fn op_fs_file_sync_data_async( state: Rc>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file.datasync_async().await?; Ok(()) } @@ -1441,8 +1597,9 @@ pub async fn op_fs_file_sync_data_async( pub fn op_fs_file_sync_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.sync_sync()?; Ok(()) } @@ -1451,8 +1608,9 @@ pub fn op_fs_file_sync_sync( pub async fn op_fs_file_sync_async( state: Rc>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file.sync_async().await?; Ok(()) } @@ -1462,8 +1620,9 @@ pub fn op_fs_file_stat_sync( state: &mut OpState, #[smi] rid: ResourceId, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; let stat = file.stat_sync()?; let serializable_stat = SerializableStat::from(stat); serializable_stat.write(stat_out_buf); @@ -1475,8 +1634,9 @@ pub fn op_fs_file_stat_sync( pub async fn op_fs_file_stat_async( state: Rc>, #[smi] rid: ResourceId, -) -> Result { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; let stat = file.stat_async().await?; Ok(stat.into()) } @@ -1486,8 +1646,9 @@ pub fn op_fs_flock_sync( state: &mut OpState, #[smi] rid: ResourceId, exclusive: bool, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.lock_sync(exclusive)?; Ok(()) } @@ -1497,8 +1658,9 @@ pub async fn op_fs_flock_async( state: Rc>, #[smi] rid: ResourceId, exclusive: bool, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file.lock_async(exclusive).await?; Ok(()) } @@ -1507,8 +1669,9 @@ pub async fn op_fs_flock_async( pub fn op_fs_funlock_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.unlock_sync()?; Ok(()) } @@ -1517,8 +1680,9 @@ pub fn op_fs_funlock_sync( pub async fn op_fs_funlock_async( state: Rc>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file.unlock_async().await?; Ok(()) } @@ -1528,8 +1692,9 @@ pub fn op_fs_ftruncate_sync( state: &mut OpState, #[smi] rid: ResourceId, #[number] len: u64, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.truncate_sync(len)?; Ok(()) } @@ -1539,8 +1704,9 @@ pub async fn op_fs_file_truncate_async( state: Rc>, #[smi] rid: ResourceId, #[number] len: u64, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file.truncate_async(len).await?; Ok(()) } @@ -1553,8 +1719,9 @@ pub fn op_fs_futime_sync( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; Ok(()) } @@ -1567,42 +1734,64 @@ pub async fn op_fs_futime_async( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; file .utime_async(atime_secs, atime_nanos, mtime_secs, mtime_nanos) .await?; Ok(()) } -trait WithContext { - fn context>>( - self, - desc: E, - ) -> AnyError; +#[derive(Debug)] +pub struct OperationError { + operation: &'static str, + kind: OperationErrorKind, + pub err: FsError, } -impl WithContext for FsError { - fn context>>( - self, - desc: E, - ) -> AnyError { - match self { - FsError::Io(io) => { - AnyError::new(io::Error::new(io.kind(), desc)).context(io) +impl std::fmt::Display for OperationError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let FsError::Io(e) = &self.err { + std::fmt::Display::fmt(&e, f)?; + f.write_str(": ")?; + } + + f.write_str(self.operation)?; + + match &self.kind { + OperationErrorKind::Bare => Ok(()), + OperationErrorKind::WithPath(path) => write!(f, " '{}'", path.display()), + OperationErrorKind::WithTwoPaths(from, to) => { + write!(f, " '{}' -> '{}'", from.display(), to.display()) } - _ => self.into(), } } } +impl std::error::Error for OperationError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + if let FsError::Io(err) = &self.err { + Some(err) + } else { + None + } + } +} + +#[derive(Debug)] +pub enum OperationErrorKind { + Bare, + WithPath(PathBuf), + WithTwoPaths(PathBuf, PathBuf), +} + trait MapErrContext { type R; - fn context_fn(self, f: F) -> Self::R + fn context_fn(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into>; + F: FnOnce(FsError) -> OperationError; fn context(self, desc: &'static str) -> Self::R; @@ -1617,25 +1806,29 @@ trait MapErrContext { } impl MapErrContext for Result { - type R = Result; + type R = Result; - fn context_fn(self, f: F) -> Self::R + fn context_fn(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into>, + F: FnOnce(FsError) -> OperationError, { - self.map_err(|err| { - let message = f(); - err.context(message) + self.map_err(|err| FsOpsError::OperationError(f(err))) + } + + fn context(self, operation: &'static str) -> Self::R { + self.context_fn(move |err| OperationError { + operation, + kind: OperationErrorKind::Bare, + err, }) } - fn context(self, desc: &'static str) -> Self::R { - self.context_fn(move || desc) - } - fn context_path(self, operation: &'static str, path: &Path) -> Self::R { - self.context_fn(|| format!("{operation} '{}'", path.display())) + self.context_fn(|err| OperationError { + operation, + kind: OperationErrorKind::WithPath(path.to_path_buf()), + err, + }) } fn context_two_path( @@ -1644,21 +1837,19 @@ impl MapErrContext for Result { oldpath: &Path, newpath: &Path, ) -> Self::R { - self.context_fn(|| { - format!( - "{operation} '{}' -> '{}'", - oldpath.display(), - newpath.display() - ) + self.context_fn(|err| OperationError { + operation, + kind: OperationErrorKind::WithTwoPaths( + oldpath.to_path_buf(), + newpath.to_path_buf(), + ), + err, }) } } -fn path_into_string(s: std::ffi::OsString) -> Result { - s.into_string().map_err(|s| { - let message = format!("File name or path {s:?} is not valid UTF-8"); - custom_error("InvalidData", message) - }) +fn path_into_string(s: std::ffi::OsString) -> Result { + s.into_string().map_err(FsOpsError::InvalidUtf8) } macro_rules! create_struct_writer { diff --git a/runtime/errors.rs b/runtime/errors.rs index 935f62d264..25fc664a57 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -23,6 +23,8 @@ use deno_ffi::DlfcnError; use deno_ffi::IRError; use deno_ffi::ReprError; use deno_ffi::StaticError; +use deno_fs::FsOpsError; +use deno_io::fs::FsError; use deno_kv::KvCheckError; use deno_kv::KvError; use deno_kv::KvMutationError; @@ -366,6 +368,34 @@ fn get_broadcast_channel_error(error: &BroadcastChannelError) -> &'static str { } } +fn get_fs_error(error: &FsOpsError) -> &'static str { + match error { + FsOpsError::Io(e) => get_io_error_class(e), + FsOpsError::OperationError(e) => match &e.err { + FsError::Io(e) => get_io_error_class(e), + FsError::FileBusy => "Busy", + FsError::NotSupported => "NotSupported", + FsError::NotCapable(_) => "NotCapable", + }, + FsOpsError::Permission(e) + | FsOpsError::Resource(e) + | FsOpsError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + FsOpsError::InvalidUtf8(_) => "InvalidData", + FsOpsError::StripPrefix(_) => "Error", + FsOpsError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + FsOpsError::InvalidSeekMode(_) => "TypeError", + FsOpsError::InvalidControlCharacter(_) => "Error", + FsOpsError::InvalidCharacter(_) => "Error", + #[cfg(windows)] + FsOpsError::InvalidTrailingCharacter => "Error", + FsOpsError::NotCapableAccess { .. } => "NotCapable", + FsOpsError::NotCapable(_) => "NotCapable", + } +} + fn get_kv_error(error: &KvError) -> &'static str { match error { KvError::DatabaseHandler(e) | KvError::Resource(e) | KvError::Kv(e) => { @@ -470,6 +500,7 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .or_else(|| e.downcast_ref::().map(get_web_blob_error_class)) .or_else(|| e.downcast_ref::().map(|_| "TypeError")) .or_else(|| e.downcast_ref::().map(get_ffi_repr_error_class)) + .or_else(|| e.downcast_ref::().map(get_fs_error)) .or_else(|| { e.downcast_ref::() .map(get_ffi_dlfcn_error_class) From 1bccf45ecb8b5ce2aa685d650c6654bf6c25e605 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:11:06 -0700 Subject: [PATCH 03/16] fix(ext/node): properly map reparse point error in readlink (#26375) --- ext/node/ops/winerror.rs | 1 + ext/node/polyfills/_fs/_fs_readlink.ts | 33 ++++++++++++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ext/node/ops/winerror.rs b/ext/node/ops/winerror.rs index c0d66f7d0b..e9dbadb6f7 100644 --- a/ext/node/ops/winerror.rs +++ b/ext/node/ops/winerror.rs @@ -66,6 +66,7 @@ pub fn op_node_sys_to_uv_error(err: i32) -> String { ERROR_INVALID_PARAMETER => "EINVAL", WSAEINVAL => "EINVAL", WSAEPFNOSUPPORT => "EINVAL", + ERROR_NOT_A_REPARSE_POINT => "EINVAL", ERROR_BEGINNING_OF_MEDIA => "EIO", ERROR_BUS_RESET => "EIO", ERROR_CRC => "EIO", diff --git a/ext/node/polyfills/_fs/_fs_readlink.ts b/ext/node/polyfills/_fs/_fs_readlink.ts index 5f23127982..08bea843fa 100644 --- a/ext/node/polyfills/_fs/_fs_readlink.ts +++ b/ext/node/polyfills/_fs/_fs_readlink.ts @@ -4,13 +4,10 @@ // deno-lint-ignore-file prefer-primordials import { TextEncoder } from "ext:deno_web/08_text_encoding.js"; -import { - intoCallbackAPIWithIntercept, - MaybeEmpty, - notImplemented, -} from "ext:deno_node/_utils.ts"; +import { MaybeEmpty, notImplemented } from "ext:deno_node/_utils.ts"; import { pathFromURL } from "ext:deno_web/00_infra.js"; import { promisify } from "ext:deno_node/internal/util.mjs"; +import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; type ReadlinkCallback = ( err: MaybeEmpty, @@ -69,12 +66,17 @@ export function readlink( const encoding = getEncoding(optOrCallback); - intoCallbackAPIWithIntercept( - Deno.readLink, - (data: string): string | Uint8Array => maybeEncode(data, encoding), - cb, - path, - ); + Deno.readLink(path).then((data: string) => { + const res = maybeEncode(data, encoding); + if (cb) cb(null, res); + }, (err: Error) => { + if (cb) { + (cb as (e: Error) => void)(denoErrorToNodeError(err, { + syscall: "readlink", + path, + })); + } + }); } export const readlinkPromise = promisify(readlink) as ( @@ -88,5 +90,12 @@ export function readlinkSync( ): string | Uint8Array { path = path instanceof URL ? pathFromURL(path) : path; - return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + try { + return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + } catch (error) { + throw denoErrorToNodeError(error, { + syscall: "readlink", + path, + }); + } } From 4b99cde504854baabdcf912f2fce3a7448119653 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 18 Oct 2024 20:38:57 +0200 Subject: [PATCH 04/16] fix(npm): ensure scoped package name is encoded in URLs (#26390) Fixes https://github.com/denoland/deno/issues/26385 --- cli/npm/managed/cache/registry_info.rs | 15 ++++++-- tests/integration/compile_tests.rs | 2 +- tests/integration/npm_tests.rs | 36 +++++++++---------- tests/integration/run_tests.rs | 2 +- tests/specs/add/dev/add.out | 2 +- tests/specs/add/exact_version/npm_add.out | 2 +- .../specs/add/only_unstable_versions/add.out | 2 +- .../add/package_json_and_deno_json/add.out | 4 +-- .../add_esm_basic.out | 2 +- tests/specs/bench/package_json/install.out | 2 +- tests/specs/cache/package_json/main.cache.out | 2 +- tests/specs/check/cjs_default_export/main.out | 2 +- tests/specs/check/package_json/install.out | 2 +- .../check/package_json_auto_install/check.out | 2 +- .../check/package_json_fail_check/install.out | 2 +- .../package_json_with_deno_json/install.out | 2 +- tests/specs/compile/npmrc_byonm/install.out | 4 +-- .../specs/info/package_json_basic/install.out | 2 +- .../install/future_install_global/install.out | 2 +- .../future_install_local_add_deno/install.out | 2 +- .../future_install_local_add_npm/install.out | 2 +- .../future_install_local_deno/install.out | 2 +- .../future_install_node_modules/install.out | 2 +- .../install_add_dep_existing/install.out | 2 +- .../specs/install/install_add_dev/install.out | 2 +- .../install_add_dev_existing/install.out | 2 +- .../install_deprecated_package/install.out | 2 +- .../install/install_entrypoint/install.out | 2 +- .../install/install_entrypoint/lifecycle.out | 4 +-- .../non_existent_optional_peer/install.out | 2 +- .../workspace_member_with_tag_dep/install.out | 2 +- .../frozen_new_dep_dynamic_npm.out | 2 +- .../frozen_lockfile/frozen_new_dep_run.out | 2 +- .../frozen_package_json_changed.out | 2 +- .../frozen_lockfile/no_lockfile_run.out | 2 +- .../lockfile/only_package_json/install.out | 2 +- .../main.out | 2 +- .../adding_npm_dep_in_dynamic_import/main.out | 4 +-- .../npm/bin_entries_prefer_closer/install.out | 4 +-- .../npm/check_all/check_errors/main_all.out | 2 +- .../npm/check_all/check_errors/main_local.out | 2 +- .../npm/check_local/check_errors/main_all.out | 2 +- .../check_local/check_errors/main_local.out | 2 +- .../file_dts_dmts_dcts/main.out | 2 +- .../specs/npm/check_pkg_json_import/main.out | 2 +- tests/specs/npm/cjs_import_dual/check.out | 4 +-- tests/specs/npm/cjs_import_dual/run.out | 4 +-- .../cjs_local_global_decls/main.out | 2 +- .../cjs_pkg_imports/cjs_pkg_imports/main.out | 2 +- .../compare_globals/compare_globals/main.out | 4 +-- tests/specs/npm/conditional_exports/main.out | 4 +-- .../main.out | 4 +-- .../directory_import/folder_index_js.out | 2 +- .../directory_import/folder_no_index.out | 2 +- .../directory_import/folder_index_js.out | 2 +- .../directory_import/folder_no_index.out | 2 +- .../dynamic_npm_resolution_failure/main.out | 2 +- .../specs/npm/esm_import_cjs_default/main.out | 4 +-- .../specs/npm/import_map/import_map/main.out | 2 +- .../import_not_defined.out | 2 +- .../imports_package_json/main.out | 2 +- .../sub_path_import_not_defined.out | 2 +- .../import_not_defined.out | 2 +- .../imports_package_json/main.out | 2 +- .../sub_path_import_not_defined.out | 2 +- .../import_not_defined.out | 2 +- .../imports_package_json/main.out | 2 +- .../sub_path_import_not_defined.out | 2 +- .../peer_deps_with_copied_folders/main.out | 6 ++-- .../main_node_modules_reload.out | 6 ++-- .../peer_deps_with_copied_folders/main.out | 6 ++-- .../main_node_modules_reload.out | 6 ++-- .../all_lifecycles_not_run.out | 4 +-- .../all_lifecycles_not_run_global.out | 4 +-- .../npm/lifecycle_scripts/conflicting_bin.out | 6 ++-- .../future_install_all_lifecycles_not_run.out | 4 +-- .../npm/lifecycle_scripts/no_deno_json.out | 4 +-- .../lifecycle_scripts/node_gyp_not_found.out | 2 +- .../lifecycle_scripts/node_gyp_not_run.out | 2 +- .../lifecycle_scripts/only_warns_first1.out | 4 +-- tests/specs/npm/lossy_utf8_module/main.out | 2 +- .../npm/lossy_utf8_package_json/main.out | 2 +- tests/specs/npm/lossy_utf8_script/main.out | 2 +- .../npm/lossy_utf8_script_from_cjs/main.out | 2 +- .../mixed_case_package_name/global.out | 4 +-- .../mixed_case_package_name/local.out | 4 +-- .../mixed_case_package_name/global.out | 4 +-- .../mixed_case_package_name/local.out | 4 +-- tests/specs/npm/no_types_cjs/main.out | 2 +- .../no_types_in_conditional_exports/main.out | 2 +- .../specs/npm/node_modules_import/install.out | 2 +- .../npm/node_modules_import_auto/main.out | 2 +- .../node_modules_import_auto/main_check.out | 2 +- .../specs/npm/non_existent_dep/__test__.jsonc | 2 +- .../non_existent_dep_version/__test__.jsonc | 2 +- tests/specs/npm/npmrc/info.out | 4 +-- tests/specs/npm/npmrc/install.out | 4 +-- .../npm/npmrc_bad_registry_config/main.out | 4 +-- tests/specs/npm/npmrc_bad_token/main.out | 4 +-- tests/specs/npm/npmrc_basic_auth/install.out | 4 +-- tests/specs/npm/npmrc_deno_json/main.out | 2 +- tests/specs/npm/npmrc_homedir/install.out | 4 +-- .../npmrc_not_next_to_package_json/main.out | 2 +- .../npmrc_password_no_username/install.out | 2 +- .../npmrc_tarball_other_server/fail/main.out | 2 +- .../success/main.out | 2 +- .../npmrc_username_no_password/install.out | 2 +- .../npm/npmrc_username_password/install.out | 4 +-- .../permissions_outside_package/main.out | 2 +- .../main.out | 4 +-- .../reserved_word_exports/main.out | 2 +- .../main.out | 2 +- tests/specs/npm/types_d_ext/d_ext/main.out | 2 +- .../types_entry_value_not_exists/main.out | 2 +- .../types_exports_import_types/main.out | 2 +- tests/specs/npm/types_general/main.out | 6 ++-- .../types_no_types_entry/main.out | 4 +-- .../typescript_file_in_package/main.out | 2 +- .../workspace_basic/b/main_global_cache.out | 2 +- .../b/main_node_modules_dir.out | 2 +- tests/specs/npm/workspace_basic/main.out | 2 +- .../bare_node_builtins/bare_node_builtins.out | 2 +- .../publish/missing_constraint/publish.out | 2 +- .../publish/node_specifier/node_specifier.out | 2 +- tests/specs/remove/package_json/rm_add.out | 2 +- tests/specs/run/no_deno_json/code/install.out | 4 +-- .../package_json/invalid_value/error_auto.out | 2 +- .../run/package_json/invalid_value/task.out | 2 +- tests/specs/task/bin_package/task.out | 2 +- .../task/bin_pkg_with_scope_auto/bin_auto.out | 2 +- .../task/bin_pkg_with_scope_auto/bin_none.out | 2 +- .../task/bin_pkg_with_scope_none/bin_none.out | 2 +- .../both_package_json_selected/install.out | 2 +- .../bin.out | 2 +- .../specs/test/package_json_basic/install.out | 2 +- .../package_json_basic_auto_install/test.out | 2 +- .../main_compile_folder.out | 2 +- .../peer_deps_with_copied_folders/main.out | 6 ++-- .../main_node_modules_reload.out | 6 ++-- .../npm/run_existing_npm_package/main.out | 2 +- 140 files changed, 215 insertions(+), 204 deletions(-) diff --git a/cli/npm/managed/cache/registry_info.rs b/cli/npm/managed/cache/registry_info.rs index 597a2283f4..6c4a7503b5 100644 --- a/cli/npm/managed/cache/registry_info.rs +++ b/cli/npm/managed/cache/registry_info.rs @@ -242,6 +242,14 @@ impl RegistryInfoDownloader { fn get_package_url(&self, name: &str) -> Url { let registry_url = self.npmrc.get_registry_url(name); + // The '/' character in scoped package names "@scope/name" must be + // encoded for older third party registries. Newer registries and + // npm itself support both ways + // - encoded: https://registry.npmjs.org/@rollup%2fplugin-json + // - non-ecoded: https://registry.npmjs.org/@rollup/plugin-json + // To support as many third party registries as possible we'll + // always encode the '/' character. + // list of all characters used in npm packages: // !, ', (, ), *, -, ., /, [0-9], @, [A-Za-z], _, ~ const ASCII_SET: percent_encoding::AsciiSet = @@ -253,11 +261,14 @@ impl RegistryInfoDownloader { .remove(b'*') .remove(b'-') .remove(b'.') - .remove(b'/') .remove(b'@') .remove(b'_') .remove(b'~'); let name = percent_encoding::utf8_percent_encode(name, &ASCII_SET); - registry_url.join(&name.to_string()).unwrap() + registry_url + // Ensure that scoped package name percent encoding is lower cased + // to match npm. + .join(&name.to_string().replace("%2F", "%2f")) + .unwrap() } } diff --git a/tests/integration/compile_tests.rs b/tests/integration/compile_tests.rs index a8ea5b038b..fa6364a136 100644 --- a/tests/integration/compile_tests.rs +++ b/tests/integration/compile_tests.rs @@ -1111,7 +1111,7 @@ console.log(getValue());"#, .run(); output.assert_exit_code(0); output.assert_matches_text( - r#"Download http://localhost:4260/@denotest/esm-basic + r#"Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file:///[WILDCARD]/main.ts diff --git a/tests/integration/npm_tests.rs b/tests/integration/npm_tests.rs index f47b1bdd4a..f8c6eebf39 100644 --- a/tests/integration/npm_tests.rs +++ b/tests/integration/npm_tests.rs @@ -876,7 +876,7 @@ fn auto_discover_lock_file() { .run(); output .assert_matches_text( -r#"Download http://localhost:4260/@denotest/bin +r#"Download http://localhost:4260/@denotest%2fbin error: Integrity check failed for package: "npm:@denotest/bin@1.0.0". Unable to verify that the package is the same as when the lockfile was generated. @@ -1058,10 +1058,10 @@ fn reload_info_not_found_cache_but_exists_remote() { .run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/esm-basic\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", - "Download http://localhost:4260/@denotest/cjs-default-export/1.0.0.tgz\n", + "Download http://localhost:4260/@denotest%2fesm-basic\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", + "Download http://localhost:4260/@denotestcjs-default-export/1.0.0.tgz\n", "Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz\n", "Download http://localhost:4260/@denotest/esm-import-cjs-default/1.0.0.tgz\n", "[UNORDERED_END]\n", @@ -1088,8 +1088,8 @@ fn reload_info_not_found_cache_but_exists_remote() { let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", "[UNORDERED_END]\n", "Node esm importing node cjs\n[WILDCARD]", )); @@ -1120,8 +1120,8 @@ fn reload_info_not_found_cache_but_exists_remote() { let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", "[UNORDERED_END]\n", "Node esm importing node cjs\n[WILDCARD]", )); @@ -1159,8 +1159,8 @@ fn reload_info_not_found_cache_but_exists_remote() { let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", "[UNORDERED_END]\n", "[UNORDERED_START]\n", "Initialize @denotest/cjs-default-export@1.0.0\n", @@ -1196,9 +1196,9 @@ fn reload_info_not_found_cache_but_exists_remote() { let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/esm-basic\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", + "Download http://localhost:4260/@denotest%2fesm-basic\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", "[UNORDERED_END]\n", "Initialize @denotest/esm-basic@1.0.0\n", "Node esm importing node cjs\n[WILDCARD]", @@ -1237,9 +1237,9 @@ fn reload_info_not_found_cache_but_exists_remote() { let output = test_context.new_command().args("run main.ts").run(); output.assert_matches_text(concat!( "[UNORDERED_START]\n", - "Download http://localhost:4260/@denotest/cjs-default-export\n", - "Download http://localhost:4260/@denotest/esm-basic\n", - "Download http://localhost:4260/@denotest/esm-import-cjs-default\n", + "Download http://localhost:4260/@denotest%2fcjs-default-export\n", + "Download http://localhost:4260/@denotest%2fesm-basic\n", + "Download http://localhost:4260/@denotest%2fesm-import-cjs-default\n", "[UNORDERED_END]\n", "Node esm importing node cjs\n[WILDCARD]", )); @@ -1419,7 +1419,7 @@ fn top_level_install_package_json_explicit_opt_in() { temp_dir.write("main.ts", "console.log(5);"); let output = test_context.new_command().args("cache main.ts").run(); output.assert_matches_text(concat!( - "Download http://localhost:4260/@denotest/esm-basic\n", + "Download http://localhost:4260/@denotest%2fesm-basic\n", "Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz\n", "Initialize @denotest/esm-basic@1.0.0\n", )); diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index ca24f18f46..db9f79556e 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -898,7 +898,7 @@ fn lock_redirects() { .run() .assert_matches_text(concat!( "Download http://localhost:4545/echo.ts\n", - "Download http://localhost:4260/@denotest/esm-basic\n", + "Download http://localhost:4260/@denotest%2fesm-basic\n", "Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz\n", "Hi, there", )); diff --git a/tests/specs/add/dev/add.out b/tests/specs/add/dev/add.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/add/dev/add.out +++ b/tests/specs/add/dev/add.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/add/exact_version/npm_add.out b/tests/specs/add/exact_version/npm_add.out index 93894a020c..2f73e5afb5 100644 --- a/tests/specs/add/exact_version/npm_add.out +++ b/tests/specs/add/exact_version/npm_add.out @@ -1,3 +1,3 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz diff --git a/tests/specs/add/only_unstable_versions/add.out b/tests/specs/add/only_unstable_versions/add.out index 48462b8bea..8bd8d93038 100644 --- a/tests/specs/add/only_unstable_versions/add.out +++ b/tests/specs/add/only_unstable_versions/add.out @@ -1,3 +1,3 @@ Add npm:@denotest/unstable@1.0.0-beta.1 -Download http://localhost:4260/@denotest/unstable +Download http://localhost:4260/@denotest%2funstable Download http://localhost:4260/@denotest/unstable/1.0.0-beta.1.tgz diff --git a/tests/specs/add/package_json_and_deno_json/add.out b/tests/specs/add/package_json_and_deno_json/add.out index 5577a55acc..4ce7bb8ee1 100644 --- a/tests/specs/add/package_json_and_deno_json/add.out +++ b/tests/specs/add/package_json_and_deno_json/add.out @@ -2,9 +2,9 @@ Add npm:@denotest/esm-basic@1.0.0 Add jsr:@denotest/add@1.0.0 Add npm:@denotest/say-hello@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz -Download http://localhost:4260/@denotest/say-hello +Download http://localhost:4260/@denotest%2fsay-hello Download http://localhost:4260/@denotest/say-hello/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Initialize @denotest/say-hello@1.0.0 diff --git a/tests/specs/add/package_json_and_deno_json/add_esm_basic.out b/tests/specs/add/package_json_and_deno_json/add_esm_basic.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/add/package_json_and_deno_json/add_esm_basic.out +++ b/tests/specs/add/package_json_and_deno_json/add_esm_basic.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/bench/package_json/install.out b/tests/specs/bench/package_json/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/bench/package_json/install.out +++ b/tests/specs/bench/package_json/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/cache/package_json/main.cache.out b/tests/specs/cache/package_json/main.cache.out index f5a1cd15ee..af6bcb03b3 100644 --- a/tests/specs/cache/package_json/main.cache.out +++ b/tests/specs/cache/package_json/main.cache.out @@ -1,2 +1,2 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz diff --git a/tests/specs/check/cjs_default_export/main.out b/tests/specs/check/cjs_default_export/main.out index f284593a19..6c5f18b632 100644 --- a/tests/specs/check/cjs_default_export/main.out +++ b/tests/specs/check/cjs_default_export/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/cjs-default-export +Download http://localhost:4260/@denotest%2fcjs-default-export Download http://localhost:4260/@denotest/cjs-default-export/1.0.0.tgz Check file:///[WILDCARD]/main.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. diff --git a/tests/specs/check/package_json/install.out b/tests/specs/check/package_json/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/check/package_json/install.out +++ b/tests/specs/check/package_json/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/check/package_json_auto_install/check.out b/tests/specs/check/package_json_auto_install/check.out index db9bd198ca..6b75c8642d 100644 --- a/tests/specs/check/package_json_auto_install/check.out +++ b/tests/specs/check/package_json_auto_install/check.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file://[WILDCARD]/main.ts diff --git a/tests/specs/check/package_json_fail_check/install.out b/tests/specs/check/package_json_fail_check/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/check/package_json_fail_check/install.out +++ b/tests/specs/check/package_json_fail_check/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/check/package_json_with_deno_json/install.out b/tests/specs/check/package_json_with_deno_json/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/check/package_json_with_deno_json/install.out +++ b/tests/specs/check/package_json_with_deno_json/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/compile/npmrc_byonm/install.out b/tests/specs/compile/npmrc_byonm/install.out index 30643527e6..f2ea78c3f8 100644 --- a/tests/specs/compile/npmrc_byonm/install.out +++ b/tests/specs/compile/npmrc_byonm/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4261/@denotest/basic -Download http://localhost:4262/@denotest2/basic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz Initialize @denotest2/basic@1.0.0 diff --git a/tests/specs/info/package_json_basic/install.out b/tests/specs/info/package_json_basic/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/info/package_json_basic/install.out +++ b/tests/specs/info/package_json_basic/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/future_install_global/install.out b/tests/specs/install/future_install_global/install.out index 58cd88ada1..0fa1b97191 100644 --- a/tests/specs/install/future_install_global/install.out +++ b/tests/specs/install/future_install_global/install.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz ✅ Successfully installed deno-test-bin[WILDCARD] [WILDCARD] diff --git a/tests/specs/install/future_install_local_add_deno/install.out b/tests/specs/install/future_install_local_add_deno/install.out index 93894a020c..2f73e5afb5 100644 --- a/tests/specs/install/future_install_local_add_deno/install.out +++ b/tests/specs/install/future_install_local_add_deno/install.out @@ -1,3 +1,3 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz diff --git a/tests/specs/install/future_install_local_add_npm/install.out b/tests/specs/install/future_install_local_add_npm/install.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/install/future_install_local_add_npm/install.out +++ b/tests/specs/install/future_install_local_add_npm/install.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/future_install_local_deno/install.out b/tests/specs/install/future_install_local_deno/install.out index 15263a37b5..d0972f69e8 100644 --- a/tests/specs/install/future_install_local_deno/install.out +++ b/tests/specs/install/future_install_local_deno/install.out @@ -8,7 +8,7 @@ Download http://localhost:4545/subdir/print_hello.ts Download http://127.0.0.1:4250/@denotest/add/meta.json Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Download http://127.0.0.1:4250/@std/testing/1.0.0/bdd.ts Download http://127.0.0.1:4250/@std/testing/1.0.0/types.ts diff --git a/tests/specs/install/future_install_node_modules/install.out b/tests/specs/install/future_install_node_modules/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/install/future_install_node_modules/install.out +++ b/tests/specs/install/future_install_node_modules/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/install_add_dep_existing/install.out b/tests/specs/install/install_add_dep_existing/install.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/install/install_add_dep_existing/install.out +++ b/tests/specs/install/install_add_dep_existing/install.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/install_add_dev/install.out b/tests/specs/install/install_add_dev/install.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/install/install_add_dev/install.out +++ b/tests/specs/install/install_add_dev/install.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/install_add_dev_existing/install.out b/tests/specs/install/install_add_dev_existing/install.out index 42161f3ae1..44bd9cac7e 100644 --- a/tests/specs/install/install_add_dev_existing/install.out +++ b/tests/specs/install/install_add_dev_existing/install.out @@ -1,4 +1,4 @@ Add npm:@denotest/esm-basic@1.0.0 -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/install/install_deprecated_package/install.out b/tests/specs/install/install_deprecated_package/install.out index 95c0759092..32661d971d 100644 --- a/tests/specs/install/install_deprecated_package/install.out +++ b/tests/specs/install/install_deprecated_package/install.out @@ -1,5 +1,5 @@ Add npm:@denotest/deprecated-package@1.0.0 -Download http://localhost:4260/@denotest/deprecated-package +Download http://localhost:4260/@denotest%2fdeprecated-package Download http://localhost:4260/@denotest/deprecated-package/1.0.0.tgz Initialize @denotest/deprecated-package@1.0.0 Warning The following packages are deprecated: diff --git a/tests/specs/install/install_entrypoint/install.out b/tests/specs/install/install_entrypoint/install.out index d702cf45ac..da10f3aa03 100644 --- a/tests/specs/install/install_entrypoint/install.out +++ b/tests/specs/install/install_entrypoint/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] Download http://127.0.0.1:4250/@denotest/add/meta.json -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz diff --git a/tests/specs/install/install_entrypoint/lifecycle.out b/tests/specs/install/install_entrypoint/lifecycle.out index 2fd2798754..b302ea3341 100644 --- a/tests/specs/install/install_entrypoint/lifecycle.out +++ b/tests/specs/install/install_entrypoint/lifecycle.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-lifecycle-scripts -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fnode-lifecycle-scripts +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/node-lifecycle-scripts/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/node-lifecycle-scripts@1.0.0 diff --git a/tests/specs/install/non_existent_optional_peer/install.out b/tests/specs/install/non_existent_optional_peer/install.out index 292c1a4e2f..694d4b51b8 100644 --- a/tests/specs/install/non_existent_optional_peer/install.out +++ b/tests/specs/install/non_existent_optional_peer/install.out @@ -1,5 +1,5 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/non-existent-optional-peer +Download http://localhost:4260/@denotest%2fnon-existent-optional-peer Download http://localhost:4260/uWebSockets.js Download http://localhost:4260/@denotest/non-existent-optional-peer/1.0.0.tgz [UNORDERED_END] diff --git a/tests/specs/install/workspace_member_with_tag_dep/install.out b/tests/specs/install/workspace_member_with_tag_dep/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/install/workspace_member_with_tag_dep/install.out +++ b/tests/specs/install/workspace_member_with_tag_dep/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_dynamic_npm.out b/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_dynamic_npm.out index 4ae4d059cc..4fc016c57c 100644 --- a/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_dynamic_npm.out +++ b/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_dynamic_npm.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/subtract +Download http://localhost:4260/@denotest%2fsubtract error: Uncaught (in promise) TypeError: The lockfile is out of date. Run `deno install --frozen=false`, or rerun with `--frozen=false` to update it. changes: 4 | - "npm:@denotest/add@1": "1.0.0" diff --git a/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_run.out b/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_run.out index 057b9632b3..07b37b925a 100644 --- a/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_run.out +++ b/tests/specs/lockfile/frozen_lockfile/frozen_new_dep_run.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/subtract +Download http://localhost:4260/@denotest%2fsubtract error: The lockfile is out of date. Run `deno install --frozen=false`, or rerun with `--frozen=false` to update it. changes: 4 | - "npm:@denotest/add@1": "1.0.0" diff --git a/tests/specs/lockfile/frozen_lockfile/frozen_package_json_changed.out b/tests/specs/lockfile/frozen_lockfile/frozen_package_json_changed.out index dc31a1a653..626007478f 100644 --- a/tests/specs/lockfile/frozen_lockfile/frozen_package_json_changed.out +++ b/tests/specs/lockfile/frozen_lockfile/frozen_package_json_changed.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin error: The lockfile is out of date. Run `deno install --frozen=false`, or rerun with `--frozen=false` to update it. changes: 4 | - "npm:@denotest/add@1": "1.0.0" diff --git a/tests/specs/lockfile/frozen_lockfile/no_lockfile_run.out b/tests/specs/lockfile/frozen_lockfile/no_lockfile_run.out index 27dc66fd2f..047bfdb4ac 100644 --- a/tests/specs/lockfile/frozen_lockfile/no_lockfile_run.out +++ b/tests/specs/lockfile/frozen_lockfile/no_lockfile_run.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/add +Download http://localhost:4260/@denotest%2fadd error: The lockfile is out of date. Run `deno install --frozen=false`, or rerun with `--frozen=false` to update it. changes: 1 | - diff --git a/tests/specs/lockfile/only_package_json/install.out b/tests/specs/lockfile/only_package_json/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/lockfile/only_package_json/install.out +++ b/tests/specs/lockfile/only_package_json/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/node/cjs_reexport_same_specifier_in_sub_folder/main.out b/tests/specs/node/cjs_reexport_same_specifier_in_sub_folder/main.out index 321d995b8a..c50f064d7f 100644 --- a/tests/specs/node/cjs_reexport_same_specifier_in_sub_folder/main.out +++ b/tests/specs/node/cjs_reexport_same_specifier_in_sub_folder/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/cjs-reexport-same-specifier-in-sub-folder +Download http://localhost:4260/@denotest%2fcjs-reexport-same-specifier-in-sub-folder Download http://localhost:4260/@denotest/cjs-reexport-same-specifier-in-sub-folder/1.0.0.tgz [Module: null prototype] { default: { main: [Getter], sub: [Getter] }, diff --git a/tests/specs/npm/adding_npm_dep_in_dynamic_import/main.out b/tests/specs/npm/adding_npm_dep_in_dynamic_import/main.out index fe612aa3c1..44c76f28d1 100644 --- a/tests/specs/npm/adding_npm_dep_in_dynamic_import/main.out +++ b/tests/specs/npm/adding_npm_dep_in_dynamic_import/main.out @@ -1,8 +1,8 @@ -Download http://localhost:4260/@denotest/add +Download http://localhost:4260/@denotest%2fadd Download http://localhost:4260/@denotest/add/1.0.0.tgz Initialize @denotest/add@1.0.0 3 -Download http://localhost:4260/@denotest/subtract +Download http://localhost:4260/@denotest%2fsubtract Download http://localhost:4260/@denotest/subtract/1.0.0.tgz Initialize @denotest/subtract@1.0.0 1 diff --git a/tests/specs/npm/bin_entries_prefer_closer/install.out b/tests/specs/npm/bin_entries_prefer_closer/install.out index 1d3a995b80..1c9472f46f 100644 --- a/tests/specs/npm/bin_entries_prefer_closer/install.out +++ b/tests/specs/npm/bin_entries_prefer_closer/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/transitive-bin -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2ftransitive-bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/1.0.0.tgz Download http://localhost:4260/@denotest/transitive-bin/1.0.0.tgz Download http://localhost:4260/@denotest/bin/0.7.0.tgz diff --git a/tests/specs/npm/check_all/check_errors/main_all.out b/tests/specs/npm/check_all/check_errors/main_all.out index 4c624c0eaa..c7797e43d2 100644 --- a/tests/specs/npm/check_all/check_errors/main_all.out +++ b/tests/specs/npm/check_all/check_errors/main_all.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/check-error +Download http://localhost:4260/@denotest%2fcheck-error Download http://localhost:4260/@denotest/check-error/1.0.0.tgz Check file:///[WILDCARD]/check_errors/main.ts error: TS2506 [ERROR]: 'Class1' is referenced directly or indirectly in its own base expression. diff --git a/tests/specs/npm/check_all/check_errors/main_local.out b/tests/specs/npm/check_all/check_errors/main_local.out index 4d3a892e74..ac58a29c76 100644 --- a/tests/specs/npm/check_all/check_errors/main_local.out +++ b/tests/specs/npm/check_all/check_errors/main_local.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/check-error +Download http://localhost:4260/@denotest%2fcheck-error Download http://localhost:4260/@denotest/check-error/1.0.0.tgz Check file:///[WILDCARD]/check_errors/main.ts error: TS2339 [ERROR]: Property 'Asdf' does not exist on type 'typeof import("file:///[WILDCARD]/@denotest/check-error/1.0.0/index.d.ts")'. diff --git a/tests/specs/npm/check_local/check_errors/main_all.out b/tests/specs/npm/check_local/check_errors/main_all.out index 4c624c0eaa..c7797e43d2 100644 --- a/tests/specs/npm/check_local/check_errors/main_all.out +++ b/tests/specs/npm/check_local/check_errors/main_all.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/check-error +Download http://localhost:4260/@denotest%2fcheck-error Download http://localhost:4260/@denotest/check-error/1.0.0.tgz Check file:///[WILDCARD]/check_errors/main.ts error: TS2506 [ERROR]: 'Class1' is referenced directly or indirectly in its own base expression. diff --git a/tests/specs/npm/check_local/check_errors/main_local.out b/tests/specs/npm/check_local/check_errors/main_local.out index 1a150b5e69..1eb8eef9fe 100644 --- a/tests/specs/npm/check_local/check_errors/main_local.out +++ b/tests/specs/npm/check_local/check_errors/main_local.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/check-error +Download http://localhost:4260/@denotest%2fcheck-error Download http://localhost:4260/@denotest/check-error/1.0.0.tgz Check file:///[WILDCARD]/check_errors/main.ts error: TS2339 [ERROR]: Property 'Asdf' does not exist on type 'typeof import("file:///[WILDCARD]/@denotest/check-error/1.0.0/index.d.ts")'. diff --git a/tests/specs/npm/check_package_file_dts_dmts_dcts/file_dts_dmts_dcts/main.out b/tests/specs/npm/check_package_file_dts_dmts_dcts/file_dts_dmts_dcts/main.out index 507d2c2f79..6ab72f53b7 100644 --- a/tests/specs/npm/check_package_file_dts_dmts_dcts/file_dts_dmts_dcts/main.out +++ b/tests/specs/npm/check_package_file_dts_dmts_dcts/file_dts_dmts_dcts/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/file-dts-dmts-dcts +Download http://localhost:4260/@denotest%2ffile-dts-dmts-dcts Download http://localhost:4260/@denotest/file-dts-dmts-dcts/1.0.0.tgz Check file:///[WILDCARD]/main.ts error: TS2322 [ERROR]: Type '5' is not assignable to type '"dts"'. diff --git a/tests/specs/npm/check_pkg_json_import/main.out b/tests/specs/npm/check_pkg_json_import/main.out index b60509c9a7..11f94272a5 100644 --- a/tests/specs/npm/check_pkg_json_import/main.out +++ b/tests/specs/npm/check_pkg_json_import/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/types-pkg-json-import +Download http://localhost:4260/@denotest%2ftypes-pkg-json-import Download http://localhost:4260/@denotest/types-pkg-json-import/1.0.0.tgz Check file:///[WILDLINE]/main.ts diff --git a/tests/specs/npm/cjs_import_dual/check.out b/tests/specs/npm/cjs_import_dual/check.out index be1fe86a69..c2d1daa17b 100644 --- a/tests/specs/npm/cjs_import_dual/check.out +++ b/tests/specs/npm/cjs_import_dual/check.out @@ -1,5 +1,5 @@ -Download http://localhost:4260/@denotest/cjs-import-dual -Download http://localhost:4260/@denotest/dual-cjs-esm +Download http://localhost:4260/@denotest%2fcjs-import-dual +Download http://localhost:4260/@denotest%2fdual-cjs-esm Download http://localhost:4260/@denotest/cjs-import-dual/1.0.0.tgz Download http://localhost:4260/@denotest/dual-cjs-esm/1.0.0.tgz Check file:///[WILDLINE]/cjs_import_dual/main.ts diff --git a/tests/specs/npm/cjs_import_dual/run.out b/tests/specs/npm/cjs_import_dual/run.out index 2c05e125e1..0307bf2503 100644 --- a/tests/specs/npm/cjs_import_dual/run.out +++ b/tests/specs/npm/cjs_import_dual/run.out @@ -1,5 +1,5 @@ -Download http://localhost:4260/@denotest/cjs-import-dual -Download http://localhost:4260/@denotest/dual-cjs-esm +Download http://localhost:4260/@denotest%2fcjs-import-dual +Download http://localhost:4260/@denotest%2fdual-cjs-esm Download http://localhost:4260/@denotest/cjs-import-dual/1.0.0.tgz Download http://localhost:4260/@denotest/dual-cjs-esm/1.0.0.tgz cjs diff --git a/tests/specs/npm/cjs_local_global_decls/cjs_local_global_decls/main.out b/tests/specs/npm/cjs_local_global_decls/cjs_local_global_decls/main.out index 5e7a36c8d0..5c48b5b5cf 100644 --- a/tests/specs/npm/cjs_local_global_decls/cjs_local_global_decls/main.out +++ b/tests/specs/npm/cjs_local_global_decls/cjs_local_global_decls/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/cjs-local-global-decls +Download http://localhost:4260/@denotest%2fcjs-local-global-decls Download http://localhost:4260/@denotest/cjs-local-global-decls/1.0.0.tgz Loaded. diff --git a/tests/specs/npm/cjs_pkg_imports/cjs_pkg_imports/main.out b/tests/specs/npm/cjs_pkg_imports/cjs_pkg_imports/main.out index 661146bd03..f9390fb1bc 100644 --- a/tests/specs/npm/cjs_pkg_imports/cjs_pkg_imports/main.out +++ b/tests/specs/npm/cjs_pkg_imports/cjs_pkg_imports/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/cjs-pkg-imports +Download http://localhost:4260/@denotest%2fcjs-pkg-imports Download http://localhost:4260/@denotest/cjs-pkg-imports/1.0.0.tgz { crypto: Crypto { subtle: SubtleCrypto {} }, number: 5 } diff --git a/tests/specs/npm/compare_globals/compare_globals/main.out b/tests/specs/npm/compare_globals/compare_globals/main.out index a1b0a566d5..5be6125b08 100644 --- a/tests/specs/npm/compare_globals/compare_globals/main.out +++ b/tests/specs/npm/compare_globals/compare_globals/main.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@types/node +Download http://localhost:4260/@types%2fnode Download http://localhost:4260/undici-types -Download http://localhost:4260/@denotest/globals +Download http://localhost:4260/@denotest%2fglobals [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/globals/1.0.0.tgz diff --git a/tests/specs/npm/conditional_exports/main.out b/tests/specs/npm/conditional_exports/main.out index 8bd31321b6..8b5b2715be 100644 --- a/tests/specs/npm/conditional_exports/main.out +++ b/tests/specs/npm/conditional_exports/main.out @@ -1,8 +1,8 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/conditional-exports +Download http://localhost:4260/@denotest%2fconditional-exports Download http://localhost:4260/supports-esm Download http://localhost:4260/has-package-exports -Download http://localhost:4260/@ljharb/has-package-exports-patterns +Download http://localhost:4260/@ljharb%2fhas-package-exports-patterns [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/conditional-exports/1.0.0.tgz diff --git a/tests/specs/npm/conditional_exports_node_modules_dir/main.out b/tests/specs/npm/conditional_exports_node_modules_dir/main.out index 5fbba560d3..ee4a64be61 100644 --- a/tests/specs/npm/conditional_exports_node_modules_dir/main.out +++ b/tests/specs/npm/conditional_exports_node_modules_dir/main.out @@ -1,8 +1,8 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/conditional-exports +Download http://localhost:4260/@denotest%2fconditional-exports Download http://localhost:4260/supports-esm Download http://localhost:4260/has-package-exports -Download http://localhost:4260/@ljharb/has-package-exports-patterns +Download http://localhost:4260/@ljharb%2fhas-package-exports-patterns [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/conditional-exports/1.0.0.tgz diff --git a/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_index_js.out b/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_index_js.out index a700b8fdb6..c1eb2a4801 100644 --- a/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_index_js.out +++ b/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_index_js.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/sub-folders +Download http://localhost:4260/@denotest%2fsub-folders Download http://localhost:4260/@denotest/sub-folders/1.0.0.tgz error: Directory import [WILDCARD]folder_index_js is not supported resolving import from file:///[WILDCARD]/directory_import/folder_index_js.ts Did you mean to import index.js within the directory? diff --git a/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_no_index.out b/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_no_index.out index 844419b4c6..c19c4bcaa4 100644 --- a/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_no_index.out +++ b/tests/specs/npm/directory_import_folder_index_js/directory_import/folder_no_index.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/sub-folders +Download http://localhost:4260/@denotest%2fsub-folders Download http://localhost:4260/@denotest/sub-folders/1.0.0.tgz error: Directory import [WILDCARD]folder_no_index is not supported resolving import from file:///[WILDCARD]/folder_no_index.ts diff --git a/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_index_js.out b/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_index_js.out index a700b8fdb6..c1eb2a4801 100644 --- a/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_index_js.out +++ b/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_index_js.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/sub-folders +Download http://localhost:4260/@denotest%2fsub-folders Download http://localhost:4260/@denotest/sub-folders/1.0.0.tgz error: Directory import [WILDCARD]folder_index_js is not supported resolving import from file:///[WILDCARD]/directory_import/folder_index_js.ts Did you mean to import index.js within the directory? diff --git a/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_no_index.out b/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_no_index.out index 844419b4c6..c19c4bcaa4 100644 --- a/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_no_index.out +++ b/tests/specs/npm/directory_import_folder_no_index/directory_import/folder_no_index.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/sub-folders +Download http://localhost:4260/@denotest%2fsub-folders Download http://localhost:4260/@denotest/sub-folders/1.0.0.tgz error: Directory import [WILDCARD]folder_no_index is not supported resolving import from file:///[WILDCARD]/folder_no_index.ts diff --git a/tests/specs/npm/dynamic_npm_resolution_failure/main.out b/tests/specs/npm/dynamic_npm_resolution_failure/main.out index d9eb7182fa..8888c4f0ae 100644 --- a/tests/specs/npm/dynamic_npm_resolution_failure/main.out +++ b/tests/specs/npm/dynamic_npm_resolution_failure/main.out @@ -1,6 +1,6 @@ [UNORDERED_START] Download http://localhost:4260/chalk -Download http://localhost:4260/@denotest/dep-cannot-parse +Download http://localhost:4260/@denotest%2fdep-cannot-parse [UNORDERED_END] Download http://localhost:4260/chalk/chalk-5.0.1.tgz Hi diff --git a/tests/specs/npm/esm_import_cjs_default/main.out b/tests/specs/npm/esm_import_cjs_default/main.out index ec7962e5a3..6528a68d7c 100644 --- a/tests/specs/npm/esm_import_cjs_default/main.out +++ b/tests/specs/npm/esm_import_cjs_default/main.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/esm-import-cjs-default -Download http://localhost:4260/@denotest/cjs-default-export +Download http://localhost:4260/@denotest%2fesm-import-cjs-default +Download http://localhost:4260/@denotest%2fcjs-default-export Download http://localhost:4260/@denotest/cjs-default-export/1.0.0.tgz Download http://localhost:4260/@denotest/esm-import-cjs-default/1.0.0.tgz [UNORDERED_END] diff --git a/tests/specs/npm/import_map/import_map/main.out b/tests/specs/npm/import_map/import_map/main.out index 05f313d4e9..2698fb695c 100644 --- a/tests/specs/npm/import_map/import_map/main.out +++ b/tests/specs/npm/import_map/import_map/main.out @@ -1,6 +1,6 @@ [UNORDERED_START] Download http://localhost:4260/chalk -Download http://localhost:4260/@denotest/dual-cjs-esm +Download http://localhost:4260/@denotest%2fdual-cjs-esm [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/dual-cjs-esm/1.0.0.tgz diff --git a/tests/specs/npm/imports_package_json/imports_package_json/import_not_defined.out b/tests/specs/npm/imports_package_json/imports_package_json/import_not_defined.out index 70d1145a37..abfa414030 100644 --- a/tests/specs/npm/imports_package_json/imports_package_json/import_not_defined.out +++ b/tests/specs/npm/imports_package_json/imports_package_json/import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#not-defined" is not defined in package [WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/imports_package_json/imports_package_json/main.out b/tests/specs/npm/imports_package_json/imports_package_json/main.out index 979e355765..70d787b251 100644 --- a/tests/specs/npm/imports_package_json/imports_package_json/main.out +++ b/tests/specs/npm/imports_package_json/imports_package_json/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz hi bye diff --git a/tests/specs/npm/imports_package_json/imports_package_json/sub_path_import_not_defined.out b/tests/specs/npm/imports_package_json/imports_package_json/sub_path_import_not_defined.out index 7c803f2bf1..95524202ae 100644 --- a/tests/specs/npm/imports_package_json/imports_package_json/sub_path_import_not_defined.out +++ b/tests/specs/npm/imports_package_json/imports_package_json/sub_path_import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#hi" is not defined in package [WILDCARD]sub_path[WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/import_not_defined.out b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/import_not_defined.out index 70d1145a37..abfa414030 100644 --- a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/import_not_defined.out +++ b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#not-defined" is not defined in package [WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/main.out b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/main.out index 979e355765..70d787b251 100644 --- a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/main.out +++ b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz hi bye diff --git a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/sub_path_import_not_defined.out b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/sub_path_import_not_defined.out index 7c803f2bf1..95524202ae 100644 --- a/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/sub_path_import_not_defined.out +++ b/tests/specs/npm/imports_package_json_import_not_defined/imports_package_json/sub_path_import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#hi" is not defined in package [WILDCARD]sub_path[WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/import_not_defined.out b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/import_not_defined.out index 70d1145a37..abfa414030 100644 --- a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/import_not_defined.out +++ b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#not-defined" is not defined in package [WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/main.out b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/main.out index 979e355765..70d787b251 100644 --- a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/main.out +++ b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz hi bye diff --git a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/sub_path_import_not_defined.out b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/sub_path_import_not_defined.out index 7c803f2bf1..95524202ae 100644 --- a/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/sub_path_import_not_defined.out +++ b/tests/specs/npm/imports_package_json_sub_path_import_not_defined/imports_package_json/sub_path_import_not_defined.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/imports-package-json +Download http://localhost:4260/@denotest%2fimports-package-json Download http://localhost:4260/@denotest/imports-package-json/1.0.0.tgz error: [ERR_PACKAGE_IMPORT_NOT_DEFINED] Package import specifier "#hi" is not defined in package [WILDCARD]sub_path[WILDCARD]package.json imported from '[WILDCARD]import_not_defined.js' diff --git a/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main.out b/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main.out index 3c133bcde4..b7a5835577 100644 --- a/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main.out +++ b/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main_node_modules_reload.out b/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main_node_modules_reload.out index 9c81452118..18d7f7865b 100644 --- a/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main_node_modules_reload.out +++ b/tests/specs/npm/info_peer_deps/peer_deps_with_copied_folders/main_node_modules_reload.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main.out b/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main.out index 3c133bcde4..b7a5835577 100644 --- a/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main.out +++ b/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main_node_modules_reload.out b/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main_node_modules_reload.out index 9c81452118..18d7f7865b 100644 --- a/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main_node_modules_reload.out +++ b/tests/specs/npm/info_peer_deps_json/peer_deps_with_copied_folders/main_node_modules_reload.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run.out b/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run.out index cdfdeabe24..645556b26d 100644 --- a/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run.out +++ b/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-lifecycle-scripts -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fnode-lifecycle-scripts +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/node-lifecycle-scripts/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/node-lifecycle-scripts@1.0.0 diff --git a/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run_global.out b/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run_global.out index 93b5a14cc0..1b06bccb8a 100644 --- a/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run_global.out +++ b/tests/specs/npm/lifecycle_scripts/all_lifecycles_not_run_global.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-lifecycle-scripts -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fnode-lifecycle-scripts +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/node-lifecycle-scripts/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz [UNORDERED_END] diff --git a/tests/specs/npm/lifecycle_scripts/conflicting_bin.out b/tests/specs/npm/lifecycle_scripts/conflicting_bin.out index bae5275dca..33282ee81c 100644 --- a/tests/specs/npm/lifecycle_scripts/conflicting_bin.out +++ b/tests/specs/npm/lifecycle_scripts/conflicting_bin.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/better-say-hello -Download http://localhost:4260/@denotest/say-hello-on-install -Download http://localhost:4260/@denotest/say-hello +Download http://localhost:4260/@denotest%2fbetter-say-hello +Download http://localhost:4260/@denotest%2fsay-hello-on-install +Download http://localhost:4260/@denotest%2fsay-hello Download http://localhost:4260/@denotest/better-say-hello/1.0.0.tgz Download http://localhost:4260/@denotest/say-hello-on-install/1.0.0.tgz Download http://localhost:4260/@denotest/say-hello/1.0.0.tgz diff --git a/tests/specs/npm/lifecycle_scripts/future_install_all_lifecycles_not_run.out b/tests/specs/npm/lifecycle_scripts/future_install_all_lifecycles_not_run.out index 09324c8452..d72f3c8b2d 100644 --- a/tests/specs/npm/lifecycle_scripts/future_install_all_lifecycles_not_run.out +++ b/tests/specs/npm/lifecycle_scripts/future_install_all_lifecycles_not_run.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-lifecycle-scripts -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fnode-lifecycle-scripts +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/node-lifecycle-scripts/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/node-lifecycle-scripts@1.0.0 diff --git a/tests/specs/npm/lifecycle_scripts/no_deno_json.out b/tests/specs/npm/lifecycle_scripts/no_deno_json.out index 8509d8f9f5..1966396a31 100644 --- a/tests/specs/npm/lifecycle_scripts/no_deno_json.out +++ b/tests/specs/npm/lifecycle_scripts/no_deno_json.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/lifecycle-scripts-cjs -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2flifecycle-scripts-cjs +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/lifecycle-scripts-cjs/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/lifecycle-scripts-cjs@1.0.0 diff --git a/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out b/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out index 81577e6ba0..339384f732 100644 --- a/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out +++ b/tests/specs/npm/lifecycle_scripts/node_gyp_not_found.out @@ -1,5 +1,5 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-addon-implicit-node-gyp +Download http://localhost:4260/@denotest%2fnode-addon-implicit-node-gyp Download http://localhost:4260/@denotest/node-addon-implicit-node-gyp/1.0.0.tgz Initialize @denotest/node-addon-implicit-node-gyp@1.0.0 Initialize @denotest/node-addon-implicit-node-gyp@1.0.0: running 'install' script diff --git a/tests/specs/npm/lifecycle_scripts/node_gyp_not_run.out b/tests/specs/npm/lifecycle_scripts/node_gyp_not_run.out index e0e51b26c0..da5bb4f7b5 100644 --- a/tests/specs/npm/lifecycle_scripts/node_gyp_not_run.out +++ b/tests/specs/npm/lifecycle_scripts/node_gyp_not_run.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/node-addon +Download http://localhost:4260/@denotest%2fnode-addon Download http://localhost:4260/node-gyp [WILDCARD] Warning The following packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed: diff --git a/tests/specs/npm/lifecycle_scripts/only_warns_first1.out b/tests/specs/npm/lifecycle_scripts/only_warns_first1.out index cdfdeabe24..645556b26d 100644 --- a/tests/specs/npm/lifecycle_scripts/only_warns_first1.out +++ b/tests/specs/npm/lifecycle_scripts/only_warns_first1.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/node-lifecycle-scripts -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fnode-lifecycle-scripts +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/node-lifecycle-scripts/1.0.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/node-lifecycle-scripts@1.0.0 diff --git a/tests/specs/npm/lossy_utf8_module/main.out b/tests/specs/npm/lossy_utf8_module/main.out index 0e96f9ebb3..d046d2ec55 100644 --- a/tests/specs/npm/lossy_utf8_module/main.out +++ b/tests/specs/npm/lossy_utf8_module/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/lossy-utf8-module +Download http://localhost:4260/@denotest%2flossy-utf8-module Download http://localhost:4260/@denotest/lossy-utf8-module/1.0.0.tgz ���� diff --git a/tests/specs/npm/lossy_utf8_package_json/main.out b/tests/specs/npm/lossy_utf8_package_json/main.out index 99aa5ab61f..69f6870392 100644 --- a/tests/specs/npm/lossy_utf8_package_json/main.out +++ b/tests/specs/npm/lossy_utf8_package_json/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/lossy-utf8-package-json +Download http://localhost:4260/@denotest%2flossy-utf8-package-json Download http://localhost:4260/@denotest/lossy-utf8-package-json/1.0.0.tgz hello diff --git a/tests/specs/npm/lossy_utf8_script/main.out b/tests/specs/npm/lossy_utf8_script/main.out index 180ecdf1c3..0883392fd9 100644 --- a/tests/specs/npm/lossy_utf8_script/main.out +++ b/tests/specs/npm/lossy_utf8_script/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/lossy-utf8-script +Download http://localhost:4260/@denotest%2flossy-utf8-script Download http://localhost:4260/@denotest/lossy-utf8-script/1.0.0.tgz ���� diff --git a/tests/specs/npm/lossy_utf8_script_from_cjs/main.out b/tests/specs/npm/lossy_utf8_script_from_cjs/main.out index 4f062a2aef..635ea5b3a1 100644 --- a/tests/specs/npm/lossy_utf8_script_from_cjs/main.out +++ b/tests/specs/npm/lossy_utf8_script_from_cjs/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/lossy-utf8-script +Download http://localhost:4260/@denotest%2flossy-utf8-script Download http://localhost:4260/@denotest/lossy-utf8-script/1.0.0.tgz Initialize @denotest/lossy-utf8-script@1.0.0 ���� diff --git a/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/global.out b/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/global.out index fdacea3852..a1d3a6e2c5 100644 --- a/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/global.out +++ b/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/global.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/MixedCase -Download http://localhost:4260/@denotest/CAPITALS +Download http://localhost:4260/@denotest%2fMixedCase +Download http://localhost:4260/@denotest%2fCAPITALS [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/CAPITALS/1.0.0.tgz diff --git a/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/local.out b/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/local.out index 6ab989d80c..c8c9ed396c 100644 --- a/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/local.out +++ b/tests/specs/npm/mixed_case_package_name_global_dir/mixed_case_package_name/local.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/MixedCase -Download http://localhost:4260/@denotest/CAPITALS +Download http://localhost:4260/@denotest%2fMixedCase +Download http://localhost:4260/@denotest%2fCAPITALS [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/CAPITALS/1.0.0.tgz diff --git a/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/global.out b/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/global.out index fdacea3852..a1d3a6e2c5 100644 --- a/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/global.out +++ b/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/global.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/MixedCase -Download http://localhost:4260/@denotest/CAPITALS +Download http://localhost:4260/@denotest%2fMixedCase +Download http://localhost:4260/@denotest%2fCAPITALS [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/CAPITALS/1.0.0.tgz diff --git a/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/local.out b/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/local.out index 6ab989d80c..c8c9ed396c 100644 --- a/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/local.out +++ b/tests/specs/npm/mixed_case_package_name_local_dir/mixed_case_package_name/local.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/MixedCase -Download http://localhost:4260/@denotest/CAPITALS +Download http://localhost:4260/@denotest%2fMixedCase +Download http://localhost:4260/@denotest%2fCAPITALS [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/CAPITALS/1.0.0.tgz diff --git a/tests/specs/npm/no_types_cjs/main.out b/tests/specs/npm/no_types_cjs/main.out index 4747a9a325..c993d5fbf4 100644 --- a/tests/specs/npm/no_types_cjs/main.out +++ b/tests/specs/npm/no_types_cjs/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/no-types-cjs +Download http://localhost:4260/@denotest%2fno-types-cjs Download http://localhost:4260/@denotest/no-types-cjs/1.0.0.tgz Check file:///[WILDLINE]/no_types_cjs/main.ts diff --git a/tests/specs/npm/no_types_in_conditional_exports/main.out b/tests/specs/npm/no_types_in_conditional_exports/main.out index 46c5832347..593cb23370 100644 --- a/tests/specs/npm/no_types_in_conditional_exports/main.out +++ b/tests/specs/npm/no_types_in_conditional_exports/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/no-types-in-conditional-exports +Download http://localhost:4260/@denotest%2fno-types-in-conditional-exports Download http://localhost:4260/@denotest/no-types-in-conditional-exports/1.0.0.tgz Check [WILDCARD]npm/no_types_in_conditional_exports/main.ts { foo: "bar" } diff --git a/tests/specs/npm/node_modules_import/install.out b/tests/specs/npm/node_modules_import/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/npm/node_modules_import/install.out +++ b/tests/specs/npm/node_modules_import/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/npm/node_modules_import_auto/main.out b/tests/specs/npm/node_modules_import_auto/main.out index 8fda5cda1e..18194e3a0c 100644 --- a/tests/specs/npm/node_modules_import_auto/main.out +++ b/tests/specs/npm/node_modules_import_auto/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 2 diff --git a/tests/specs/npm/node_modules_import_auto/main_check.out b/tests/specs/npm/node_modules_import_auto/main_check.out index 24249f357b..093b0c051a 100644 --- a/tests/specs/npm/node_modules_import_auto/main_check.out +++ b/tests/specs/npm/node_modules_import_auto/main_check.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file:///[WILDCARD]/main.ts diff --git a/tests/specs/npm/non_existent_dep/__test__.jsonc b/tests/specs/npm/non_existent_dep/__test__.jsonc index bc38414195..8608e00f3e 100644 --- a/tests/specs/npm/non_existent_dep/__test__.jsonc +++ b/tests/specs/npm/non_existent_dep/__test__.jsonc @@ -1,5 +1,5 @@ { "args": "cache npm:@denotest/non-existent-dep", "exitCode": 1, - "output": "[UNORDERED_START]\nDownload http://localhost:4260/@denotest/non-existent-dep\nDownload http://localhost:4260/@denotest/non-existent\n[UNORDERED_END]\nerror: npm package '@denotest/non-existent' does not exist.\n" + "output": "[UNORDERED_START]\nDownload http://localhost:4260/@denotest%2fnon-existent-dep\nDownload http://localhost:4260/@denotest%2fnon-existent\n[UNORDERED_END]\nerror: npm package '@denotest/non-existent' does not exist.\n" } diff --git a/tests/specs/npm/non_existent_dep_version/__test__.jsonc b/tests/specs/npm/non_existent_dep_version/__test__.jsonc index 4bc084ce3d..dd91c66243 100644 --- a/tests/specs/npm/non_existent_dep_version/__test__.jsonc +++ b/tests/specs/npm/non_existent_dep_version/__test__.jsonc @@ -1,5 +1,5 @@ { "args": "cache npm:@denotest/non-existent-dep-version", "exitCode": 1, - "output": "[UNORDERED_START]\nDownload http://localhost:4260/@denotest/non-existent-dep-version\nDownload http://localhost:4260/@denotest/esm-basic\n[UNORDERED_END]\nerror: Could not find npm package '@denotest/esm-basic' matching '=99.99.99'.\n" + "output": "[UNORDERED_START]\nDownload http://localhost:4260/@denotest%2fnon-existent-dep-version\nDownload http://localhost:4260/@denotest%2fesm-basic\n[UNORDERED_END]\nerror: Could not find npm package '@denotest/esm-basic' matching '=99.99.99'.\n" } diff --git a/tests/specs/npm/npmrc/info.out b/tests/specs/npm/npmrc/info.out index 8f82b10c9a..ee9aa9ee96 100644 --- a/tests/specs/npm/npmrc/info.out +++ b/tests/specs/npm/npmrc/info.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4262/@denotest2/basic -Download http://localhost:4261/@denotest/basic +Download http://localhost:4262/@denotest2%2fbasic +Download http://localhost:4261/@denotest%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz [UNORDERED_END] diff --git a/tests/specs/npm/npmrc/install.out b/tests/specs/npm/npmrc/install.out index 30643527e6..f2ea78c3f8 100644 --- a/tests/specs/npm/npmrc/install.out +++ b/tests/specs/npm/npmrc/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4261/@denotest/basic -Download http://localhost:4262/@denotest2/basic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz Initialize @denotest2/basic@1.0.0 diff --git a/tests/specs/npm/npmrc_bad_registry_config/main.out b/tests/specs/npm/npmrc_bad_registry_config/main.out index 990d4a47fa..5d778d32e9 100644 --- a/tests/specs/npm/npmrc_bad_registry_config/main.out +++ b/tests/specs/npm/npmrc_bad_registry_config/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4261/@denotest/basic -error: Error getting response at http://localhost:4261/@denotest/basic for package "@denotest/basic": Bad response: 401 +Download http://localhost:4261/@denotest%2fbasic +error: Error getting response at http://localhost:4261/@denotest%2fbasic for package "@denotest/basic": Bad response: 401 [WILDCARD] \ No newline at end of file diff --git a/tests/specs/npm/npmrc_bad_token/main.out b/tests/specs/npm/npmrc_bad_token/main.out index 990d4a47fa..5d778d32e9 100644 --- a/tests/specs/npm/npmrc_bad_token/main.out +++ b/tests/specs/npm/npmrc_bad_token/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4261/@denotest/basic -error: Error getting response at http://localhost:4261/@denotest/basic for package "@denotest/basic": Bad response: 401 +Download http://localhost:4261/@denotest%2fbasic +error: Error getting response at http://localhost:4261/@denotest%2fbasic for package "@denotest/basic": Bad response: 401 [WILDCARD] \ No newline at end of file diff --git a/tests/specs/npm/npmrc_basic_auth/install.out b/tests/specs/npm/npmrc_basic_auth/install.out index 30643527e6..f2ea78c3f8 100644 --- a/tests/specs/npm/npmrc_basic_auth/install.out +++ b/tests/specs/npm/npmrc_basic_auth/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4261/@denotest/basic -Download http://localhost:4262/@denotest2/basic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz Initialize @denotest2/basic@1.0.0 diff --git a/tests/specs/npm/npmrc_deno_json/main.out b/tests/specs/npm/npmrc_deno_json/main.out index 62750088bd..c3d6be8a1a 100644 --- a/tests/specs/npm/npmrc_deno_json/main.out +++ b/tests/specs/npm/npmrc_deno_json/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4261/@denotest/basic +Download http://localhost:4261/@denotest%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz 0 42 diff --git a/tests/specs/npm/npmrc_homedir/install.out b/tests/specs/npm/npmrc_homedir/install.out index 30643527e6..f2ea78c3f8 100644 --- a/tests/specs/npm/npmrc_homedir/install.out +++ b/tests/specs/npm/npmrc_homedir/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4261/@denotest/basic -Download http://localhost:4262/@denotest2/basic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz Initialize @denotest2/basic@1.0.0 diff --git a/tests/specs/npm/npmrc_not_next_to_package_json/main.out b/tests/specs/npm/npmrc_not_next_to_package_json/main.out index b583868ffb..b128959a7c 100644 --- a/tests/specs/npm/npmrc_not_next_to_package_json/main.out +++ b/tests/specs/npm/npmrc_not_next_to_package_json/main.out @@ -2,7 +2,7 @@ [# that serves the same packages. The important bit is the message below.] [WILDCARD] No .npmrc file found [WILDCARD] -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic [WILDCARD] Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz [WILDCARD] diff --git a/tests/specs/npm/npmrc_password_no_username/install.out b/tests/specs/npm/npmrc_password_no_username/install.out index d49a2ba0d8..b198bcd27e 100644 --- a/tests/specs/npm/npmrc_password_no_username/install.out +++ b/tests/specs/npm/npmrc_password_no_username/install.out @@ -1,3 +1,3 @@ [UNORDERED_START] -error: Error getting response at http://localhost:4261/@denotest/basic for package "@denotest/basic": Both the username and password must be provided for basic auth +error: Error getting response at http://localhost:4261/@denotest%2fbasic for package "@denotest/basic": Both the username and password must be provided for basic auth [UNORDERED_END] diff --git a/tests/specs/npm/npmrc_tarball_other_server/fail/main.out b/tests/specs/npm/npmrc_tarball_other_server/fail/main.out index 08a84a477c..2c68dba54e 100644 --- a/tests/specs/npm/npmrc_tarball_other_server/fail/main.out +++ b/tests/specs/npm/npmrc_tarball_other_server/fail/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4261/@denotest/tarballs-privateserver2 +Download http://localhost:4261/@denotest%2ftarballs-privateserver2 Download http://localhost:4262/@denotest/tarballs-privateserver2/1.0.0.tgz error: Failed caching npm package '@denotest/tarballs-privateserver2@1.0.0'. diff --git a/tests/specs/npm/npmrc_tarball_other_server/success/main.out b/tests/specs/npm/npmrc_tarball_other_server/success/main.out index d75f26e336..5322a1a17d 100644 --- a/tests/specs/npm/npmrc_tarball_other_server/success/main.out +++ b/tests/specs/npm/npmrc_tarball_other_server/success/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4261/@denotest/tarballs-privateserver2 +Download http://localhost:4261/@denotest%2ftarballs-privateserver2 Download http://localhost:4262/@denotest/tarballs-privateserver2/1.0.0.tgz [# This fails on a checksum issue, because the test server isn't smart enough] [# to serve proper checksums for a package at another registry. That's fine] diff --git a/tests/specs/npm/npmrc_username_no_password/install.out b/tests/specs/npm/npmrc_username_no_password/install.out index d49a2ba0d8..b198bcd27e 100644 --- a/tests/specs/npm/npmrc_username_no_password/install.out +++ b/tests/specs/npm/npmrc_username_no_password/install.out @@ -1,3 +1,3 @@ [UNORDERED_START] -error: Error getting response at http://localhost:4261/@denotest/basic for package "@denotest/basic": Both the username and password must be provided for basic auth +error: Error getting response at http://localhost:4261/@denotest%2fbasic for package "@denotest/basic": Both the username and password must be provided for basic auth [UNORDERED_END] diff --git a/tests/specs/npm/npmrc_username_password/install.out b/tests/specs/npm/npmrc_username_password/install.out index 30643527e6..f2ea78c3f8 100644 --- a/tests/specs/npm/npmrc_username_password/install.out +++ b/tests/specs/npm/npmrc_username_password/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4261/@denotest/basic -Download http://localhost:4262/@denotest2/basic +Download http://localhost:4261/@denotest%2fbasic +Download http://localhost:4262/@denotest2%2fbasic Download http://localhost:4261/@denotest/basic/1.0.0.tgz Download http://localhost:4262/@denotest2/basic/1.0.0.tgz Initialize @denotest2/basic@1.0.0 diff --git a/tests/specs/npm/permissions_outside_package/permissions_outside_package/main.out b/tests/specs/npm/permissions_outside_package/permissions_outside_package/main.out index 089f329c4f..0fde8cf87f 100644 --- a/tests/specs/npm/permissions_outside_package/permissions_outside_package/main.out +++ b/tests/specs/npm/permissions_outside_package/permissions_outside_package/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/permissions-outside-package +Download http://localhost:4260/@denotest%2fpermissions-outside-package Download http://localhost:4260/@denotest/permissions-outside-package/1.0.0.tgz { name: "foobar", version: "0.0.1" } diff --git a/tests/specs/npm/require_resolve_bad_paths_global_cache/main.out b/tests/specs/npm/require_resolve_bad_paths_global_cache/main.out index 3cc8a82794..2e82e68027 100644 --- a/tests/specs/npm/require_resolve_bad_paths_global_cache/main.out +++ b/tests/specs/npm/require_resolve_bad_paths_global_cache/main.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/esm-basic -Download http://localhost:4260/@denotest/require-resolve +Download http://localhost:4260/@denotest%2fesm-basic +Download http://localhost:4260/@denotest%2frequire-resolve [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz diff --git a/tests/specs/npm/reserved_word_exports/reserved_word_exports/main.out b/tests/specs/npm/reserved_word_exports/reserved_word_exports/main.out index ccfd35bc86..2a82194e37 100644 --- a/tests/specs/npm/reserved_word_exports/reserved_word_exports/main.out +++ b/tests/specs/npm/reserved_word_exports/reserved_word_exports/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/reserved-word-exports +Download http://localhost:4260/@denotest%2freserved-word-exports Download http://localhost:4260/@denotest/reserved-word-exports/1.0.0.tgz [Module: null prototype] { abstract: "abstract", diff --git a/tests/specs/npm/run_existing_npm_package_with_subpath/run_existing_npm_package_with_subpath/main.out b/tests/specs/npm/run_existing_npm_package_with_subpath/run_existing_npm_package_with_subpath/main.out index f6ee03ef1b..a473c32cc6 100644 --- a/tests/specs/npm/run_existing_npm_package_with_subpath/run_existing_npm_package_with_subpath/main.out +++ b/tests/specs/npm/run_existing_npm_package_with_subpath/run_existing_npm_package_with_subpath/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/bin@1.0.0 dev diff --git a/tests/specs/npm/types_d_ext/d_ext/main.out b/tests/specs/npm/types_d_ext/d_ext/main.out index e99df66965..5a834fbeb6 100644 --- a/tests/specs/npm/types_d_ext/d_ext/main.out +++ b/tests/specs/npm/types_d_ext/d_ext/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/d-ext +Download http://localhost:4260/@denotest%2fd-ext Download http://localhost:4260/@denotest/d-ext/1.0.0.tgz Check file:///[WILDCARD]/d_ext/main.ts diff --git a/tests/specs/npm/types_entry_value_not_exists/types_entry_value_not_exists/main.out b/tests/specs/npm/types_entry_value_not_exists/types_entry_value_not_exists/main.out index 017a17ea2e..62ef51d9f1 100644 --- a/tests/specs/npm/types_entry_value_not_exists/types_entry_value_not_exists/main.out +++ b/tests/specs/npm/types_entry_value_not_exists/types_entry_value_not_exists/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/types-entry-value-not-exists +Download http://localhost:4260/@denotest%2ftypes-entry-value-not-exists Download http://localhost:4260/@denotest/types-entry-value-not-exists/1.0.0.tgz Check file://[WILDCARD]/types_entry_value_not_exists/main.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. diff --git a/tests/specs/npm/types_exports_import_types/types_exports_import_types/main.out b/tests/specs/npm/types_exports_import_types/types_exports_import_types/main.out index 10f9425ca4..bb69bf86fb 100644 --- a/tests/specs/npm/types_exports_import_types/types_exports_import_types/main.out +++ b/tests/specs/npm/types_exports_import_types/types_exports_import_types/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/types-exports-import-types +Download http://localhost:4260/@denotest%2ftypes-exports-import-types Download http://localhost:4260/@denotest/types-exports-import-types/1.0.0.tgz Check file://[WILDCARD]/types_exports_import_types/main.ts error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. diff --git a/tests/specs/npm/types_general/main.out b/tests/specs/npm/types_general/main.out index ffba4f84db..edc83f2a26 100644 --- a/tests/specs/npm/types_general/main.out +++ b/tests/specs/npm/types_general/main.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/types -Download http://localhost:4260/@denotest/types_imported -Download http://localhost:4260/@denotest/types-exports-subpaths +Download http://localhost:4260/@denotest%2ftypes +Download http://localhost:4260/@denotest%2ftypes_imported +Download http://localhost:4260/@denotest%2ftypes-exports-subpaths Download http://localhost:4260/@denotest/types_imported/1.0.0.tgz Download http://localhost:4260/@denotest/types-exports-subpaths/1.0.0.tgz Download http://localhost:4260/@denotest/types/1.0.0.tgz diff --git a/tests/specs/npm/types_no_types_entry/types_no_types_entry/main.out b/tests/specs/npm/types_no_types_entry/types_no_types_entry/main.out index 53e872eaf1..c867253ad9 100644 --- a/tests/specs/npm/types_no_types_entry/types_no_types_entry/main.out +++ b/tests/specs/npm/types_no_types_entry/types_no_types_entry/main.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/types-no-types-entry -Download http://localhost:4260/@denotest/types-entry-value-not-exists +Download http://localhost:4260/@denotest%2ftypes-no-types-entry +Download http://localhost:4260/@denotest%2ftypes-entry-value-not-exists [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/types-entry-value-not-exists/1.0.0.tgz diff --git a/tests/specs/npm/typescript_file_in_package/typescript_file_in_package/main.out b/tests/specs/npm/typescript_file_in_package/typescript_file_in_package/main.out index 3c3b971907..b3faa87900 100644 --- a/tests/specs/npm/typescript_file_in_package/typescript_file_in_package/main.out +++ b/tests/specs/npm/typescript_file_in_package/typescript_file_in_package/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/typescript-file +Download http://localhost:4260/@denotest%2ftypescript-file Download http://localhost:4260/@denotest/typescript-file/1.0.0.tgz error: Could not resolve 'npm:@denotest/typescript-file@1.0.0'. diff --git a/tests/specs/npm/workspace_basic/b/main_global_cache.out b/tests/specs/npm/workspace_basic/b/main_global_cache.out index 203ac5a991..879f6ab050 100644 --- a/tests/specs/npm/workspace_basic/b/main_global_cache.out +++ b/tests/specs/npm/workspace_basic/b/main_global_cache.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Hello 5 Hello 5 diff --git a/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out index eeb4556526..464c05c57c 100644 --- a/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out +++ b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Hello 5 diff --git a/tests/specs/npm/workspace_basic/main.out b/tests/specs/npm/workspace_basic/main.out index 92404af878..52d8267bb4 100644 --- a/tests/specs/npm/workspace_basic/main.out +++ b/tests/specs/npm/workspace_basic/main.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Hello 5 C: Hi! diff --git a/tests/specs/publish/bare_node_builtins/bare_node_builtins.out b/tests/specs/publish/bare_node_builtins/bare_node_builtins.out index 36ba9c7ce2..6baedd906f 100644 --- a/tests/specs/publish/bare_node_builtins/bare_node_builtins.out +++ b/tests/specs/publish/bare_node_builtins/bare_node_builtins.out @@ -1,7 +1,7 @@ Warning Resolving "url" as "node:url" at file:///[WILDLINE]/mod.ts:1:22. If you want to use a built-in Node module, add a "node:" prefix. Warning Resolving "url" as "node:url" at file:///[WILDLINE]/mod.ts:1:22. If you want to use a built-in Node module, add a "node:" prefix. [UNORDERED_START] -Download http://localhost:4260/@types/node +Download http://localhost:4260/@types%2fnode Download http://localhost:4260/undici-types Download http://localhost:4260/@types/node/node-22.5.4.tgz Download http://localhost:4260/undici-types/undici-types-6.19.8.tgz diff --git a/tests/specs/publish/missing_constraint/publish.out b/tests/specs/publish/missing_constraint/publish.out index d9fb6408fb..7d2c293c0e 100644 --- a/tests/specs/publish/missing_constraint/publish.out +++ b/tests/specs/publish/missing_constraint/publish.out @@ -1,7 +1,7 @@ [UNORDERED_START] Download http://127.0.0.1:4250/@denotest/add/meta.json Download http://127.0.0.1:4250/@denotest/deps/meta.json -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json Download http://127.0.0.1:4250/@denotest/deps/1.0.0_meta.json Download http://127.0.0.1:4250/@denotest/module-graph/meta.json diff --git a/tests/specs/publish/node_specifier/node_specifier.out b/tests/specs/publish/node_specifier/node_specifier.out index 4c9d7cb3e2..0ba14043df 100644 --- a/tests/specs/publish/node_specifier/node_specifier.out +++ b/tests/specs/publish/node_specifier/node_specifier.out @@ -1,5 +1,5 @@ [UNORDERED_START] -Download http://localhost:4260/@types/node +Download http://localhost:4260/@types%2fnode Download http://localhost:4260/undici-types Download http://localhost:4260/@types/node/node-22.5.4.tgz Download http://localhost:4260/undici-types/undici-types-6.19.8.tgz diff --git a/tests/specs/remove/package_json/rm_add.out b/tests/specs/remove/package_json/rm_add.out index b98c27bae6..af72f5f99d 100644 --- a/tests/specs/remove/package_json/rm_add.out +++ b/tests/specs/remove/package_json/rm_add.out @@ -1,4 +1,4 @@ Removed @denotest/add -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/run/no_deno_json/code/install.out b/tests/specs/run/no_deno_json/code/install.out index 321dc8ebcb..ddaf104c3b 100644 --- a/tests/specs/run/no_deno_json/code/install.out +++ b/tests/specs/run/no_deno_json/code/install.out @@ -1,6 +1,6 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/check-error -Download http://localhost:4260/@denotest/cjs-default-export +Download http://localhost:4260/@denotest%2fcheck-error +Download http://localhost:4260/@denotest%2fcjs-default-export Download http://localhost:4260/chalk Download http://localhost:4260/ansi-styles Download http://localhost:4260/supports-color diff --git a/tests/specs/run/package_json/invalid_value/error_auto.out b/tests/specs/run/package_json/invalid_value/error_auto.out index 7641a4d863..966375a3df 100644 --- a/tests/specs/run/package_json/invalid_value/error_auto.out +++ b/tests/specs/run/package_json/invalid_value/error_auto.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 error: Invalid version requirement diff --git a/tests/specs/run/package_json/invalid_value/task.out b/tests/specs/run/package_json/invalid_value/task.out index d0adb05256..2f9580fa61 100644 --- a/tests/specs/run/package_json/invalid_value/task.out +++ b/tests/specs/run/package_json/invalid_value/task.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Task test echo 1 diff --git a/tests/specs/task/bin_package/task.out b/tests/specs/task/bin_package/task.out index 69b4f75082..d2c130905a 100644 --- a/tests/specs/task/bin_package/task.out +++ b/tests/specs/task/bin_package/task.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/bin@1.0.0 Task sayhi cli-esm hi hello diff --git a/tests/specs/task/bin_pkg_with_scope_auto/bin_auto.out b/tests/specs/task/bin_pkg_with_scope_auto/bin_auto.out index 20865898e3..1eb196a7aa 100644 --- a/tests/specs/task/bin_pkg_with_scope_auto/bin_auto.out +++ b/tests/specs/task/bin_pkg_with_scope_auto/bin_auto.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin [UNORDERED_START] Download http://localhost:4260/@denotest/bin/0.5.0.tgz Initialize @denotest/bin@0.5.0 diff --git a/tests/specs/task/bin_pkg_with_scope_auto/bin_none.out b/tests/specs/task/bin_pkg_with_scope_auto/bin_none.out index 066c67bd8a..f44b234bf3 100644 --- a/tests/specs/task/bin_pkg_with_scope_auto/bin_none.out +++ b/tests/specs/task/bin_pkg_with_scope_auto/bin_none.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin [UNORDERED_START] Download http://localhost:4260/@denotest/bin/0.5.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz diff --git a/tests/specs/task/bin_pkg_with_scope_none/bin_none.out b/tests/specs/task/bin_pkg_with_scope_none/bin_none.out index 066c67bd8a..f44b234bf3 100644 --- a/tests/specs/task/bin_pkg_with_scope_none/bin_none.out +++ b/tests/specs/task/bin_pkg_with_scope_none/bin_none.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin [UNORDERED_START] Download http://localhost:4260/@denotest/bin/0.5.0.tgz Download http://localhost:4260/@denotest/bin/1.0.0.tgz diff --git a/tests/specs/task/both_package_json_selected/install.out b/tests/specs/task/both_package_json_selected/install.out index 645501a3d9..08b70a0f21 100644 --- a/tests/specs/task/both_package_json_selected/install.out +++ b/tests/specs/task/both_package_json_selected/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/1.0.0.tgz Initialize @denotest/bin@1.0.0 diff --git a/tests/specs/task/package_json_node_modules_dir_none/bin.out b/tests/specs/task/package_json_node_modules_dir_none/bin.out index c86261d9e1..ff60cc3d0b 100644 --- a/tests/specs/task/package_json_node_modules_dir_none/bin.out +++ b/tests/specs/task/package_json_node_modules_dir_none/bin.out @@ -1,5 +1,5 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/1.0.0.tgz Download http://localhost:4260/@denotest/bin/0.5.0.tgz [UNORDERED_END] diff --git a/tests/specs/test/package_json_basic/install.out b/tests/specs/test/package_json_basic/install.out index b8114c12a0..8f03ba6407 100644 --- a/tests/specs/test/package_json_basic/install.out +++ b/tests/specs/test/package_json_basic/install.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 diff --git a/tests/specs/test/package_json_basic_auto_install/test.out b/tests/specs/test/package_json_basic_auto_install/test.out index 2f109ef38e..749c395485 100644 --- a/tests/specs/test/package_json_basic_auto_install/test.out +++ b/tests/specs/test/package_json_basic_auto_install/test.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file://[WILDCARD]/lib.test.ts diff --git a/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out b/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out index 2067bf1c60..1a7eb0a4f2 100644 --- a/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out +++ b/tests/testdata/compile/node_modules_symlink_outside/main_compile_folder.out @@ -1,4 +1,4 @@ -Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest%2fesm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Check file:///[WILDCARD]/node_modules_symlink_outside/main.ts diff --git a/tests/testdata/npm/peer_deps_with_copied_folders/main.out b/tests/testdata/npm/peer_deps_with_copied_folders/main.out index 3c133bcde4..b7a5835577 100644 --- a/tests/testdata/npm/peer_deps_with_copied_folders/main.out +++ b/tests/testdata/npm/peer_deps_with_copied_folders/main.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/testdata/npm/peer_deps_with_copied_folders/main_node_modules_reload.out b/tests/testdata/npm/peer_deps_with_copied_folders/main_node_modules_reload.out index 9c81452118..18d7f7865b 100644 --- a/tests/testdata/npm/peer_deps_with_copied_folders/main_node_modules_reload.out +++ b/tests/testdata/npm/peer_deps_with_copied_folders/main_node_modules_reload.out @@ -1,7 +1,7 @@ [UNORDERED_START] -Download http://localhost:4260/@denotest/peer-dep-test-child -Download http://localhost:4260/@denotest/peer-dep-test-grandchild -Download http://localhost:4260/@denotest/peer-dep-test-peer +Download http://localhost:4260/@denotest%2fpeer-dep-test-child +Download http://localhost:4260/@denotest%2fpeer-dep-test-grandchild +Download http://localhost:4260/@denotest%2fpeer-dep-test-peer [UNORDERED_END] [UNORDERED_START] Download http://localhost:4260/@denotest/peer-dep-test-child/1.0.0.tgz diff --git a/tests/testdata/npm/run_existing_npm_package/main.out b/tests/testdata/npm/run_existing_npm_package/main.out index 147ed6a995..373059db53 100644 --- a/tests/testdata/npm/run_existing_npm_package/main.out +++ b/tests/testdata/npm/run_existing_npm_package/main.out @@ -1,3 +1,3 @@ -Download http://localhost:4260/@denotest/bin +Download http://localhost:4260/@denotest%2fbin Download http://localhost:4260/@denotest/bin/0.5.0.tgz Initialize @denotest/bin@0.5.0 From d047cab14b754d20a43c7119e327b451440aaed9 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 12:30:46 -0700 Subject: [PATCH 05/16] refactor(ext/websocket): use concrete error type (#26226) --- Cargo.lock | 1 + ext/http/http_next.rs | 6 +- ext/http/lib.rs | 8 +- ext/websocket/Cargo.toml | 1 + ext/websocket/lib.rs | 189 +++++++++++++++------------ runtime/errors.rs | 45 ++++++- runtime/ops/web_worker/sync_fetch.rs | 19 ++- 7 files changed, 172 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b2baf79c2..28b789c0cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2225,6 +2225,7 @@ dependencies = [ "once_cell", "rustls-tokio-stream", "serde", + "thiserror", "tokio", ] diff --git a/ext/http/http_next.rs b/ext/http/http_next.rs index abc54c8994..7a6cbfa45e 100644 --- a/ext/http/http_next.rs +++ b/ext/http/http_next.rs @@ -246,7 +246,11 @@ pub async fn op_http_upgrade_websocket_next( // Stage 3: take the extracted raw network stream and upgrade it to a websocket, then return it let (stream, bytes) = extract_network_stream(upgraded); - ws_create_server_stream(&mut state.borrow_mut(), stream, bytes) + Ok(ws_create_server_stream( + &mut state.borrow_mut(), + stream, + bytes, + )) } #[op2(fast)] diff --git a/ext/http/lib.rs b/ext/http/lib.rs index 934f8a0024..5461713aa8 100644 --- a/ext/http/lib.rs +++ b/ext/http/lib.rs @@ -1053,9 +1053,11 @@ async fn op_http_upgrade_websocket( let (transport, bytes) = extract_network_stream(hyper_v014::upgrade::on(request).await?); - let ws_rid = - ws_create_server_stream(&mut state.borrow_mut(), transport, bytes)?; - Ok(ws_rid) + Ok(ws_create_server_stream( + &mut state.borrow_mut(), + transport, + bytes, + )) } // Needed so hyper can use non Send futures diff --git a/ext/websocket/Cargo.toml b/ext/websocket/Cargo.toml index b265ba78a2..ec359100d4 100644 --- a/ext/websocket/Cargo.toml +++ b/ext/websocket/Cargo.toml @@ -28,4 +28,5 @@ hyper-util.workspace = true once_cell.workspace = true rustls-tokio-stream.workspace = true serde.workspace = true +thiserror.workspace = true tokio.workspace = true diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index b8043516b0..2a67ac5a17 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -1,10 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::stream::WebSocketStream; use bytes::Bytes; -use deno_core::anyhow::bail; -use deno_core::error::invalid_hostname; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::TryFutureExt; use deno_core::op2; use deno_core::unsync::spawn; @@ -43,7 +39,6 @@ use serde::Serialize; use std::borrow::Cow; use std::cell::Cell; use std::cell::RefCell; -use std::fmt; use std::future::Future; use std::num::NonZeroUsize; use std::path::PathBuf; @@ -75,11 +70,33 @@ static USE_WRITEV: Lazy = Lazy::new(|| { false }); +#[derive(Debug, thiserror::Error)] +pub enum WebsocketError { + #[error(transparent)] + Url(url::ParseError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Uri(#[from] http::uri::InvalidUri), + #[error("{0}")] + Io(#[from] std::io::Error), + #[error(transparent)] + WebSocket(#[from] fastwebsockets::WebSocketError), + #[error("failed to connect to WebSocket: {0}")] + ConnectionFailed(#[from] HandshakeError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), +} + #[derive(Clone)] pub struct WsRootStoreProvider(Option>); impl WsRootStoreProvider { - pub fn get_or_try_init(&self) -> Result, AnyError> { + pub fn get_or_try_init( + &self, + ) -> Result, deno_core::error::AnyError> { Ok(match &self.0 { Some(provider) => Some(provider.get_or_try_init()?.clone()), None => None, @@ -95,7 +112,7 @@ pub trait WebSocketPermissions { &mut self, _url: &url::Url, _api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<(), deno_core::error::AnyError>; } impl WebSocketPermissions for deno_permissions::PermissionsContainer { @@ -104,7 +121,7 @@ impl WebSocketPermissions for deno_permissions::PermissionsContainer { &mut self, url: &url::Url, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), deno_core::error::AnyError> { deno_permissions::PermissionsContainer::check_net_url(self, url, api_name) } } @@ -137,13 +154,17 @@ pub fn op_ws_check_permission_and_cancel_handle( #[string] api_name: String, #[string] url: String, cancel_handle: bool, -) -> Result, AnyError> +) -> Result, WebsocketError> where WP: WebSocketPermissions + 'static, { state .borrow_mut::() - .check_net_url(&url::Url::parse(&url)?, &api_name)?; + .check_net_url( + &url::Url::parse(&url).map_err(WebsocketError::Url)?, + &api_name, + ) + .map_err(WebsocketError::Permission)?; if cancel_handle { let rid = state @@ -163,16 +184,46 @@ pub struct CreateResponse { extensions: String, } +#[derive(Debug, thiserror::Error)] +pub enum HandshakeError { + #[error("Missing path in url")] + MissingPath, + #[error("Invalid status code {0}")] + InvalidStatusCode(StatusCode), + #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] + WebSocket(#[from] fastwebsockets::WebSocketError), + #[error("Didn't receive h2 alpn, aborting connection")] + NoH2Alpn, + #[error(transparent)] + Rustls(#[from] deno_tls::rustls::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + H2(#[from] h2::Error), + #[error("Invalid hostname: '{0}'")] + InvalidHostname(String), + #[error(transparent)] + RootStoreError(deno_core::error::AnyError), + #[error(transparent)] + Tls(deno_tls::TlsError), + #[error(transparent)] + HeaderName(#[from] http::header::InvalidHeaderName), + #[error(transparent)] + HeaderValue(#[from] http::header::InvalidHeaderValue), +} + async fn handshake_websocket( state: &Rc>, uri: &Uri, protocols: &str, headers: Option>, -) -> Result<(WebSocket, http::HeaderMap), AnyError> { +) -> Result<(WebSocket, http::HeaderMap), HandshakeError> { let mut request = Request::builder().method(Method::GET).uri( uri .path_and_query() - .ok_or(type_error("Missing path in url".to_string()))? + .ok_or(HandshakeError::MissingPath)? .as_str(), ); @@ -194,7 +245,9 @@ async fn handshake_websocket( request = populate_common_request_headers(request, &user_agent, protocols, &headers)?; - let request = request.body(http_body_util::Empty::new())?; + let request = request + .body(http_body_util::Empty::new()) + .map_err(HandshakeError::Http)?; let domain = &uri.host().unwrap().to_string(); let port = &uri.port_u16().unwrap_or(match uri.scheme_str() { Some("wss") => 443, @@ -231,7 +284,7 @@ async fn handshake_websocket( async fn handshake_http1_ws( request: Request>, addr: &String, -) -> Result<(WebSocket, http::HeaderMap), AnyError> { +) -> Result<(WebSocket, http::HeaderMap), HandshakeError> { let tcp_socket = TcpStream::connect(addr).await?; handshake_connection(request, tcp_socket).await } @@ -241,11 +294,11 @@ async fn handshake_http1_wss( request: Request>, domain: &str, addr: &str, -) -> Result<(WebSocket, http::HeaderMap), AnyError> { +) -> Result<(WebSocket, http::HeaderMap), HandshakeError> { let tcp_socket = TcpStream::connect(addr).await?; let tls_config = create_ws_client_config(state, SocketUse::Http1Only)?; let dnsname = ServerName::try_from(domain.to_string()) - .map_err(|_| invalid_hostname(domain))?; + .map_err(|_| HandshakeError::InvalidHostname(domain.to_string()))?; let mut tls_connector = TlsStream::new_client_side( tcp_socket, ClientConnection::new(tls_config.into(), dnsname)?, @@ -266,11 +319,11 @@ async fn handshake_http2_wss( domain: &str, headers: &Option>, addr: &str, -) -> Result<(WebSocket, http::HeaderMap), AnyError> { +) -> Result<(WebSocket, http::HeaderMap), HandshakeError> { let tcp_socket = TcpStream::connect(addr).await?; let tls_config = create_ws_client_config(state, SocketUse::Http2Only)?; let dnsname = ServerName::try_from(domain.to_string()) - .map_err(|_| invalid_hostname(domain))?; + .map_err(|_| HandshakeError::InvalidHostname(domain.to_string()))?; // We need to better expose the underlying errors here let mut tls_connector = TlsStream::new_client_side( tcp_socket, @@ -279,7 +332,7 @@ async fn handshake_http2_wss( ); let handshake = tls_connector.handshake().await?; if handshake.alpn.is_none() { - bail!("Didn't receive h2 alpn, aborting connection"); + return Err(HandshakeError::NoH2Alpn); } let h2 = h2::client::Builder::new(); let (mut send, conn) = h2.handshake::<_, Bytes>(tls_connector).await?; @@ -298,7 +351,7 @@ async fn handshake_http2_wss( let (resp, send) = send.send_request(request.body(())?, false)?; let resp = resp.await?; if resp.status() != StatusCode::OK { - bail!("Invalid status code: {}", resp.status()); + return Err(HandshakeError::InvalidStatusCode(resp.status())); } let (http::response::Parts { headers, .. }, recv) = resp.into_parts(); let mut stream = WebSocket::after_handshake( @@ -317,7 +370,7 @@ async fn handshake_connection< >( request: Request>, socket: S, -) -> Result<(WebSocket, http::HeaderMap), AnyError> { +) -> Result<(WebSocket, http::HeaderMap), HandshakeError> { let (upgraded, response) = fastwebsockets::handshake::client(&LocalExecutor, request, socket).await?; @@ -332,7 +385,7 @@ async fn handshake_connection< pub fn create_ws_client_config( state: &Rc>, socket_use: SocketUse, -) -> Result { +) -> Result { let unsafely_ignore_certificate_errors: Option> = state .borrow() .try_borrow::() @@ -340,7 +393,8 @@ pub fn create_ws_client_config( let root_cert_store = state .borrow() .borrow::() - .get_or_try_init()?; + .get_or_try_init() + .map_err(HandshakeError::RootStoreError)?; create_client_config( root_cert_store, @@ -349,7 +403,7 @@ pub fn create_ws_client_config( TlsKeys::Null, socket_use, ) - .map_err(|e| e.into()) + .map_err(HandshakeError::Tls) } /// Headers common to both http/1.1 and h2 requests. @@ -358,7 +412,7 @@ fn populate_common_request_headers( user_agent: &str, protocols: &str, headers: &Option>, -) -> Result { +) -> Result { request = request .header("User-Agent", user_agent) .header("Sec-WebSocket-Version", "13"); @@ -369,10 +423,8 @@ fn populate_common_request_headers( if let Some(headers) = headers { for (key, value) in headers { - let name = HeaderName::from_bytes(key) - .map_err(|err| type_error(err.to_string()))?; - let v = HeaderValue::from_bytes(value) - .map_err(|err| type_error(err.to_string()))?; + let name = HeaderName::from_bytes(key)?; + let v = HeaderValue::from_bytes(value)?; let is_disallowed_header = matches!( name, @@ -402,14 +454,17 @@ pub async fn op_ws_create( #[string] protocols: String, #[smi] cancel_handle: Option, #[serde] headers: Option>, -) -> Result +) -> Result where WP: WebSocketPermissions + 'static, { { let mut s = state.borrow_mut(); s.borrow_mut::() - .check_net_url(&url::Url::parse(&url)?, &api_name) + .check_net_url( + &url::Url::parse(&url).map_err(WebsocketError::Url)?, + &api_name, + ) .expect( "Permission check should have been done in op_ws_check_permission", ); @@ -419,7 +474,8 @@ where let r = state .borrow_mut() .resource_table - .get::(cancel_rid)?; + .get::(cancel_rid) + .map_err(WebsocketError::Resource)?; Some(r.0.clone()) } else { None @@ -428,15 +484,11 @@ where let uri: Uri = url.parse()?; let handshake = handshake_websocket(&state, &uri, &protocols, headers) - .map_err(|err| { - AnyError::from(DomExceptionNetworkError::new(&format!( - "failed to connect to WebSocket: {err}" - ))) - }); + .map_err(WebsocketError::ConnectionFailed); let (stream, response) = match cancel_resource { - Some(rc) => handshake.try_or_cancel(rc).await, - None => handshake.await, - }?; + Some(rc) => handshake.try_or_cancel(rc).await?, + None => handshake.await?, + }; if let Some(cancel_rid) = cancel_handle { if let Ok(res) = state.borrow_mut().resource_table.take_any(cancel_rid) { @@ -521,14 +573,12 @@ impl ServerWebSocket { self: &Rc, lock: AsyncMutFuture>>, frame: Frame<'_>, - ) -> Result<(), AnyError> { + ) -> Result<(), WebsocketError> { let mut ws = lock.await; if ws.is_closed() { return Ok(()); } - ws.write_frame(frame) - .await - .map_err(|err| type_error(err.to_string()))?; + ws.write_frame(frame).await?; Ok(()) } } @@ -543,7 +593,7 @@ pub fn ws_create_server_stream( state: &mut OpState, transport: NetworkStream, read_buf: Bytes, -) -> Result { +) -> ResourceId { let mut ws = WebSocket::after_handshake( WebSocketStream::new( stream::WsStreamKind::Network(transport), @@ -555,8 +605,7 @@ pub fn ws_create_server_stream( ws.set_auto_close(true); ws.set_auto_pong(true); - let rid = state.resource_table.add(ServerWebSocket::new(ws)); - Ok(rid) + state.resource_table.add(ServerWebSocket::new(ws)) } fn send_binary(state: &mut OpState, rid: ResourceId, data: &[u8]) { @@ -626,11 +675,12 @@ pub async fn op_ws_send_binary_async( state: Rc>, #[smi] rid: ResourceId, #[buffer] data: JsBuffer, -) -> Result<(), AnyError> { +) -> Result<(), WebsocketError> { let resource = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(WebsocketError::Resource)?; let data = data.to_vec(); let lock = resource.reserve_lock(); resource @@ -644,11 +694,12 @@ pub async fn op_ws_send_text_async( state: Rc>, #[smi] rid: ResourceId, #[string] data: String, -) -> Result<(), AnyError> { +) -> Result<(), WebsocketError> { let resource = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(WebsocketError::Resource)?; let lock = resource.reserve_lock(); resource .write_frame( @@ -678,11 +729,12 @@ pub fn op_ws_get_buffered_amount( pub async fn op_ws_send_ping( state: Rc>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), WebsocketError> { let resource = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(WebsocketError::Resource)?; let lock = resource.reserve_lock(); resource .write_frame( @@ -698,7 +750,7 @@ pub async fn op_ws_close( #[smi] rid: ResourceId, #[smi] code: Option, #[string] reason: Option, -) -> Result<(), AnyError> { +) -> Result<(), WebsocketError> { let Ok(resource) = state .borrow_mut() .resource_table @@ -713,8 +765,7 @@ pub async fn op_ws_close( resource.closed.set(true); let lock = resource.reserve_lock(); - resource.write_frame(lock, frame).await?; - Ok(()) + resource.write_frame(lock, frame).await } #[op2] @@ -868,32 +919,6 @@ pub fn get_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_websocket.d.ts") } -#[derive(Debug)] -pub struct DomExceptionNetworkError { - pub msg: String, -} - -impl DomExceptionNetworkError { - pub fn new(msg: &str) -> Self { - DomExceptionNetworkError { - msg: msg.to_string(), - } - } -} - -impl fmt::Display for DomExceptionNetworkError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad(&self.msg) - } -} - -impl std::error::Error for DomExceptionNetworkError {} - -pub fn get_network_error_class_name(e: &AnyError) -> Option<&'static str> { - e.downcast_ref::() - .map(|_| "DOMExceptionNetworkError") -} - // Needed so hyper can use non Send futures #[derive(Clone)] struct LocalExecutor; diff --git a/runtime/errors.rs b/runtime/errors.rs index 25fc664a57..0e551d1219 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -36,6 +36,8 @@ use deno_web::CompressionError; use deno_web::MessagePortError; use deno_web::StreamResourceError; use deno_web::WebError; +use deno_websocket::HandshakeError; +use deno_websocket::WebsocketError; use deno_webstorage::WebStorageError; use std::env; use std::error::Error; @@ -368,6 +370,43 @@ fn get_broadcast_channel_error(error: &BroadcastChannelError) -> &'static str { } } +fn get_websocket_error(error: &WebsocketError) -> &'static str { + match error { + WebsocketError::Permission(e) | WebsocketError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + WebsocketError::Url(e) => get_url_parse_error_class(e), + WebsocketError::Io(e) => get_io_error_class(e), + WebsocketError::WebSocket(_) => "TypeError", + WebsocketError::ConnectionFailed(_) => "DOMExceptionNetworkError", + WebsocketError::Uri(_) => "Error", + WebsocketError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + } +} + +fn get_websocket_handshake_error(error: &HandshakeError) -> &'static str { + match error { + HandshakeError::RootStoreError(e) => { + get_error_class_name(e).unwrap_or("Error") + } + HandshakeError::Tls(e) => get_tls_error_class(e), + HandshakeError::MissingPath => "TypeError", + HandshakeError::Http(_) => "Error", + HandshakeError::InvalidHostname(_) => "TypeError", + HandshakeError::Io(e) => get_io_error_class(e), + HandshakeError::Rustls(_) => "Error", + HandshakeError::H2(_) => "Error", + HandshakeError::NoH2Alpn => "Error", + HandshakeError::InvalidStatusCode(_) => "Error", + HandshakeError::WebSocket(_) => "TypeError", + HandshakeError::HeaderName(_) => "TypeError", + HandshakeError::HeaderValue(_) => "TypeError", + } +} + fn get_fs_error(error: &FsOpsError) -> &'static str { match error { FsOpsError::Io(e) => get_io_error_class(e), @@ -482,7 +521,6 @@ fn get_net_map_error(error: &deno_net::io::MapError) -> &'static str { pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) .or_else(|| deno_webgpu::error::get_error_class_name(e)) - .or_else(|| deno_websocket::get_network_error_class_name(e)) .or_else(|| e.downcast_ref::().map(get_napi_error_class)) .or_else(|| e.downcast_ref::().map(get_web_error_class)) .or_else(|| { @@ -518,6 +556,11 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .or_else(|| e.downcast_ref::().map(get_cron_error_class)) .or_else(|| e.downcast_ref::().map(get_canvas_error)) .or_else(|| e.downcast_ref::().map(get_cache_error)) + .or_else(|| e.downcast_ref::().map(get_websocket_error)) + .or_else(|| { + e.downcast_ref::() + .map(get_websocket_handshake_error) + }) .or_else(|| e.downcast_ref::().map(get_kv_error)) .or_else(|| e.downcast_ref::().map(get_net_error)) .or_else(|| { diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index 87fc558405..bd55a5fc8c 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; +use deno_core::error::custom_error; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; @@ -12,7 +13,6 @@ use deno_core::url::Url; use deno_core::OpState; use deno_fetch::data_url::DataUrl; use deno_web::BlobStore; -use deno_websocket::DomExceptionNetworkError; use http_body_util::BodyExt; use hyper::body::Bytes; use serde::Deserialize; @@ -151,17 +151,16 @@ pub fn op_worker_sync_fetch( match mime_type.as_deref() { Some("application/javascript" | "text/javascript") => {} Some(mime_type) => { - return Err( - DomExceptionNetworkError { - msg: format!("Invalid MIME type {mime_type:?}."), - } - .into(), - ) + return Err(custom_error( + "DOMExceptionNetworkError", + format!("Invalid MIME type {mime_type:?}."), + )) } None => { - return Err( - DomExceptionNetworkError::new("Missing MIME type.").into(), - ) + return Err(custom_error( + "DOMExceptionNetworkError", + "Missing MIME type.", + )) } } } From 0e60bb9cf701fea3864403e6851abad86bc0b65d Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 18 Oct 2024 21:45:05 +0200 Subject: [PATCH 06/16] fix(info): resolve workspace member mappings (#26350) This PR fixes the issue where mapped specifiers in a workspace member would never be found. Only mapped paths from the workspace root would resolve. This was caused by always passing the workspace root url to the import map resolver instead of the workspace member one. Fixes https://github.com/denoland/deno/issues/26138 Fixes https://github.com/denoland/fresh/issues/2615 --------- Signed-off-by: Marvin Hagemeister Co-authored-by: David Sherret --- cli/tools/info.rs | 23 +++++++++++-------- tests/specs/info/import_map/__test__.jsonc | 10 +++++--- .../info/workspace_member/__test__.jsonc | 16 +++++++++++++ tests/specs/info/workspace_member/deno.json | 3 +++ .../info_workspace_member.out | 6 +++++ .../info_workspace_member_sub.out | 6 +++++ .../info/workspace_member/member/deno.json | 5 ++++ .../info/workspace_member/member/sub/file.ts | 1 + 8 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 tests/specs/info/workspace_member/__test__.jsonc create mode 100644 tests/specs/info/workspace_member/deno.json create mode 100644 tests/specs/info/workspace_member/info_workspace_member.out create mode 100644 tests/specs/info/workspace_member/info_workspace_member_sub.out create mode 100644 tests/specs/info/workspace_member/member/deno.json create mode 100644 tests/specs/info/workspace_member/member/sub/file.ts diff --git a/cli/tools/info.rs b/cli/tools/info.rs index 7f8d68ae1c..3febaff579 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -11,6 +11,7 @@ use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_core::serde_json; +use deno_core::url; use deno_graph::Dependency; use deno_graph::GraphKind; use deno_graph::Module; @@ -51,18 +52,20 @@ pub async fn info( let npmrc = cli_options.npmrc(); let resolver = factory.workspace_resolver().await?; - let maybe_import_specifier = - if let Some(import_map) = resolver.maybe_import_map() { - if let Ok(imports_specifier) = - import_map.resolve(&specifier, import_map.base_url()) - { - Some(imports_specifier) - } else { - None - } + let cwd_url = + url::Url::from_directory_path(cli_options.initial_cwd()).unwrap(); + + let maybe_import_specifier = if let Some(import_map) = + resolver.maybe_import_map() + { + if let Ok(imports_specifier) = import_map.resolve(&specifier, &cwd_url) { + Some(imports_specifier) } else { None - }; + } + } else { + None + }; let specifier = match maybe_import_specifier { Some(specifier) => specifier, diff --git a/tests/specs/info/import_map/__test__.jsonc b/tests/specs/info/import_map/__test__.jsonc index 725276925e..7aba603e0b 100644 --- a/tests/specs/info/import_map/__test__.jsonc +++ b/tests/specs/info/import_map/__test__.jsonc @@ -1,5 +1,9 @@ { - "args": "info preact/debug", - "output": "with_import_map.out", - "exitCode": 0 + "steps": [ + { + "args": "info preact/debug", + "output": "with_import_map.out", + "exitCode": 0 + } + ] } diff --git a/tests/specs/info/workspace_member/__test__.jsonc b/tests/specs/info/workspace_member/__test__.jsonc new file mode 100644 index 0000000000..d13d3b03e8 --- /dev/null +++ b/tests/specs/info/workspace_member/__test__.jsonc @@ -0,0 +1,16 @@ +{ + "tests": { + "member_folder": { + "args": "info --quiet foo", + "cwd": "member", + "output": "info_workspace_member.out", + "exitCode": 0 + }, + "member_folder_sub": { + "args": "info --quiet foo", + "cwd": "member/sub", + "output": "info_workspace_member_sub.out", + "exitCode": 0 + } + } +} diff --git a/tests/specs/info/workspace_member/deno.json b/tests/specs/info/workspace_member/deno.json new file mode 100644 index 0000000000..f88028aeab --- /dev/null +++ b/tests/specs/info/workspace_member/deno.json @@ -0,0 +1,3 @@ +{ + "workspace": ["./member"] +} diff --git a/tests/specs/info/workspace_member/info_workspace_member.out b/tests/specs/info/workspace_member/info_workspace_member.out new file mode 100644 index 0000000000..e0dc0b6753 --- /dev/null +++ b/tests/specs/info/workspace_member/info_workspace_member.out @@ -0,0 +1,6 @@ +local: [WILDCARD]file.ts +type: TypeScript +dependencies: 0 unique +size: [WILDCARD] + +file://[WILDCARD]/member/sub/file.ts ([WILDCARD]) diff --git a/tests/specs/info/workspace_member/info_workspace_member_sub.out b/tests/specs/info/workspace_member/info_workspace_member_sub.out new file mode 100644 index 0000000000..e0dc0b6753 --- /dev/null +++ b/tests/specs/info/workspace_member/info_workspace_member_sub.out @@ -0,0 +1,6 @@ +local: [WILDCARD]file.ts +type: TypeScript +dependencies: 0 unique +size: [WILDCARD] + +file://[WILDCARD]/member/sub/file.ts ([WILDCARD]) diff --git a/tests/specs/info/workspace_member/member/deno.json b/tests/specs/info/workspace_member/member/deno.json new file mode 100644 index 0000000000..66aac29046 --- /dev/null +++ b/tests/specs/info/workspace_member/member/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "foo": "./sub/file.ts" + } +} diff --git a/tests/specs/info/workspace_member/member/sub/file.ts b/tests/specs/info/workspace_member/member/sub/file.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/tests/specs/info/workspace_member/member/sub/file.ts @@ -0,0 +1 @@ +export {}; From d48434e91fcb2945f103521fde782adad5742c63 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:39:32 -0700 Subject: [PATCH 07/16] fix(ext/node): stub HTTPParser internal binding (#26401) Fixes https://github.com/denoland/deno/issues/26394. --- ext/node/lib.rs | 1 + .../polyfills/internal_binding/http_parser.ts | 159 ++++++++++++++++++ ext/node/polyfills/internal_binding/mod.ts | 3 +- 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 ext/node/polyfills/internal_binding/http_parser.ts diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 03462f36fa..9b22add453 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -469,6 +469,7 @@ deno_core::extension!(deno_node, "internal_binding/constants.ts", "internal_binding/crypto.ts", "internal_binding/handle_wrap.ts", + "internal_binding/http_parser.ts", "internal_binding/mod.ts", "internal_binding/node_file.ts", "internal_binding/node_options.ts", diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts new file mode 100644 index 0000000000..ca4f896e20 --- /dev/null +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -0,0 +1,159 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { primordials } from "ext:core/mod.js"; +import { AsyncWrap } from "ext:deno_node/internal_binding/async_wrap.ts"; + +const { + ObjectDefineProperty, + ObjectEntries, + ObjectSetPrototypeOf, + SafeArrayIterator, +} = primordials; + +export const methods = [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "QUERY", +]; + +export const allMethods = [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "PRI", + "DESCRIBE", + "ANNOUNCE", + "SETUP", + "PLAY", + "PAUSE", + "TEARDOWN", + "GET_PARAMETER", + "SET_PARAMETER", + "REDIRECT", + "RECORD", + "FLUSH", + "QUERY", +]; + +export function HTTPParser() { +} + +ObjectSetPrototypeOf(HTTPParser.prototype, AsyncWrap.prototype); + +function defineProps(obj: object, props: Record) { + for (const entry of new SafeArrayIterator(ObjectEntries(props))) { + ObjectDefineProperty(obj, entry[0], { + value: entry[1], + enumerable: true, + writable: true, + configurable: true, + }); + } +} + +defineProps(HTTPParser, { + REQUEST: 1, + RESPONSE: 2, + kOnMessageBegin: 0, + kOnHeaders: 1, + kOnHeadersComplete: 2, + kOnBody: 3, + kOnMessageComplete: 4, + kOnExecute: 5, + kOnTimeout: 6, + kLenientNone: 0, + kLenientHeaders: 1, + kLenientChunkedLength: 2, + kLenientKeepAlive: 4, + kLenientTransferEncoding: 8, + kLenientVersion: 16, + kLenientDataAfterClose: 32, + kLenientOptionalLFAfterCR: 64, + kLenientOptionalCRLFAfterChunk: 128, + kLenientOptionalCRBeforeLF: 256, + kLenientSpacesAfterChunkSize: 512, + kLenientAll: 1023, +}); diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts index f2d7f55bc7..ebbfc629f1 100644 --- a/ext/node/polyfills/internal_binding/mod.ts +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -17,6 +17,7 @@ import * as types from "ext:deno_node/internal_binding/types.ts"; import * as udpWrap from "ext:deno_node/internal_binding/udp_wrap.ts"; import * as util from "ext:deno_node/internal_binding/util.ts"; import * as uv from "ext:deno_node/internal_binding/uv.ts"; +import * as httpParser from "ext:deno_node/internal_binding/http_parser.ts"; const modules = { "async_wrap": asyncWrap, @@ -32,7 +33,7 @@ const modules = { "fs_dir": {}, "fs_event_wrap": {}, "heap_utils": {}, - "http_parser": {}, + "http_parser": httpParser, icu: {}, inspector: {}, "js_stream": {}, From e22d0e91ef7ce17dca299a44d1ccd292abde34f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 18 Oct 2024 23:14:11 +0100 Subject: [PATCH 08/16] ci: use self-hosted mac arm runner (#26366) Supersedes #26337 --- .github/workflows/ci.generate.ts | 3 ++- .github/workflows/ci.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 98272abe4e..ab0b54bdee 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -40,7 +40,8 @@ const Runners = { macosArm: { os: "macos", arch: "aarch64", - runner: macosArmRunner, + runner: + `\${{ github.repository == 'denoland/deno' && 'self-hosted' || '${macosArmRunner}' }}`, }, windowsX86: { os: "windows", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 161b8719c8..63c14de4af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,12 +68,12 @@ jobs: skip: '${{ !contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'') }}' - os: macos arch: aarch64 - runner: macos-14 + runner: '${{ github.repository == ''denoland/deno'' && ''self-hosted'' || ''macos-14'' }}' job: test profile: debug - os: macos arch: aarch64 - runner: '${{ (!contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'')) && ''ubuntu-22.04'' || ''macos-14'' }}' + runner: '${{ (!contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'')) && ''ubuntu-22.04'' || github.repository == ''denoland/deno'' && ''self-hosted'' || ''macos-14'' }}' job: test profile: release skip: '${{ !contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'') }}' From 8ca8174c81a3de35bcb02fc371c90f9d0a7303ab Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 15:23:20 -0700 Subject: [PATCH 09/16] refactor(ext/crypto): use concrete error types (#26167) --- Cargo.lock | 2 + ext/crypto/Cargo.toml | 2 + ext/crypto/decrypt.rs | 124 ++++++++-------- ext/crypto/ed25519.rs | 22 +-- ext/crypto/encrypt.rs | 74 ++++++---- ext/crypto/export_key.rs | 79 ++++------ ext/crypto/generate_key.rs | 51 +++++-- ext/crypto/import_key.rs | 288 +++++++++++++++++++++---------------- ext/crypto/lib.rs | 270 +++++++++++++++++++--------------- ext/crypto/shared.rs | 87 ++++++----- ext/crypto/x25519.rs | 18 ++- ext/crypto/x448.rs | 19 ++- runtime/errors.rs | 204 ++++++++++++++++++++++++++ 13 files changed, 783 insertions(+), 457 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28b789c0cf..cda1499a1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1503,6 +1503,8 @@ dependencies = [ "sha2", "signature", "spki", + "thiserror", + "tokio", "uuid", "x25519-dalek", ] diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index e3b59ed469..2f970ca535 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -41,5 +41,7 @@ sha1.workspace = true sha2.workspace = true signature.workspace = true spki.workspace = true +thiserror.workspace = true +tokio.workspace = true uuid.workspace = true x25519-dalek = "2.0.0" diff --git a/ext/crypto/decrypt.rs b/ext/crypto/decrypt.rs index 9b104e1784..1140475183 100644 --- a/ext/crypto/decrypt.rs +++ b/ext/crypto/decrypt.rs @@ -16,9 +16,6 @@ use ctr::cipher::StreamCipher; use ctr::Ctr128BE; use ctr::Ctr32BE; use ctr::Ctr64BE; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; @@ -73,12 +70,36 @@ pub enum DecryptAlgorithm { }, } +#[derive(Debug, thiserror::Error)] +pub enum DecryptError { + #[error(transparent)] + General(#[from] SharedError), + #[error(transparent)] + Pkcs1(#[from] rsa::pkcs1::Error), + #[error("Decryption failed")] + Failed, + #[error("invalid length")] + InvalidLength, + #[error("invalid counter length. Currently supported 32/64/128 bits")] + InvalidCounterLength, + #[error("tag length not equal to 128")] + InvalidTagLength, + #[error("invalid key or iv")] + InvalidKeyOrIv, + #[error("tried to decrypt too much data")] + TooMuchData, + #[error("iv length not equal to 12 or 16")] + InvalidIvLength, + #[error("{0}")] + Rsa(rsa::Error), +} + #[op2(async)] #[serde] pub async fn op_crypto_decrypt( #[serde] opts: DecryptOptions, #[buffer] data: JsBuffer, -) -> Result { +) -> Result { let key = opts.key; let fun = move || match opts.algorithm { DecryptAlgorithm::RsaOaep { hash, label } => { @@ -108,7 +129,7 @@ fn decrypt_rsa_oaep( hash: ShaHash, label: Vec, data: &[u8], -) -> Result, deno_core::anyhow::Error> { +) -> Result, DecryptError> { let key = key.as_rsa_private_key()?; let private_key = rsa::RsaPrivateKey::from_pkcs1_der(key)?; @@ -139,7 +160,7 @@ fn decrypt_rsa_oaep( private_key .decrypt(padding, data) - .map_err(|e| custom_error("DOMExceptionOperationError", e.to_string())) + .map_err(DecryptError::Rsa) } fn decrypt_aes_cbc( @@ -147,7 +168,7 @@ fn decrypt_aes_cbc( length: usize, iv: Vec, data: &[u8], -) -> Result, deno_core::anyhow::Error> { +) -> Result, DecryptError> { let key = key.as_secret_key()?; // 2. @@ -155,53 +176,32 @@ fn decrypt_aes_cbc( 128 => { // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 type Aes128CbcDec = cbc::Decryptor; - let cipher = Aes128CbcDec::new_from_slices(key, &iv).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Invalid key or iv".to_string(), - ) - })?; + let cipher = Aes128CbcDec::new_from_slices(key, &iv) + .map_err(|_| DecryptError::InvalidKeyOrIv)?; - cipher.decrypt_padded_vec_mut::(data).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Decryption failed".to_string(), - ) - })? + cipher + .decrypt_padded_vec_mut::(data) + .map_err(|_| DecryptError::Failed)? } 192 => { // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 type Aes192CbcDec = cbc::Decryptor; - let cipher = Aes192CbcDec::new_from_slices(key, &iv).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Invalid key or iv".to_string(), - ) - })?; + let cipher = Aes192CbcDec::new_from_slices(key, &iv) + .map_err(|_| DecryptError::InvalidKeyOrIv)?; - cipher.decrypt_padded_vec_mut::(data).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Decryption failed".to_string(), - ) - })? + cipher + .decrypt_padded_vec_mut::(data) + .map_err(|_| DecryptError::Failed)? } 256 => { // Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315 type Aes256CbcDec = cbc::Decryptor; - let cipher = Aes256CbcDec::new_from_slices(key, &iv).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Invalid key or iv".to_string(), - ) - })?; + let cipher = Aes256CbcDec::new_from_slices(key, &iv) + .map_err(|_| DecryptError::InvalidKeyOrIv)?; - cipher.decrypt_padded_vec_mut::(data).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "Decryption failed".to_string(), - ) - })? + cipher + .decrypt_padded_vec_mut::(data) + .map_err(|_| DecryptError::Failed)? } _ => unreachable!(), }; @@ -214,7 +214,7 @@ fn decrypt_aes_ctr_gen( key: &[u8], counter: &[u8], data: &[u8], -) -> Result, AnyError> +) -> Result, DecryptError> where B: KeyIvInit + StreamCipher, { @@ -223,7 +223,7 @@ where let mut plaintext = data.to_vec(); cipher .try_apply_keystream(&mut plaintext) - .map_err(|_| operation_error("tried to decrypt too much data"))?; + .map_err(|_| DecryptError::TooMuchData)?; Ok(plaintext) } @@ -235,12 +235,12 @@ fn decrypt_aes_gcm_gen>( length: usize, additional_data: Vec, plaintext: &mut [u8], -) -> Result<(), AnyError> { +) -> Result<(), DecryptError> { let nonce = Nonce::from_slice(nonce); match length { 128 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Decryption failed"))?; + .map_err(|_| DecryptError::Failed)?; cipher .decrypt_in_place_detached( nonce, @@ -248,11 +248,11 @@ fn decrypt_aes_gcm_gen>( plaintext, tag, ) - .map_err(|_| operation_error("Decryption failed"))? + .map_err(|_| DecryptError::Failed)? } 192 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Decryption failed"))?; + .map_err(|_| DecryptError::Failed)?; cipher .decrypt_in_place_detached( nonce, @@ -260,11 +260,11 @@ fn decrypt_aes_gcm_gen>( plaintext, tag, ) - .map_err(|_| operation_error("Decryption failed"))? + .map_err(|_| DecryptError::Failed)? } 256 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Decryption failed"))?; + .map_err(|_| DecryptError::Failed)?; cipher .decrypt_in_place_detached( nonce, @@ -272,9 +272,9 @@ fn decrypt_aes_gcm_gen>( plaintext, tag, ) - .map_err(|_| operation_error("Decryption failed"))? + .map_err(|_| DecryptError::Failed)? } - _ => return Err(type_error("invalid length")), + _ => return Err(DecryptError::InvalidLength), }; Ok(()) @@ -286,7 +286,7 @@ fn decrypt_aes_ctr( counter: &[u8], ctr_length: usize, data: &[u8], -) -> Result, deno_core::anyhow::Error> { +) -> Result, DecryptError> { let key = key.as_secret_key()?; match ctr_length { @@ -294,23 +294,21 @@ fn decrypt_aes_ctr( 128 => decrypt_aes_ctr_gen::>(key, counter, data), 192 => decrypt_aes_ctr_gen::>(key, counter, data), 256 => decrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(DecryptError::InvalidLength), }, 64 => match key_length { 128 => decrypt_aes_ctr_gen::>(key, counter, data), 192 => decrypt_aes_ctr_gen::>(key, counter, data), 256 => decrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(DecryptError::InvalidLength), }, 128 => match key_length { 128 => decrypt_aes_ctr_gen::>(key, counter, data), 192 => decrypt_aes_ctr_gen::>(key, counter, data), 256 => decrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(DecryptError::InvalidLength), }, - _ => Err(type_error( - "invalid counter length. Currently supported 32/64/128 bits", - )), + _ => Err(DecryptError::InvalidCounterLength), } } @@ -321,7 +319,7 @@ fn decrypt_aes_gcm( iv: Vec, additional_data: Option>, data: &[u8], -) -> Result, AnyError> { +) -> Result, DecryptError> { let key = key.as_secret_key()?; let additional_data = additional_data.unwrap_or_default(); @@ -330,7 +328,7 @@ fn decrypt_aes_gcm( // Note that encryption won't fail, it instead truncates the tag // to the specified tag length as specified in the spec. if tag_length != 128 { - return Err(type_error("tag length not equal to 128")); + return Err(DecryptError::InvalidTagLength); } let sep = data.len() - (tag_length / 8); @@ -357,7 +355,7 @@ fn decrypt_aes_gcm( additional_data, &mut plaintext, )?, - _ => return Err(type_error("iv length not equal to 12 or 16")), + _ => return Err(DecryptError::InvalidIvLength), } Ok(plaintext) diff --git a/ext/crypto/ed25519.rs b/ext/crypto/ed25519.rs index 4f604fe513..da34b7d25d 100644 --- a/ext/crypto/ed25519.rs +++ b/ext/crypto/ed25519.rs @@ -2,8 +2,6 @@ use base64::prelude::BASE64_URL_SAFE_NO_PAD; use base64::Engine; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ToJsBuffer; use elliptic_curve::pkcs8::PrivateKeyInfo; @@ -15,6 +13,16 @@ use spki::der::asn1::BitString; use spki::der::Decode; use spki::der::Encode; +#[derive(Debug, thiserror::Error)] +pub enum Ed25519Error { + #[error("Failed to export key")] + FailedExport, + #[error(transparent)] + Der(#[from] rsa::pkcs1::der::Error), + #[error(transparent)] + KeyRejected(#[from] ring::error::KeyRejected), +} + #[op2(fast)] pub fn op_crypto_generate_ed25519_keypair( #[buffer] pkey: &mut [u8], @@ -116,7 +124,7 @@ pub fn op_crypto_import_pkcs8_ed25519( #[serde] pub fn op_crypto_export_spki_ed25519( #[buffer] pubkey: &[u8], -) -> Result { +) -> Result { let key_info = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifierOwned { // id-Ed25519 @@ -128,9 +136,7 @@ pub fn op_crypto_export_spki_ed25519( Ok( key_info .to_der() - .map_err(|_| { - custom_error("DOMExceptionOperationError", "Failed to export key") - })? + .map_err(|_| Ed25519Error::FailedExport)? .into(), ) } @@ -139,7 +145,7 @@ pub fn op_crypto_export_spki_ed25519( #[serde] pub fn op_crypto_export_pkcs8_ed25519( #[buffer] pkey: &[u8], -) -> Result { +) -> Result { use rsa::pkcs1::der::Encode; // This should probably use OneAsymmetricKey instead @@ -164,7 +170,7 @@ pub fn op_crypto_export_pkcs8_ed25519( #[string] pub fn op_crypto_jwk_x_ed25519( #[buffer] pkey: &[u8], -) -> Result { +) -> Result { let pair = Ed25519KeyPair::from_seed_unchecked(pkey)?; Ok(BASE64_URL_SAFE_NO_PAD.encode(pair.public_key().as_ref())) } diff --git a/ext/crypto/encrypt.rs b/ext/crypto/encrypt.rs index 204648e892..66b27657f8 100644 --- a/ext/crypto/encrypt.rs +++ b/ext/crypto/encrypt.rs @@ -16,8 +16,6 @@ use aes_gcm::Nonce; use ctr::Ctr128BE; use ctr::Ctr32BE; use ctr::Ctr64BE; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; @@ -73,12 +71,30 @@ pub enum EncryptAlgorithm { }, } +#[derive(Debug, thiserror::Error)] +pub enum EncryptError { + #[error(transparent)] + General(#[from] SharedError), + #[error("invalid length")] + InvalidLength, + #[error("invalid key or iv")] + InvalidKeyOrIv, + #[error("iv length not equal to 12 or 16")] + InvalidIvLength, + #[error("invalid counter length. Currently supported 32/64/128 bits")] + InvalidCounterLength, + #[error("tried to encrypt too much data")] + TooMuchData, + #[error("Encryption failed")] + Failed, +} + #[op2(async)] #[serde] pub async fn op_crypto_encrypt( #[serde] opts: EncryptOptions, #[buffer] data: JsBuffer, -) -> Result { +) -> Result { let key = opts.key; let fun = move || match opts.algorithm { EncryptAlgorithm::RsaOaep { hash, label } => { @@ -108,12 +124,12 @@ fn encrypt_rsa_oaep( hash: ShaHash, label: Vec, data: &[u8], -) -> Result, AnyError> { +) -> Result, EncryptError> { let label = String::from_utf8_lossy(&label).to_string(); let public_key = key.as_rsa_public_key()?; let public_key = rsa::RsaPublicKey::from_pkcs1_der(&public_key) - .map_err(|_| operation_error("failed to decode public key"))?; + .map_err(|_| SharedError::FailedDecodePublicKey)?; let mut rng = OsRng; let padding = match hash { ShaHash::Sha1 => rsa::Oaep { @@ -139,7 +155,7 @@ fn encrypt_rsa_oaep( }; let encrypted = public_key .encrypt(&mut rng, padding, data) - .map_err(|_| operation_error("Encryption failed"))?; + .map_err(|_| EncryptError::Failed)?; Ok(encrypted) } @@ -148,7 +164,7 @@ fn encrypt_aes_cbc( length: usize, iv: Vec, data: &[u8], -) -> Result, AnyError> { +) -> Result, EncryptError> { let key = key.as_secret_key()?; let ciphertext = match length { 128 => { @@ -156,7 +172,7 @@ fn encrypt_aes_cbc( type Aes128CbcEnc = cbc::Encryptor; let cipher = Aes128CbcEnc::new_from_slices(key, &iv) - .map_err(|_| operation_error("invalid key or iv".to_string()))?; + .map_err(|_| EncryptError::InvalidKeyOrIv)?; cipher.encrypt_padded_vec_mut::(data) } 192 => { @@ -164,7 +180,7 @@ fn encrypt_aes_cbc( type Aes192CbcEnc = cbc::Encryptor; let cipher = Aes192CbcEnc::new_from_slices(key, &iv) - .map_err(|_| operation_error("invalid key or iv".to_string()))?; + .map_err(|_| EncryptError::InvalidKeyOrIv)?; cipher.encrypt_padded_vec_mut::(data) } 256 => { @@ -172,10 +188,10 @@ fn encrypt_aes_cbc( type Aes256CbcEnc = cbc::Encryptor; let cipher = Aes256CbcEnc::new_from_slices(key, &iv) - .map_err(|_| operation_error("invalid key or iv".to_string()))?; + .map_err(|_| EncryptError::InvalidKeyOrIv)?; cipher.encrypt_padded_vec_mut::(data) } - _ => return Err(type_error("invalid length")), + _ => return Err(EncryptError::InvalidLength), }; Ok(ciphertext) } @@ -186,31 +202,31 @@ fn encrypt_aes_gcm_general>( length: usize, ciphertext: &mut [u8], additional_data: Vec, -) -> Result { +) -> Result { let nonce = Nonce::::from_slice(&iv); let tag = match length { 128 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Encryption failed"))?; + .map_err(|_| EncryptError::Failed)?; cipher .encrypt_in_place_detached(nonce, &additional_data, ciphertext) - .map_err(|_| operation_error("Encryption failed"))? + .map_err(|_| EncryptError::Failed)? } 192 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Encryption failed"))?; + .map_err(|_| EncryptError::Failed)?; cipher .encrypt_in_place_detached(nonce, &additional_data, ciphertext) - .map_err(|_| operation_error("Encryption failed"))? + .map_err(|_| EncryptError::Failed)? } 256 => { let cipher = aes_gcm::AesGcm::::new_from_slice(key) - .map_err(|_| operation_error("Encryption failed"))?; + .map_err(|_| EncryptError::Failed)?; cipher .encrypt_in_place_detached(nonce, &additional_data, ciphertext) - .map_err(|_| operation_error("Encryption failed"))? + .map_err(|_| EncryptError::Failed)? } - _ => return Err(type_error("invalid length")), + _ => return Err(EncryptError::InvalidLength), }; Ok(tag) @@ -223,7 +239,7 @@ fn encrypt_aes_gcm( iv: Vec, additional_data: Option>, data: &[u8], -) -> Result, AnyError> { +) -> Result, EncryptError> { let key = key.as_secret_key()?; let additional_data = additional_data.unwrap_or_default(); @@ -244,7 +260,7 @@ fn encrypt_aes_gcm( &mut ciphertext, additional_data, )?, - _ => return Err(type_error("iv length not equal to 12 or 16")), + _ => return Err(EncryptError::InvalidIvLength), }; // Truncated tag to the specified tag length. @@ -261,7 +277,7 @@ fn encrypt_aes_ctr_gen( key: &[u8], counter: &[u8], data: &[u8], -) -> Result, AnyError> +) -> Result, EncryptError> where B: KeyIvInit + StreamCipher, { @@ -270,7 +286,7 @@ where let mut ciphertext = data.to_vec(); cipher .try_apply_keystream(&mut ciphertext) - .map_err(|_| operation_error("tried to encrypt too much data"))?; + .map_err(|_| EncryptError::TooMuchData)?; Ok(ciphertext) } @@ -281,7 +297,7 @@ fn encrypt_aes_ctr( counter: &[u8], ctr_length: usize, data: &[u8], -) -> Result, AnyError> { +) -> Result, EncryptError> { let key = key.as_secret_key()?; match ctr_length { @@ -289,22 +305,20 @@ fn encrypt_aes_ctr( 128 => encrypt_aes_ctr_gen::>(key, counter, data), 192 => encrypt_aes_ctr_gen::>(key, counter, data), 256 => encrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(EncryptError::InvalidLength), }, 64 => match key_length { 128 => encrypt_aes_ctr_gen::>(key, counter, data), 192 => encrypt_aes_ctr_gen::>(key, counter, data), 256 => encrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(EncryptError::InvalidLength), }, 128 => match key_length { 128 => encrypt_aes_ctr_gen::>(key, counter, data), 192 => encrypt_aes_ctr_gen::>(key, counter, data), 256 => encrypt_aes_ctr_gen::>(key, counter, data), - _ => Err(type_error("invalid length")), + _ => Err(EncryptError::InvalidLength), }, - _ => Err(type_error( - "invalid counter length. Currently supported 32/64/128 bits", - )), + _ => Err(EncryptError::InvalidCounterLength), } } diff --git a/ext/crypto/export_key.rs b/ext/crypto/export_key.rs index 00ce7e11c6..edf0d7239c 100644 --- a/ext/crypto/export_key.rs +++ b/ext/crypto/export_key.rs @@ -4,8 +4,6 @@ use base64::prelude::BASE64_URL_SAFE_NO_PAD; use base64::Engine; use const_oid::AssociatedOid; use const_oid::ObjectIdentifier; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ToJsBuffer; use elliptic_curve::sec1::ToEncodedPoint; @@ -22,6 +20,16 @@ use spki::AlgorithmIdentifierOwned; use crate::shared::*; +#[derive(Debug, thiserror::Error)] +pub enum ExportKeyError { + #[error(transparent)] + General(#[from] SharedError), + #[error(transparent)] + Der(#[from] spki::der::Error), + #[error("Unsupported named curve")] + UnsupportedNamedCurve, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExportKeyOptions { @@ -99,7 +107,7 @@ pub enum ExportKeyResult { pub fn op_crypto_export_key( #[serde] opts: ExportKeyOptions, #[serde] key_data: V8RawKeyData, -) -> Result { +) -> Result { match opts.algorithm { ExportKeyAlgorithm::RsassaPkcs1v15 {} | ExportKeyAlgorithm::RsaPss {} @@ -125,7 +133,7 @@ fn bytes_to_b64(bytes: &[u8]) -> String { fn export_key_rsa( format: ExportKeyFormat, key_data: V8RawKeyData, -) -> Result { +) -> Result { match format { ExportKeyFormat::Spki => { let subject_public_key = &key_data.as_rsa_public_key()?; @@ -181,12 +189,7 @@ fn export_key_rsa( ExportKeyFormat::JwkPublic => { let public_key = key_data.as_rsa_public_key()?; let public_key = rsa::pkcs1::RsaPublicKey::from_der(&public_key) - .map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "failed to decode public key", - ) - })?; + .map_err(|_| SharedError::FailedDecodePublicKey)?; Ok(ExportKeyResult::JwkPublicRsa { n: uint_to_b64(public_key.modulus), @@ -196,12 +199,7 @@ fn export_key_rsa( ExportKeyFormat::JwkPrivate => { let private_key = key_data.as_rsa_private_key()?; let private_key = rsa::pkcs1::RsaPrivateKey::from_der(private_key) - .map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "failed to decode private key", - ) - })?; + .map_err(|_| SharedError::FailedDecodePrivateKey)?; Ok(ExportKeyResult::JwkPrivateRsa { n: uint_to_b64(private_key.modulus), @@ -214,14 +212,14 @@ fn export_key_rsa( qi: uint_to_b64(private_key.coefficient), }) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } fn export_key_symmetric( format: ExportKeyFormat, key_data: V8RawKeyData, -) -> Result { +) -> Result { match format { ExportKeyFormat::JwkSecret => { let bytes = key_data.as_secret_key()?; @@ -230,7 +228,7 @@ fn export_key_symmetric( k: bytes_to_b64(bytes), }) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } @@ -239,7 +237,7 @@ fn export_key_ec( key_data: V8RawKeyData, algorithm: ExportKeyAlgorithm, named_curve: EcNamedCurve, -) -> Result { +) -> Result { match format { ExportKeyFormat::Raw => { let subject_public_key = match named_curve { @@ -332,10 +330,7 @@ fn export_key_ec( y: bytes_to_b64(y), }) } else { - Err(custom_error( - "DOMExceptionOperationError", - "failed to decode public key", - )) + Err(SharedError::FailedDecodePublicKey.into()) } } EcNamedCurve::P384 => { @@ -350,10 +345,7 @@ fn export_key_ec( y: bytes_to_b64(y), }) } else { - Err(custom_error( - "DOMExceptionOperationError", - "failed to decode public key", - )) + Err(SharedError::FailedDecodePublicKey.into()) } } EcNamedCurve::P521 => { @@ -368,10 +360,7 @@ fn export_key_ec( y: bytes_to_b64(y), }) } else { - Err(custom_error( - "DOMExceptionOperationError", - "failed to decode public key", - )) + Err(SharedError::FailedDecodePublicKey.into()) } } }, @@ -380,13 +369,8 @@ fn export_key_ec( match named_curve { EcNamedCurve::P256 => { - let ec_key = - p256::SecretKey::from_pkcs8_der(private_key).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "failed to decode private key", - ) - })?; + let ec_key = p256::SecretKey::from_pkcs8_der(private_key) + .map_err(|_| SharedError::FailedDecodePrivateKey)?; let point = ec_key.public_key().to_encoded_point(false); if let elliptic_curve::sec1::Coordinates::Uncompressed { x, y } = @@ -398,18 +382,13 @@ fn export_key_ec( d: bytes_to_b64(&ec_key.to_bytes()), }) } else { - Err(data_error("expected valid public EC key")) + Err(SharedError::ExpectedValidPublicECKey.into()) } } EcNamedCurve::P384 => { - let ec_key = - p384::SecretKey::from_pkcs8_der(private_key).map_err(|_| { - custom_error( - "DOMExceptionOperationError", - "failed to decode private key", - ) - })?; + let ec_key = p384::SecretKey::from_pkcs8_der(private_key) + .map_err(|_| SharedError::FailedDecodePrivateKey)?; let point = ec_key.public_key().to_encoded_point(false); if let elliptic_curve::sec1::Coordinates::Uncompressed { x, y } = @@ -421,12 +400,12 @@ fn export_key_ec( d: bytes_to_b64(&ec_key.to_bytes()), }) } else { - Err(data_error("expected valid public EC key")) + Err(SharedError::ExpectedValidPublicECKey.into()) } } - _ => Err(not_supported_error("Unsupported namedCurve")), + _ => Err(ExportKeyError::UnsupportedNamedCurve), } } - ExportKeyFormat::JwkSecret => Err(unsupported_format()), + ExportKeyFormat::JwkSecret => Err(SharedError::UnsupportedFormat.into()), } } diff --git a/ext/crypto/generate_key.rs b/ext/crypto/generate_key.rs index 43aea2c705..3c0bd77c22 100644 --- a/ext/crypto/generate_key.rs +++ b/ext/crypto/generate_key.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::unsync::spawn_blocking; use deno_core::ToJsBuffer; @@ -16,6 +15,26 @@ use serde::Deserialize; use crate::shared::*; +#[derive(Debug, thiserror::Error)] +pub enum GenerateKeyError { + #[error(transparent)] + General(#[from] SharedError), + #[error("Bad public exponent")] + BadPublicExponent, + #[error("Invalid HMAC key length")] + InvalidHMACKeyLength, + #[error("Failed to serialize RSA key")] + FailedRSAKeySerialization, + #[error("Invalid AES key length")] + InvalidAESKeyLength, + #[error("Failed to generate RSA key")] + FailedRSAKeyGeneration, + #[error("Failed to generate EC key")] + FailedECKeyGeneration, + #[error("Failed to generate key")] + FailedKeyGeneration, +} + // Allowlist for RSA public exponents. static PUB_EXPONENT_1: Lazy = Lazy::new(|| BigUint::from_u64(3).unwrap()); @@ -46,7 +65,7 @@ pub enum GenerateKeyOptions { #[serde] pub async fn op_crypto_generate_key( #[serde] opts: GenerateKeyOptions, -) -> Result { +) -> Result { let fun = || match opts { GenerateKeyOptions::Rsa { modulus_length, @@ -65,21 +84,21 @@ pub async fn op_crypto_generate_key( fn generate_key_rsa( modulus_length: u32, public_exponent: &[u8], -) -> Result, AnyError> { +) -> Result, GenerateKeyError> { let exponent = BigUint::from_bytes_be(public_exponent); if exponent != *PUB_EXPONENT_1 && exponent != *PUB_EXPONENT_2 { - return Err(operation_error("Bad public exponent")); + return Err(GenerateKeyError::BadPublicExponent); } let mut rng = OsRng; let private_key = RsaPrivateKey::new_with_exp(&mut rng, modulus_length as usize, &exponent) - .map_err(|_| operation_error("Failed to generate RSA key"))?; + .map_err(|_| GenerateKeyError::FailedRSAKeyGeneration)?; let private_key = private_key .to_pkcs1_der() - .map_err(|_| operation_error("Failed to serialize RSA key"))?; + .map_err(|_| GenerateKeyError::FailedRSAKeySerialization)?; Ok(private_key.as_bytes().to_vec()) } @@ -90,7 +109,9 @@ fn generate_key_ec_p521() -> Vec { key.to_nonzero_scalar().to_bytes().to_vec() } -fn generate_key_ec(named_curve: EcNamedCurve) -> Result, AnyError> { +fn generate_key_ec( + named_curve: EcNamedCurve, +) -> Result, GenerateKeyError> { let curve = match named_curve { EcNamedCurve::P256 => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, EcNamedCurve::P384 => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, @@ -100,21 +121,21 @@ fn generate_key_ec(named_curve: EcNamedCurve) -> Result, AnyError> { let rng = ring::rand::SystemRandom::new(); let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng) - .map_err(|_| operation_error("Failed to generate EC key"))?; + .map_err(|_| GenerateKeyError::FailedECKeyGeneration)?; Ok(pkcs8.as_ref().to_vec()) } -fn generate_key_aes(length: usize) -> Result, AnyError> { +fn generate_key_aes(length: usize) -> Result, GenerateKeyError> { if length % 8 != 0 || length > 256 { - return Err(operation_error("Invalid AES key length")); + return Err(GenerateKeyError::InvalidAESKeyLength); } let mut key = vec![0u8; length / 8]; let rng = ring::rand::SystemRandom::new(); rng .fill(&mut key) - .map_err(|_| operation_error("Failed to generate key"))?; + .map_err(|_| GenerateKeyError::FailedKeyGeneration)?; Ok(key) } @@ -122,7 +143,7 @@ fn generate_key_aes(length: usize) -> Result, AnyError> { fn generate_key_hmac( hash: ShaHash, length: Option, -) -> Result, AnyError> { +) -> Result, GenerateKeyError> { let hash = match hash { ShaHash::Sha1 => &ring::hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, ShaHash::Sha256 => &ring::hmac::HMAC_SHA256, @@ -132,12 +153,12 @@ fn generate_key_hmac( let length = if let Some(length) = length { if length % 8 != 0 { - return Err(operation_error("Invalid HMAC key length")); + return Err(GenerateKeyError::InvalidHMACKeyLength); } let length = length / 8; if length > ring::digest::MAX_BLOCK_LEN { - return Err(operation_error("Invalid HMAC key length")); + return Err(GenerateKeyError::InvalidHMACKeyLength); } length @@ -149,7 +170,7 @@ fn generate_key_hmac( let mut key = vec![0u8; length]; rng .fill(&mut key) - .map_err(|_| operation_error("Failed to generate key"))?; + .map_err(|_| GenerateKeyError::FailedKeyGeneration)?; Ok(key) } diff --git a/ext/crypto/import_key.rs b/ext/crypto/import_key.rs index e30baea03a..3463ca2beb 100644 --- a/ext/crypto/import_key.rs +++ b/ext/crypto/import_key.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use base64::Engine; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::JsBuffer; use deno_core::ToJsBuffer; @@ -15,6 +14,70 @@ use spki::der::Decode; use crate::shared::*; +#[derive(Debug, thiserror::Error)] +pub enum ImportKeyError { + #[error(transparent)] + General(#[from] SharedError), + #[error("invalid modulus")] + InvalidModulus, + #[error("invalid public exponent")] + InvalidPublicExponent, + #[error("invalid private exponent")] + InvalidPrivateExponent, + #[error("invalid first prime factor")] + InvalidFirstPrimeFactor, + #[error("invalid second prime factor")] + InvalidSecondPrimeFactor, + #[error("invalid first CRT exponent")] + InvalidFirstCRTExponent, + #[error("invalid second CRT exponent")] + InvalidSecondCRTExponent, + #[error("invalid CRT coefficient")] + InvalidCRTCoefficient, + #[error("invalid b64 coordinate")] + InvalidB64Coordinate, + #[error("invalid RSA public key")] + InvalidRSAPublicKey, + #[error("invalid RSA private key")] + InvalidRSAPrivateKey, + #[error("unsupported algorithm")] + UnsupportedAlgorithm, + #[error("public key is invalid (too long)")] + PublicKeyTooLong, + #[error("private key is invalid (too long)")] + PrivateKeyTooLong, + #[error("invalid P-256 elliptic curve point")] + InvalidP256ECPoint, + #[error("invalid P-384 elliptic curve point")] + InvalidP384ECPoint, + #[error("invalid P-521 elliptic curve point")] + InvalidP521ECPoint, + #[error("invalid P-256 elliptic curve SPKI data")] + InvalidP256ECSPKIData, + #[error("invalid P-384 elliptic curve SPKI data")] + InvalidP384ECSPKIData, + #[error("invalid P-521 elliptic curve SPKI data")] + InvalidP521ECSPKIData, + #[error("curve mismatch")] + CurveMismatch, + #[error("Unsupported named curve")] + UnsupportedNamedCurve, + #[error("invalid key data")] + InvalidKeyData, + #[error("invalid JWK private key")] + InvalidJWKPrivateKey, + #[error(transparent)] + EllipticCurve(#[from] elliptic_curve::Error), + #[error("expected valid PKCS#8 data")] + ExpectedValidPkcs8Data, + #[error("malformed parameters")] + MalformedParameters, + #[error(transparent)] + Spki(#[from] spki::Error), + #[error(transparent)] + Der(#[from] rsa::pkcs1::der::Error), +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub enum KeyData { @@ -93,7 +156,7 @@ pub enum ImportKeyResult { pub fn op_crypto_import_key( #[serde] opts: ImportKeyOptions, #[serde] key_data: KeyData, -) -> Result { +) -> Result { match opts { ImportKeyOptions::RsassaPkcs1v15 {} => import_key_rsassa(key_data), ImportKeyOptions::RsaPss {} => import_key_rsapss(key_data), @@ -117,21 +180,21 @@ const BASE64_URL_SAFE_FORGIVING: ); macro_rules! jwt_b64_int_or_err { - ($name:ident, $b64:expr, $err:expr) => { + ($name:ident, $b64:expr, $err:tt) => { let bytes = BASE64_URL_SAFE_FORGIVING .decode($b64) - .map_err(|_| data_error($err))?; - let $name = UintRef::new(&bytes).map_err(|_| data_error($err))?; + .map_err(|_| ImportKeyError::$err)?; + let $name = UintRef::new(&bytes).map_err(|_| ImportKeyError::$err)?; }; } fn import_key_rsa_jwk( key_data: KeyData, -) -> Result { +) -> Result { match key_data { KeyData::JwkPublicRsa { n, e } => { - jwt_b64_int_or_err!(modulus, &n, "invalid modulus"); - jwt_b64_int_or_err!(public_exponent, &e, "invalid public exponent"); + jwt_b64_int_or_err!(modulus, &n, InvalidModulus); + jwt_b64_int_or_err!(public_exponent, &e, InvalidPublicExponent); let public_key = rsa::pkcs1::RsaPublicKey { modulus, @@ -141,7 +204,7 @@ fn import_key_rsa_jwk( let mut data = Vec::new(); public_key .encode_to_vec(&mut data) - .map_err(|_| data_error("invalid rsa public key"))?; + .map_err(|_| ImportKeyError::InvalidRSAPublicKey)?; let public_exponent = public_key.public_exponent.as_bytes().to_vec().into(); @@ -163,14 +226,14 @@ fn import_key_rsa_jwk( dq, qi, } => { - jwt_b64_int_or_err!(modulus, &n, "invalid modulus"); - jwt_b64_int_or_err!(public_exponent, &e, "invalid public exponent"); - jwt_b64_int_or_err!(private_exponent, &d, "invalid private exponent"); - jwt_b64_int_or_err!(prime1, &p, "invalid first prime factor"); - jwt_b64_int_or_err!(prime2, &q, "invalid second prime factor"); - jwt_b64_int_or_err!(exponent1, &dp, "invalid first CRT exponent"); - jwt_b64_int_or_err!(exponent2, &dq, "invalid second CRT exponent"); - jwt_b64_int_or_err!(coefficient, &qi, "invalid CRT coefficient"); + jwt_b64_int_or_err!(modulus, &n, InvalidModulus); + jwt_b64_int_or_err!(public_exponent, &e, InvalidPublicExponent); + jwt_b64_int_or_err!(private_exponent, &d, InvalidPrivateExponent); + jwt_b64_int_or_err!(prime1, &p, InvalidFirstPrimeFactor); + jwt_b64_int_or_err!(prime2, &q, InvalidSecondPrimeFactor); + jwt_b64_int_or_err!(exponent1, &dp, InvalidFirstCRTExponent); + jwt_b64_int_or_err!(exponent2, &dq, InvalidSecondCRTExponent); + jwt_b64_int_or_err!(coefficient, &qi, InvalidCRTCoefficient); let private_key = rsa::pkcs1::RsaPrivateKey { modulus, @@ -187,7 +250,7 @@ fn import_key_rsa_jwk( let mut data = Vec::new(); private_key .encode_to_vec(&mut data) - .map_err(|_| data_error("invalid rsa private key"))?; + .map_err(|_| ImportKeyError::InvalidRSAPrivateKey)?; let public_exponent = private_key.public_exponent.as_bytes().to_vec().into(); @@ -205,37 +268,33 @@ fn import_key_rsa_jwk( fn import_key_rsassa( key_data: KeyData, -) -> Result { +) -> Result { match key_data { KeyData::Spki(data) => { // 2-3. - let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let public_key = rsa::pkcs1::RsaPublicKey::from_der( pk_info.subject_public_key.raw_bytes(), - ) - .map_err(|e| data_error(e.to_string()))?; + )?; - let bytes_consumed = public_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = public_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new( pk_info.subject_public_key.raw_bytes().len() as u16, ) { - return Err(data_error("public key is invalid (too long)")); + return Err(ImportKeyError::PublicKeyTooLong); } let data = pk_info.subject_public_key.raw_bytes().to_vec().into(); @@ -251,30 +310,26 @@ fn import_key_rsassa( } KeyData::Pkcs8(data) => { // 2-3. - let pk_info = PrivateKeyInfo::from_der(&data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = PrivateKeyInfo::from_der(&data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key) - .map_err(|e| data_error(e.to_string()))?; + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; - let bytes_consumed = private_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = private_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) { - return Err(data_error("private key is invalid (too long)")); + return Err(ImportKeyError::PrivateKeyTooLong); } let data = pk_info.private_key.to_vec().into(); @@ -291,43 +346,39 @@ fn import_key_rsassa( KeyData::JwkPublicRsa { .. } | KeyData::JwkPrivateRsa { .. } => { import_key_rsa_jwk(key_data) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } fn import_key_rsapss( key_data: KeyData, -) -> Result { +) -> Result { match key_data { KeyData::Spki(data) => { // 2-3. - let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let public_key = rsa::pkcs1::RsaPublicKey::from_der( pk_info.subject_public_key.raw_bytes(), - ) - .map_err(|e| data_error(e.to_string()))?; + )?; - let bytes_consumed = public_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = public_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new( pk_info.subject_public_key.raw_bytes().len() as u16, ) { - return Err(data_error("public key is invalid (too long)")); + return Err(ImportKeyError::PublicKeyTooLong); } let data = pk_info.subject_public_key.raw_bytes().to_vec().into(); @@ -343,30 +394,26 @@ fn import_key_rsapss( } KeyData::Pkcs8(data) => { // 2-3. - let pk_info = PrivateKeyInfo::from_der(&data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = PrivateKeyInfo::from_der(&data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key) - .map_err(|e| data_error(e.to_string()))?; + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; - let bytes_consumed = private_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = private_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) { - return Err(data_error("private key is invalid (too long)")); + return Err(ImportKeyError::PrivateKeyTooLong); } let data = pk_info.private_key.to_vec().into(); @@ -383,43 +430,39 @@ fn import_key_rsapss( KeyData::JwkPublicRsa { .. } | KeyData::JwkPrivateRsa { .. } => { import_key_rsa_jwk(key_data) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } fn import_key_rsaoaep( key_data: KeyData, -) -> Result { +) -> Result { match key_data { KeyData::Spki(data) => { // 2-3. - let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let public_key = rsa::pkcs1::RsaPublicKey::from_der( pk_info.subject_public_key.raw_bytes(), - ) - .map_err(|e| data_error(e.to_string()))?; + )?; - let bytes_consumed = public_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = public_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new( pk_info.subject_public_key.raw_bytes().len() as u16, ) { - return Err(data_error("public key is invalid (too long)")); + return Err(ImportKeyError::PublicKeyTooLong); } let data = pk_info.subject_public_key.raw_bytes().to_vec().into(); @@ -435,30 +478,26 @@ fn import_key_rsaoaep( } KeyData::Pkcs8(data) => { // 2-3. - let pk_info = PrivateKeyInfo::from_der(&data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = PrivateKeyInfo::from_der(&data)?; // 4-5. let alg = pk_info.algorithm.oid; // 6-7. (skipped, only support rsaEncryption for interoperability) if alg != RSA_ENCRYPTION_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 8-9. let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key) - .map_err(|e| data_error(e.to_string()))?; + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; - let bytes_consumed = private_key - .encoded_len() - .map_err(|e| data_error(e.to_string()))?; + let bytes_consumed = private_key.encoded_len()?; if bytes_consumed != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) { - return Err(data_error("private key is invalid (too long)")); + return Err(ImportKeyError::PrivateKeyTooLong); } let data = pk_info.private_key.to_vec().into(); @@ -475,14 +514,14 @@ fn import_key_rsaoaep( KeyData::JwkPublicRsa { .. } | KeyData::JwkPrivateRsa { .. } => { import_key_rsa_jwk(key_data) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } fn decode_b64url_to_field_bytes( b64: &str, -) -> Result, deno_core::anyhow::Error> { - jwt_b64_int_or_err!(val, b64, "invalid b64 coordinate"); +) -> Result, ImportKeyError> { + jwt_b64_int_or_err!(val, b64, InvalidB64Coordinate); let mut bytes = elliptic_curve::FieldBytes::::default(); let original_bytes = val.as_bytes(); @@ -495,7 +534,7 @@ fn decode_b64url_to_field_bytes( let val = new_bytes.as_slice(); if val.len() != bytes.len() { - return Err(data_error("invalid b64 coordinate")); + return Err(ImportKeyError::InvalidB64Coordinate); } bytes.copy_from_slice(val); @@ -506,7 +545,7 @@ fn import_key_ec_jwk_to_point( x: String, y: String, named_curve: EcNamedCurve, -) -> Result, deno_core::anyhow::Error> { +) -> Result, ImportKeyError> { let point_bytes = match named_curve { EcNamedCurve::P256 => { let x = decode_b64url_to_field_bytes::(&x)?; @@ -534,7 +573,7 @@ fn import_key_ec_jwk_to_point( fn import_key_ec_jwk( key_data: KeyData, named_curve: EcNamedCurve, -) -> Result { +) -> Result { match key_data { KeyData::JwkPublicEc { x, y } => { let point_bytes = import_key_ec_jwk_to_point(x, y, named_curve)?; @@ -550,21 +589,21 @@ fn import_key_ec_jwk( let pk = p256::SecretKey::from_bytes(&d)?; pk.to_pkcs8_der() - .map_err(|_| data_error("invalid JWK private key"))? + .map_err(|_| ImportKeyError::InvalidJWKPrivateKey)? } EcNamedCurve::P384 => { let d = decode_b64url_to_field_bytes::(&d)?; let pk = p384::SecretKey::from_bytes(&d)?; pk.to_pkcs8_der() - .map_err(|_| data_error("invalid JWK private key"))? + .map_err(|_| ImportKeyError::InvalidJWKPrivateKey)? } EcNamedCurve::P521 => { let d = decode_b64url_to_field_bytes::(&d)?; let pk = p521::SecretKey::from_bytes(&d)?; pk.to_pkcs8_der() - .map_err(|_| data_error("invalid JWK private key"))? + .map_err(|_| ImportKeyError::InvalidJWKPrivateKey)? } }; @@ -595,7 +634,7 @@ impl<'a> TryFrom> for ECParametersSpki { fn import_key_ec( key_data: KeyData, named_curve: EcNamedCurve, -) -> Result { +) -> Result { match key_data { KeyData::Raw(data) => { // The point is parsed and validated, ultimately the original data is @@ -604,28 +643,28 @@ fn import_key_ec( EcNamedCurve::P256 => { // 1-2. let point = p256::EncodedPoint::from_bytes(&data) - .map_err(|_| data_error("invalid P-256 elliptic curve point"))?; + .map_err(|_| ImportKeyError::InvalidP256ECPoint)?; // 3. if point.is_identity() { - return Err(data_error("invalid P-256 elliptic curve point")); + return Err(ImportKeyError::InvalidP256ECPoint); } } EcNamedCurve::P384 => { // 1-2. let point = p384::EncodedPoint::from_bytes(&data) - .map_err(|_| data_error("invalid P-384 elliptic curve point"))?; + .map_err(|_| ImportKeyError::InvalidP384ECPoint)?; // 3. if point.is_identity() { - return Err(data_error("invalid P-384 elliptic curve point")); + return Err(ImportKeyError::InvalidP384ECPoint); } } EcNamedCurve::P521 => { // 1-2. let point = p521::EncodedPoint::from_bytes(&data) - .map_err(|_| data_error("invalid P-521 elliptic curve point"))?; + .map_err(|_| ImportKeyError::InvalidP521ECPoint)?; // 3. if point.is_identity() { - return Err(data_error("invalid P-521 elliptic curve point")); + return Err(ImportKeyError::InvalidP521ECPoint); } } }; @@ -635,11 +674,11 @@ fn import_key_ec( } KeyData::Pkcs8(data) => { let pk = PrivateKeyInfo::from_der(data.as_ref()) - .map_err(|_| data_error("expected valid PKCS#8 data"))?; + .map_err(|_| ImportKeyError::ExpectedValidPkcs8Data)?; let named_curve_alg = pk .algorithm .parameters - .ok_or_else(|| data_error("malformed parameters"))? + .ok_or(ImportKeyError::MalformedParameters)? .try_into() .unwrap(); @@ -654,7 +693,7 @@ fn import_key_ec( }; if pk_named_curve != Some(named_curve) { - return Err(data_error("curve mismatch")); + return Err(ImportKeyError::CurveMismatch); } Ok(ImportKeyResult::Ec { @@ -663,14 +702,13 @@ fn import_key_ec( } KeyData::Spki(data) => { // 2-3. - let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data) - .map_err(|e| data_error(e.to_string()))?; + let pk_info = spki::SubjectPublicKeyInfoRef::try_from(&*data)?; // 4. let alg = pk_info.algorithm.oid; // id-ecPublicKey if alg != elliptic_curve::ALGORITHM_OID { - return Err(data_error("unsupported algorithm")); + return Err(ImportKeyError::UnsupportedAlgorithm); } // 5-7. @@ -678,9 +716,9 @@ fn import_key_ec( pk_info .algorithm .parameters - .ok_or_else(|| data_error("malformed parameters"))?, + .ok_or(ImportKeyError::MalformedParameters)?, ) - .map_err(|_| data_error("malformed parameters"))?; + .map_err(|_| ImportKeyError::MalformedParameters)?; // 8-9. let named_curve_alg = params.named_curve_alg; @@ -704,36 +742,30 @@ fn import_key_ec( let bytes_consumed = match named_curve { EcNamedCurve::P256 => { - let point = - p256::EncodedPoint::from_bytes(&*encoded_key).map_err(|_| { - data_error("invalid P-256 elliptic curve SPKI data") - })?; + let point = p256::EncodedPoint::from_bytes(&*encoded_key) + .map_err(|_| ImportKeyError::InvalidP256ECSPKIData)?; if point.is_identity() { - return Err(data_error("invalid P-256 elliptic curve point")); + return Err(ImportKeyError::InvalidP256ECPoint); } point.as_bytes().len() } EcNamedCurve::P384 => { - let point = - p384::EncodedPoint::from_bytes(&*encoded_key).map_err(|_| { - data_error("invalid P-384 elliptic curve SPKI data") - })?; + let point = p384::EncodedPoint::from_bytes(&*encoded_key) + .map_err(|_| ImportKeyError::InvalidP384ECSPKIData)?; if point.is_identity() { - return Err(data_error("invalid P-384 elliptic curve point")); + return Err(ImportKeyError::InvalidP384ECPoint); } point.as_bytes().len() } EcNamedCurve::P521 => { - let point = - p521::EncodedPoint::from_bytes(&*encoded_key).map_err(|_| { - data_error("invalid P-521 elliptic curve SPKI data") - })?; + let point = p521::EncodedPoint::from_bytes(&*encoded_key) + .map_err(|_| ImportKeyError::InvalidP521ECSPKIData)?; if point.is_identity() { - return Err(data_error("invalid P-521 elliptic curve point")); + return Err(ImportKeyError::InvalidP521ECPoint); } point.as_bytes().len() @@ -741,15 +773,15 @@ fn import_key_ec( }; if bytes_consumed != pk_info.subject_public_key.raw_bytes().len() { - return Err(data_error("public key is invalid (too long)")); + return Err(ImportKeyError::PublicKeyTooLong); } // 11. if named_curve != pk_named_curve { - return Err(data_error("curve mismatch")); + return Err(ImportKeyError::CurveMismatch); } } else { - return Err(data_error("Unsupported named curve")); + return Err(ImportKeyError::UnsupportedNamedCurve); } Ok(ImportKeyResult::Ec { @@ -759,34 +791,38 @@ fn import_key_ec( KeyData::JwkPublicEc { .. } | KeyData::JwkPrivateEc { .. } => { import_key_ec_jwk(key_data, named_curve) } - _ => Err(unsupported_format()), + _ => Err(SharedError::UnsupportedFormat.into()), } } -fn import_key_aes(key_data: KeyData) -> Result { +fn import_key_aes( + key_data: KeyData, +) -> Result { Ok(match key_data { KeyData::JwkSecret { k } => { let data = BASE64_URL_SAFE_FORGIVING .decode(k) - .map_err(|_| data_error("invalid key data"))?; + .map_err(|_| ImportKeyError::InvalidKeyData)?; ImportKeyResult::Hmac { raw_data: RustRawKeyData::Secret(data.into()), } } - _ => return Err(unsupported_format()), + _ => return Err(SharedError::UnsupportedFormat.into()), }) } -fn import_key_hmac(key_data: KeyData) -> Result { +fn import_key_hmac( + key_data: KeyData, +) -> Result { Ok(match key_data { KeyData::JwkSecret { k } => { let data = BASE64_URL_SAFE_FORGIVING .decode(k) - .map_err(|_| data_error("invalid key data"))?; + .map_err(|_| ImportKeyError::InvalidKeyData)?; ImportKeyResult::Hmac { raw_data: RustRawKeyData::Secret(data.into()), } } - _ => return Err(unsupported_format()), + _ => return Err(SharedError::UnsupportedFormat.into()), }) } diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index b5bafc580b..69dcd1413a 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -6,10 +6,7 @@ use aes_kw::KekAes256; use base64::prelude::BASE64_URL_SAFE_NO_PAD; use base64::Engine; -use deno_core::error::custom_error; use deno_core::error::not_supported; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ToJsBuffer; @@ -17,7 +14,6 @@ use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; use deno_core::OpState; use serde::Deserialize; -use shared::operation_error; use p256::elliptic_curve::sec1::FromEncodedPoint; use p256::pkcs8::DecodePrivateKey; @@ -67,15 +63,24 @@ mod x25519; mod x448; pub use crate::decrypt::op_crypto_decrypt; +pub use crate::decrypt::DecryptError; +pub use crate::ed25519::Ed25519Error; pub use crate::encrypt::op_crypto_encrypt; +pub use crate::encrypt::EncryptError; pub use crate::export_key::op_crypto_export_key; +pub use crate::export_key::ExportKeyError; pub use crate::generate_key::op_crypto_generate_key; +pub use crate::generate_key::GenerateKeyError; pub use crate::import_key::op_crypto_import_key; +pub use crate::import_key::ImportKeyError; use crate::key::Algorithm; use crate::key::CryptoHash; use crate::key::CryptoNamedCurve; use crate::key::HkdfOutput; +pub use crate::shared::SharedError; use crate::shared::V8RawKeyData; +pub use crate::x25519::X25519Error; +pub use crate::x448::X448Error; deno_core::extension!(deno_crypto, deps = [ deno_webidl, deno_web ], @@ -127,11 +132,63 @@ deno_core::extension!(deno_crypto, }, ); +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + General(#[from] SharedError), + #[error(transparent)] + JoinError(#[from] tokio::task::JoinError), + #[error(transparent)] + Der(#[from] rsa::pkcs1::der::Error), + #[error("Missing argument hash")] + MissingArgumentHash, + #[error("Missing argument saltLength")] + MissingArgumentSaltLength, + #[error("unsupported algorithm")] + UnsupportedAlgorithm, + #[error(transparent)] + KeyRejected(#[from] ring::error::KeyRejected), + #[error(transparent)] + RSA(#[from] rsa::Error), + #[error(transparent)] + Pkcs1(#[from] rsa::pkcs1::Error), + #[error(transparent)] + Unspecified(#[from] ring::error::Unspecified), + #[error("Invalid key format")] + InvalidKeyFormat, + #[error(transparent)] + P256Ecdsa(#[from] p256::ecdsa::Error), + #[error("Unexpected error decoding private key")] + DecodePrivateKey, + #[error("Missing argument publicKey")] + MissingArgumentPublicKey, + #[error("Missing argument namedCurve")] + MissingArgumentNamedCurve, + #[error("Missing argument info")] + MissingArgumentInfo, + #[error("The length provided for HKDF is too large")] + HKDFLengthTooLarge, + #[error(transparent)] + Base64Decode(#[from] base64::DecodeError), + #[error("Data must be multiple of 8 bytes")] + DataInvalidSize, + #[error("Invalid key length")] + InvalidKeyLength, + #[error("encryption error")] + EncryptionError, + #[error("decryption error - integrity check failed")] + DecryptionError, + #[error("The ArrayBufferView's byte length ({0}) exceeds the number of bytes of entropy available via this API (65536)")] + ArrayBufferViewLengthExceeded(usize), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[op2] #[serde] pub fn op_crypto_base64url_decode( #[string] data: String, -) -> Result { +) -> Result { let data: Vec = BASE64_URL_SAFE_NO_PAD.decode(data)?; Ok(data.into()) } @@ -147,9 +204,9 @@ pub fn op_crypto_base64url_encode(#[buffer] data: JsBuffer) -> String { pub fn op_crypto_get_random_values( state: &mut OpState, #[buffer] out: &mut [u8], -) -> Result<(), AnyError> { +) -> Result<(), Error> { if out.len() > 65536 { - return Err(custom_error("DOMExceptionQuotaExceededError", format!("The ArrayBufferView's byte length ({}) exceeds the number of bytes of entropy available via this API (65536)", out.len()))); + return Err(Error::ArrayBufferViewLengthExceeded(out.len())); } let maybe_seeded_rng = state.try_borrow_mut::(); @@ -201,7 +258,7 @@ pub struct SignArg { pub async fn op_crypto_sign_key( #[serde] args: SignArg, #[buffer] zero_copy: JsBuffer, -) -> Result { +) -> Result { deno_core::unsync::spawn_blocking(move || { let data = &*zero_copy; let algorithm = args.algorithm; @@ -210,10 +267,7 @@ pub async fn op_crypto_sign_key( Algorithm::RsassaPkcs1v15 => { use rsa::pkcs1v15::SigningKey; let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?; - match args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))? - { + match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let signing_key = SigningKey::::new(private_key); signing_key.sign(data) @@ -236,15 +290,13 @@ pub async fn op_crypto_sign_key( Algorithm::RsaPss => { let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?; - let salt_len = args.salt_length.ok_or_else(|| { - type_error("Missing argument saltLength".to_string()) - })? as usize; + let salt_len = args + .salt_length + .ok_or_else(|| Error::MissingArgumentSaltLength)? + as usize; let mut rng = OsRng; - match args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))? - { + match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let signing_key = Pss::new_with_salt::(salt_len); let hashed = Sha1::digest(data); @@ -269,8 +321,10 @@ pub async fn op_crypto_sign_key( .to_vec() } Algorithm::Ecdsa => { - let curve: &EcdsaSigningAlgorithm = - args.named_curve.ok_or_else(not_supported)?.into(); + let curve: &EcdsaSigningAlgorithm = args + .named_curve + .ok_or_else(|| Error::Other(not_supported()))? + .into(); let rng = RingRand::SystemRandom::new(); let key_pair = EcdsaKeyPair::from_pkcs8(curve, &args.key.data, &rng)?; @@ -279,7 +333,7 @@ pub async fn op_crypto_sign_key( if let Some(hash) = args.hash { match hash { CryptoHash::Sha256 | CryptoHash::Sha384 => (), - _ => return Err(type_error("Unsupported algorithm")), + _ => return Err(Error::UnsupportedAlgorithm), } }; @@ -289,14 +343,17 @@ pub async fn op_crypto_sign_key( signature.as_ref().to_vec() } Algorithm::Hmac => { - let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into(); + let hash: HmacAlgorithm = args + .hash + .ok_or_else(|| Error::Other(not_supported()))? + .into(); let key = HmacKey::new(hash, &args.key.data); let signature = ring::hmac::sign(&key, data); signature.as_ref().to_vec() } - _ => return Err(type_error("Unsupported algorithm".to_string())), + _ => return Err(Error::UnsupportedAlgorithm), }; Ok(signature.into()) @@ -319,7 +376,7 @@ pub struct VerifyArg { pub async fn op_crypto_verify_key( #[serde] args: VerifyArg, #[buffer] zero_copy: JsBuffer, -) -> Result { +) -> Result { deno_core::unsync::spawn_blocking(move || { let data = &*zero_copy; let algorithm = args.algorithm; @@ -330,10 +387,7 @@ pub async fn op_crypto_verify_key( use rsa::pkcs1v15::VerifyingKey; let public_key = read_rsa_public_key(args.key)?; let signature: Signature = args.signature.as_ref().try_into()?; - match args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))? - { + match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let verifying_key = VerifyingKey::::new(public_key); verifying_key.verify(data, &signature).is_ok() @@ -356,14 +410,12 @@ pub async fn op_crypto_verify_key( let public_key = read_rsa_public_key(args.key)?; let signature = args.signature.as_ref(); - let salt_len = args.salt_length.ok_or_else(|| { - type_error("Missing argument saltLength".to_string()) - })? as usize; + let salt_len = args + .salt_length + .ok_or_else(|| Error::MissingArgumentSaltLength)? + as usize; - match args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))? - { + match args.hash.ok_or_else(|| Error::MissingArgumentHash)? { CryptoHash::Sha1 => { let pss = Pss::new_with_salt::(salt_len); let hashed = Sha1::digest(data); @@ -387,15 +439,22 @@ pub async fn op_crypto_verify_key( } } Algorithm::Hmac => { - let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into(); + let hash: HmacAlgorithm = args + .hash + .ok_or_else(|| Error::Other(not_supported()))? + .into(); let key = HmacKey::new(hash, &args.key.data); ring::hmac::verify(&key, data, &args.signature).is_ok() } Algorithm::Ecdsa => { - let signing_alg: &EcdsaSigningAlgorithm = - args.named_curve.ok_or_else(not_supported)?.into(); - let verify_alg: &EcdsaVerificationAlgorithm = - args.named_curve.ok_or_else(not_supported)?.into(); + let signing_alg: &EcdsaSigningAlgorithm = args + .named_curve + .ok_or_else(|| Error::Other(not_supported()))? + .into(); + let verify_alg: &EcdsaVerificationAlgorithm = args + .named_curve + .ok_or_else(|| Error::Other(not_supported()))? + .into(); let private_key; @@ -408,7 +467,7 @@ pub async fn op_crypto_verify_key( private_key.public_key().as_ref() } KeyType::Public => &*args.key.data, - _ => return Err(type_error("Invalid Key format".to_string())), + _ => return Err(Error::InvalidKeyFormat), }; let public_key = @@ -416,7 +475,7 @@ pub async fn op_crypto_verify_key( public_key.verify(data, &args.signature).is_ok() } - _ => return Err(type_error("Unsupported algorithm".to_string())), + _ => return Err(Error::UnsupportedAlgorithm), }; Ok(verification) @@ -444,70 +503,68 @@ pub struct DeriveKeyArg { pub async fn op_crypto_derive_bits( #[serde] args: DeriveKeyArg, #[buffer] zero_copy: Option, -) -> Result { +) -> Result { deno_core::unsync::spawn_blocking(move || { let algorithm = args.algorithm; match algorithm { Algorithm::Pbkdf2 => { - let zero_copy = zero_copy.ok_or_else(not_supported)?; + let zero_copy = + zero_copy.ok_or_else(|| Error::Other(not_supported()))?; let salt = &*zero_copy; // The caller must validate these cases. assert!(args.length > 0); assert!(args.length % 8 == 0); - let algorithm = match args.hash.ok_or_else(not_supported)? { - CryptoHash::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, - CryptoHash::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, - CryptoHash::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, - CryptoHash::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, - }; + let algorithm = + match args.hash.ok_or_else(|| Error::Other(not_supported()))? { + CryptoHash::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1, + CryptoHash::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256, + CryptoHash::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384, + CryptoHash::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512, + }; // This will never panic. We have already checked length earlier. - let iterations = - NonZeroU32::new(args.iterations.ok_or_else(not_supported)?).unwrap(); + let iterations = NonZeroU32::new( + args + .iterations + .ok_or_else(|| Error::Other(not_supported()))?, + ) + .unwrap(); let secret = args.key.data; let mut out = vec![0; args.length / 8]; pbkdf2::derive(algorithm, iterations, salt, &secret, &mut out); Ok(out.into()) } Algorithm::Ecdh => { - let named_curve = args.named_curve.ok_or_else(|| { - type_error("Missing argument namedCurve".to_string()) - })?; + let named_curve = args + .named_curve + .ok_or_else(|| Error::MissingArgumentNamedCurve)?; let public_key = args .public_key - .ok_or_else(|| type_error("Missing argument publicKey"))?; + .ok_or_else(|| Error::MissingArgumentPublicKey)?; match named_curve { CryptoNamedCurve::P256 => { let secret_key = p256::SecretKey::from_pkcs8_der(&args.key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })?; + .map_err(|_| Error::DecodePrivateKey)?; let public_key = match public_key.r#type { KeyType::Private => { p256::SecretKey::from_pkcs8_der(&public_key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })? + .map_err(|_| Error::DecodePrivateKey)? .public_key() } KeyType::Public => { let point = p256::EncodedPoint::from_bytes(public_key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })?; + .map_err(|_| Error::DecodePrivateKey)?; let pk = p256::PublicKey::from_encoded_point(&point); // pk is a constant time Option. if pk.is_some().into() { pk.unwrap() } else { - return Err(type_error( - "Unexpected error decoding private key", - )); + return Err(Error::DecodePrivateKey); } } _ => unreachable!(), @@ -523,32 +580,24 @@ pub async fn op_crypto_derive_bits( } CryptoNamedCurve::P384 => { let secret_key = p384::SecretKey::from_pkcs8_der(&args.key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })?; + .map_err(|_| Error::DecodePrivateKey)?; let public_key = match public_key.r#type { KeyType::Private => { p384::SecretKey::from_pkcs8_der(&public_key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })? + .map_err(|_| Error::DecodePrivateKey)? .public_key() } KeyType::Public => { let point = p384::EncodedPoint::from_bytes(public_key.data) - .map_err(|_| { - type_error("Unexpected error decoding private key") - })?; + .map_err(|_| Error::DecodePrivateKey)?; let pk = p384::PublicKey::from_encoded_point(&point); // pk is a constant time Option. if pk.is_some().into() { pk.unwrap() } else { - return Err(type_error( - "Unexpected error decoding private key", - )); + return Err(Error::DecodePrivateKey); } } _ => unreachable!(), @@ -565,18 +614,18 @@ pub async fn op_crypto_derive_bits( } } Algorithm::Hkdf => { - let zero_copy = zero_copy.ok_or_else(not_supported)?; + let zero_copy = + zero_copy.ok_or_else(|| Error::Other(not_supported()))?; let salt = &*zero_copy; - let algorithm = match args.hash.ok_or_else(not_supported)? { - CryptoHash::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, - CryptoHash::Sha256 => hkdf::HKDF_SHA256, - CryptoHash::Sha384 => hkdf::HKDF_SHA384, - CryptoHash::Sha512 => hkdf::HKDF_SHA512, - }; + let algorithm = + match args.hash.ok_or_else(|| Error::Other(not_supported()))? { + CryptoHash::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY, + CryptoHash::Sha256 => hkdf::HKDF_SHA256, + CryptoHash::Sha384 => hkdf::HKDF_SHA384, + CryptoHash::Sha512 => hkdf::HKDF_SHA512, + }; - let info = args - .info - .ok_or_else(|| type_error("Missing argument info".to_string()))?; + let info = args.info.ok_or_else(|| Error::MissingArgumentInfo)?; // IKM let secret = args.key.data; // L @@ -585,23 +634,20 @@ pub async fn op_crypto_derive_bits( let salt = hkdf::Salt::new(algorithm, salt); let prk = salt.extract(&secret); let info = &[&*info]; - let okm = prk.expand(info, HkdfOutput(length)).map_err(|_e| { - custom_error( - "DOMExceptionOperationError", - "The length provided for HKDF is too large", - ) - })?; + let okm = prk + .expand(info, HkdfOutput(length)) + .map_err(|_e| Error::HKDFLengthTooLarge)?; let mut r = vec![0u8; length]; okm.fill(&mut r)?; Ok(r.into()) } - _ => Err(type_error("Unsupported algorithm".to_string())), + _ => Err(Error::UnsupportedAlgorithm), } }) .await? } -fn read_rsa_public_key(key_data: KeyData) -> Result { +fn read_rsa_public_key(key_data: KeyData) -> Result { let public_key = match key_data.r#type { KeyType::Private => { RsaPrivateKey::from_pkcs1_der(&key_data.data)?.to_public_key() @@ -614,7 +660,7 @@ fn read_rsa_public_key(key_data: KeyData) -> Result { #[op2] #[string] -pub fn op_crypto_random_uuid(state: &mut OpState) -> Result { +pub fn op_crypto_random_uuid(state: &mut OpState) -> Result { let maybe_seeded_rng = state.try_borrow_mut::(); let uuid = if let Some(seeded_rng) = maybe_seeded_rng { let mut bytes = [0u8; 16]; @@ -635,7 +681,7 @@ pub fn op_crypto_random_uuid(state: &mut OpState) -> Result { pub async fn op_crypto_subtle_digest( #[serde] algorithm: CryptoHash, #[buffer] data: JsBuffer, -) -> Result { +) -> Result { let output = spawn_blocking(move || { digest::digest(algorithm.into(), &data) .as_ref() @@ -659,7 +705,7 @@ pub struct WrapUnwrapKeyArg { pub fn op_crypto_wrap_key( #[serde] args: WrapUnwrapKeyArg, #[buffer] data: JsBuffer, -) -> Result { +) -> Result { let algorithm = args.algorithm; match algorithm { @@ -667,20 +713,20 @@ pub fn op_crypto_wrap_key( let key = args.key.as_secret_key()?; if data.len() % 8 != 0 { - return Err(type_error("Data must be multiple of 8 bytes")); + return Err(Error::DataInvalidSize); } let wrapped_key = match key.len() { 16 => KekAes128::new(key.into()).wrap_vec(&data), 24 => KekAes192::new(key.into()).wrap_vec(&data), 32 => KekAes256::new(key.into()).wrap_vec(&data), - _ => return Err(type_error("Invalid key length")), + _ => return Err(Error::InvalidKeyLength), } - .map_err(|_| operation_error("encryption error"))?; + .map_err(|_| Error::EncryptionError)?; Ok(wrapped_key.into()) } - _ => Err(type_error("Unsupported algorithm")), + _ => Err(Error::UnsupportedAlgorithm), } } @@ -689,29 +735,27 @@ pub fn op_crypto_wrap_key( pub fn op_crypto_unwrap_key( #[serde] args: WrapUnwrapKeyArg, #[buffer] data: JsBuffer, -) -> Result { +) -> Result { let algorithm = args.algorithm; match algorithm { Algorithm::AesKw => { let key = args.key.as_secret_key()?; if data.len() % 8 != 0 { - return Err(type_error("Data must be multiple of 8 bytes")); + return Err(Error::DataInvalidSize); } let unwrapped_key = match key.len() { 16 => KekAes128::new(key.into()).unwrap_vec(&data), 24 => KekAes192::new(key.into()).unwrap_vec(&data), 32 => KekAes256::new(key.into()).unwrap_vec(&data), - _ => return Err(type_error("Invalid key length")), + _ => return Err(Error::InvalidKeyLength), } - .map_err(|_| { - operation_error("decryption error - integrity check failed") - })?; + .map_err(|_| Error::DecryptionError)?; Ok(unwrapped_key.into()) } - _ => Err(type_error("Unsupported algorithm")), + _ => Err(Error::UnsupportedAlgorithm), } } diff --git a/ext/crypto/shared.rs b/ext/crypto/shared.rs index d06a268cd6..f70d32856c 100644 --- a/ext/crypto/shared.rs +++ b/ext/crypto/shared.rs @@ -2,9 +2,6 @@ use std::borrow::Cow; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::JsBuffer; use deno_core::ToJsBuffer; use elliptic_curve::sec1::ToEncodedPoint; @@ -63,47 +60,73 @@ pub enum RustRawKeyData { Public(ToJsBuffer), } +#[derive(Debug, thiserror::Error)] +pub enum SharedError { + #[error("expected valid private key")] + ExpectedValidPrivateKey, + #[error("expected valid public key")] + ExpectedValidPublicKey, + #[error("expected valid private EC key")] + ExpectedValidPrivateECKey, + #[error("expected valid public EC key")] + ExpectedValidPublicECKey, + #[error("expected private key")] + ExpectedPrivateKey, + #[error("expected public key")] + ExpectedPublicKey, + #[error("expected secret key")] + ExpectedSecretKey, + #[error("failed to decode private key")] + FailedDecodePrivateKey, + #[error("failed to decode public key")] + FailedDecodePublicKey, + #[error("unsupported format")] + UnsupportedFormat, +} + impl V8RawKeyData { - pub fn as_rsa_public_key(&self) -> Result, AnyError> { + pub fn as_rsa_public_key(&self) -> Result, SharedError> { match self { V8RawKeyData::Public(data) => Ok(Cow::Borrowed(data)), V8RawKeyData::Private(data) => { let private_key = RsaPrivateKey::from_pkcs1_der(data) - .map_err(|_| type_error("expected valid private key"))?; + .map_err(|_| SharedError::ExpectedValidPrivateKey)?; let public_key_doc = private_key .to_public_key() .to_pkcs1_der() - .map_err(|_| type_error("expected valid public key"))?; + .map_err(|_| SharedError::ExpectedValidPublicKey)?; Ok(Cow::Owned(public_key_doc.as_bytes().into())) } - _ => Err(type_error("expected public key")), + _ => Err(SharedError::ExpectedPublicKey), } } - pub fn as_rsa_private_key(&self) -> Result<&[u8], AnyError> { + pub fn as_rsa_private_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Private(data) => Ok(data), - _ => Err(type_error("expected private key")), + _ => Err(SharedError::ExpectedPrivateKey), } } - pub fn as_secret_key(&self) -> Result<&[u8], AnyError> { + pub fn as_secret_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Secret(data) => Ok(data), - _ => Err(type_error("expected secret key")), + _ => Err(SharedError::ExpectedSecretKey), } } - pub fn as_ec_public_key_p256(&self) -> Result { + pub fn as_ec_public_key_p256( + &self, + ) -> Result { match self { V8RawKeyData::Public(data) => p256::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) - .map_err(|_| type_error("expected valid public EC key")), + .map_err(|_| SharedError::ExpectedValidPublicECKey), V8RawKeyData::Private(data) => { let signing_key = p256::SecretKey::from_pkcs8_der(data) - .map_err(|_| type_error("expected valid private EC key"))?; + .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. @@ -111,14 +134,16 @@ impl V8RawKeyData { } } - pub fn as_ec_public_key_p384(&self) -> Result { + pub fn as_ec_public_key_p384( + &self, + ) -> Result { match self { V8RawKeyData::Public(data) => p384::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) - .map_err(|_| type_error("expected valid public EC key")), + .map_err(|_| SharedError::ExpectedValidPublicECKey), V8RawKeyData::Private(data) => { let signing_key = p384::SecretKey::from_pkcs8_der(data) - .map_err(|_| type_error("expected valid private EC key"))?; + .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. @@ -126,16 +151,18 @@ impl V8RawKeyData { } } - pub fn as_ec_public_key_p521(&self) -> Result { + pub fn as_ec_public_key_p521( + &self, + ) -> Result { match self { V8RawKeyData::Public(data) => { // public_key is a serialized EncodedPoint p521::EncodedPoint::from_bytes(data) - .map_err(|_| type_error("expected valid public EC key")) + .map_err(|_| SharedError::ExpectedValidPublicECKey) } V8RawKeyData::Private(data) => { let signing_key = p521::SecretKey::from_pkcs8_der(data) - .map_err(|_| type_error("expected valid private EC key"))?; + .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. @@ -143,26 +170,10 @@ impl V8RawKeyData { } } - pub fn as_ec_private_key(&self) -> Result<&[u8], AnyError> { + pub fn as_ec_private_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Private(data) => Ok(data), - _ => Err(type_error("expected private key")), + _ => Err(SharedError::ExpectedPrivateKey), } } } - -pub fn data_error(msg: impl Into>) -> AnyError { - custom_error("DOMExceptionDataError", msg) -} - -pub fn not_supported_error(msg: impl Into>) -> AnyError { - custom_error("DOMExceptionNotSupportedError", msg) -} - -pub fn operation_error(msg: impl Into>) -> AnyError { - custom_error("DOMExceptionOperationError", msg) -} - -pub fn unsupported_format() -> AnyError { - not_supported_error("unsupported format") -} diff --git a/ext/crypto/x25519.rs b/ext/crypto/x25519.rs index cdbd1d7c8f..d2c4d986b9 100644 --- a/ext/crypto/x25519.rs +++ b/ext/crypto/x25519.rs @@ -1,8 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use curve25519_dalek::montgomery::MontgomeryPoint; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ToJsBuffer; use elliptic_curve::pkcs8::PrivateKeyInfo; @@ -13,6 +11,14 @@ use spki::der::asn1::BitString; use spki::der::Decode; use spki::der::Encode; +#[derive(Debug, thiserror::Error)] +pub enum X25519Error { + #[error("Failed to export key")] + FailedExport, + #[error(transparent)] + Der(#[from] spki::der::Error), +} + #[op2(fast)] pub fn op_crypto_generate_x25519_keypair( #[buffer] pkey: &mut [u8], @@ -113,7 +119,7 @@ pub fn op_crypto_import_pkcs8_x25519( #[serde] pub fn op_crypto_export_spki_x25519( #[buffer] pubkey: &[u8], -) -> Result { +) -> Result { let key_info = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifierRef { // id-X25519 @@ -125,9 +131,7 @@ pub fn op_crypto_export_spki_x25519( Ok( key_info .to_der() - .map_err(|_| { - custom_error("DOMExceptionOperationError", "Failed to export key") - })? + .map_err(|_| X25519Error::FailedExport)? .into(), ) } @@ -136,7 +140,7 @@ pub fn op_crypto_export_spki_x25519( #[serde] pub fn op_crypto_export_pkcs8_x25519( #[buffer] pkey: &[u8], -) -> Result { +) -> Result { use rsa::pkcs1::der::Encode; // This should probably use OneAsymmetricKey instead diff --git a/ext/crypto/x448.rs b/ext/crypto/x448.rs index 3c8f24c319..89bf48e28b 100644 --- a/ext/crypto/x448.rs +++ b/ext/crypto/x448.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::custom_error; -use deno_core::error::AnyError; + use deno_core::op2; use deno_core::ToJsBuffer; use ed448_goldilocks::curve::MontgomeryPoint; @@ -13,6 +12,14 @@ use spki::der::asn1::BitString; use spki::der::Decode; use spki::der::Encode; +#[derive(Debug, thiserror::Error)] +pub enum X448Error { + #[error("Failed to export key")] + FailedExport, + #[error(transparent)] + Der(#[from] spki::der::Error), +} + #[op2(fast)] pub fn op_crypto_generate_x448_keypair( #[buffer] pkey: &mut [u8], @@ -56,7 +63,7 @@ const X448_OID: const_oid::ObjectIdentifier = #[serde] pub fn op_crypto_export_spki_x448( #[buffer] pubkey: &[u8], -) -> Result { +) -> Result { let key_info = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifierRef { oid: X448_OID, @@ -67,9 +74,7 @@ pub fn op_crypto_export_spki_x448( Ok( key_info .to_der() - .map_err(|_| { - custom_error("DOMExceptionOperationError", "Failed to export key") - })? + .map_err(|_| X448Error::FailedExport)? .into(), ) } @@ -78,7 +83,7 @@ pub fn op_crypto_export_spki_x448( #[serde] pub fn op_crypto_export_pkcs8_x448( #[buffer] pkey: &[u8], -) -> Result { +) -> Result { use rsa::pkcs1::der::Encode; let pk_info = rsa::pkcs8::PrivateKeyInfo { diff --git a/runtime/errors.rs b/runtime/errors.rs index 0e551d1219..45442a11c3 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -17,6 +17,11 @@ use deno_core::serde_json; use deno_core::url; use deno_core::ModuleResolutionError; use deno_cron::CronError; +use deno_crypto::DecryptError; +use deno_crypto::EncryptError; +use deno_crypto::ExportKeyError; +use deno_crypto::GenerateKeyError; +use deno_crypto::ImportKeyError; use deno_ffi::CallError; use deno_ffi::CallbackError; use deno_ffi::DlfcnError; @@ -179,6 +184,165 @@ pub fn get_nix_error_class(error: &nix::Error) -> &'static str { } } +fn get_crypto_decrypt_error_class(e: &DecryptError) -> &'static str { + match e { + DecryptError::General(e) => get_crypto_shared_error_class(e), + DecryptError::Pkcs1(_) => "Error", + DecryptError::Failed => "DOMExceptionOperationError", + DecryptError::InvalidLength => "TypeError", + DecryptError::InvalidCounterLength => "TypeError", + DecryptError::InvalidTagLength => "TypeError", + DecryptError::InvalidKeyOrIv => "DOMExceptionOperationError", + DecryptError::TooMuchData => "DOMExceptionOperationError", + DecryptError::InvalidIvLength => "TypeError", + DecryptError::Rsa(_) => "DOMExceptionOperationError", + } +} + +fn get_crypto_encrypt_error_class(e: &EncryptError) -> &'static str { + match e { + EncryptError::General(e) => get_crypto_shared_error_class(e), + EncryptError::InvalidKeyOrIv => "DOMExceptionOperationError", + EncryptError::Failed => "DOMExceptionOperationError", + EncryptError::InvalidLength => "TypeError", + EncryptError::InvalidIvLength => "TypeError", + EncryptError::InvalidCounterLength => "TypeError", + EncryptError::TooMuchData => "DOMExceptionOperationError", + } +} + +fn get_crypto_shared_error_class(e: &deno_crypto::SharedError) -> &'static str { + match e { + deno_crypto::SharedError::ExpectedValidPrivateKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPublicKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPrivateECKey => "TypeError", + deno_crypto::SharedError::ExpectedValidPublicECKey => "TypeError", + deno_crypto::SharedError::ExpectedPrivateKey => "TypeError", + deno_crypto::SharedError::ExpectedPublicKey => "TypeError", + deno_crypto::SharedError::ExpectedSecretKey => "TypeError", + deno_crypto::SharedError::FailedDecodePrivateKey => { + "DOMExceptionOperationError" + } + deno_crypto::SharedError::FailedDecodePublicKey => { + "DOMExceptionOperationError" + } + deno_crypto::SharedError::UnsupportedFormat => { + "DOMExceptionNotSupportedError" + } + } +} + +fn get_crypto_ed25519_error_class( + e: &deno_crypto::Ed25519Error, +) -> &'static str { + match e { + deno_crypto::Ed25519Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::Ed25519Error::Der(_) => "Error", + deno_crypto::Ed25519Error::KeyRejected(_) => "Error", + } +} + +fn get_crypto_export_key_error_class(e: &ExportKeyError) -> &'static str { + match e { + ExportKeyError::General(e) => get_crypto_shared_error_class(e), + ExportKeyError::Der(_) => "Error", + ExportKeyError::UnsupportedNamedCurve => "DOMExceptionNotSupportedError", + } +} + +fn get_crypto_generate_key_error_class(e: &GenerateKeyError) -> &'static str { + match e { + GenerateKeyError::General(e) => get_crypto_shared_error_class(e), + GenerateKeyError::BadPublicExponent => "DOMExceptionOperationError", + GenerateKeyError::InvalidHMACKeyLength => "DOMExceptionOperationError", + GenerateKeyError::FailedRSAKeySerialization => "DOMExceptionOperationError", + GenerateKeyError::InvalidAESKeyLength => "DOMExceptionOperationError", + GenerateKeyError::FailedRSAKeyGeneration => "DOMExceptionOperationError", + GenerateKeyError::FailedECKeyGeneration => "DOMExceptionOperationError", + GenerateKeyError::FailedKeyGeneration => "DOMExceptionOperationError", + } +} + +fn get_crypto_import_key_error_class(e: &ImportKeyError) -> &'static str { + match e { + ImportKeyError::General(e) => get_crypto_shared_error_class(e), + ImportKeyError::InvalidModulus => "DOMExceptionDataError", + ImportKeyError::InvalidPublicExponent => "DOMExceptionDataError", + ImportKeyError::InvalidPrivateExponent => "DOMExceptionDataError", + ImportKeyError::InvalidFirstPrimeFactor => "DOMExceptionDataError", + ImportKeyError::InvalidSecondPrimeFactor => "DOMExceptionDataError", + ImportKeyError::InvalidFirstCRTExponent => "DOMExceptionDataError", + ImportKeyError::InvalidSecondCRTExponent => "DOMExceptionDataError", + ImportKeyError::InvalidCRTCoefficient => "DOMExceptionDataError", + ImportKeyError::InvalidB64Coordinate => "DOMExceptionDataError", + ImportKeyError::InvalidRSAPublicKey => "DOMExceptionDataError", + ImportKeyError::InvalidRSAPrivateKey => "DOMExceptionDataError", + ImportKeyError::UnsupportedAlgorithm => "DOMExceptionDataError", + ImportKeyError::PublicKeyTooLong => "DOMExceptionDataError", + ImportKeyError::PrivateKeyTooLong => "DOMExceptionDataError", + ImportKeyError::InvalidP256ECPoint => "DOMExceptionDataError", + ImportKeyError::InvalidP384ECPoint => "DOMExceptionDataError", + ImportKeyError::InvalidP521ECPoint => "DOMExceptionDataError", + ImportKeyError::UnsupportedNamedCurve => "DOMExceptionDataError", + ImportKeyError::CurveMismatch => "DOMExceptionDataError", + ImportKeyError::InvalidKeyData => "DOMExceptionDataError", + ImportKeyError::InvalidJWKPrivateKey => "DOMExceptionDataError", + ImportKeyError::EllipticCurve(_) => "DOMExceptionDataError", + ImportKeyError::ExpectedValidPkcs8Data => "DOMExceptionDataError", + ImportKeyError::MalformedParameters => "DOMExceptionDataError", + ImportKeyError::Spki(_) => "DOMExceptionDataError", + ImportKeyError::InvalidP256ECSPKIData => "DOMExceptionDataError", + ImportKeyError::InvalidP384ECSPKIData => "DOMExceptionDataError", + ImportKeyError::InvalidP521ECSPKIData => "DOMExceptionDataError", + ImportKeyError::Der(_) => "DOMExceptionDataError", + } +} + +fn get_crypto_x448_error_class(e: &deno_crypto::X448Error) -> &'static str { + match e { + deno_crypto::X448Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::X448Error::Der(_) => "Error", + } +} + +fn get_crypto_x25519_error_class(e: &deno_crypto::X25519Error) -> &'static str { + match e { + deno_crypto::X25519Error::FailedExport => "DOMExceptionOperationError", + deno_crypto::X25519Error::Der(_) => "Error", + } +} + +fn get_crypto_error_class(e: &deno_crypto::Error) -> &'static str { + match e { + deno_crypto::Error::Der(_) => "Error", + deno_crypto::Error::JoinError(_) => "Error", + deno_crypto::Error::MissingArgumentHash => "TypeError", + deno_crypto::Error::MissingArgumentSaltLength => "TypeError", + deno_crypto::Error::Other(e) => get_error_class_name(e).unwrap_or("Error"), + deno_crypto::Error::UnsupportedAlgorithm => "TypeError", + deno_crypto::Error::KeyRejected(_) => "Error", + deno_crypto::Error::RSA(_) => "Error", + deno_crypto::Error::Pkcs1(_) => "Error", + deno_crypto::Error::Unspecified(_) => "Error", + deno_crypto::Error::InvalidKeyFormat => "TypeError", + deno_crypto::Error::MissingArgumentPublicKey => "TypeError", + deno_crypto::Error::P256Ecdsa(_) => "Error", + deno_crypto::Error::DecodePrivateKey => "TypeError", + deno_crypto::Error::MissingArgumentNamedCurve => "TypeError", + deno_crypto::Error::MissingArgumentInfo => "TypeError", + deno_crypto::Error::HKDFLengthTooLarge => "DOMExceptionOperationError", + deno_crypto::Error::General(e) => get_crypto_shared_error_class(e), + deno_crypto::Error::Base64Decode(_) => "Error", + deno_crypto::Error::DataInvalidSize => "TypeError", + deno_crypto::Error::InvalidKeyLength => "TypeError", + deno_crypto::Error::EncryptionError => "DOMExceptionOperationError", + deno_crypto::Error::DecryptionError => "DOMExceptionOperationError", + deno_crypto::Error::ArrayBufferViewLengthExceeded(_) => { + "DOMExceptionQuotaExceededError" + } + } +} + fn get_napi_error_class(e: &NApiError) -> &'static str { match e { NApiError::InvalidPath @@ -571,6 +735,46 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { e.downcast_ref::() .map(get_broadcast_channel_error) }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_decrypt_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_encrypt_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_shared_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_ed25519_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_export_key_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_generate_key_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_import_key_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_x448_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_x25519_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_crypto_error_class) + }) .or_else(|| { e.downcast_ref::() .map(get_webstorage_class_name) From 2c3900370ac3e0b62f1e0dfb86a883c75952146d Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 15:57:12 -0700 Subject: [PATCH 10/16] refactor(ext/http): use concrete error types (#26377) --- ext/http/http_next.rs | 132 +++++++++++++++++++------------ ext/http/lib.rs | 145 +++++++++++++++++++++++----------- ext/http/request_body.rs | 12 +-- ext/http/service.rs | 13 +-- ext/http/websocket_upgrade.rs | 60 +++++++++----- runtime/errors.rs | 62 +++++++++++++++ runtime/ops/http.rs | 11 ++- 7 files changed, 305 insertions(+), 130 deletions(-) diff --git a/ext/http/http_next.rs b/ext/http/http_next.rs index 7a6cbfa45e..56c46de925 100644 --- a/ext/http/http_next.rs +++ b/ext/http/http_next.rs @@ -19,7 +19,6 @@ use crate::service::SignallingRc; use crate::websocket_upgrade::WebSocketUpgrade; use crate::LocalExecutor; use cache_control::CacheControl; -use deno_core::error::AnyError; use deno_core::external; use deno_core::futures::future::poll_fn; use deno_core::futures::TryFutureExt; @@ -146,12 +145,32 @@ macro_rules! clone_external { }}; } +#[derive(Debug, thiserror::Error)] +pub enum HttpNextError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("{0}")] + Io(#[from] io::Error), + #[error(transparent)] + WebSocketUpgrade(crate::websocket_upgrade::WebSocketUpgradeError), + #[error("{0}")] + Hyper(#[from] hyper::Error), + #[error(transparent)] + JoinError(#[from] tokio::task::JoinError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), + #[error(transparent)] + HttpPropertyExtractor(deno_core::error::AnyError), + #[error(transparent)] + UpgradeUnavailable(#[from] crate::service::UpgradeUnavailableError), +} + #[op2(fast)] #[smi] pub fn op_http_upgrade_raw( state: &mut OpState, external: *const c_void, -) -> Result { +) -> Result { // SAFETY: external is deleted before calling this op. let http = unsafe { take_external!(external, "op_http_upgrade_raw") }; @@ -177,7 +196,7 @@ pub fn op_http_upgrade_raw( upgraded.write_all(&bytes).await?; break upgraded; } - Err(err) => return Err(err), + Err(err) => return Err(HttpNextError::WebSocketUpgrade(err)), } }; @@ -193,7 +212,7 @@ pub fn op_http_upgrade_raw( } read_tx.write_all(&buf[..read]).await?; } - Ok::<_, AnyError>(()) + Ok::<_, HttpNextError>(()) }); spawn(async move { let mut buf = [0; 1024]; @@ -204,7 +223,7 @@ pub fn op_http_upgrade_raw( } upgraded_tx.write_all(&buf[..read]).await?; } - Ok::<_, AnyError>(()) + Ok::<_, HttpNextError>(()) }); Ok(()) @@ -223,7 +242,7 @@ pub async fn op_http_upgrade_websocket_next( state: Rc>, external: *const c_void, #[serde] headers: Vec<(ByteString, ByteString)>, -) -> Result { +) -> Result { let http = // SAFETY: external is deleted before calling this op. unsafe { take_external!(external, "op_http_upgrade_websocket_next") }; @@ -690,7 +709,7 @@ pub async fn op_http_set_response_body_resource( #[smi] stream_rid: ResourceId, auto_close: bool, status: u16, -) -> Result { +) -> Result { let http = // SAFETY: op is called with external. unsafe { clone_external!(external, "op_http_set_response_body_resource") }; @@ -705,9 +724,15 @@ pub async fn op_http_set_response_body_resource( let resource = { let mut state = state.borrow_mut(); if auto_close { - state.resource_table.take_any(stream_rid)? + state + .resource_table + .take_any(stream_rid) + .map_err(HttpNextError::Resource)? } else { - state.resource_table.get_any(stream_rid)? + state + .resource_table + .get_any(stream_rid) + .map_err(HttpNextError::Resource)? } }; @@ -814,17 +839,17 @@ async fn serve_http2_autodetect( io: impl HttpServeStream, svc: impl HttpService + 'static, cancel: Rc, -) -> Result<(), AnyError> { +) -> Result<(), HttpNextError> { let prefix = NetworkStreamPrefixCheck::new(io, HTTP2_PREFIX); let (matches, io) = prefix.match_prefix().await?; if matches { serve_http2_unconditional(io, svc, cancel) .await - .map_err(|e| e.into()) + .map_err(HttpNextError::Hyper) } else { serve_http11_unconditional(io, svc, cancel) .await - .map_err(|e| e.into()) + .map_err(HttpNextError::Hyper) } } @@ -833,7 +858,7 @@ fn serve_https( request_info: HttpConnectionProperties, lifetime: HttpLifetime, tx: tokio::sync::mpsc::Sender>, -) -> JoinHandle> { +) -> JoinHandle> { let HttpLifetime { server_state, connection_cancel_handle, @@ -852,11 +877,11 @@ fn serve_https( if Some(TLS_ALPN_HTTP_2) == handshake.as_deref() { serve_http2_unconditional(io, svc, listen_cancel_handle) .await - .map_err(|e| e.into()) + .map_err(HttpNextError::Hyper) } else if Some(TLS_ALPN_HTTP_11) == handshake.as_deref() { serve_http11_unconditional(io, svc, listen_cancel_handle) .await - .map_err(|e| e.into()) + .map_err(HttpNextError::Hyper) } else { serve_http2_autodetect(io, svc, listen_cancel_handle).await } @@ -870,7 +895,7 @@ fn serve_http( request_info: HttpConnectionProperties, lifetime: HttpLifetime, tx: tokio::sync::mpsc::Sender>, -) -> JoinHandle> { +) -> JoinHandle> { let HttpLifetime { server_state, connection_cancel_handle, @@ -891,7 +916,7 @@ fn serve_http_on( listen_properties: &HttpListenProperties, lifetime: HttpLifetime, tx: tokio::sync::mpsc::Sender>, -) -> JoinHandle> +) -> JoinHandle> where HTTP: HttpPropertyExtractor, { @@ -922,7 +947,7 @@ struct HttpLifetime { } struct HttpJoinHandle { - join_handle: AsyncRefCell>>>, + join_handle: AsyncRefCell>>>, connection_cancel_handle: Rc, listen_cancel_handle: Rc, rx: AsyncRefCell>>, @@ -982,12 +1007,13 @@ impl Drop for HttpJoinHandle { pub fn op_http_serve( state: Rc>, #[smi] listener_rid: ResourceId, -) -> Result<(ResourceId, &'static str, String), AnyError> +) -> Result<(ResourceId, &'static str, String), HttpNextError> where HTTP: HttpPropertyExtractor, { let listener = - HTTP::get_listener_for_rid(&mut state.borrow_mut(), listener_rid)?; + HTTP::get_listener_for_rid(&mut state.borrow_mut(), listener_rid) + .map_err(HttpNextError::Resource)?; let listen_properties = HTTP::listen_properties_from_listener(&listener)?; @@ -1002,7 +1028,8 @@ where loop { let conn = HTTP::accept_connection_from_listener(&listener) .try_or_cancel(listen_cancel_clone.clone()) - .await?; + .await + .map_err(HttpNextError::HttpPropertyExtractor)?; serve_http_on::( conn, &listen_properties_clone, @@ -1011,7 +1038,7 @@ where ); } #[allow(unreachable_code)] - Ok::<_, AnyError>(()) + Ok::<_, HttpNextError>(()) }); // Set the handle after we start the future @@ -1031,25 +1058,25 @@ where pub fn op_http_serve_on( state: Rc>, #[smi] connection_rid: ResourceId, -) -> Result<(ResourceId, &'static str, String), AnyError> +) -> Result<(ResourceId, &'static str, String), HttpNextError> where HTTP: HttpPropertyExtractor, { let connection = - HTTP::get_connection_for_rid(&mut state.borrow_mut(), connection_rid)?; + HTTP::get_connection_for_rid(&mut state.borrow_mut(), connection_rid) + .map_err(HttpNextError::Resource)?; let listen_properties = HTTP::listen_properties_from_connection(&connection)?; let (tx, rx) = tokio::sync::mpsc::channel(10); let resource: Rc = Rc::new(HttpJoinHandle::new(rx)); - let handle: JoinHandle> = - serve_http_on::( - connection, - &listen_properties, - resource.lifetime(), - tx, - ); + let handle = serve_http_on::( + connection, + &listen_properties, + resource.lifetime(), + tx, + ); // Set the handle after we start the future *RcRef::map(&resource, |this| &this.join_handle) @@ -1095,12 +1122,13 @@ pub fn op_http_try_wait( pub async fn op_http_wait( state: Rc>, #[smi] rid: ResourceId, -) -> Result<*const c_void, AnyError> { +) -> Result<*const c_void, HttpNextError> { // We will get the join handle initially, as we might be consuming requests still let join_handle = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpNextError::Resource)?; let cancel = join_handle.listen_cancel_handle(); let next = async { @@ -1127,13 +1155,12 @@ pub async fn op_http_wait( // Filter out shutdown (ENOTCONN) errors if let Err(err) = res { - if let Some(err) = err.source() { - if let Some(err) = err.downcast_ref::() { - if err.kind() == io::ErrorKind::NotConnected { - return Ok(null()); - } + if let HttpNextError::Io(err) = &err { + if err.kind() == io::ErrorKind::NotConnected { + return Ok(null()); } } + return Err(err); } @@ -1146,7 +1173,7 @@ pub fn op_http_cancel( state: &mut OpState, #[smi] rid: ResourceId, graceful: bool, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let join_handle = state.resource_table.get::(rid)?; if graceful { @@ -1166,11 +1193,12 @@ pub async fn op_http_close( state: Rc>, #[smi] rid: ResourceId, graceful: bool, -) -> Result<(), AnyError> { +) -> Result<(), HttpNextError> { let join_handle = state .borrow_mut() .resource_table - .take::(rid)?; + .take::(rid) + .map_err(HttpNextError::Resource)?; if graceful { http_general_trace!("graceful shutdown"); @@ -1216,23 +1244,26 @@ impl UpgradeStream { } } - async fn read(self: Rc, buf: &mut [u8]) -> Result { + async fn read( + self: Rc, + buf: &mut [u8], + ) -> Result { let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle); async { let read = RcRef::map(self, |this| &this.read); let mut read = read.borrow_mut().await; - Ok(Pin::new(&mut *read).read(buf).await?) + Pin::new(&mut *read).read(buf).await } .try_or_cancel(cancel_handle) .await } - async fn write(self: Rc, buf: &[u8]) -> Result { + async fn write(self: Rc, buf: &[u8]) -> Result { let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle); async { let write = RcRef::map(self, |this| &this.write); let mut write = write.borrow_mut().await; - Ok(Pin::new(&mut *write).write(buf).await?) + Pin::new(&mut *write).write(buf).await } .try_or_cancel(cancel_handle) .await @@ -1242,7 +1273,7 @@ impl UpgradeStream { self: Rc, buf1: &[u8], buf2: &[u8], - ) -> Result { + ) -> Result { let mut wr = RcRef::map(self, |r| &r.write).borrow_mut().await; let total = buf1.len() + buf2.len(); @@ -1295,9 +1326,12 @@ pub async fn op_raw_write_vectored( #[smi] rid: ResourceId, #[buffer] buf1: JsBuffer, #[buffer] buf2: JsBuffer, -) -> Result { - let resource: Rc = - state.borrow().resource_table.get::(rid)?; +) -> Result { + let resource: Rc = state + .borrow() + .resource_table + .get::(rid) + .map_err(HttpNextError::Resource)?; let nwritten = resource.write_vectored(&buf1, &buf2).await?; Ok(nwritten) } diff --git a/ext/http/lib.rs b/ext/http/lib.rs index 5461713aa8..6243804a14 100644 --- a/ext/http/lib.rs +++ b/ext/http/lib.rs @@ -6,8 +6,6 @@ use async_compression::Level; use base64::prelude::BASE64_STANDARD; use base64::Engine; use cache_control::CacheControl; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::futures::channel::mpsc; use deno_core::futures::channel::oneshot; use deno_core::futures::future::pending; @@ -89,11 +87,14 @@ mod service; mod websocket_upgrade; use fly_accept_encoding::Encoding; +pub use http_next::HttpNextError; pub use request_properties::DefaultHttpPropertyExtractor; pub use request_properties::HttpConnectionProperties; pub use request_properties::HttpListenProperties; pub use request_properties::HttpPropertyExtractor; pub use request_properties::HttpRequestProperties; +pub use service::UpgradeUnavailableError; +pub use websocket_upgrade::WebSocketUpgradeError; deno_core::extension!( deno_http, @@ -134,6 +135,38 @@ deno_core::extension!( esm = ["00_serve.ts", "01_http.js", "02_websocket.ts"], ); +#[derive(Debug, thiserror::Error)] +pub enum HttpError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), + #[error("{0}")] + HyperV014(#[source] Arc), + #[error("{0}")] + InvalidHeaderName(#[from] hyper_v014::header::InvalidHeaderName), + #[error("{0}")] + InvalidHeaderValue(#[from] hyper_v014::header::InvalidHeaderValue), + #[error("{0}")] + Http(#[from] hyper_v014::http::Error), + #[error("response headers already sent")] + ResponseHeadersAlreadySent, + #[error("connection closed while sending response")] + ConnectionClosedWhileSendingResponse, + #[error("already in use")] + AlreadyInUse, + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("no response headers")] + NoResponseHeaders, + #[error("response already completed")] + ResponseAlreadyCompleted, + #[error("cannot upgrade because request body was used")] + UpgradeBodyUsed, + #[error(transparent)] + Other(deno_core::error::AnyError), +} + pub enum HttpSocketAddr { IpSocket(std::net::SocketAddr), #[cfg(unix)] @@ -216,7 +249,7 @@ impl HttpConnResource { String, String, )>, - AnyError, + HttpError, > { let fut = async { let (request_tx, request_rx) = oneshot::channel(); @@ -259,8 +292,8 @@ impl HttpConnResource { } /// A future that completes when this HTTP connection is closed or errors. - async fn closed(&self) -> Result<(), AnyError> { - self.closed_fut.clone().map_err(AnyError::from).await + async fn closed(&self) -> Result<(), HttpError> { + self.closed_fut.clone().map_err(HttpError::HyperV014).await } } @@ -280,14 +313,13 @@ pub fn http_create_conn_resource( io: S, addr: A, scheme: &'static str, -) -> Result +) -> ResourceId where S: AsyncRead + AsyncWrite + Unpin + Send + 'static, A: Into, { let conn = HttpConnResource::new(io, scheme, addr.into()); - let rid = state.resource_table.add(conn); - Ok(rid) + state.resource_table.add(conn) } /// An object that implements the `hyper::Service` trait, through which Hyper @@ -423,7 +455,9 @@ impl Resource for HttpStreamReadResource { // safely call `await` on it without creating a race condition. Some(_) => match body.as_mut().next().await.unwrap() { Ok(chunk) => assert!(chunk.is_empty()), - Err(err) => break Err(AnyError::from(err)), + Err(err) => { + break Err(HttpError::HyperV014(Arc::new(err)).into()) + } }, None => break Ok(BufView::empty()), } @@ -545,8 +579,12 @@ struct NextRequestResponse( async fn op_http_accept( state: Rc>, #[smi] rid: ResourceId, -) -> Result, AnyError> { - let conn = state.borrow().resource_table.get::(rid)?; +) -> Result, HttpError> { + let conn = state + .borrow() + .resource_table + .get::(rid) + .map_err(HttpError::Resource)?; match conn.accept().await { Ok(Some((read_stream, write_stream, method, url))) => { @@ -657,11 +695,12 @@ async fn op_http_write_headers( #[smi] status: u16, #[serde] headers: Vec<(ByteString, ByteString)>, #[serde] data: Option, -) -> Result<(), AnyError> { +) -> Result<(), HttpError> { let stream = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpError::Resource)?; // Track supported encoding let encoding = stream.accept_encoding; @@ -708,14 +747,14 @@ async fn op_http_write_headers( let mut old_wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await; let response_tx = match replace(&mut *old_wr, new_wr) { HttpResponseWriter::Headers(response_tx) => response_tx, - _ => return Err(http_error("response headers already sent")), + _ => return Err(HttpError::ResponseHeadersAlreadySent), }; match response_tx.send(body) { Ok(_) => Ok(()), Err(_) => { stream.conn.closed().await?; - Err(http_error("connection closed while sending response")) + Err(HttpError::ConnectionClosedWhileSendingResponse) } } } @@ -725,11 +764,14 @@ async fn op_http_write_headers( fn op_http_headers( state: &mut OpState, #[smi] rid: u32, -) -> Result, AnyError> { - let stream = state.resource_table.get::(rid)?; +) -> Result, HttpError> { + let stream = state + .resource_table + .get::(rid) + .map_err(HttpError::Resource)?; let rd = RcRef::map(&stream, |r| &r.rd) .try_borrow() - .ok_or_else(|| http_error("already in use"))?; + .ok_or(HttpError::AlreadyInUse)?; match &*rd { HttpRequestReader::Headers(request) => Ok(req_headers(request.headers())), HttpRequestReader::Body(headers, _) => Ok(req_headers(headers)), @@ -741,7 +783,7 @@ fn http_response( data: Option, compressing: bool, encoding: Encoding, -) -> Result<(HttpResponseWriter, hyper_v014::Body), AnyError> { +) -> Result<(HttpResponseWriter, hyper_v014::Body), HttpError> { // Gzip, after level 1, doesn't produce significant size difference. // This default matches nginx default gzip compression level (1): // https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_comp_level @@ -878,25 +920,34 @@ async fn op_http_write_resource( state: Rc>, #[smi] rid: ResourceId, #[smi] stream: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), HttpError> { let http_stream = state .borrow() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpError::Resource)?; let mut wr = RcRef::map(&http_stream, |r| &r.wr).borrow_mut().await; - let resource = state.borrow().resource_table.get_any(stream)?; + let resource = state + .borrow() + .resource_table + .get_any(stream) + .map_err(HttpError::Resource)?; loop { match *wr { HttpResponseWriter::Headers(_) => { - return Err(http_error("no response headers")) + return Err(HttpError::NoResponseHeaders) } HttpResponseWriter::Closed => { - return Err(http_error("response already completed")) + return Err(HttpError::ResponseAlreadyCompleted) } _ => {} }; - let view = resource.clone().read(64 * 1024).await?; // 64KB + let view = resource + .clone() + .read(64 * 1024) + .await + .map_err(HttpError::Other)?; // 64KB if view.is_empty() { break; } @@ -937,16 +988,17 @@ async fn op_http_write( state: Rc>, #[smi] rid: ResourceId, #[buffer] buf: JsBuffer, -) -> Result<(), AnyError> { +) -> Result<(), HttpError> { let stream = state .borrow() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpError::Resource)?; let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await; match &mut *wr { - HttpResponseWriter::Headers(_) => Err(http_error("no response headers")), - HttpResponseWriter::Closed => Err(http_error("response already completed")), + HttpResponseWriter::Headers(_) => Err(HttpError::NoResponseHeaders), + HttpResponseWriter::Closed => Err(HttpError::ResponseAlreadyCompleted), HttpResponseWriter::Body { writer, .. } => { let mut result = writer.write_all(&buf).await; if result.is_ok() { @@ -961,7 +1013,7 @@ async fn op_http_write( stream.conn.closed().await?; // If there was no connection error, drop body_tx. *wr = HttpResponseWriter::Closed; - Err(http_error("response already completed")) + Err(HttpError::ResponseAlreadyCompleted) } } } @@ -975,7 +1027,7 @@ async fn op_http_write( stream.conn.closed().await?; // If there was no connection error, drop body_tx. *wr = HttpResponseWriter::Closed; - Err(http_error("response already completed")) + Err(HttpError::ResponseAlreadyCompleted) } } } @@ -989,11 +1041,12 @@ async fn op_http_write( async fn op_http_shutdown( state: Rc>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), HttpError> { let stream = state .borrow() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpError::Resource)?; let mut wr = RcRef::map(&stream, |r| &r.wr).borrow_mut().await; let wr = take(&mut *wr); match wr { @@ -1022,14 +1075,12 @@ async fn op_http_shutdown( #[op2] #[string] -fn op_http_websocket_accept_header( - #[string] key: String, -) -> Result { +fn op_http_websocket_accept_header(#[string] key: String) -> String { let digest = ring::digest::digest( &ring::digest::SHA1_FOR_LEGACY_USE_ONLY, format!("{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11").as_bytes(), ); - Ok(BASE64_STANDARD.encode(digest)) + BASE64_STANDARD.encode(digest) } #[op2(async)] @@ -1037,22 +1088,24 @@ fn op_http_websocket_accept_header( async fn op_http_upgrade_websocket( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let stream = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(rid) + .map_err(HttpError::Resource)?; let mut rd = RcRef::map(&stream, |r| &r.rd).borrow_mut().await; let request = match &mut *rd { HttpRequestReader::Headers(request) => request, - _ => { - return Err(http_error("cannot upgrade because request body was used")) - } + _ => return Err(HttpError::UpgradeBodyUsed), }; - let (transport, bytes) = - extract_network_stream(hyper_v014::upgrade::on(request).await?); + let (transport, bytes) = extract_network_stream( + hyper_v014::upgrade::on(request) + .await + .map_err(|err| HttpError::HyperV014(Arc::new(err)))?, + ); Ok(ws_create_server_stream( &mut state.borrow_mut(), transport, @@ -1084,10 +1137,6 @@ where } } -fn http_error(message: &'static str) -> AnyError { - custom_error("Http", message) -} - /// Filters out the ever-surprising 'shutdown ENOTCONN' errors. fn filter_enotconn( result: Result<(), hyper_v014::Error>, diff --git a/ext/http/request_body.rs b/ext/http/request_body.rs index 45df12457c..f1c3f358ea 100644 --- a/ext/http/request_body.rs +++ b/ext/http/request_body.rs @@ -1,9 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use bytes::Bytes; -use deno_core::error::AnyError; use deno_core::futures::stream::Peekable; use deno_core::futures::Stream; use deno_core::futures::StreamExt; +use deno_core::futures::TryFutureExt; use deno_core::AsyncRefCell; use deno_core::AsyncResult; use deno_core::BufView; @@ -22,7 +22,7 @@ use std::task::Poll; struct ReadFuture(Incoming); impl Stream for ReadFuture { - type Item = Result; + type Item = Result; fn poll_next( self: Pin<&mut Self>, @@ -37,13 +37,13 @@ impl Stream for ReadFuture { if let Ok(data) = frame.into_data() { // Ensure that we never yield an empty frame if !data.is_empty() { - break Poll::Ready(Some(Ok::<_, AnyError>(data))); + break Poll::Ready(Some(Ok(data))); } } // Loop again so we don't lose the waker continue; } - Some(Err(e)) => Poll::Ready(Some(Err(e.into()))), + Some(Err(e)) => Poll::Ready(Some(Err(e))), None => Poll::Ready(None), }; } @@ -58,7 +58,7 @@ impl HttpRequestBody { Self(AsyncRefCell::new(ReadFuture(body).peekable()), size_hint) } - async fn read(self: Rc, limit: usize) -> Result { + async fn read(self: Rc, limit: usize) -> Result { let peekable = RcRef::map(self, |this| &this.0); let mut peekable = peekable.borrow_mut().await; match Pin::new(&mut *peekable).peek_mut().await { @@ -82,7 +82,7 @@ impl Resource for HttpRequestBody { } fn read(self: Rc, limit: usize) -> AsyncResult { - Box::pin(HttpRequestBody::read(self, limit)) + Box::pin(HttpRequestBody::read(self, limit).map_err(Into::into)) } fn size_hint(&self) -> (u64, Option) { diff --git a/ext/http/service.rs b/ext/http/service.rs index 787e9babf8..75f93d77c2 100644 --- a/ext/http/service.rs +++ b/ext/http/service.rs @@ -2,7 +2,6 @@ use crate::request_properties::HttpConnectionProperties; use crate::response_body::ResponseBytesInner; use crate::response_body::ResponseStreamResult; -use deno_core::error::AnyError; use deno_core::futures::ready; use deno_core::BufView; use deno_core::OpState; @@ -206,6 +205,10 @@ pub(crate) async fn handle_request( Ok(response) } +#[derive(Debug, thiserror::Error)] +#[error("upgrade unavailable")] +pub struct UpgradeUnavailableError; + struct HttpRecordInner { server_state: SignallingRc, request_info: HttpConnectionProperties, @@ -344,14 +347,14 @@ impl HttpRecord { } /// Perform the Hyper upgrade on this record. - pub fn upgrade(&self) -> Result { + pub fn upgrade(&self) -> Result { // Manually perform the upgrade. We're peeking into hyper's underlying machinery here a bit self .self_mut() .request_parts .extensions .remove::() - .ok_or_else(|| AnyError::msg("upgrade unavailable")) + .ok_or(UpgradeUnavailableError) } /// Take the Hyper body from this record. @@ -515,7 +518,7 @@ pub struct HttpRecordResponse(ManuallyDrop>); impl Body for HttpRecordResponse { type Data = BufView; - type Error = AnyError; + type Error = deno_core::error::AnyError; fn poll_frame( self: Pin<&mut Self>, @@ -640,7 +643,7 @@ mod tests { } #[tokio::test] - async fn test_handle_request() -> Result<(), AnyError> { + async fn test_handle_request() -> Result<(), deno_core::error::AnyError> { let (tx, mut rx) = tokio::sync::mpsc::channel(10); let server_state = HttpServerState::new(); let server_state_check = server_state.clone(); diff --git a/ext/http/websocket_upgrade.rs b/ext/http/websocket_upgrade.rs index 4dead767a3..af9504717e 100644 --- a/ext/http/websocket_upgrade.rs +++ b/ext/http/websocket_upgrade.rs @@ -4,7 +4,6 @@ use std::marker::PhantomData; use bytes::Bytes; use bytes::BytesMut; -use deno_core::error::AnyError; use httparse::Status; use hyper::header::HeaderName; use hyper::header::HeaderValue; @@ -13,12 +12,30 @@ use memmem::Searcher; use memmem::TwoWaySearcher; use once_cell::sync::OnceCell; -use crate::http_error; +#[derive(Debug, thiserror::Error)] +pub enum WebSocketUpgradeError { + #[error("invalid headers")] + InvalidHeaders, + #[error("{0}")] + HttpParse(#[from] httparse::Error), + #[error("{0}")] + Http(#[from] http::Error), + #[error("{0}")] + Utf8(#[from] std::str::Utf8Error), + #[error("{0}")] + InvalidHeaderName(#[from] http::header::InvalidHeaderName), + #[error("{0}")] + InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), + #[error("invalid HTTP status line")] + InvalidHttpStatusLine, + #[error("attempted to write to completed upgrade buffer")] + UpgradeBufferAlreadyCompleted, +} /// Given a buffer that ends in `\n\n` or `\r\n\r\n`, returns a parsed [`Request`]. fn parse_response( header_bytes: &[u8], -) -> Result<(usize, Response), AnyError> { +) -> Result<(usize, Response), WebSocketUpgradeError> { let mut headers = [httparse::EMPTY_HEADER; 16]; let status = httparse::parse_headers(header_bytes, &mut headers)?; match status { @@ -32,7 +49,7 @@ fn parse_response( } Ok((index, resp)) } - _ => Err(http_error("invalid headers")), + _ => Err(WebSocketUpgradeError::InvalidHeaders), } } @@ -69,11 +86,14 @@ pub struct WebSocketUpgrade { impl WebSocketUpgrade { /// Ensures that the status line starts with "HTTP/1.1 101 " which matches all of the node.js /// WebSocket libraries that are known. We don't care about the trailing status text. - fn validate_status(&self, status: &[u8]) -> Result<(), AnyError> { + fn validate_status( + &self, + status: &[u8], + ) -> Result<(), WebSocketUpgradeError> { if status.starts_with(b"HTTP/1.1 101 ") { Ok(()) } else { - Err(http_error("invalid HTTP status line")) + Err(WebSocketUpgradeError::InvalidHttpStatusLine) } } @@ -82,7 +102,7 @@ impl WebSocketUpgrade { pub fn write( &mut self, bytes: &[u8], - ) -> Result, Bytes)>, AnyError> { + ) -> Result, Bytes)>, WebSocketUpgradeError> { use WebSocketUpgradeState::*; match self.state { @@ -142,9 +162,7 @@ impl WebSocketUpgrade { Ok(None) } } - Complete => { - Err(http_error("attempted to write to completed upgrade buffer")) - } + Complete => Err(WebSocketUpgradeError::UpgradeBufferAlreadyCompleted), } } } @@ -157,8 +175,8 @@ mod tests { type ExpectedResponseAndHead = Option<(Response, &'static [u8])>; fn assert_response( - result: Result, Bytes)>, AnyError>, - expected: Result, + result: Result, Bytes)>, WebSocketUpgradeError>, + expected: Result, chunk_info: Option<(usize, usize)>, ) { let formatted = format!("{result:?}"); @@ -189,8 +207,8 @@ mod tests { "Expected Ok(None), was {formatted}", ), Err(e) => assert_eq!( - e, - result.err().map(|e| format!("{e:?}")).unwrap_or_default(), + format!("{e:?}"), + format!("{:?}", result.unwrap_err()), "Expected error, was {formatted}", ), } @@ -198,7 +216,7 @@ mod tests { fn validate_upgrade_all_at_once( s: &str, - expected: Result, + expected: Result, ) { let mut upgrade = WebSocketUpgrade::default(); let res = upgrade.write(s.as_bytes()); @@ -209,7 +227,7 @@ mod tests { fn validate_upgrade_chunks( s: &str, size: usize, - expected: Result, + expected: Result, ) { let chunk_info = Some((s.as_bytes().len(), size)); let mut upgrade = WebSocketUpgrade::default(); @@ -226,7 +244,7 @@ mod tests { fn validate_upgrade( s: &str, - expected: fn() -> Result, + expected: fn() -> Result, ) { validate_upgrade_all_at_once(s, expected()); validate_upgrade_chunks(s, 1, expected()); @@ -315,7 +333,7 @@ mod tests { #[test] fn upgrade_invalid_status() { validate_upgrade("HTTP/1.1 200 OK\nConnection: Upgrade\n\n", || { - Err("invalid HTTP status line") + Err(WebSocketUpgradeError::InvalidHttpStatusLine) }); } @@ -327,7 +345,11 @@ mod tests { .join("\n"); validate_upgrade( &format!("HTTP/1.1 101 Switching Protocols\n{headers}\n\n"), - || Err("too many headers"), + || { + Err(WebSocketUpgradeError::HttpParse( + httparse::Error::TooManyHeaders, + )) + }, ); } } diff --git a/runtime/errors.rs b/runtime/errors.rs index 45442a11c3..dcd626dc65 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -29,6 +29,9 @@ use deno_ffi::IRError; use deno_ffi::ReprError; use deno_ffi::StaticError; use deno_fs::FsOpsError; +use deno_http::HttpError; +use deno_http::HttpNextError; +use deno_http::WebSocketUpgradeError; use deno_io::fs::FsError; use deno_kv::KvCheckError; use deno_kv::KvError; @@ -682,6 +685,59 @@ fn get_net_map_error(error: &deno_net::io::MapError) -> &'static str { } } +fn get_http_error(error: &HttpError) -> &'static str { + match error { + HttpError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + HttpError::HyperV014(e) => get_hyper_v014_error_class(e), + HttpError::InvalidHeaderName(_) => "Error", + HttpError::InvalidHeaderValue(_) => "Error", + HttpError::Http(_) => "Error", + HttpError::ResponseHeadersAlreadySent => "Http", + HttpError::ConnectionClosedWhileSendingResponse => "Http", + HttpError::AlreadyInUse => "Http", + HttpError::Io(e) => get_io_error_class(e), + HttpError::NoResponseHeaders => "Http", + HttpError::ResponseAlreadyCompleted => "Http", + HttpError::UpgradeBodyUsed => "Http", + HttpError::Resource(e) | HttpError::Other(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } +} + +fn get_http_next_error(error: &HttpNextError) -> &'static str { + match error { + HttpNextError::Io(e) => get_io_error_class(e), + HttpNextError::WebSocketUpgrade(e) => get_websocket_upgrade_error(e), + HttpNextError::Hyper(e) => get_hyper_error_class(e), + HttpNextError::JoinError(_) => "Error", + HttpNextError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + HttpNextError::UpgradeUnavailable(_) => "Error", + HttpNextError::HttpPropertyExtractor(e) | HttpNextError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + } +} + +fn get_websocket_upgrade_error(error: &WebSocketUpgradeError) -> &'static str { + match error { + WebSocketUpgradeError::InvalidHeaders => "Http", + WebSocketUpgradeError::HttpParse(_) => "Error", + WebSocketUpgradeError::Http(_) => "Error", + WebSocketUpgradeError::Utf8(_) => "Error", + WebSocketUpgradeError::InvalidHeaderName(_) => "Error", + WebSocketUpgradeError::InvalidHeaderValue(_) => "Error", + WebSocketUpgradeError::InvalidHttpStatusLine => "Http", + WebSocketUpgradeError::UpgradeBufferAlreadyCompleted => "Http", + } +} + pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) .or_else(|| deno_webgpu::error::get_error_class_name(e)) @@ -702,6 +758,12 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .or_else(|| e.downcast_ref::().map(get_web_blob_error_class)) .or_else(|| e.downcast_ref::().map(|_| "TypeError")) .or_else(|| e.downcast_ref::().map(get_ffi_repr_error_class)) + .or_else(|| e.downcast_ref::().map(get_http_error)) + .or_else(|| e.downcast_ref::().map(get_http_next_error)) + .or_else(|| { + e.downcast_ref::() + .map(get_websocket_upgrade_error) + }) .or_else(|| e.downcast_ref::().map(get_fs_error)) .or_else(|| { e.downcast_ref::() diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index cec8b0ef8f..cbabbe22c5 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -34,7 +34,7 @@ fn op_http_start( let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; let addr = tcp_stream.local_addr()?; - return http_create_conn_resource(state, tcp_stream, addr, "http"); + return Ok(http_create_conn_resource(state, tcp_stream, addr, "http")); } if let Ok(resource_rc) = state @@ -49,7 +49,7 @@ fn op_http_start( let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.unsplit(write_half); let addr = tls_stream.local_addr()?; - return http_create_conn_resource(state, tls_stream, addr, "https"); + return Ok(http_create_conn_resource(state, tls_stream, addr, "https")); } #[cfg(unix)] @@ -65,7 +65,12 @@ fn op_http_start( let (read_half, write_half) = resource.into_inner(); let unix_stream = read_half.reunite(write_half)?; let addr = unix_stream.local_addr()?; - return http_create_conn_resource(state, unix_stream, addr, "http+unix"); + return Ok(http_create_conn_resource( + state, + unix_stream, + addr, + "http+unix", + )); } Err(bad_resource_id()) From d1cd1fafa4f09b215a9ce2eb330dc810a6635afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 19 Oct 2024 01:42:54 +0100 Subject: [PATCH 11/16] Revert "ci: use self-hosted mac arm runner (#26366)" (#26408) This reverts commit e22d0e91ef7ce17dca299a44d1ccd292abde34f2. Reverting because the CI pipeline is actually incorrect. I intended to only use this self-hosted runner for "release" builds on `main` branch, but now all PRs are queued waiting for a runner for a "debug" build. --- .github/workflows/ci.generate.ts | 3 +-- .github/workflows/ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index ab0b54bdee..98272abe4e 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -40,8 +40,7 @@ const Runners = { macosArm: { os: "macos", arch: "aarch64", - runner: - `\${{ github.repository == 'denoland/deno' && 'self-hosted' || '${macosArmRunner}' }}`, + runner: macosArmRunner, }, windowsX86: { os: "windows", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63c14de4af..161b8719c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,12 +68,12 @@ jobs: skip: '${{ !contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'') }}' - os: macos arch: aarch64 - runner: '${{ github.repository == ''denoland/deno'' && ''self-hosted'' || ''macos-14'' }}' + runner: macos-14 job: test profile: debug - os: macos arch: aarch64 - runner: '${{ (!contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'')) && ''ubuntu-22.04'' || github.repository == ''denoland/deno'' && ''self-hosted'' || ''macos-14'' }}' + runner: '${{ (!contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'')) && ''ubuntu-22.04'' || ''macos-14'' }}' job: test profile: release skip: '${{ !contains(github.event.pull_request.labels.*.name, ''ci-full'') && (github.event_name == ''pull_request'') }}' From 6c4ef11f04cc35b61417bf08d5e7592d44197c75 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 18:20:58 -0700 Subject: [PATCH 12/16] refactor(ext/fetch): use concrete error types (#26220) --- Cargo.lock | 1 + ext/fetch/Cargo.toml | 1 + ext/fetch/fs_fetch_handler.rs | 5 +- ext/fetch/lib.rs | 244 +++++++++++++++++++++------------- ext/node/ops/http.rs | 95 +++++++------ runtime/errors.rs | 43 ++++++ 6 files changed, 255 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cda1499a1a..7a396addf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,6 +1557,7 @@ dependencies = [ "rustls-webpki", "serde", "serde_json", + "thiserror", "tokio", "tokio-rustls", "tokio-socks", diff --git a/ext/fetch/Cargo.toml b/ext/fetch/Cargo.toml index ddb58a3f3f..afffd3ffb1 100644 --- a/ext/fetch/Cargo.toml +++ b/ext/fetch/Cargo.toml @@ -32,6 +32,7 @@ percent-encoding.workspace = true rustls-webpki.workspace = true serde.workspace = true serde_json.workspace = true +thiserror.workspace = true tokio.workspace = true tokio-rustls.workspace = true tokio-socks.workspace = true diff --git a/ext/fetch/fs_fetch_handler.rs b/ext/fetch/fs_fetch_handler.rs index 4c2b81f356..c236dd9c67 100644 --- a/ext/fetch/fs_fetch_handler.rs +++ b/ext/fetch/fs_fetch_handler.rs @@ -4,7 +4,6 @@ use crate::CancelHandle; use crate::CancelableResponseFuture; use crate::FetchHandler; -use deno_core::error::type_error; use deno_core::futures::FutureExt; use deno_core::futures::TryFutureExt; use deno_core::futures::TryStreamExt; @@ -42,9 +41,7 @@ impl FetchHandler for FsFetchHandler { .map_err(|_| ())?; Ok::<_, ()>(response) } - .map_err(move |_| { - type_error("NetworkError when attempting to fetch resource") - }) + .map_err(move |_| super::FetchError::NetworkError) .or_cancel(&cancel_handle) .boxed_local(); diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 88f3038528..4df8dc3d72 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -17,10 +17,6 @@ use std::sync::Arc; use std::task::Context; use std::task::Poll; -use deno_core::anyhow::anyhow; -use deno_core::anyhow::Error; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::stream::Peekable; use deno_core::futures::Future; use deno_core::futures::FutureExt; @@ -28,6 +24,7 @@ use deno_core::futures::Stream; use deno_core::futures::StreamExt; use deno_core::futures::TryFutureExt; use deno_core::op2; +use deno_core::url; use deno_core::url::Url; use deno_core::AsyncRefCell; use deno_core::AsyncResult; @@ -87,15 +84,18 @@ pub struct Options { pub root_cert_store_provider: Option>, pub proxy: Option, #[allow(clippy::type_complexity)] - pub request_builder_hook: - Option) -> Result<(), AnyError>>, + pub request_builder_hook: Option< + fn(&mut http::Request) -> Result<(), deno_core::error::AnyError>, + >, pub unsafely_ignore_certificate_errors: Option>, pub client_cert_chain_and_key: TlsKeys, pub file_fetch_handler: Rc, } impl Options { - pub fn root_cert_store(&self) -> Result, AnyError> { + pub fn root_cert_store( + &self, + ) -> Result, deno_core::error::AnyError> { Ok(match &self.root_cert_store_provider { Some(provider) => Some(provider.get_or_try_init()?.clone()), None => None, @@ -144,6 +144,51 @@ deno_core::extension!(deno_fetch, }, ); +#[derive(Debug, thiserror::Error)] +pub enum FetchError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error("NetworkError when attempting to fetch resource")] + NetworkError, + #[error("Fetching files only supports the GET method: received {0}")] + FsNotGet(Method), + #[error("Invalid URL {0}")] + InvalidUrl(Url), + #[error(transparent)] + InvalidHeaderName(#[from] http::header::InvalidHeaderName), + #[error(transparent)] + InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), + #[error("{0:?}")] + DataUrl(data_url::DataUrlError), + #[error("{0:?}")] + Base64(data_url::forgiving_base64::InvalidBase64), + #[error("Blob for the given URL not found.")] + BlobNotFound, + #[error("Url scheme '{0}' not supported")] + SchemeNotSupported(String), + #[error("Request was cancelled")] + RequestCanceled, + #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] + ClientCreate(#[from] HttpClientCreateError), + #[error(transparent)] + Url(#[from] url::ParseError), + #[error(transparent)] + Method(#[from] http::method::InvalidMethod), + #[error(transparent)] + ClientSend(#[from] ClientSendError), + #[error(transparent)] + RequestBuilderHook(deno_core::error::AnyError), + #[error(transparent)] + Io(#[from] std::io::Error), + // Only used for node upgrade + #[error(transparent)] + Hyper(#[from] hyper::Error), +} + pub type CancelableResponseFuture = Pin>>; @@ -170,11 +215,7 @@ impl FetchHandler for DefaultFileFetchHandler { _state: &mut OpState, _url: &Url, ) -> (CancelableResponseFuture, Option>) { - let fut = async move { - Ok(Err(type_error( - "NetworkError when attempting to fetch resource", - ))) - }; + let fut = async move { Ok(Err(FetchError::NetworkError)) }; (Box::pin(fut), None) } } @@ -191,7 +232,7 @@ pub struct FetchReturn { pub fn get_or_create_client_from_state( state: &mut OpState, -) -> Result { +) -> Result { if let Some(client) = state.try_borrow::() { Ok(client.clone()) } else { @@ -204,11 +245,13 @@ pub fn get_or_create_client_from_state( pub fn create_client_from_options( options: &Options, -) -> Result { +) -> Result { create_http_client( &options.user_agent, CreateHttpClientOptions { - root_cert_store: options.root_cert_store()?, + root_cert_store: options + .root_cert_store() + .map_err(HttpClientCreateError::RootCertStore)?, ca_certs: vec![], proxy: options.proxy.clone(), unsafely_ignore_certificate_errors: options @@ -230,7 +273,9 @@ pub fn create_client_from_options( #[allow(clippy::type_complexity)] pub struct ResourceToBodyAdapter( Rc, - Option>>>>, + Option< + Pin>>>, + >, ); impl ResourceToBodyAdapter { @@ -246,7 +291,7 @@ unsafe impl Send for ResourceToBodyAdapter {} unsafe impl Sync for ResourceToBodyAdapter {} impl Stream for ResourceToBodyAdapter { - type Item = Result; + type Item = Result; fn poll_next( self: Pin<&mut Self>, @@ -276,7 +321,7 @@ impl Stream for ResourceToBodyAdapter { impl hyper::body::Body for ResourceToBodyAdapter { type Data = Bytes; - type Error = Error; + type Error = deno_core::error::AnyError; fn poll_frame( self: Pin<&mut Self>, @@ -301,13 +346,13 @@ pub trait FetchPermissions { &mut self, url: &Url, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<(), deno_core::error::AnyError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_read<'a>( &mut self, p: &'a Path, api_name: &str, - ) -> Result, AnyError>; + ) -> Result, deno_core::error::AnyError>; } impl FetchPermissions for deno_permissions::PermissionsContainer { @@ -316,7 +361,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, url: &Url, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), deno_core::error::AnyError> { deno_permissions::PermissionsContainer::check_net_url(self, url, api_name) } @@ -325,7 +370,7 @@ impl FetchPermissions for deno_permissions::PermissionsContainer { &mut self, path: &'a Path, api_name: &str, - ) -> Result, AnyError> { + ) -> Result, deno_core::error::AnyError> { deno_permissions::PermissionsContainer::check_read_path( self, path, @@ -346,12 +391,15 @@ pub fn op_fetch( has_body: bool, #[buffer] data: Option, #[smi] resource: Option, -) -> Result +) -> Result where FP: FetchPermissions + 'static, { let (client, allow_host) = if let Some(rid) = client_rid { - let r = state.resource_table.get::(rid)?; + let r = state + .resource_table + .get::(rid) + .map_err(FetchError::Resource)?; (r.client.clone(), r.allow_host) } else { (get_or_create_client_from_state(state)?, false) @@ -364,20 +412,18 @@ where let scheme = url.scheme(); let (request_rid, cancel_handle_rid) = match scheme { "file" => { - let path = url.to_file_path().map_err(|_| { - type_error("NetworkError when attempting to fetch resource") - })?; + let path = url.to_file_path().map_err(|_| FetchError::NetworkError)?; let permissions = state.borrow_mut::(); - let path = permissions.check_read(&path, "fetch()")?; + let path = permissions + .check_read(&path, "fetch()") + .map_err(FetchError::Permission)?; let url = match path { Cow::Owned(path) => Url::from_file_path(path).unwrap(), Cow::Borrowed(_) => url, }; if method != Method::GET { - return Err(type_error(format!( - "Fetching files only supports the GET method: received {method}" - ))); + return Err(FetchError::FsNotGet(method)); } let Options { @@ -396,13 +442,15 @@ where } "http" | "https" => { let permissions = state.borrow_mut::(); - permissions.check_net_url(&url, "fetch()")?; + permissions + .check_net_url(&url, "fetch()") + .map_err(FetchError::Resource)?; let maybe_authority = extract_authority(&mut url); let uri = url .as_str() .parse::() - .map_err(|_| type_error(format!("Invalid URL {url}")))?; + .map_err(|_| FetchError::InvalidUrl(url.clone()))?; let mut con_len = None; let body = if has_body { @@ -416,7 +464,10 @@ where .boxed() } (_, Some(resource)) => { - let resource = state.resource_table.take_any(resource)?; + let resource = state + .resource_table + .take_any(resource) + .map_err(FetchError::Resource)?; match resource.size_hint() { (body_size, Some(n)) if body_size == n && body_size > 0 => { con_len = Some(body_size); @@ -453,10 +504,8 @@ where } for (key, value) in headers { - let name = HeaderName::from_bytes(&key) - .map_err(|err| type_error(err.to_string()))?; - let v = HeaderValue::from_bytes(&value) - .map_err(|err| type_error(err.to_string()))?; + let name = HeaderName::from_bytes(&key)?; + let v = HeaderValue::from_bytes(&value)?; if (name != HOST || allow_host) && name != CONTENT_LENGTH { request.headers_mut().append(name, v); @@ -474,20 +523,18 @@ where let options = state.borrow::(); if let Some(request_builder_hook) = options.request_builder_hook { request_builder_hook(&mut request) - .map_err(|err| type_error(err.to_string()))?; + .map_err(FetchError::RequestBuilderHook)?; } let cancel_handle = CancelHandle::new_rc(); let cancel_handle_ = cancel_handle.clone(); - let fut = { - async move { - client - .send(request) - .map_err(Into::into) - .or_cancel(cancel_handle_) - .await - } + let fut = async move { + client + .send(request) + .map_err(Into::into) + .or_cancel(cancel_handle_) + .await }; let request_rid = state.resource_table.add(FetchRequestResource { @@ -501,12 +548,10 @@ where (request_rid, Some(cancel_handle_rid)) } "data" => { - let data_url = DataUrl::process(url.as_str()) - .map_err(|e| type_error(format!("{e:?}")))?; + let data_url = + DataUrl::process(url.as_str()).map_err(FetchError::DataUrl)?; - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{e:?}")))?; + let (body, _) = data_url.decode_to_vec().map_err(FetchError::Base64)?; let body = http_body_util::Full::new(body.into()) .map_err(|never| match never {}) .boxed(); @@ -528,11 +573,9 @@ where "blob" => { // Blob URL resolution happens in the JS side of fetch. If we got here is // because the URL isn't an object URL. - return Err(type_error("Blob for the given URL not found.")); - } - _ => { - return Err(type_error(format!("Url scheme '{scheme}' not supported"))) + return Err(FetchError::BlobNotFound); } + _ => return Err(FetchError::SchemeNotSupported(scheme.to_string())), }; Ok(FetchReturn { @@ -564,11 +607,12 @@ pub struct FetchResponse { pub async fn op_fetch_send( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let request = state .borrow_mut() .resource_table - .take::(rid)?; + .take::(rid) + .map_err(FetchError::Resource)?; let request = Rc::try_unwrap(request) .ok() @@ -581,22 +625,23 @@ pub async fn op_fetch_send( // If any error in the chain is a hyper body error, return that as a special result we can use to // reconstruct an error chain (eg: `new TypeError(..., { cause: new Error(...) })`). // TODO(mmastrac): it would be a lot easier if we just passed a v8::Global through here instead - let mut err_ref: &dyn std::error::Error = err.as_ref(); - while let Some(err_src) = std::error::Error::source(err_ref) { - if let Some(err_src) = err_src.downcast_ref::() { - if let Some(err_src) = std::error::Error::source(err_src) { - return Ok(FetchResponse { - error: Some((err.to_string(), err_src.to_string())), - ..Default::default() - }); + + if let FetchError::ClientSend(err_src) = &err { + if let Some(client_err) = std::error::Error::source(&err_src.source) { + if let Some(err_src) = client_err.downcast_ref::() { + if let Some(err_src) = std::error::Error::source(err_src) { + return Ok(FetchResponse { + error: Some((err.to_string(), err_src.to_string())), + ..Default::default() + }); + } } } - err_ref = err_src; } - return Err(type_error(err.to_string())); + return Err(err); } - Err(_) => return Err(type_error("Request was cancelled")), + Err(_) => return Err(FetchError::RequestCanceled), }; let status = res.status(); @@ -636,7 +681,7 @@ pub async fn op_fetch_send( } type CancelableResponseResult = - Result, AnyError>, Canceled>; + Result, FetchError>, Canceled>; pub struct FetchRequestResource { pub future: Pin>>, @@ -691,7 +736,7 @@ impl FetchResponseResource { } } - pub async fn upgrade(self) -> Result { + pub async fn upgrade(self) -> Result { let reader = self.response_reader.into_inner(); match reader { FetchResponseReader::Start(resp) => Ok(hyper::upgrade::on(resp).await?), @@ -746,7 +791,9 @@ impl Resource for FetchResponseResource { // safely call `await` on it without creating a race condition. Some(_) => match reader.as_mut().next().await.unwrap() { Ok(chunk) => assert!(chunk.is_empty()), - Err(err) => break Err(type_error(err.to_string())), + Err(err) => { + break Err(deno_core::error::type_error(err.to_string())) + } }, None => break Ok(BufView::empty()), } @@ -809,14 +856,16 @@ pub fn op_fetch_custom_client( state: &mut OpState, #[serde] args: CreateHttpClientArgs, #[cppgc] tls_keys: &TlsKeysHolder, -) -> Result +) -> Result where FP: FetchPermissions + 'static, { if let Some(proxy) = args.proxy.clone() { let permissions = state.borrow_mut::(); let url = Url::parse(&proxy.url)?; - permissions.check_net_url(&url, "Deno.createHttpClient()")?; + permissions + .check_net_url(&url, "Deno.createHttpClient()") + .map_err(FetchError::Permission)?; } let options = state.borrow::(); @@ -829,7 +878,9 @@ where let client = create_http_client( &options.user_agent, CreateHttpClientOptions { - root_cert_store: options.root_cert_store()?, + root_cert_store: options + .root_cert_store() + .map_err(HttpClientCreateError::RootCertStore)?, ca_certs, proxy: args.proxy, unsafely_ignore_certificate_errors: options @@ -887,19 +938,34 @@ impl Default for CreateHttpClientOptions { } } +#[derive(Debug, thiserror::Error)] +pub enum HttpClientCreateError { + #[error(transparent)] + Tls(deno_tls::TlsError), + #[error("Illegal characters in User-Agent: received {0}")] + InvalidUserAgent(String), + #[error("invalid proxy url")] + InvalidProxyUrl, + #[error("Cannot create Http Client: either `http1` or `http2` needs to be set to true")] + HttpVersionSelectionInvalid, + #[error(transparent)] + RootCertStore(deno_core::error::AnyError), +} + /// Create new instance of async Client. This client supports /// proxies and doesn't follow redirects. pub fn create_http_client( user_agent: &str, options: CreateHttpClientOptions, -) -> Result { +) -> Result { let mut tls_config = deno_tls::create_client_config( options.root_cert_store, options.ca_certs, options.unsafely_ignore_certificate_errors, options.client_cert_chain_and_key.into(), deno_tls::SocketUse::Http, - )?; + ) + .map_err(HttpClientCreateError::Tls)?; // Proxy TLS should not send ALPN tls_config.alpn_protocols.clear(); @@ -919,9 +985,7 @@ pub fn create_http_client( http_connector.enforce_http(false); let user_agent = user_agent.parse::().map_err(|_| { - type_error(format!( - "Illegal characters in User-Agent: received {user_agent}" - )) + HttpClientCreateError::InvalidUserAgent(user_agent.to_string()) })?; let mut builder = @@ -932,7 +996,7 @@ pub fn create_http_client( let mut proxies = proxy::from_env(); if let Some(proxy) = options.proxy { let mut intercept = proxy::Intercept::all(&proxy.url) - .ok_or_else(|| type_error("invalid proxy url"))?; + .ok_or_else(|| HttpClientCreateError::InvalidProxyUrl)?; if let Some(basic_auth) = &proxy.basic_auth { intercept.set_auth(&basic_auth.username, &basic_auth.password); } @@ -964,7 +1028,7 @@ pub fn create_http_client( } (true, true) => {} (false, false) => { - return Err(type_error("Cannot create Http Client: either `http1` or `http2` needs to be set to true")) + return Err(HttpClientCreateError::HttpVersionSelectionInvalid) } } @@ -980,10 +1044,8 @@ pub fn create_http_client( #[op2] #[serde] -pub fn op_utf8_to_byte_string( - #[string] input: String, -) -> Result { - Ok(input.into()) +pub fn op_utf8_to_byte_string(#[string] input: String) -> ByteString { + input.into() } #[derive(Clone, Debug)] @@ -1003,7 +1065,7 @@ const STAR_STAR: HeaderValue = HeaderValue::from_static("*/*"); #[derive(Debug)] pub struct ClientSendError { uri: Uri, - source: hyper_util::client::legacy::Error, + pub source: hyper_util::client::legacy::Error, } impl ClientSendError { @@ -1075,12 +1137,14 @@ impl Client { .oneshot(req) .await .map_err(|e| ClientSendError { uri, source: e })?; - Ok(resp.map(|b| b.map_err(|e| anyhow!(e)).boxed())) + Ok(resp.map(|b| b.map_err(|e| deno_core::anyhow::anyhow!(e)).boxed())) } } -pub type ReqBody = http_body_util::combinators::BoxBody; -pub type ResBody = http_body_util::combinators::BoxBody; +pub type ReqBody = + http_body_util::combinators::BoxBody; +pub type ResBody = + http_body_util::combinators::BoxBody; /// Copied from https://github.com/seanmonstar/reqwest/blob/b9d62a0323d96f11672a61a17bf8849baec00275/src/async_impl/request.rs#L572 /// Check the request URL for a "username:password" type authority, and if diff --git a/ext/node/ops/http.rs b/ext/node/ops/http.rs index 773902dedf..730e1e482b 100644 --- a/ext/node/ops/http.rs +++ b/ext/node/ops/http.rs @@ -8,14 +8,12 @@ use std::task::Context; use std::task::Poll; use bytes::Bytes; -use deno_core::anyhow; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::stream::Peekable; use deno_core::futures::Future; use deno_core::futures::FutureExt; use deno_core::futures::Stream; use deno_core::futures::StreamExt; +use deno_core::futures::TryFutureExt; use deno_core::op2; use deno_core::serde::Serialize; use deno_core::unsync::spawn; @@ -33,6 +31,7 @@ use deno_core::Resource; use deno_core::ResourceId; use deno_fetch::get_or_create_client_from_state; use deno_fetch::FetchCancelHandle; +use deno_fetch::FetchError; use deno_fetch::FetchRequestResource; use deno_fetch::FetchReturn; use deno_fetch::HttpClientResource; @@ -59,12 +58,15 @@ pub fn op_node_http_request

( #[serde] headers: Vec<(ByteString, ByteString)>, #[smi] client_rid: Option, #[smi] body: Option, -) -> Result +) -> Result where P: crate::NodePermissions + 'static, { let client = if let Some(rid) = client_rid { - let r = state.resource_table.get::(rid)?; + let r = state + .resource_table + .get::(rid) + .map_err(FetchError::Resource)?; r.client.clone() } else { get_or_create_client_from_state(state)? @@ -76,15 +78,15 @@ where { let permissions = state.borrow_mut::

(); - permissions.check_net_url(&url, "ClientRequest")?; + permissions + .check_net_url(&url, "ClientRequest") + .map_err(FetchError::Permission)?; } let mut header_map = HeaderMap::new(); for (key, value) in headers { - let name = HeaderName::from_bytes(&key) - .map_err(|err| type_error(err.to_string()))?; - let v = HeaderValue::from_bytes(&value) - .map_err(|err| type_error(err.to_string()))?; + let name = HeaderName::from_bytes(&key)?; + let v = HeaderValue::from_bytes(&value)?; header_map.append(name, v); } @@ -92,7 +94,10 @@ where let (body, con_len) = if let Some(body) = body { ( BodyExt::boxed(NodeHttpResourceToBodyAdapter::new( - state.resource_table.take_any(body)?, + state + .resource_table + .take_any(body) + .map_err(FetchError::Resource)?, )), None, ) @@ -117,7 +122,7 @@ where *request.uri_mut() = url .as_str() .parse() - .map_err(|_| type_error("Invalid URL"))?; + .map_err(|_| FetchError::InvalidUrl(url.clone()))?; *request.headers_mut() = header_map; if let Some((username, password)) = maybe_authority { @@ -136,9 +141,9 @@ where let fut = async move { client .send(request) + .map_err(Into::into) .or_cancel(cancel_handle_) .await - .map(|res| res.map_err(|err| type_error(err.to_string()))) }; let request_rid = state.resource_table.add(FetchRequestResource { @@ -174,11 +179,12 @@ pub struct NodeHttpFetchResponse { pub async fn op_node_http_fetch_send( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let request = state .borrow_mut() .resource_table - .take::(rid)?; + .take::(rid) + .map_err(FetchError::Resource)?; let request = Rc::try_unwrap(request) .ok() @@ -191,22 +197,23 @@ pub async fn op_node_http_fetch_send( // If any error in the chain is a hyper body error, return that as a special result we can use to // reconstruct an error chain (eg: `new TypeError(..., { cause: new Error(...) })`). // TODO(mmastrac): it would be a lot easier if we just passed a v8::Global through here instead - let mut err_ref: &dyn std::error::Error = err.as_ref(); - while let Some(err) = std::error::Error::source(err_ref) { - if let Some(err) = err.downcast_ref::() { - if let Some(err) = std::error::Error::source(err) { - return Ok(NodeHttpFetchResponse { - error: Some(err.to_string()), - ..Default::default() - }); + + if let FetchError::ClientSend(err_src) = &err { + if let Some(client_err) = std::error::Error::source(&err_src.source) { + if let Some(err_src) = client_err.downcast_ref::() { + if let Some(err_src) = std::error::Error::source(err_src) { + return Ok(NodeHttpFetchResponse { + error: Some(err_src.to_string()), + ..Default::default() + }); + } } } - err_ref = err; } - return Err(type_error(err.to_string())); + return Err(err); } - Err(_) => return Err(type_error("request was cancelled")), + Err(_) => return Err(FetchError::RequestCanceled), }; let status = res.status(); @@ -250,11 +257,12 @@ pub async fn op_node_http_fetch_send( pub async fn op_node_http_fetch_response_upgrade( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let raw_response = state .borrow_mut() .resource_table - .take::(rid)?; + .take::(rid) + .map_err(FetchError::Resource)?; let raw_response = Rc::try_unwrap(raw_response) .expect("Someone is holding onto NodeHttpFetchResponseResource"); @@ -277,7 +285,7 @@ pub async fn op_node_http_fetch_response_upgrade( } read_tx.write_all(&buf[..read]).await?; } - Ok::<_, AnyError>(()) + Ok::<_, FetchError>(()) }); spawn(async move { let mut buf = [0; 1024]; @@ -288,7 +296,7 @@ pub async fn op_node_http_fetch_response_upgrade( } upgraded_tx.write_all(&buf[..read]).await?; } - Ok::<_, AnyError>(()) + Ok::<_, FetchError>(()) }); } @@ -318,23 +326,26 @@ impl UpgradeStream { } } - async fn read(self: Rc, buf: &mut [u8]) -> Result { + async fn read( + self: Rc, + buf: &mut [u8], + ) -> Result { let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle); async { let read = RcRef::map(self, |this| &this.read); let mut read = read.borrow_mut().await; - Ok(Pin::new(&mut *read).read(buf).await?) + Pin::new(&mut *read).read(buf).await } .try_or_cancel(cancel_handle) .await } - async fn write(self: Rc, buf: &[u8]) -> Result { + async fn write(self: Rc, buf: &[u8]) -> Result { let cancel_handle = RcRef::map(self.clone(), |this| &this.cancel_handle); async { let write = RcRef::map(self, |this| &this.write); let mut write = write.borrow_mut().await; - Ok(Pin::new(&mut *write).write(buf).await?) + Pin::new(&mut *write).write(buf).await } .try_or_cancel(cancel_handle) .await @@ -387,7 +398,7 @@ impl NodeHttpFetchResponseResource { } } - pub async fn upgrade(self) -> Result { + pub async fn upgrade(self) -> Result { let reader = self.response_reader.into_inner(); match reader { NodeHttpFetchResponseReader::Start(resp) => { @@ -445,7 +456,9 @@ impl Resource for NodeHttpFetchResponseResource { // safely call `await` on it without creating a race condition. Some(_) => match reader.as_mut().next().await.unwrap() { Ok(chunk) => assert!(chunk.is_empty()), - Err(err) => break Err(type_error(err.to_string())), + Err(err) => { + break Err(deno_core::error::type_error(err.to_string())) + } }, None => break Ok(BufView::empty()), } @@ -453,7 +466,7 @@ impl Resource for NodeHttpFetchResponseResource { }; let cancel_handle = RcRef::map(self, |r| &r.cancel); - fut.try_or_cancel(cancel_handle).await + fut.try_or_cancel(cancel_handle).await.map_err(Into::into) }) } @@ -469,7 +482,9 @@ impl Resource for NodeHttpFetchResponseResource { #[allow(clippy::type_complexity)] pub struct NodeHttpResourceToBodyAdapter( Rc, - Option>>>>, + Option< + Pin>>>, + >, ); impl NodeHttpResourceToBodyAdapter { @@ -485,7 +500,7 @@ unsafe impl Send for NodeHttpResourceToBodyAdapter {} unsafe impl Sync for NodeHttpResourceToBodyAdapter {} impl Stream for NodeHttpResourceToBodyAdapter { - type Item = Result; + type Item = Result; fn poll_next( self: Pin<&mut Self>, @@ -515,7 +530,7 @@ impl Stream for NodeHttpResourceToBodyAdapter { impl hyper::body::Body for NodeHttpResourceToBodyAdapter { type Data = Bytes; - type Error = anyhow::Error; + type Error = deno_core::anyhow::Error; fn poll_frame( self: Pin<&mut Self>, diff --git a/runtime/errors.rs b/runtime/errors.rs index dcd626dc65..d96f451486 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -22,6 +22,8 @@ use deno_crypto::EncryptError; use deno_crypto::ExportKeyError; use deno_crypto::GenerateKeyError; use deno_crypto::ImportKeyError; +use deno_fetch::FetchError; +use deno_fetch::HttpClientCreateError; use deno_ffi::CallError; use deno_ffi::CallbackError; use deno_ffi::DlfcnError; @@ -537,6 +539,42 @@ fn get_broadcast_channel_error(error: &BroadcastChannelError) -> &'static str { } } +fn get_fetch_error(error: &FetchError) -> &'static str { + match error { + FetchError::Resource(e) | FetchError::Permission(e) => { + get_error_class_name(e).unwrap_or("Error") + } + FetchError::NetworkError => "TypeError", + FetchError::FsNotGet(_) => "TypeError", + FetchError::InvalidUrl(_) => "TypeError", + FetchError::InvalidHeaderName(_) => "TypeError", + FetchError::InvalidHeaderValue(_) => "TypeError", + FetchError::DataUrl(_) => "TypeError", + FetchError::Base64(_) => "TypeError", + FetchError::BlobNotFound => "TypeError", + FetchError::SchemeNotSupported(_) => "TypeError", + FetchError::RequestCanceled => "TypeError", + FetchError::Http(_) => "Error", + FetchError::ClientCreate(e) => get_http_client_create_error(e), + FetchError::Url(e) => get_url_parse_error_class(e), + FetchError::Method(_) => "TypeError", + FetchError::ClientSend(_) => "TypeError", + FetchError::RequestBuilderHook(_) => "TypeError", + FetchError::Io(e) => get_io_error_class(e), + FetchError::Hyper(e) => get_hyper_error_class(e), + } +} + +fn get_http_client_create_error(error: &HttpClientCreateError) -> &'static str { + match error { + HttpClientCreateError::Tls(_) => "TypeError", + HttpClientCreateError::InvalidUserAgent(_) => "TypeError", + HttpClientCreateError::InvalidProxyUrl => "TypeError", + HttpClientCreateError::HttpVersionSelectionInvalid => "TypeError", + HttpClientCreateError::RootCertStore(_) => "TypeError", + } +} + fn get_websocket_error(error: &WebsocketError) -> &'static str { match error { WebsocketError::Permission(e) | WebsocketError::Resource(e) => { @@ -788,6 +826,11 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .map(get_websocket_handshake_error) }) .or_else(|| e.downcast_ref::().map(get_kv_error)) + .or_else(|| e.downcast_ref::().map(get_fetch_error)) + .or_else(|| { + e.downcast_ref::() + .map(get_http_client_create_error) + }) .or_else(|| e.downcast_ref::().map(get_net_error)) .or_else(|| { e.downcast_ref::() From 615e6b7cc2e89a2a5cc3ab614eecde5aa7de0596 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 18:53:04 -0700 Subject: [PATCH 13/16] refactor(ext/webgpu): use concrete error type (#26198) --- Cargo.lock | 1 + ext/webgpu/Cargo.toml | 1 + ext/webgpu/buffer.rs | 54 ++++++++++++------- ext/webgpu/bundle.rs | 67 ++++++++++++----------- ext/webgpu/byow.rs | 84 +++++++++++++++++++++-------- ext/webgpu/error.rs | 28 ---------- ext/webgpu/lib.rs | 59 ++++++++++++++------- ext/webgpu/render_pass.rs | 72 +++++++++++++------------ ext/webgpu/sampler.rs | 3 +- ext/webgpu/shader.rs | 3 +- ext/webgpu/surface.rs | 43 ++++++++++----- ext/webgpu/texture.rs | 5 +- runtime/errors.rs | 108 +++++++++++++++++++++++++++++++++++++- 13 files changed, 351 insertions(+), 177 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a396addf0..2032d55ab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2197,6 +2197,7 @@ dependencies = [ "deno_core", "raw-window-handle", "serde", + "thiserror", "tokio", "wgpu-core", "wgpu-types", diff --git a/ext/webgpu/Cargo.toml b/ext/webgpu/Cargo.toml index 8f22ca7647..7354919d4a 100644 --- a/ext/webgpu/Cargo.toml +++ b/ext/webgpu/Cargo.toml @@ -25,6 +25,7 @@ serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["full"] } wgpu-types = { workspace = true, features = ["serde"] } raw-window-handle = { workspace = true } +thiserror.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core] workspace = true diff --git a/ext/webgpu/buffer.rs b/ext/webgpu/buffer.rs index c6cd6f0a74..c2b53890e0 100644 --- a/ext/webgpu/buffer.rs +++ b/ext/webgpu/buffer.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -13,9 +11,18 @@ use std::sync::Arc; use std::sync::Mutex; use std::time::Duration; -use super::error::DomExceptionOperationError; use super::error::WebGpuResult; +#[derive(Debug, thiserror::Error)] +pub enum BufferError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("usage is not valid")] + InvalidUsage, + #[error(transparent)] + Access(wgpu_core::resource::BufferAccessError), +} + pub(crate) struct WebGpuBuffer( pub(crate) super::Instance, pub(crate) wgpu_core::id::BufferId, @@ -46,18 +53,19 @@ pub fn op_webgpu_create_buffer( #[number] size: u64, usage: u32, mapped_at_creation: bool, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(BufferError::Resource)?; let device = device_resource.1; let descriptor = wgpu_core::resource::BufferDescriptor { label: Some(label), size, usage: wgpu_types::BufferUsages::from_bits(usage) - .ok_or_else(|| type_error("usage is not valid"))?, + .ok_or(BufferError::InvalidUsage)?, mapped_at_creation, }; @@ -77,18 +85,21 @@ pub async fn op_webgpu_buffer_get_map_async( mode: u32, #[number] offset: u64, #[number] size: u64, -) -> Result { +) -> Result { let device; let done = Arc::new(Mutex::new(None)); { let state_ = state.borrow(); let instance = state_.borrow::(); - let buffer_resource = - state_.resource_table.get::(buffer_rid)?; + let buffer_resource = state_ + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; let device_resource = state_ .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(BufferError::Resource)?; device = device_resource.1; let done_ = done.clone(); @@ -120,9 +131,7 @@ pub async fn op_webgpu_buffer_get_map_async( let result = done.lock().unwrap().take(); match result { Some(Ok(())) => return Ok(WebGpuResult::empty()), - Some(Err(e)) => { - return Err(DomExceptionOperationError::new(&e.to_string()).into()) - } + Some(Err(e)) => return Err(BufferError::Access(e)), None => { { let state = state.borrow(); @@ -143,9 +152,12 @@ pub fn op_webgpu_buffer_get_mapped_range( #[number] offset: u64, #[number] size: Option, #[buffer] buf: &mut [u8], -) -> Result { +) -> Result { let instance = state.borrow::(); - let buffer_resource = state.resource_table.get::(buffer_rid)?; + let buffer_resource = state + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; let (slice_pointer, range_size) = @@ -154,7 +166,7 @@ pub fn op_webgpu_buffer_get_mapped_range( offset, size )) - .map_err(|e| DomExceptionOperationError::new(&e.to_string()))?; + .map_err(BufferError::Access)?; // SAFETY: guarantee to be safe from wgpu let slice = unsafe { @@ -176,12 +188,16 @@ pub fn op_webgpu_buffer_unmap( #[smi] buffer_rid: ResourceId, #[smi] mapped_rid: ResourceId, #[buffer] buf: Option<&[u8]>, -) -> Result { +) -> Result { let mapped_resource = state .resource_table - .take::(mapped_rid)?; + .take::(mapped_rid) + .map_err(BufferError::Resource)?; let instance = state.borrow::(); - let buffer_resource = state.resource_table.get::(buffer_rid)?; + let buffer_resource = state + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; if let Some(buf) = buf { diff --git a/ext/webgpu/bundle.rs b/ext/webgpu/bundle.rs index 57158271cc..d9a5b29539 100644 --- a/ext/webgpu/bundle.rs +++ b/ext/webgpu/bundle.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -13,6 +11,14 @@ use std::rc::Rc; use super::error::WebGpuResult; +#[derive(Debug, thiserror::Error)] +pub enum BundleError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("size must be larger than 0")] + InvalidSize, +} + struct WebGpuRenderBundleEncoder( RefCell, ); @@ -53,7 +59,7 @@ pub struct CreateRenderBundleEncoderArgs { pub fn op_webgpu_create_render_bundle_encoder( state: &mut OpState, #[serde] args: CreateRenderBundleEncoderArgs, -) -> Result { +) -> Result { let device_resource = state .resource_table .get::(args.device_rid)?; @@ -100,7 +106,7 @@ pub fn op_webgpu_render_bundle_encoder_finish( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] label: Cow, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -131,7 +137,7 @@ pub fn op_webgpu_render_bundle_encoder_set_bind_group( #[buffer] dynamic_offsets_data: &[u32], #[number] dynamic_offsets_data_start: usize, #[number] dynamic_offsets_data_length: usize, -) -> Result { +) -> Result { let bind_group_resource = state .resource_table @@ -171,7 +177,7 @@ pub fn op_webgpu_render_bundle_encoder_push_debug_group( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] group_label: &str, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -195,7 +201,7 @@ pub fn op_webgpu_render_bundle_encoder_push_debug_group( pub fn op_webgpu_render_bundle_encoder_pop_debug_group( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -214,7 +220,7 @@ pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] marker_label: &str, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -239,7 +245,7 @@ pub fn op_webgpu_render_bundle_encoder_set_pipeline( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[smi] pipeline: ResourceId, -) -> Result { +) -> Result { let render_pipeline_resource = state .resource_table @@ -266,18 +272,17 @@ pub fn op_webgpu_render_bundle_encoder_set_index_buffer( #[serde] index_format: wgpu_types::IndexFormat, #[number] offset: u64, #[number] size: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; - let render_bundle_encoder_resource = - state - .resource_table - .get::(render_bundle_encoder_rid)?; - let size = Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ); + .get::(buffer) + .map_err(BundleError::Resource)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(render_bundle_encoder_rid) + .map_err(BundleError::Resource)?; + let size = + Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?); render_bundle_encoder_resource .0 @@ -296,19 +301,17 @@ pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( #[smi] buffer: ResourceId, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; - let render_bundle_encoder_resource = - state - .resource_table - .get::(render_bundle_encoder_rid)?; + .get::(buffer) + .map_err(BundleError::Resource)?; + let render_bundle_encoder_resource = state + .resource_table + .get::(render_bundle_encoder_rid) + .map_err(BundleError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?) } else { None }; @@ -333,7 +336,7 @@ pub fn op_webgpu_render_bundle_encoder_draw( instance_count: u32, first_vertex: u32, first_instance: u32, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -360,7 +363,7 @@ pub fn op_webgpu_render_bundle_encoder_draw_indexed( first_index: u32, base_vertex: i32, first_instance: u32, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -385,7 +388,7 @@ pub fn op_webgpu_render_bundle_encoder_draw_indirect( #[smi] render_bundle_encoder_rid: ResourceId, #[smi] indirect_buffer: ResourceId, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; diff --git a/ext/webgpu/byow.rs b/ext/webgpu/byow.rs index 3a43f416e5..c9e1177b1e 100644 --- a/ext/webgpu/byow.rs +++ b/ext/webgpu/byow.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; @@ -16,6 +14,47 @@ use std::ptr::NonNull; use crate::surface::WebGpuSurface; +#[derive(Debug, thiserror::Error)] +pub enum ByowError { + #[error("Cannot create surface outside of WebGPU context. Did you forget to call `navigator.gpu.requestAdapter()`?")] + WebGPUNotInitiated, + #[error("Invalid parameters")] + InvalidParameters, + #[error(transparent)] + CreateSurface(wgpu_core::instance::CreateSurfaceError), + #[cfg(target_os = "windows")] + #[error("Invalid system on Windows")] + InvalidSystem, + #[cfg(target_os = "macos")] + #[error("Invalid system on macOS")] + InvalidSystem, + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + #[error("Invalid system on Linux/BSD")] + InvalidSystem, + #[cfg(any( + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + #[error("window is null")] + NullWindow, + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + #[error("display is null")] + NullDisplay, + #[cfg(target_os = "macos")] + #[error("ns_view is null")] + NSViewDisplay, +} + #[op2(fast)] #[smi] pub fn op_webgpu_surface_create( @@ -23,10 +62,10 @@ pub fn op_webgpu_surface_create( #[string] system: &str, p1: *const c_void, p2: *const c_void, -) -> Result { - let instance = state.try_borrow::().ok_or_else(|| { - type_error("Cannot create surface outside of WebGPU context. Did you forget to call `navigator.gpu.requestAdapter()`?") - })?; +) -> Result { + let instance = state + .try_borrow::() + .ok_or(ByowError::WebGPUNotInitiated)?; // Security note: // // The `p1` and `p2` parameters are pointers to platform-specific window @@ -41,13 +80,15 @@ pub fn op_webgpu_surface_create( // // - Only FFI can export v8::External to user code. if p1.is_null() { - return Err(type_error("Invalid parameters")); + return Err(ByowError::InvalidParameters); } let (win_handle, display_handle) = raw_window(system, p1, p2)?; // SAFETY: see above comment let surface = unsafe { - instance.instance_create_surface(display_handle, win_handle, None)? + instance + .instance_create_surface(display_handle, win_handle, None) + .map_err(ByowError::CreateSurface)? }; let rid = state @@ -66,15 +107,14 @@ fn raw_window( system: &str, _ns_window: *const c_void, ns_view: *const c_void, -) -> Result { +) -> Result { if system != "cocoa" { - return Err(type_error("Invalid system on macOS")); + return Err(ByowError::InvalidSystem); } let win_handle = raw_window_handle::RawWindowHandle::AppKit( raw_window_handle::AppKitWindowHandle::new( - NonNull::new(ns_view as *mut c_void) - .ok_or(type_error("ns_view is null"))?, + NonNull::new(ns_view as *mut c_void).ok_or(ByowError::NSViewDisplay)?, ), ); @@ -89,16 +129,16 @@ fn raw_window( system: &str, window: *const c_void, hinstance: *const c_void, -) -> Result { +) -> Result { use raw_window_handle::WindowsDisplayHandle; if system != "win32" { - return Err(type_error("Invalid system on Windows")); + return Err(ByowError::InvalidSystem); } let win_handle = { let mut handle = raw_window_handle::Win32WindowHandle::new( std::num::NonZeroIsize::new(window as isize) - .ok_or(type_error("window is null"))?, + .ok_or(ByowError::NullWindow)?, ); handle.hinstance = std::num::NonZeroIsize::new(hinstance as isize); @@ -115,7 +155,7 @@ fn raw_window( system: &str, window: *const c_void, display: *const c_void, -) -> Result { +) -> Result { let (win_handle, display_handle); if system == "x11" { win_handle = raw_window_handle::RawWindowHandle::Xlib( @@ -131,19 +171,17 @@ fn raw_window( } else if system == "wayland" { win_handle = raw_window_handle::RawWindowHandle::Wayland( raw_window_handle::WaylandWindowHandle::new( - NonNull::new(window as *mut c_void) - .ok_or(type_error("window is null"))?, + NonNull::new(window as *mut c_void).ok_or(ByowError::NullWindow)?, ), ); display_handle = raw_window_handle::RawDisplayHandle::Wayland( raw_window_handle::WaylandDisplayHandle::new( - NonNull::new(display as *mut c_void) - .ok_or(type_error("display is null"))?, + NonNull::new(display as *mut c_void).ok_or(ByowError::NullDisplay)?, ), ); } else { - return Err(type_error("Invalid system on Linux/BSD")); + return Err(ByowError::InvalidSystem); } Ok((win_handle, display_handle)) @@ -160,6 +198,6 @@ fn raw_window( _system: &str, _window: *const c_void, _display: *const c_void, -) -> Result { - Err(type_error("Unsupported platform")) +) -> Result { + Err(deno_core::error::type_error("Unsupported platform")) } diff --git a/ext/webgpu/error.rs b/ext/webgpu/error.rs index 5b55d506ad..f08f765386 100644 --- a/ext/webgpu/error.rs +++ b/ext/webgpu/error.rs @@ -1,11 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::ResourceId; use serde::Serialize; use std::convert::From; use std::error::Error; -use std::fmt; use wgpu_core::binding_model::CreateBindGroupError; use wgpu_core::binding_model::CreateBindGroupLayoutError; use wgpu_core::binding_model::CreatePipelineLayoutError; @@ -286,29 +284,3 @@ impl From for WebGpuError { WebGpuError::Validation(fmt_err(&err)) } } - -#[derive(Debug)] -pub struct DomExceptionOperationError { - pub msg: String, -} - -impl DomExceptionOperationError { - pub fn new(msg: &str) -> Self { - DomExceptionOperationError { - msg: msg.to_string(), - } - } -} - -impl fmt::Display for DomExceptionOperationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad(&self.msg) - } -} - -impl std::error::Error for DomExceptionOperationError {} - -pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { - e.downcast_ref::() - .map(|_| "DOMExceptionOperationError") -} diff --git a/ext/webgpu/lib.rs b/ext/webgpu/lib.rs index 9cf4ca914b..5dc8278e41 100644 --- a/ext/webgpu/lib.rs +++ b/ext/webgpu/lib.rs @@ -2,7 +2,6 @@ #![cfg(not(target_arch = "wasm32"))] #![warn(unsafe_op_in_unsafe_fn)] -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -16,7 +15,6 @@ use std::rc::Rc; pub use wgpu_core; pub use wgpu_types; -use error::DomExceptionOperationError; use error::WebGpuResult; pub const UNSTABLE_FEATURE_NAME: &str = "webgpu"; @@ -85,6 +83,18 @@ pub mod shader; pub mod surface; pub mod texture; +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + InvalidAdapter(wgpu_core::instance::InvalidAdapter), + #[error(transparent)] + RequestDevice(wgpu_core::instance::RequestDeviceError), + #[error(transparent)] + InvalidDevice(wgpu_core::device::InvalidDevice), +} + pub type Instance = std::sync::Arc; struct WebGpuAdapter(Instance, wgpu_core::id::AdapterId); @@ -400,7 +410,7 @@ pub fn op_webgpu_request_adapter( state: Rc>, #[serde] power_preference: Option, force_fallback_adapter: bool, -) -> Result { +) -> Result { let mut state = state.borrow_mut(); let backends = std::env::var("DENO_WEBGPU_BACKEND").map_or_else( @@ -441,10 +451,11 @@ pub fn op_webgpu_request_adapter( } }; let adapter_features = - gfx_select!(adapter => instance.adapter_features(adapter))?; + gfx_select!(adapter => instance.adapter_features(adapter)) + .map_err(InitError::InvalidAdapter)?; let features = deserialize_features(&adapter_features); - let adapter_limits = - gfx_select!(adapter => instance.adapter_limits(adapter))?; + let adapter_limits = gfx_select!(adapter => instance.adapter_limits(adapter)) + .map_err(InitError::InvalidAdapter)?; let instance = instance.clone(); @@ -663,10 +674,12 @@ pub fn op_webgpu_request_device( #[string] label: String, #[serde] required_features: GpuRequiredFeatures, #[serde] required_limits: Option, -) -> Result { +) -> Result { let mut state = state.borrow_mut(); - let adapter_resource = - state.resource_table.take::(adapter_rid)?; + let adapter_resource = state + .resource_table + .take::(adapter_rid) + .map_err(InitError::Resource)?; let adapter = adapter_resource.1; let instance = state.borrow::(); @@ -685,13 +698,14 @@ pub fn op_webgpu_request_device( )); adapter_resource.close(); if let Some(err) = maybe_err { - return Err(DomExceptionOperationError::new(&err.to_string()).into()); + return Err(InitError::RequestDevice(err)); } - let device_features = - gfx_select!(device => instance.device_features(device))?; + let device_features = gfx_select!(device => instance.device_features(device)) + .map_err(InitError::InvalidDevice)?; let features = deserialize_features(&device_features); - let limits = gfx_select!(device => instance.device_limits(device))?; + let limits = gfx_select!(device => instance.device_limits(device)) + .map_err(InitError::InvalidDevice)?; let instance = instance.clone(); let instance2 = instance.clone(); @@ -722,14 +736,17 @@ pub struct GPUAdapterInfo { pub fn op_webgpu_request_adapter_info( state: Rc>, #[smi] adapter_rid: ResourceId, -) -> Result { +) -> Result { let state = state.borrow_mut(); - let adapter_resource = - state.resource_table.get::(adapter_rid)?; + let adapter_resource = state + .resource_table + .get::(adapter_rid) + .map_err(InitError::Resource)?; let adapter = adapter_resource.1; let instance = state.borrow::(); - let info = gfx_select!(adapter => instance.adapter_get_info(adapter))?; + let info = gfx_select!(adapter => instance.adapter_get_info(adapter)) + .map_err(InitError::InvalidAdapter)?; Ok(GPUAdapterInfo { vendor: info.vendor.to_string(), @@ -770,9 +787,11 @@ impl From for wgpu_types::QueryType { pub fn op_webgpu_create_query_set( state: &mut OpState, #[serde] args: CreateQuerySetArgs, -) -> Result { - let device_resource = - state.resource_table.get::(args.device_rid)?; +) -> Result { + let device_resource = state + .resource_table + .get::(args.device_rid) + .map_err(InitError::Resource)?; let device = device_resource.1; let instance = state.borrow::(); diff --git a/ext/webgpu/render_pass.rs b/ext/webgpu/render_pass.rs index c68be3d99a..9b9d87d9fc 100644 --- a/ext/webgpu/render_pass.rs +++ b/ext/webgpu/render_pass.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -12,6 +10,14 @@ use std::cell::RefCell; use super::error::WebGpuResult; +#[derive(Debug, thiserror::Error)] +pub enum RenderPassError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("size must be larger than 0")] + InvalidSize, +} + pub(crate) struct WebGpuRenderPass( pub(crate) RefCell, ); @@ -38,7 +44,7 @@ pub struct RenderPassSetViewportArgs { pub fn op_webgpu_render_pass_set_viewport( state: &mut OpState, #[serde] args: RenderPassSetViewportArgs, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(args.render_pass_rid)?; @@ -65,7 +71,7 @@ pub fn op_webgpu_render_pass_set_scissor_rect( y: u32, width: u32, height: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -87,7 +93,7 @@ pub fn op_webgpu_render_pass_set_blend_constant( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[serde] color: wgpu_types::Color, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -106,7 +112,7 @@ pub fn op_webgpu_render_pass_set_stencil_reference( state: &mut OpState, #[smi] render_pass_rid: ResourceId, reference: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -125,7 +131,7 @@ pub fn op_webgpu_render_pass_begin_occlusion_query( state: &mut OpState, #[smi] render_pass_rid: ResourceId, query_index: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -143,7 +149,7 @@ pub fn op_webgpu_render_pass_begin_occlusion_query( pub fn op_webgpu_render_pass_end_occlusion_query( state: &mut OpState, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -161,7 +167,7 @@ pub fn op_webgpu_render_pass_execute_bundles( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[serde] bundles: Vec, -) -> Result { +) -> Result { let bundles = bundles .iter() .map(|rid| { @@ -171,7 +177,7 @@ pub fn op_webgpu_render_pass_execute_bundles( .get::(*rid)?; Ok(render_bundle_resource.1) }) - .collect::, AnyError>>()?; + .collect::, deno_core::error::AnyError>>()?; let render_pass_resource = state .resource_table @@ -191,7 +197,7 @@ pub fn op_webgpu_render_pass_end( state: &mut OpState, #[smi] command_encoder_rid: ResourceId, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let command_encoder_resource = state .resource_table .get::( @@ -217,7 +223,7 @@ pub fn op_webgpu_render_pass_set_bind_group( #[buffer] dynamic_offsets_data: &[u32], #[number] dynamic_offsets_data_start: usize, #[number] dynamic_offsets_data_length: usize, -) -> Result { +) -> Result { let bind_group_resource = state .resource_table @@ -251,7 +257,7 @@ pub fn op_webgpu_render_pass_push_debug_group( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[string] group_label: &str, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -270,7 +276,7 @@ pub fn op_webgpu_render_pass_push_debug_group( pub fn op_webgpu_render_pass_pop_debug_group( state: &mut OpState, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -288,7 +294,7 @@ pub fn op_webgpu_render_pass_insert_debug_marker( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[string] marker_label: &str, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -308,7 +314,7 @@ pub fn op_webgpu_render_pass_set_pipeline( state: &mut OpState, #[smi] render_pass_rid: ResourceId, pipeline: u32, -) -> Result { +) -> Result { let render_pipeline_resource = state .resource_table @@ -334,19 +340,18 @@ pub fn op_webgpu_render_pass_set_index_buffer( #[serde] index_format: wgpu_types::IndexFormat, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(RenderPassError::Resource)?; let render_pass_resource = state .resource_table - .get::(render_pass_rid)?; + .get::(render_pass_rid) + .map_err(RenderPassError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?) } else { None }; @@ -370,19 +375,18 @@ pub fn op_webgpu_render_pass_set_vertex_buffer( buffer: u32, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(RenderPassError::Resource)?; let render_pass_resource = state .resource_table - .get::(render_pass_rid)?; + .get::(render_pass_rid) + .map_err(RenderPassError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?) } else { None }; @@ -407,7 +411,7 @@ pub fn op_webgpu_render_pass_draw( instance_count: u32, first_vertex: u32, first_instance: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -433,7 +437,7 @@ pub fn op_webgpu_render_pass_draw_indexed( first_index: u32, base_vertex: i32, first_instance: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -457,7 +461,7 @@ pub fn op_webgpu_render_pass_draw_indirect( #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; @@ -481,7 +485,7 @@ pub fn op_webgpu_render_pass_draw_indexed_indirect( #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; diff --git a/ext/webgpu/sampler.rs b/ext/webgpu/sampler.rs index 27c36802e6..9fc1269ea7 100644 --- a/ext/webgpu/sampler.rs +++ b/ext/webgpu/sampler.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -47,7 +46,7 @@ pub struct CreateSamplerArgs { pub fn op_webgpu_create_sampler( state: &mut OpState, #[serde] args: CreateSamplerArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table diff --git a/ext/webgpu/shader.rs b/ext/webgpu/shader.rs index 0b3991c5de..4653bd85bf 100644 --- a/ext/webgpu/shader.rs +++ b/ext/webgpu/shader.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -31,7 +30,7 @@ pub fn op_webgpu_create_shader_module( #[smi] device_rid: ResourceId, #[string] label: Cow, #[string] code: Cow, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table diff --git a/ext/webgpu/surface.rs b/ext/webgpu/surface.rs index 1f6d2c87d2..297eaeb008 100644 --- a/ext/webgpu/surface.rs +++ b/ext/webgpu/surface.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use super::WebGpuResult; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -11,6 +10,16 @@ use std::borrow::Cow; use std::rc::Rc; use wgpu_types::SurfaceStatus; +#[derive(Debug, thiserror::Error)] +pub enum SurfaceError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("Invalid Surface Status")] + InvalidStatus, + #[error(transparent)] + Surface(wgpu_core::present::SurfaceError), +} + pub struct WebGpuSurface(pub crate::Instance, pub wgpu_core::id::SurfaceId); impl Resource for WebGpuSurface { fn name(&self) -> Cow { @@ -41,7 +50,7 @@ pub struct SurfaceConfigureArgs { pub fn op_webgpu_surface_configure( state: &mut OpState, #[serde] args: SurfaceConfigureArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table @@ -75,18 +84,22 @@ pub fn op_webgpu_surface_get_current_texture( state: &mut OpState, #[smi] device_rid: ResourceId, #[smi] surface_rid: ResourceId, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(SurfaceError::Resource)?; let device = device_resource.1; - let surface_resource = - state.resource_table.get::(surface_rid)?; + let surface_resource = state + .resource_table + .get::(surface_rid) + .map_err(SurfaceError::Resource)?; let surface = surface_resource.1; let output = - gfx_select!(device => instance.surface_get_current_texture(surface, None))?; + gfx_select!(device => instance.surface_get_current_texture(surface, None)) + .map_err(SurfaceError::Surface)?; match output.status { SurfaceStatus::Good | SurfaceStatus::Suboptimal => { @@ -98,7 +111,7 @@ pub fn op_webgpu_surface_get_current_texture( }); Ok(WebGpuResult::rid(rid)) } - _ => Err(AnyError::msg("Invalid Surface Status")), + _ => Err(SurfaceError::InvalidStatus), } } @@ -107,17 +120,21 @@ pub fn op_webgpu_surface_present( state: &mut OpState, #[smi] device_rid: ResourceId, #[smi] surface_rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), SurfaceError> { let instance = state.borrow::(); let device_resource = state .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(SurfaceError::Resource)?; let device = device_resource.1; - let surface_resource = - state.resource_table.get::(surface_rid)?; + let surface_resource = state + .resource_table + .get::(surface_rid) + .map_err(SurfaceError::Resource)?; let surface = surface_resource.1; - let _ = gfx_select!(device => instance.surface_present(surface))?; + let _ = gfx_select!(device => instance.surface_present(surface)) + .map_err(SurfaceError::Surface)?; Ok(()) } diff --git a/ext/webgpu/texture.rs b/ext/webgpu/texture.rs index 44edd1a887..f8a5e05a3e 100644 --- a/ext/webgpu/texture.rs +++ b/ext/webgpu/texture.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -62,7 +61,7 @@ pub struct CreateTextureArgs { pub fn op_webgpu_create_texture( state: &mut OpState, #[serde] args: CreateTextureArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table @@ -111,7 +110,7 @@ pub struct CreateTextureViewArgs { pub fn op_webgpu_create_texture_view( state: &mut OpState, #[serde] args: CreateTextureViewArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let texture_resource = state .resource_table diff --git a/runtime/errors.rs b/runtime/errors.rs index d96f451486..9fc84407a9 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -189,6 +189,89 @@ pub fn get_nix_error_class(error: &nix::Error) -> &'static str { } } +fn get_webgpu_error_class(e: &deno_webgpu::InitError) -> &'static str { + match e { + deno_webgpu::InitError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::InitError::InvalidAdapter(_) => "Error", + deno_webgpu::InitError::RequestDevice(_) => "DOMExceptionOperationError", + deno_webgpu::InitError::InvalidDevice(_) => "Error", + } +} + +fn get_webgpu_buffer_error_class( + e: &deno_webgpu::buffer::BufferError, +) -> &'static str { + match e { + deno_webgpu::buffer::BufferError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::buffer::BufferError::InvalidUsage => "TypeError", + deno_webgpu::buffer::BufferError::Access(_) => "DOMExceptionOperationError", + } +} + +fn get_webgpu_bundle_error_class( + e: &deno_webgpu::bundle::BundleError, +) -> &'static str { + match e { + deno_webgpu::bundle::BundleError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::bundle::BundleError::InvalidSize => "TypeError", + } +} + +fn get_webgpu_byow_error_class( + e: &deno_webgpu::byow::ByowError, +) -> &'static str { + match e { + deno_webgpu::byow::ByowError::WebGPUNotInitiated => "TypeError", + deno_webgpu::byow::ByowError::InvalidParameters => "TypeError", + deno_webgpu::byow::ByowError::CreateSurface(_) => "Error", + deno_webgpu::byow::ByowError::InvalidSystem => "TypeError", + #[cfg(any( + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + deno_webgpu::byow::ByowError::NullWindow => "TypeError", + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + deno_webgpu::byow::ByowError::NullDisplay => "TypeError", + #[cfg(target_os = "macos")] + deno_webgpu::byow::ByowError::NSViewDisplay => "TypeError", + } +} + +fn get_webgpu_render_pass_error_class( + e: &deno_webgpu::render_pass::RenderPassError, +) -> &'static str { + match e { + deno_webgpu::render_pass::RenderPassError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::render_pass::RenderPassError::InvalidSize => "TypeError", + } +} + +fn get_webgpu_surface_error_class( + e: &deno_webgpu::surface::SurfaceError, +) -> &'static str { + match e { + deno_webgpu::surface::SurfaceError::Resource(e) => { + get_error_class_name(e).unwrap_or("Error") + } + deno_webgpu::surface::SurfaceError::Surface(_) => "Error", + deno_webgpu::surface::SurfaceError::InvalidStatus => "Error", + } +} + fn get_crypto_decrypt_error_class(e: &DecryptError) -> &'static str { match e { DecryptError::General(e) => get_crypto_shared_error_class(e), @@ -778,7 +861,6 @@ fn get_websocket_upgrade_error(error: &WebSocketUpgradeError) -> &'static str { pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) - .or_else(|| deno_webgpu::error::get_error_class_name(e)) .or_else(|| e.downcast_ref::().map(get_napi_error_class)) .or_else(|| e.downcast_ref::().map(get_web_error_class)) .or_else(|| { @@ -840,6 +922,30 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { e.downcast_ref::() .map(get_broadcast_channel_error) }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_buffer_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_bundle_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_byow_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_render_pass_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_surface_error_class) + }) .or_else(|| { e.downcast_ref::() .map(get_crypto_decrypt_error_class) From 0710af034ffc7737cbfa689ff1826d9c1537f6da Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 19 Oct 2024 08:42:59 +0530 Subject: [PATCH 14/16] perf: avoid multiple calls to runMicrotask (#26378) Improves HTTP throughput by 8-9k rps on Linux: this patch ``` Requests/sec: 145001.69 Transfer/sec: 20.74MB ``` main ``` Requests/sec: 137866.61 Transfer/sec: 19.72MB ``` The improvements comes from the reduced number of calls to `op_run_microtask` per request. Returning `true` from a macrotask callback already calls `op_run_microtask` so the extra call was redundant. Here's `--strace-ops` output for a single request: main ``` [ 4.667] op_http_wait : CompletedAsync Async [ 4.667] op_run_microtasks : Dispatched Slow [ 4.668] op_http_try_wait : Dispatched Slow [ 4.668] op_http_try_wait : Completed Slow [ 4.668] op_http_wait : Dispatched Async [ 4.668] op_http_set_response_header : Dispatched Slow [ 4.668] op_http_set_response_header : Completed Slow [ 4.669] op_http_set_response_body_text : Dispatched Slow [ 4.669] op_http_set_response_body_text : Completed Slow [ 4.669] op_run_microtasks : Completed Slow [ 4.669] op_has_tick_scheduled : Dispatched Slow [ 4.669] op_has_tick_scheduled : Completed Slow [ 4.669] op_run_microtasks : Dispatched Slow [ 4.669] op_run_microtasks : Completed Slow [ 4.669] op_run_microtasks : Dispatched Slow [ 4.669] op_run_microtasks : Completed Slow ``` this pr ``` [ 3.726] op_http_wait : CompletedAsync Async [ 3.727] op_run_microtasks : Dispatched Slow [ 3.727] op_http_try_wait : Dispatched Slow [ 3.727] op_http_try_wait : Completed Slow [ 3.727] op_http_wait : Dispatched Async [ 3.727] op_http_set_response_header : Dispatched Slow [ 3.728] op_http_set_response_header : Completed Slow [ 3.728] op_http_set_response_body_text : Dispatched Slow [ 3.728] op_http_set_response_body_text : Completed Slow [ 3.728] op_run_microtasks : Completed Slow [ 3.728] op_run_microtasks : Dispatched Slow [ 3.728] op_run_microtasks : Completed Slow ``` --- ext/node/polyfills/_next_tick.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts index 5ee27728d0..62470c564e 100644 --- a/ext/node/polyfills/_next_tick.ts +++ b/ext/node/polyfills/_next_tick.ts @@ -87,8 +87,7 @@ export function runNextTicks() { // runMicrotasks(); // if (!hasTickScheduled() && !hasRejectionToWarn()) // return; - if (!core.hasTickScheduled()) { - core.runMicrotasks(); + if (queue.isEmpty() || !core.hasTickScheduled()) { return true; } From 85709c70abb538cf22df73261bc37453e7cb07a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 19 Oct 2024 04:42:19 +0100 Subject: [PATCH 15/16] chore: update release doc template (#26406) --- tools/release/release_doc_template.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/release/release_doc_template.md b/tools/release/release_doc_template.md index fec1746bfb..b372185337 100644 --- a/tools/release/release_doc_template.md +++ b/tools/release/release_doc_template.md @@ -30,7 +30,7 @@ Release checklist: ## Patch release preparation -**If you are cutting a patch release**: First you need to sync commits to the +⛔ **If you are cutting a patch release**: First you need to sync commits to the `v$MINOR_VERSION` branch in the `deno` repo. To do that, you need to cherry-pick commits from the main branch to the @@ -75,13 +75,13 @@ verify on GitHub that everything looks correct. 1. Click on the "Run workflow" button. 1. In the drop down, select the minor branch (`v$MINOR_VERSION`) if doing a patch release or the main branch if doing a minor release. - 1. For the kind of release, select either "patch", "minor", or "major". + 1. For the kind of release, select either `patch` or `minor`. 1. Run the workflow. - [ ] Wait for the workflow to complete and for a pull request to be automatically opened. Review the pull request, make any necessary changes, and merge it. - - ⛔ DO NOT create a release tag manually That will automatically happen. + - ⛔ **DO NOT** create a release tag manually That will automatically happen.

Failure Steps @@ -120,7 +120,8 @@ verify on GitHub that everything looks correct. (https://github.com/denoland/deno/releases). - ⛔ Verify that: - - [ ] There are 14 assets on the release draft. + - [ ] There are 24 assets on the + [GitHub release draft](https://github.com/denoland/deno/releases/v$VERSION). - [ ] There are 10 zip files for this version on [dl.deno.land](https://console.cloud.google.com/storage/browser/dl.deno.land/release/v$VERSION). From 473e3069de4bf5877a6f1140aa0462e05f745536 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Sat, 19 Oct 2024 14:59:39 -0700 Subject: [PATCH 16/16] chore: update nix crate (#26422) Dedupes nix dependency, since `rustyline` depends on a newer version that what we currently use --- Cargo.lock | 39 ++++++++---------------------------- Cargo.toml | 2 +- ext/fs/Cargo.toml | 2 +- runtime/ops/tty.rs | 3 ++- tests/util/server/Cargo.toml | 2 +- tests/util/server/src/pty.rs | 6 ++++-- 6 files changed, 17 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2032d55ab6..bdcb483e38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,7 +769,7 @@ dependencies = [ "http-body-util", "hyper 1.4.1", "hyper-util", - "nix 0.26.2", + "nix", "once_cell", "os_pipe", "pretty_assertions", @@ -1223,7 +1223,7 @@ dependencies = [ "memmem", "monch", "napi_sym", - "nix 0.26.2", + "nix", "node_resolver", "notify", "once_cell", @@ -1438,7 +1438,7 @@ dependencies = [ "deno_unsync", "futures", "libc", - "memoffset 0.9.1", + "memoffset", "parking_lot", "percent-encoding", "pin-project", @@ -1599,7 +1599,7 @@ dependencies = [ "filetime", "junction", "libc", - "nix 0.26.2", + "nix", "rand", "rayon", "serde", @@ -2035,7 +2035,7 @@ dependencies = [ "libc", "log", "netif", - "nix 0.26.2", + "nix", "node_resolver", "notify", "ntapi", @@ -4397,15 +4397,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -4563,20 +4554,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", - "static_assertions", -] - [[package]] name = "nix" version = "0.27.1" @@ -5776,7 +5753,7 @@ checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" dependencies = [ "countme", "hashbrown", - "memoffset 0.9.1", + "memoffset", "rustc-hash 1.1.0", "text-size", ] @@ -5984,7 +5961,7 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.27.1", + "nix", "radix_trie", "unicode-segmentation", "unicode-width", @@ -7187,7 +7164,7 @@ dependencies = [ "libc", "lsp-types", "monch", - "nix 0.26.2", + "nix", "once_cell", "os_pipe", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index e2f439e37b..9f15d03a54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -221,7 +221,7 @@ quote = "1" syn = { version = "2", features = ["full", "extra-traits"] } # unix -nix = "=0.26.2" +nix = "=0.27.1" # windows deps junction = "=0.2.0" diff --git a/ext/fs/Cargo.toml b/ext/fs/Cargo.toml index 313c84fdb6..ab0bf22fd3 100644 --- a/ext/fs/Cargo.toml +++ b/ext/fs/Cargo.toml @@ -31,7 +31,7 @@ serde.workspace = true thiserror.workspace = true [target.'cfg(unix)'.dependencies] -nix.workspace = true +nix = { workspace = true, features = ["user"] } [target.'cfg(windows)'.dependencies] winapi = { workspace = true, features = ["winbase"] } diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 5b49e3a248..77e1330b51 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -244,7 +244,8 @@ fn op_set_raw( let tty_mode_store = state.borrow::().clone(); let previous_mode = tty_mode_store.get(rid); - let raw_fd = handle_or_fd; + // SAFETY: Nix crate requires value to implement the AsFd trait + let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(handle_or_fd) }; if is_raw { let mut raw = match previous_mode { diff --git a/tests/util/server/Cargo.toml b/tests/util/server/Cargo.toml index a321501b85..aee7ef0be6 100644 --- a/tests/util/server/Cargo.toml +++ b/tests/util/server/Cargo.toml @@ -35,7 +35,7 @@ lazy-regex.workspace = true libc.workspace = true lsp-types.workspace = true monch.workspace = true -nix.workspace = true +nix = { workspace = true, features = ["fs", "term", "signal"] } once_cell.workspace = true os_pipe.workspace = true parking_lot.workspace = true diff --git a/tests/util/server/src/pty.rs b/tests/util/server/src/pty.rs index 5d8049fee9..8d42fed78b 100644 --- a/tests/util/server/src/pty.rs +++ b/tests/util/server/src/pty.rs @@ -297,10 +297,12 @@ fn setup_pty(fd: i32) { use nix::sys::termios::tcsetattr; use nix::sys::termios::SetArg; - let mut term = tcgetattr(fd).unwrap(); + // SAFETY: Nix crate requires value to implement the AsFd trait + let as_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) }; + let mut term = tcgetattr(as_fd).unwrap(); // disable cooked mode term.local_flags.remove(termios::LocalFlags::ICANON); - tcsetattr(fd, SetArg::TCSANOW, &term).unwrap(); + tcsetattr(as_fd, SetArg::TCSANOW, &term).unwrap(); // turn on non-blocking mode so we get timeouts let flags = fcntl(fd, FcntlArg::F_GETFL).unwrap();