From cbe3f9f5549c752d13c3a14a314d829b880d84a7 Mon Sep 17 00:00:00 2001 From: Sunshine Date: Sun, 17 May 2020 13:54:07 -0400 Subject: [PATCH] implement support for embedding images within srcset --- src/html.rs | 97 +++++++++++++++++++++- src/tests/cli.rs | 26 +++--- src/tests/data/{ => basic}/local-file.html | 0 src/tests/data/{ => basic}/local-script.js | 0 src/tests/data/{ => basic}/local-style.css | 0 src/tests/html/embed_srcset.rs | 26 ++++++ src/tests/html/mod.rs | 1 + src/tests/utils/retrieve_asset.rs | 6 +- 8 files changed, 136 insertions(+), 20 deletions(-) rename src/tests/data/{ => basic}/local-file.html (100%) rename src/tests/data/{ => basic}/local-script.js (100%) rename src/tests/data/{ => basic}/local-style.css (100%) create mode 100644 src/tests/html/embed_srcset.rs 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