diff --git a/src/css.rs b/src/css.rs index a1205a9..f5184c4 100644 --- a/src/css.rs +++ b/src/css.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use url::Url; use crate::opts::Options; -use crate::url::{data_to_data_url, resolve_url}; +use crate::url::{create_data_url, resolve_url}; use crate::utils::retrieve_asset; const CSS_PROPS_WITH_IMAGE_URLS: &[&str] = &[ @@ -55,14 +55,6 @@ pub fn embed_css( .unwrap() } -pub fn enquote(input: String, double: bool) -> String { - if double { - format!("\"{}\"", input.replace("\"", "\\\"")) - } else { - format!("'{}'", input.replace("'", "\\'")) - } -} - pub fn format_ident(ident: &str) -> String { let mut res: String = String::new(); let _ = serialize_identifier(ident, &mut res); @@ -206,7 +198,7 @@ pub fn process_css<'a>( depth + 1, ) { Ok((import_contents, import_final_url, _import_media_type)) => { - let mut import_data_url = data_to_data_url( + let mut import_data_url = create_data_url( "text/css", embed_css( cache, @@ -220,15 +212,18 @@ pub fn process_css<'a>( &import_final_url, ); import_data_url.set_fragment(import_full_url.fragment()); - result.push_str(enquote(import_data_url.to_string(), false).as_str()); + result.push_str( + format_quoted_string(&import_data_url.to_string()).as_str(), + ); } Err(_) => { // Keep remote reference if unable to retrieve the asset if import_full_url.scheme() == "http" || import_full_url.scheme() == "https" { - result - .push_str(enquote(import_full_url.to_string(), false).as_str()); + result.push_str( + format_quoted_string(&import_full_url.to_string()).as_str(), + ); } } } @@ -240,7 +235,7 @@ pub fn process_css<'a>( } if options.no_images && is_image_url_prop(curr_prop.as_str()) { - result.push_str(enquote(str!(empty_image!()), false).as_str()); + result.push_str(format_quoted_string(empty_image!()).as_str()); } else { let resolved_url: Url = resolve_url(&document_url, value); match retrieve_asset( @@ -253,9 +248,11 @@ pub fn process_css<'a>( ) { Ok((data, final_url, media_type)) => { let mut data_url = - data_to_data_url(&media_type, &data, &final_url); + create_data_url(&media_type, &data, &final_url); data_url.set_fragment(resolved_url.fragment()); - result.push_str(enquote(data_url.to_string(), false).as_str()); + result.push_str( + format_quoted_string(&data_url.to_string()).as_str(), + ); } Err(_) => { // Keep remote reference if unable to retrieve the asset @@ -263,7 +260,8 @@ pub fn process_css<'a>( || resolved_url.scheme() == "https" { result.push_str( - enquote(resolved_url.to_string(), false).as_str(), + format_quoted_string(&resolved_url.to_string()) + .as_str(), ); } } @@ -345,7 +343,7 @@ pub fn process_css<'a>( depth + 1, ) { Ok((css, final_url, _media_type)) => { - let mut data_url = data_to_data_url( + let mut data_url = create_data_url( "text/css", embed_css( cache, @@ -359,18 +357,19 @@ pub fn process_css<'a>( &final_url, ); data_url.set_fragment(full_url.fragment()); - result.push_str(enquote(data_url.to_string(), false).as_str()); + result.push_str(format_quoted_string(&data_url.to_string()).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if full_url.scheme() == "http" || full_url.scheme() == "https" { - result.push_str(enquote(full_url.to_string(), false).as_str()); + result + .push_str(format_quoted_string(&full_url.to_string()).as_str()); } } } } else { if is_image_url_prop(curr_prop.as_str()) && options.no_images { - result.push_str(enquote(str!(empty_image!()), false).as_str()); + result.push_str(format_quoted_string(empty_image!()).as_str()); } else { let full_url: Url = resolve_url(&document_url, value); match retrieve_asset( @@ -382,14 +381,17 @@ pub fn process_css<'a>( depth + 1, ) { Ok((data, final_url, media_type)) => { - let mut data_url = data_to_data_url(&media_type, &data, &final_url); + let mut data_url = create_data_url(&media_type, &data, &final_url); data_url.set_fragment(full_url.fragment()); - result.push_str(enquote(data_url.to_string(), false).as_str()); + result + .push_str(format_quoted_string(&data_url.to_string()).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if full_url.scheme() == "http" || full_url.scheme() == "https" { - result.push_str(enquote(full_url.to_string(), false).as_str()); + result.push_str( + format_quoted_string(&full_url.to_string()).as_str(), + ); } } } diff --git a/src/html.rs b/src/html.rs index 17524fb..40eb35d 100644 --- a/src/html.rs +++ b/src/html.rs @@ -17,7 +17,7 @@ use std::default::Default; use crate::css::embed_css; use crate::js::attr_is_event_handler; use crate::opts::Options; -use crate::url::{clean_url, data_to_data_url, is_url_and_has_protocol, resolve_url}; +use crate::url::{clean_url, create_data_url, is_url_and_has_protocol, resolve_url}; use crate::utils::retrieve_asset; struct SrcSetItem<'a> { @@ -190,7 +190,7 @@ pub fn embed_srcset( ) { Ok((image_data, image_final_url, image_media_type)) => { let mut image_data_url = - data_to_data_url(&image_media_type, &image_data, &image_final_url); + create_data_url(&image_media_type, &image_data, &image_final_url); // Append retreved asset as a data URL image_data_url.set_fragment(image_full_url.fragment()); result.push_str(image_data_url.as_ref()); @@ -534,7 +534,7 @@ pub fn retrieve_and_embed_asset( options, depth + 1, ); - let css_data_url = data_to_data_url("text/css", css.as_bytes(), &final_url); + let css_data_url = create_data_url("text/css", css.as_bytes(), &final_url); set_node_attr(&node, attr_name, Some(css_data_url.to_string())); @@ -559,7 +559,7 @@ pub fn retrieve_and_embed_asset( ) .unwrap(); - let mut frame_data_url = data_to_data_url(&media_type, &frame_data, &final_url); + let mut frame_data_url = create_data_url(&media_type, &frame_data, &final_url); frame_data_url.set_fragment(resolved_url.fragment()); @@ -572,7 +572,7 @@ pub fn retrieve_and_embed_asset( if node_name == "script" { media_type = "application/javascript".to_string(); } - let mut data_url = data_to_data_url(&media_type, &data, &final_url); + let mut data_url = create_data_url(&media_type, &data, &final_url); data_url.set_fragment(resolved_url.fragment()); set_node_attr(node, attr_name, Some(data_url.to_string())); } diff --git a/src/main.rs b/src/main.rs index d04fba6..23e245b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use monolith::html::{ stringify_document, walk_and_embed_assets, }; use monolith::opts::Options; -use monolith::url::{data_to_data_url, parse_data_url, resolve_url}; +use monolith::url::{create_data_url, parse_data_url, resolve_url}; use monolith::utils::retrieve_asset; mod macros; @@ -266,7 +266,7 @@ fn main() { 0, ) { Ok((data, final_url, media_type)) => { - let favicon_data_url: Url = data_to_data_url(&media_type, &data, &final_url); + let favicon_data_url: Url = create_data_url(&media_type, &data, &final_url); dom = add_favicon(&dom.document, favicon_data_url.to_string()); } Err(_) => { diff --git a/src/tests/cli/basic.rs b/src/tests/cli/basic.rs index 60ae9da..0e0d5d6 100644 --- a/src/tests/cli/basic.rs +++ b/src/tests/cli/basic.rs @@ -71,7 +71,7 @@ mod passing { // STDOUT should contain embedded CSS url()'s assert_eq!( std::str::from_utf8(&out.stdout).unwrap(), - "\n\n" + "\n\n" ); // STDERR should list files that got retrieved diff --git a/src/tests/cli/local_files.rs b/src/tests/cli/local_files.rs index 1ee2280..4c3465f 100644 --- a/src/tests/cli/local_files.rs +++ b/src/tests/cli/local_files.rs @@ -193,7 +193,7 @@ mod passing { // STDOUT should contain HTML with date URL for background-image in it assert_eq!( std::str::from_utf8(&out.stdout).unwrap(), - "
body {}"; @@ -191,9 +191,9 @@ mod passing { "\ @charset \"UTF-8\";\n\ \n\ - @import 'data:text/css;base64,aHRtbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDB9';\n\ + @import \"data:text/css;base64,aHRtbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDB9\";\n\ \n\ - @import url('data:text/css;base64,aHRtbHtjb2xvcjojZmZmfQ==')\n\ + @import url(\"data:text/css;base64,aHRtbHtjb2xvcjojZmZmfQ==\")\n\ " ); } @@ -331,7 +331,7 @@ mod passing { "; const CSS_OUT: &str = "\ #language a[href=\"#translations\"]:before {\n\ - content: url('data:;base64,') \"\\a \";\n\ + content: url(\"data:;base64,\") \"\\a \";\n\ white-space: pre }\n\ "; diff --git a/src/tests/css/enquote.rs b/src/tests/css/enquote.rs deleted file mode 100644 index d02a868..0000000 --- a/src/tests/css/enquote.rs +++ /dev/null @@ -1,53 +0,0 @@ -// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗ -// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝ -// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗ -// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║ -// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝ -// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ - -#[cfg(test)] -mod passing { - use crate::css; - - #[test] - fn empty_input_single_quotes() { - assert_eq!(css::enquote(str!(""), false), "''"); - } - - #[test] - fn empty_input_double_quotes() { - assert_eq!(css::enquote(str!(""), true), "\"\""); - } - - #[test] - fn apostrophes_single_quotes() { - assert_eq!( - css::enquote(str!("It's a lovely day, don't you think?"), false), - "'It\\'s a lovely day, don\\'t you think?'" - ); - } - - #[test] - fn apostrophes_double_quotes() { - assert_eq!( - css::enquote(str!("It's a lovely day, don't you think?"), true), - "\"It's a lovely day, don't you think?\"" - ); - } - - #[test] - fn feet_and_inches_single_quotes() { - assert_eq!( - css::enquote(str!("5'2\", 6'5\""), false), - "'5\\'2\", 6\\'5\"'" - ); - } - - #[test] - fn feet_and_inches_double_quotes() { - assert_eq!( - css::enquote(str!("5'2\", 6'5\""), true), - "\"5'2\\\", 6'5\\\"\"" - ); - } -} diff --git a/src/tests/css/mod.rs b/src/tests/css/mod.rs index 5f17fd3..15775b5 100644 --- a/src/tests/css/mod.rs +++ b/src/tests/css/mod.rs @@ -1,3 +1,2 @@ mod embed_css; -mod enquote; mod is_image_url_prop; diff --git a/src/tests/url/data_to_data_url.rs b/src/tests/url/create_data_url.rs similarity index 91% rename from src/tests/url/data_to_data_url.rs rename to src/tests/url/create_data_url.rs index bac0126..873dbda 100644 --- a/src/tests/url/data_to_data_url.rs +++ b/src/tests/url/create_data_url.rs @@ -15,7 +15,7 @@ mod passing { fn encode_string_with_specific_media_type() { let mime = "application/javascript"; let data = "var word = 'hello';\nalert(word);\n"; - let data_url = url::data_to_data_url(mime, data.as_bytes(), &Url::parse("data:,").unwrap()); + let data_url = url::create_data_url(mime, data.as_bytes(), &Url::parse("data:,").unwrap()); assert_eq!( data_url.as_str(), @@ -26,7 +26,7 @@ mod passing { #[test] fn encode_append_fragment() { let data = "\n"; - let data_url = url::data_to_data_url( + let data_url = url::create_data_url( "image/svg+xml", data.as_bytes(), &Url::parse("data:,").unwrap(), diff --git a/src/tests/url/is_http_or_https_url.rs b/src/tests/url/is_http_or_https_url.rs deleted file mode 100644 index 1e0a579..0000000 --- a/src/tests/url/is_http_or_https_url.rs +++ /dev/null @@ -1,69 +0,0 @@ -// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗ -// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝ -// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗ -// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║ -// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝ -// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ - -#[cfg(test)] -mod passing { - use reqwest::Url; - - use crate::url; - - #[test] - fn http_url() { - assert!(url::is_http_or_https_url(&Url::parse("http://kernel.org").unwrap())); - } - - #[test] - fn https_url() { - assert!(url::is_http_or_https_url(&Url::parse("https://www.rust-lang.org/").unwrap())); - } - - #[test] - fn http_url_with_backslashes() { - assert!(url::is_http_or_https_url(&Url::parse("http:\\\\freebsd.org\\").unwrap())); - } -} - -// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗ -// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝ -// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗ -// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║ -// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝ -// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ - -#[cfg(test)] -mod failing { - use reqwest::Url; - - use crate::url; - - #[test] - fn url_with_no_protocol() { - assert!(!url::is_http_or_https_url(&Url::parse("//kernel.org").unwrap())); - } - - #[test] - fn dot_slash_filename() { - assert!(!url::is_http_or_https_url(&Url::parse("./index.html").unwrap())); - } - - #[test] - fn just_filename() { - assert!(!url::is_http_or_https_url(&Url::parse("some-local-page.htm").unwrap())); - } - - #[test] - fn https_ip_port_url() { - assert!(!url::is_http_or_https_url(&Url::parse("ftp://1.2.3.4/www/index.html").unwrap())); - } - - #[test] - fn data_url() { - assert!(!url::is_http_or_https_url( - &Url::parse("data:text/html;base64,V2VsY29tZSBUbyBUaGUgUGFydHksIDxiPlBhbDwvYj4h").unwrap() - )); - } -} diff --git a/src/tests/url/is_url_and_has_protocol.rs b/src/tests/url/is_url_and_has_protocol.rs index cae497a..a46690b 100644 --- a/src/tests/url/is_url_and_has_protocol.rs +++ b/src/tests/url/is_url_and_has_protocol.rs @@ -48,6 +48,11 @@ mod passing { assert!(url::is_url_and_has_protocol("https://github.com")); } + #[test] + fn file() { + assert!(url::is_url_and_has_protocol("file:///tmp/image.png")); + } + #[test] fn mailto_uppercase() { assert!(url::is_url_and_has_protocol( @@ -59,6 +64,11 @@ mod passing { fn empty_data_url() { assert!(url::is_url_and_has_protocol("data:text/html,")); } + + #[test] + fn empty_data_url_surrounded_by_spaces() { + assert!(url::is_url_and_has_protocol(" data:text/html, ")); + } } // ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗ @@ -74,25 +84,27 @@ mod failing { #[test] fn url_with_no_protocol() { - assert!(!url::is_url_and_has_protocol( - "//some-hostname.com/some-file.html" - )); + assert_eq!( + url::is_url_and_has_protocol("//some-hostname.com/some-file.html"), + false + ); } #[test] fn relative_path() { - assert!(!url::is_url_and_has_protocol( - "some-hostname.com/some-file.html" - )); + assert_eq!( + url::is_url_and_has_protocol("some-hostname.com/some-file.html"), + false + ); } #[test] fn relative_to_root_path() { - assert!(!url::is_url_and_has_protocol("/some-file.html")); + assert_eq!(url::is_url_and_has_protocol("/some-file.html"), false); } #[test] fn empty_string() { - assert!(!url::is_url_and_has_protocol("")); + assert_eq!(url::is_url_and_has_protocol(""), false); } } diff --git a/src/tests/url/mod.rs b/src/tests/url/mod.rs index 50efbc6..e99e386 100644 --- a/src/tests/url/mod.rs +++ b/src/tests/url/mod.rs @@ -1,5 +1,5 @@ mod clean_url; -mod data_to_data_url; +mod create_data_url; mod is_url_and_has_protocol; mod parse_data_url; mod percent_decode; diff --git a/src/tests/url/resolve_url.rs b/src/tests/url/resolve_url.rs index edfe773..4abede9 100644 --- a/src/tests/url/resolve_url.rs +++ b/src/tests/url/resolve_url.rs @@ -11,6 +11,34 @@ mod passing { use crate::url; + #[test] + fn basic_httsp_relative() { + assert_eq!( + url::resolve_url( + &Url::parse("https://www.kernel.org").unwrap(), + "category/signatures.html" + ) + .as_str(), + Url::parse("https://www.kernel.org/category/signatures.html") + .unwrap() + .as_str() + ); + } + + #[test] + fn basic_httsp_absolute() { + assert_eq!( + url::resolve_url( + &Url::parse("https://www.kernel.org").unwrap(), + "/category/signatures.html" + ) + .as_str(), + Url::parse("https://www.kernel.org/category/signatures.html") + .unwrap() + .as_str() + ); + } + #[test] fn from_https_to_level_up_relative() { assert_eq!( @@ -50,7 +78,7 @@ mod passing { } #[test] - fn from_https_url_to_relative_root_path() { + fn from_https_url_to_absolute_path() { assert_eq!( url::resolve_url( &Url::parse("https://www.kernel.org/category/signatures.html").unwrap(), @@ -148,22 +176,28 @@ mod passing { ); } - // #[test] - // fn resolve_from_file_url_to_file_url() { - // assert_eq!( - // if cfg!(windows) { - // url::resolve_url(&Url::parse("file:///c:/index.html").unwrap(), "file:///c:/image.png").as_str() - // } else { - // url::resolve_url(&Url::parse("file:///tmp/index.html").unwrap(), "file:///tmp/image.png") - // .as_str() - // }, - // if cfg!(windows) { - // "file:///c:/image.png" - // } else { - // "file:///tmp/image.png" - // } - // ); - // } + #[test] + fn resolve_from_file_url_to_file_url() { + if cfg!(windows) { + assert_eq!( + url::resolve_url( + &Url::parse("file:///c:/index.html").unwrap(), + "file:///c:/image.png" + ) + .as_str(), + "file:///c:/image.png" + ); + } else { + assert_eq!( + url::resolve_url( + &Url::parse("file:///tmp/index.html").unwrap(), + "file:///tmp/image.png" + ) + .as_str(), + "file:///tmp/image.png" + ); + } + } } // ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗ diff --git a/src/tests/utils/retrieve_asset.rs b/src/tests/utils/retrieve_asset.rs index d7f7057..5e975d0 100644 --- a/src/tests/utils/retrieve_asset.rs +++ b/src/tests/utils/retrieve_asset.rs @@ -36,8 +36,8 @@ mod passing { ) .unwrap(); assert_eq!( - url::data_to_data_url(&media_type, &data, &final_url), - url::data_to_data_url( + url::create_data_url(&media_type, &data, &final_url), + url::create_data_url( "text/html", "target".as_bytes(), &Url::parse("data:text/html;base64,c291cmNl").unwrap() @@ -45,7 +45,7 @@ mod passing { ); assert_eq!( final_url, - url::data_to_data_url( + url::create_data_url( "text/html", "target".as_bytes(), &Url::parse("data:text/html;base64,c291cmNl").unwrap() @@ -85,7 +85,7 @@ mod passing { 0, ) .unwrap(); - assert_eq!(url::data_to_data_url("application/javascript", &data, &final_url), Url::parse("data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==").unwrap()); + assert_eq!(url::create_data_url("application/javascript", &data, &final_url), Url::parse("data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==").unwrap()); assert_eq!( final_url, Url::parse(&format!( diff --git a/src/url.rs b/src/url.rs index 002b6c0..878e726 100644 --- a/src/url.rs +++ b/src/url.rs @@ -12,7 +12,7 @@ pub fn clean_url(url: Url) -> Url { url } -pub fn data_to_data_url(media_type: &str, data: &[u8], final_asset_url: &Url) -> Url { +pub fn create_data_url(media_type: &str, data: &[u8], final_asset_url: &Url) -> Url { let media_type: String = if media_type.is_empty() { detect_media_type(data, &final_asset_url) } else {