diff options
Diffstat (limited to 'ext/node')
-rw-r--r-- | ext/node/lib.rs | 1 | ||||
-rw-r--r-- | ext/node/ops/mod.rs | 1 | ||||
-rw-r--r-- | ext/node/ops/require.rs | 4 | ||||
-rw-r--r-- | ext/node/ops/worker_threads.rs | 87 | ||||
-rw-r--r-- | ext/node/polyfills/worker_threads.ts | 115 |
5 files changed, 109 insertions, 99 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs index f4541f886..16e69250b 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -329,6 +329,7 @@ deno_core::extension!(deno_node, ops::require::op_require_package_imports_resolve<P>, ops::require::op_require_break_on_next_statement, ops::util::op_node_guess_handle_type, + ops::worker_threads::op_worker_threads_filename<P>, ops::crypto::op_node_create_private_key, ops::crypto::op_node_create_public_key, ops::ipc::op_node_child_ipc_pipe, diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index ae798d181..8aed274bc 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -11,4 +11,5 @@ pub mod require; pub mod util; pub mod v8; pub mod winerror; +pub mod worker_threads; pub mod zlib; diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index 6ca0b4a1a..d14e1d732 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -195,7 +195,9 @@ pub fn op_require_resolve_deno_dir( resolver .resolve_package_folder_from_package( &request, - &ModuleSpecifier::from_file_path(parent_filename).unwrap(), + &ModuleSpecifier::from_file_path(&parent_filename).unwrap_or_else(|_| { + panic!("Url::from_file_path: [{:?}]", parent_filename) + }), NodeResolutionMode::Execution, ) .ok() diff --git a/ext/node/ops/worker_threads.rs b/ext/node/ops/worker_threads.rs new file mode 100644 index 000000000..18a4157d4 --- /dev/null +++ b/ext/node/ops/worker_threads.rs @@ -0,0 +1,87 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::generic_error; +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::url::Url; +use deno_core::OpState; +use deno_fs::FileSystemRc; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; + +use crate::resolution; +use crate::NodePermissions; +use crate::NodeResolver; +use crate::NpmResolverRc; + +fn ensure_read_permission<P>( + state: &mut OpState, + file_path: &Path, +) -> Result<(), AnyError> +where + P: NodePermissions + 'static, +{ + let resolver = state.borrow::<NpmResolverRc>(); + let permissions = state.borrow::<P>(); + resolver.ensure_read_permission(permissions, file_path) +} + +#[op2] +#[string] +pub fn op_worker_threads_filename<P>( + state: &mut OpState, + #[string] specifier: String, +) -> Result<String, AnyError> +where + P: NodePermissions + 'static, +{ + if specifier.starts_with("data:") { + return Ok(specifier); + } + let url: Url = if specifier.starts_with("file:") { + Url::parse(&specifier)? + } else { + let path = PathBuf::from(&specifier); + if path.is_relative() && !specifier.starts_with('.') { + return Err(generic_error( + "Relative path entries must start with '.' or '..'", + )); + } + ensure_read_permission::<P>(state, &path)?; + let fs = state.borrow::<FileSystemRc>(); + let canonicalized_path = + deno_core::strip_unc_prefix(fs.realpath_sync(&path)?); + Url::from_file_path(canonicalized_path) + .map_err(|e| generic_error(format!("URL from Path-String: {:#?}", e)))? + }; + let url_path = url + .to_file_path() + .map_err(|e| generic_error(format!("URL to Path-String: {:#?}", e)))?; + ensure_read_permission::<P>(state, &url_path)?; + let fs = state.borrow::<FileSystemRc>(); + if !fs.exists_sync(&url_path) { + return Err(generic_error(format!("File not found [{:?}]", url_path))); + } + let node_resolver = state.borrow::<Rc<NodeResolver>>(); + match node_resolver.url_to_node_resolution(url)? { + resolution::NodeResolution::Esm(u) => Ok(u.to_string()), + resolution::NodeResolution::CommonJs(u) => wrap_cjs(u), + _ => Err(generic_error("Neither ESM nor CJS")), + } +} + +/// +/// Wrap a CJS file-URL and the required setup in a stringified `data:`-URL +/// +fn wrap_cjs(url: Url) -> Result<String, AnyError> { + let path = url + .to_file_path() + .map_err(|e| generic_error(format!("URL to Path: {:#?}", e)))?; + let filename = path.file_name().unwrap().to_string_lossy(); + Ok(format!( + "data:text/javascript,import {{ createRequire }} from \"node:module\";\ + const require = createRequire(\"{}\"); require(\"./{}\");", + url, filename, + )) +} diff --git a/ext/node/polyfills/worker_threads.ts b/ext/node/polyfills/worker_threads.ts index 49f2f3e3e..ab3834132 100644 --- a/ext/node/polyfills/worker_threads.ts +++ b/ext/node/polyfills/worker_threads.ts @@ -9,7 +9,7 @@ import { op_host_recv_message, op_host_terminate_worker, op_message_port_recv_message_sync, - op_require_read_closest_package_json, + op_worker_threads_filename, } from "ext:core/ops"; import { deserializeJsMessageData, @@ -24,7 +24,6 @@ import { log } from "ext:runtime/06_util.js"; import { notImplemented } from "ext:deno_node/_utils.ts"; import { EventEmitter } from "node:events"; import { BroadcastChannel } from "ext:deno_broadcast_channel/01_broadcast_channel.js"; -import { isAbsolute, resolve } from "node:path"; const { ObjectPrototypeIsPrototypeOf } = primordials; const { @@ -32,14 +31,8 @@ const { Symbol, SymbolFor, SymbolIterator, - StringPrototypeEndsWith, - StringPrototypeReplace, - StringPrototypeMatch, - StringPrototypeReplaceAll, - StringPrototypeToString, StringPrototypeTrim, SafeWeakMap, - SafeRegExp, SafeMap, TypeError, } = primordials; @@ -66,74 +59,6 @@ export interface WorkerOptions { name?: string; } -const WHITESPACE_ENCODINGS: Record<string, string> = { - "\u0009": "%09", - "\u000A": "%0A", - "\u000B": "%0B", - "\u000C": "%0C", - "\u000D": "%0D", - "\u0020": "%20", -}; - -function encodeWhitespace(string: string): string { - return StringPrototypeReplaceAll(string, new SafeRegExp(/[\s]/g), (c) => { - return WHITESPACE_ENCODINGS[c] ?? c; - }); -} - -function toFileUrlPosix(path: string): URL { - if (!isAbsolute(path)) { - throw new TypeError("Must be an absolute path."); - } - const url = new URL("file:///"); - url.pathname = encodeWhitespace( - StringPrototypeReplace( - StringPrototypeReplace(path, new SafeRegExp(/%/g), "%25"), - new SafeRegExp(/\\/g), - "%5C", - ), - ); - return url; -} - -function toFileUrlWin32(path: string): URL { - if (!isAbsolute(path)) { - throw new TypeError("Must be an absolute path."); - } - const { 0: _, 1: hostname, 2: pathname } = StringPrototypeMatch( - path, - new SafeRegExp(/^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/), - ); - const url = new URL("file:///"); - url.pathname = encodeWhitespace( - StringPrototypeReplace(pathname, new SafeRegExp(/%/g), "%25"), - ); - if (hostname != null && hostname != "localhost") { - url.hostname = hostname; - if (!url.hostname) { - throw new TypeError("Invalid hostname."); - } - } - return url; -} - -/** - * Converts a path string to a file URL. - * - * ```ts - * toFileUrl("/home/foo"); // new URL("file:///home/foo") - * toFileUrl("\\home\\foo"); // new URL("file:///home/foo") - * toFileUrl("C:\\Users\\foo"); // new URL("file:///C:/Users/foo") - * toFileUrl("\\\\127.0.0.1\\home\\foo"); // new URL("file://127.0.0.1/home/foo") - * ``` - * @param path to convert to file URL - */ -function toFileUrl(path: string): URL { - return core.build.os == "windows" - ? toFileUrlWin32(path) - : toFileUrlPosix(path); -} - const privateWorkerRef = Symbol("privateWorkerRef"); class NodeWorker extends EventEmitter { #id = 0; @@ -162,29 +87,23 @@ class NodeWorker extends EventEmitter { constructor(specifier: URL | string, options?: WorkerOptions) { super(); - if (options?.eval === true) { + + if ( + typeof specifier === "object" && + !(specifier.protocol === "data:" || specifier.protocol === "file:") + ) { + throw new TypeError( + "node:worker_threads support only 'file:' and 'data:' URLs", + ); + } + if (options?.eval) { specifier = `data:text/javascript,${specifier}`; - } else if (typeof specifier === "string") { - specifier = resolve(specifier); - let pkg; - try { - pkg = op_require_read_closest_package_json(specifier); - } catch (_) { - // empty catch block when package json might not be present - } - if ( - !(StringPrototypeEndsWith( - StringPrototypeToString(specifier), - ".mjs", - )) || - (pkg && pkg.exists && pkg.typ == "module") - ) { - const cwdFileUrl = toFileUrl(Deno.cwd()); - specifier = - `data:text/javascript,(async function() {const { createRequire } = await import("node:module");const require = createRequire("${cwdFileUrl}");require("${specifier}");})();`; - } else { - specifier = toFileUrl(specifier as string); - } + } else if ( + !(typeof specifier === "object" && specifier.protocol === "data:") + ) { + // deno-lint-ignore prefer-primordials + specifier = specifier.toString(); + specifier = op_worker_threads_filename(specifier); } // TODO(bartlomieu): this doesn't match the Node.js behavior, it should be |