-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
214 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
#![deny(clippy::unused_async)] | ||
#![deny(clippy::unnecessary_wraps)] | ||
|
||
use deno_error::JsError; | ||
use std::path::Component; | ||
use std::path::Path; | ||
use std::path::PathBuf; | ||
|
@@ -174,7 +175,7 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf { | |
inner(path.as_ref()) | ||
} | ||
|
||
#[derive(Debug, Error, deno_error::JsError)] | ||
#[derive(Debug, Clone, Error, deno_error::JsError)] | ||
#[class(uri)] | ||
#[error("Could not convert path to URL.\n Path: {0}")] | ||
pub struct PathToUrlError(pub PathBuf); | ||
|
@@ -312,6 +313,75 @@ pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { | |
} | ||
} | ||
|
||
/// Returns true if the input string starts with a sequence of characters | ||
/// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'. | ||
/// | ||
/// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1), | ||
/// a valid scheme has the following format: | ||
/// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) | ||
/// | ||
/// We additionally require the scheme to be at least 2 characters long, | ||
/// because otherwise a windows path like c:/foo would be treated as a URL, | ||
/// while no schemes with a one-letter name actually exist. | ||
pub fn specifier_has_uri_scheme(specifier: &str) -> bool { | ||
let mut chars = specifier.chars(); | ||
let mut len = 0usize; | ||
// The first character must be a letter. | ||
match chars.next() { | ||
Some(c) if c.is_ascii_alphabetic() => len += 1, | ||
_ => return false, | ||
} | ||
// Second and following characters must be either a letter, number, | ||
// plus sign, minus sign, or dot. | ||
loop { | ||
match chars.next() { | ||
Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1, | ||
Some(':') if len >= 2 => return true, | ||
_ => return false, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Error, JsError)] | ||
pub enum ResolveUrlOrPathError { | ||
#[error(transparent)] | ||
#[class(inherit)] | ||
UrlParse(url::ParseError), | ||
#[error(transparent)] | ||
#[class(inherit)] | ||
PathToUrl(PathToUrlError), | ||
} | ||
|
||
/// Takes a string representing either an absolute URL or a file path, | ||
/// as it may be passed to deno as a command line argument. | ||
/// The string is interpreted as a URL if it starts with a valid URI scheme, | ||
/// e.g. 'http:' or 'file:' or 'git+ssh:'. If not, it's interpreted as a | ||
/// file path; if it is a relative path it's resolved relative to passed | ||
/// `current_dir`. | ||
pub fn resolve_url_or_path( | ||
specifier: &str, | ||
current_dir: &Path, | ||
) -> Result<Url, ResolveUrlOrPathError> { | ||
if specifier_has_uri_scheme(specifier) { | ||
Url::parse(specifier).map_err(ResolveUrlOrPathError::UrlParse) | ||
} else { | ||
resolve_path(specifier, current_dir) | ||
.map_err(ResolveUrlOrPathError::PathToUrl) | ||
} | ||
} | ||
|
||
/// Converts a string representing a relative or absolute path into a | ||
/// ModuleSpecifier. A relative path is considered relative to the passed | ||
/// `current_dir`. | ||
pub fn resolve_path( | ||
path_str: impl AsRef<Path>, | ||
current_dir: &Path, | ||
) -> Result<Url, PathToUrlError> { | ||
let path = current_dir.join(path_str); | ||
let path = normalize_path(path); | ||
url_from_file_path(&path) | ||
} | ||
|
||
pub fn get_atomic_path(sys: &impl SystemRandom, path: &Path) -> PathBuf { | ||
let rand = gen_rand_path_component(sys); | ||
let extension = format!("{rand}.tmp"); | ||
|
@@ -514,4 +584,141 @@ mod tests { | |
assert_eq!(atomic_path.parent().unwrap(), path.parent().unwrap()); | ||
assert_eq!(atomic_path.file_name().unwrap(), "c.3d3d3d3d.tmp"); | ||
} | ||
|
||
#[test] | ||
fn test_specifier_has_uri_scheme() { | ||
let tests = vec![ | ||
("http://foo.bar/etc", true), | ||
("HTTP://foo.bar/etc", true), | ||
("http:ftp:", true), | ||
("http:", true), | ||
("hTtP:", true), | ||
("ftp:", true), | ||
("mailto:[email protected]", true), | ||
("git+ssh://[email protected]/denoland/deno", true), | ||
("blob:https://whatwg.org/mumbojumbo", true), | ||
("abc.123+DEF-ghi:", true), | ||
("abc.123+def-ghi:@", true), | ||
("", false), | ||
(":not", false), | ||
("http", false), | ||
("c:dir", false), | ||
("X:", false), | ||
("./http://not", false), | ||
("1abc://kinda/but/no", false), | ||
("schluẞ://no/more", false), | ||
]; | ||
|
||
for (specifier, expected) in tests { | ||
let result = specifier_has_uri_scheme(specifier); | ||
assert_eq!(result, expected); | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_resolve_url_or_path() { | ||
// Absolute URL. | ||
let mut tests: Vec<(&str, String)> = vec![ | ||
( | ||
"http://deno.land/core/tests/006_url_imports.ts", | ||
"http://deno.land/core/tests/006_url_imports.ts".to_string(), | ||
), | ||
( | ||
"https://deno.land/core/tests/006_url_imports.ts", | ||
"https://deno.land/core/tests/006_url_imports.ts".to_string(), | ||
), | ||
]; | ||
|
||
// The local path tests assume that the cwd is the deno repo root. Note | ||
// that we can't use `cwd` in miri tests, so we just use `/miri` instead. | ||
let cwd = if cfg!(miri) { | ||
PathBuf::from("/miri") | ||
} else { | ||
std::env::current_dir().unwrap() | ||
}; | ||
let cwd_str = cwd.to_str().unwrap(); | ||
|
||
if cfg!(target_os = "windows") { | ||
// Absolute local path. | ||
let expected_url = "file:///C:/deno/tests/006_url_imports.ts"; | ||
tests.extend(vec![ | ||
( | ||
r"C:/deno/tests/006_url_imports.ts", | ||
expected_url.to_string(), | ||
), | ||
( | ||
r"C:\deno\tests\006_url_imports.ts", | ||
expected_url.to_string(), | ||
), | ||
( | ||
r"\\?\C:\deno\tests\006_url_imports.ts", | ||
expected_url.to_string(), | ||
), | ||
// Not supported: `Url::from_file_path()` fails. | ||
// (r"\\.\C:\deno\tests\006_url_imports.ts", expected_url.to_string()), | ||
// Not supported: `Url::from_file_path()` performs the wrong conversion. | ||
// (r"//./C:/deno/tests/006_url_imports.ts", expected_url.to_string()), | ||
]); | ||
|
||
// Rooted local path without drive letter. | ||
let expected_url = format!( | ||
"file:///{}:/deno/tests/006_url_imports.ts", | ||
cwd_str.get(..1).unwrap(), | ||
); | ||
tests.extend(vec![ | ||
(r"/deno/tests/006_url_imports.ts", expected_url.to_string()), | ||
(r"\deno\tests\006_url_imports.ts", expected_url.to_string()), | ||
( | ||
r"\deno\..\deno\tests\006_url_imports.ts", | ||
expected_url.to_string(), | ||
), | ||
(r"\deno\.\tests\006_url_imports.ts", expected_url), | ||
]); | ||
|
||
// Relative local path. | ||
let expected_url = format!( | ||
"file:///{}/tests/006_url_imports.ts", | ||
cwd_str.replace('\\', "/") | ||
); | ||
tests.extend(vec![ | ||
(r"tests/006_url_imports.ts", expected_url.to_string()), | ||
(r"tests\006_url_imports.ts", expected_url.to_string()), | ||
(r"./tests/006_url_imports.ts", (*expected_url).to_string()), | ||
(r".\tests\006_url_imports.ts", (*expected_url).to_string()), | ||
]); | ||
|
||
// UNC network path. | ||
let expected_url = "file://server/share/deno/cool"; | ||
tests.extend(vec![ | ||
(r"\\server\share\deno\cool", expected_url.to_string()), | ||
(r"\\server/share/deno/cool", expected_url.to_string()), | ||
// Not supported: `Url::from_file_path()` performs the wrong conversion. | ||
// (r"//server/share/deno/cool", expected_url.to_string()), | ||
]); | ||
} else { | ||
// Absolute local path. | ||
let expected_url = "file:///deno/tests/006_url_imports.ts"; | ||
tests.extend(vec![ | ||
("/deno/tests/006_url_imports.ts", expected_url.to_string()), | ||
("//deno/tests/006_url_imports.ts", expected_url.to_string()), | ||
]); | ||
|
||
// Relative local path. | ||
let expected_url = format!("file://{cwd_str}/tests/006_url_imports.ts"); | ||
tests.extend(vec![ | ||
("tests/006_url_imports.ts", expected_url.to_string()), | ||
("./tests/006_url_imports.ts", expected_url.to_string()), | ||
( | ||
"tests/../tests/006_url_imports.ts", | ||
expected_url.to_string(), | ||
), | ||
("tests/./006_url_imports.ts", expected_url), | ||
]); | ||
} | ||
|
||
for (specifier, expected_url) in tests { | ||
let url = resolve_url_or_path(specifier, &cwd).unwrap().to_string(); | ||
assert_eq!(url, expected_url); | ||
} | ||
} | ||
} |