Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::html::{
has_favicon, html_to_dom, serialize_document, set_base_url, set_charset, set_robots, walk,
};
use crate::session::Session;
use crate::url::{create_data_url, resolve_url};
use crate::url::{create_data_url, is_windows_drive_path, resolve_url};

#[derive(Debug)]
pub struct MonolithError {
Expand Down Expand Up @@ -167,7 +167,7 @@ pub fn create_monolithic_document_from_data(
} else {
// Custom base URL provided
match Url::parse(&custom_base_url) {
Ok(parsed_url) => {
Ok(parsed_url) if !is_windows_drive_path(&custom_base_url) => {
if parsed_url.scheme() == "file" {
// File base URLs can only work with documents saved from filesystem
if base_url.scheme() == "file" {
Expand All @@ -177,7 +177,7 @@ pub fn create_monolithic_document_from_data(
base_url = parsed_url;
}
}
Err(_) => {
_ => {
// Failed to parse given base URL, perhaps it's a filesystem path?
if base_url.scheme() == "file" {
// Relative paths could work for documents saved from filesystem
Expand Down Expand Up @@ -358,7 +358,7 @@ pub fn create_monolithic_document(

let mut target_url = match target.as_str() {
target_str => match Url::parse(target_str) {
Ok(target_url) => match target_url.scheme() {
Ok(target_url) if !is_windows_drive_path(target_str) => match target_url.scheme() {
"data" | "file" | "http" | "https" => target_url,
unsupported_scheme => {
return Err(MonolithError::new(&format!(
Expand All @@ -367,7 +367,7 @@ pub fn create_monolithic_document(
)));
}
},
Err(_) => {
_ => {
// Failed to parse given base URL (perhaps it's a filesystem path?)
let path: &Path = Path::new(&target_str);

Expand Down
11 changes: 11 additions & 0 deletions src/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ pub fn domain_is_within_domain(domain: &str, domain_to_match_against: &str) -> b
ok
}

/// Checks if the input is an absolute Windows path (e.g. `C:\dir\page.html` or `C:/dir/page.html`),
/// which the URL parser would otherwise take for a URL with a one-letter scheme
pub fn is_windows_drive_path(input: &str) -> bool {
let bytes = input.as_bytes();

bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& (bytes[2] == b'\\' || bytes[2] == b'/')
}

pub fn is_url_and_has_protocol(input: &str) -> bool {
match Url::parse(input) {
Ok(parsed_url) => !parsed_url.scheme().is_empty(),
Expand Down
34 changes: 34 additions & 0 deletions tests/cli/local_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,40 @@ document.body.style.color = "red";
out.assert().code(0);
}

#[test]
fn local_file_target_input_full_target_path() {
let mut cmd = cargo_bin_cmd!(env!("CARGO_PKG_NAME"));
// On Windows this is a path that starts with a drive letter, e.g. "C:\...\local-file.html"
let path_html = env::current_dir()
.unwrap()
.join("tests")
.join("_data_")
.join("basic")
.join("local-file.html");

let out = cmd
.arg("-M")
.arg("-Ijci")
.arg(path_html.as_os_str())
.output()
.unwrap();

// STDERR should contain only the target file
assert_eq!(
String::from_utf8_lossy(&out.stderr),
format!(
"{file_url_html}\n",
file_url_html = Url::from_file_path(fs::canonicalize(&path_html).unwrap()).unwrap(),
)
);

// STDOUT should contain HTML from the local file
assert!(String::from_utf8_lossy(&out.stdout).contains("<title>Local HTML file</title>"));

// Exit code should be 0
out.assert().code(0);
}

#[test]
fn local_file_url_target_input() {
let mut cmd = cargo_bin_cmd!(env!("CARGO_PKG_NAME"));
Expand Down
69 changes: 69 additions & 0 deletions tests/url/is_windows_drive_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝

#[cfg(test)]
mod passing {
use monolith::url;

#[test]
fn backslashes() {
assert!(url::is_windows_drive_path(
r"C:\Users\Me\Downloads\page.html"
));
}

#[test]
fn forward_slashes() {
assert!(url::is_windows_drive_path(
"C:/Users/Me/Downloads/page.html"
));
}

#[test]
fn lowercase_drive_letter() {
assert!(url::is_windows_drive_path(r"d:\page.html"));
}
}

// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝

#[cfg(test)]
mod failing {
use monolith::url;

#[test]
fn file_url() {
assert!(!url::is_windows_drive_path(
"file:///C:/Users/Me/Downloads/page.html"
));
}

#[test]
fn http_url() {
assert!(!url::is_windows_drive_path("https://example.com/"));
}

#[test]
fn unix_path() {
assert!(!url::is_windows_drive_path("/home/me/page.html"));
}

#[test]
fn relative_path() {
assert!(!url::is_windows_drive_path("page.html"));
}

#[test]
fn one_letter_scheme() {
assert!(!url::is_windows_drive_path("c:page.html"));
}
}
1 change: 1 addition & 0 deletions tests/url/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ mod create_data_url;
mod domain_is_within_domain;
mod get_referer_url;
mod is_url_and_has_protocol;
mod is_windows_drive_path;
mod parse_data_url;
mod resolve_url;
Loading