diff --git a/Cargo.lock b/Cargo.lock index c5464c4..f312d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,7 +623,7 @@ dependencies = [ [[package]] name = "monolith" -version = "2.2.5" +version = "2.2.6" dependencies = [ "assert_cmd 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 2cf13eb..9cd97b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "monolith" -version = "2.2.5" +version = "2.2.6" edition = "2018" authors = [ "Sunshine ", diff --git a/src/html.rs b/src/html.rs index d1b8330..3a5c6f8 100644 --- a/src/html.rs +++ b/src/html.rs @@ -16,6 +16,11 @@ use sha2::{Digest, Sha256, Sha384, Sha512}; use std::collections::HashMap; use std::default::Default; +struct SrcSetItem<'a> { + path: &'a str, + descriptor: &'a str, +} + const ICON_VALUES: &[&str] = &[ "icon", "shortcut icon", @@ -58,6 +63,70 @@ pub fn has_proper_integrity(data: &[u8], integrity: &str) -> bool { } } +pub fn embed_srcset( + cache: &mut HashMap>, + client: &Client, + parent_url: &str, + srcset: &str, + opt_no_images: bool, + opt_silent: bool, +) -> String { + let mut array: Vec = vec![]; + let srcset_items: Vec<&str> = srcset.split(',').collect(); + for srcset_item in srcset_items { + let parts: Vec<&str> = srcset_item.trim().split_whitespace().collect(); + let path = parts[0].trim(); + let descriptor = if parts.len() > 1 { parts[1].trim() } else { "" }; + let srcset_real_item = SrcSetItem { path, descriptor }; + array.push(srcset_real_item); + } + + let mut result: String = str!(); + let mut i: usize = array.len(); + for part in array { + if opt_no_images { + result.push_str(empty_image!()); + } else { + let image_full_url = resolve_url(&parent_url, part.path).unwrap_or_default(); + let image_url_fragment = get_url_fragment(image_full_url.clone()); + match retrieve_asset(cache, client, &parent_url, &image_full_url, opt_silent) { + Ok((image_data, image_final_url, image_media_type)) => { + let image_data_url = data_to_data_url( + &image_media_type, + &image_data, + &image_final_url, + &image_url_fragment, + ); + // Append retreved asset as a data URL + result.push_str(image_data_url.as_ref()); + } + Err(_) => { + // Keep remote reference if unable to retrieve the asset + if is_http_url(image_full_url.clone()) { + result.push_str(image_full_url.as_ref()); + } else { + // Avoid breaking the structure in case if not an HTTP(S) URL + result.push_str(empty_image!()); + } + } + } + } + + if !part.descriptor.is_empty() { + result.push_str(" "); + result.push_str(part.descriptor); + } + + if i > 1 { + result.push_str(", "); + } + + i -= 1; + } + + result +} + pub fn walk_and_embed_assets( cache: &mut HashMap>, client: &Client, @@ -352,15 +421,18 @@ pub fn walk_and_embed_assets( } "img" => { // Find source attribute(s) - let mut img_src: String = str!(); let mut img_data_src: String = str!(); + let mut img_src: String = str!(); + let mut img_srcset: String = str!(); let mut i = 0; while i < attrs_mut.len() { let attr_name: &str = &attrs_mut[i].name.local; - if attr_name.eq_ignore_ascii_case("src") { - img_src = str!(attrs_mut.remove(i).value.trim()); - } else if attr_name.eq_ignore_ascii_case("data-src") { + if attr_name.eq_ignore_ascii_case("data-src") { img_data_src = str!(attrs_mut.remove(i).value.trim()); + } else if attr_name.eq_ignore_ascii_case("src") { + img_src = str!(attrs_mut.remove(i).value.trim()); + } else if attr_name.eq_ignore_ascii_case("srcset") { + img_srcset = str!(attrs_mut.remove(i).value.trim()); } else { i += 1; } @@ -416,6 +488,23 @@ pub fn walk_and_embed_assets( } } } + + if !img_srcset.is_empty() { + attrs_mut.push(Attribute { + name: QualName::new(None, ns!(), local_name!("srcset")), + value: Tendril::from_slice( + embed_srcset( + cache, + client, + &url, + &img_srcset, + opt_no_images, + opt_silent, + ) + .as_ref(), + ), + }); + } } "svg" => { if opt_no_images { diff --git a/src/tests/cli.rs b/src/tests/cli.rs index 905823a..2d19c32 100644 --- a/src/tests/cli.rs +++ b/src/tests/cli.rs @@ -227,9 +227,9 @@ fn passing_local_file_target_input() -> Result<(), Box> { let out = cmd .arg("-M") .arg(if cfg!(windows) { - "src\\tests\\data\\local-file.html" + "src\\tests\\data\\basic\\local-file.html" } else { - "src/tests/data/local-file.html" + "src/tests/data/basic/local-file.html" }) .output() .unwrap(); @@ -257,9 +257,9 @@ fn passing_local_file_target_input() -> Result<(), Box> { std::str::from_utf8(&out.stderr).unwrap(), format!( "\ -{file}{cwd}/src/tests/data/local-file.html\n\ -{file}{cwd}/src/tests/data/local-style.css\n\ -{file}{cwd}/src/tests/data/local-script.js\n\ +{file}{cwd}/src/tests/data/basic/local-file.html\n\ +{file}{cwd}/src/tests/data/basic/local-style.css\n\ +{file}{cwd}/src/tests/data/basic/local-script.js\n\ ", file = file_url_protocol, cwd = cwd_normalized @@ -284,12 +284,12 @@ fn passing_local_file_target_input_absolute_target_path() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box