implement support for embedding images within srcset

This commit is contained in:
Sunshine 2020-05-17 13:54:07 -04:00
parent b6a44c64cf
commit cbe3f9f554
No known key found for this signature in database
GPG key ID: B80CA68703CD8AB1
8 changed files with 136 additions and 20 deletions

View file

@ -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<String, Vec<u8>>,
client: &Client,
parent_url: &str,
srcset: &str,
opt_no_images: bool,
opt_silent: bool,
) -> String {
let mut array: Vec<SrcSetItem> = 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<String, Vec<u8>>,
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 {

View file

@ -227,9 +227,9 @@ fn passing_local_file_target_input() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn
.arg("-jciI")
.arg(if cfg!(windows) {
format!(
"{cwd}\\src\\tests\\data\\local-file.html",
"{cwd}\\src\\tests\\data\\basic\\local-file.html",
cwd = cwd.to_str().unwrap()
)
} else {
format!(
"{cwd}/src/tests/data/local-file.html",
"{cwd}/src/tests/data/basic/local-file.html",
cwd = cwd.to_str().unwrap()
)
})
@ -322,7 +322,7 @@ fn passing_local_file_target_input_absolute_target_path() -> Result<(), Box<dyn
assert_eq!(
std::str::from_utf8(&out.stderr).unwrap(),
format!(
"{file}{cwd}/src/tests/data/local-file.html\n",
"{file}{cwd}/src/tests/data/basic/local-file.html\n",
file = file_url_protocol,
cwd = cwd_normalized,
)
@ -345,13 +345,13 @@ fn passing_local_file_url_target_input() -> Result<(), Box<dyn std::error::Error
.arg("-cji")
.arg(if cfg!(windows) {
format!(
"{file}{cwd}/src/tests/data/local-file.html",
"{file}{cwd}/src/tests/data/basic/local-file.html",
file = file_url_protocol,
cwd = cwd_normalized,
)
} else {
format!(
"{file}{cwd}/src/tests/data/local-file.html",
"{file}{cwd}/src/tests/data/basic/local-file.html",
file = file_url_protocol,
cwd = cwd_normalized,
)
@ -385,13 +385,13 @@ fn passing_local_file_url_target_input() -> Result<(), Box<dyn std::error::Error
std::str::from_utf8(&out.stderr).unwrap(),
if cfg!(windows) {
format!(
"{file}{cwd}/src/tests/data/local-file.html\n",
"{file}{cwd}/src/tests/data/basic/local-file.html\n",
file = file_url_protocol,
cwd = cwd_normalized,
)
} else {
format!(
"{file}{cwd}/src/tests/data/local-file.html\n",
"{file}{cwd}/src/tests/data/basic/local-file.html\n",
file = file_url_protocol,
cwd = cwd_normalized,
)
@ -410,7 +410,7 @@ fn passing_security_disallow_local_assets_within_data_url_targets(
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME"))?;
let out = cmd
.arg("-M")
.arg("data:text/html,%3Cscript%20src=\"src/tests/data/local-script.js\"%3E%3C/script%3E")
.arg("data:text/html,%3Cscript%20src=\"src/tests/data/basic/local-script.js\"%3E%3C/script%3E")
.output()
.unwrap();

View file

@ -0,0 +1,26 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
#[cfg(test)]
mod passing {
use crate::html;
use reqwest::blocking::Client;
use std::collections::HashMap;
#[test]
fn replace_with_empty_images() {
let cache = &mut HashMap::new();
let client = Client::new();
let srcset_value = "small.png 1x, large.png 2x";
let embedded_css = html::embed_srcset(cache, &client, "", &srcset_value, true, true);
assert_eq!(
format!("{} 1x, {} 2x", empty_image!(), empty_image!()),
embedded_css
);
}
}

View file

@ -1,3 +1,4 @@
mod embed_srcset;
mod get_node_name;
mod has_proper_integrity;
mod is_icon;

View file

@ -49,12 +49,12 @@ fn passing_read_local_file_with_file_url_parent() {
cache,
&client,
&format!(
"{file}{cwd}/src/tests/data/local-file.html",
"{file}{cwd}/src/tests/data/basic/local-file.html",
file = file_url_protocol,
cwd = cwd.to_str().unwrap()
),
&format!(
"{file}{cwd}/src/tests/data/local-script.js",
"{file}{cwd}/src/tests/data/basic/local-script.js",
file = file_url_protocol,
cwd = cwd.to_str().unwrap()
),
@ -65,7 +65,7 @@ fn passing_read_local_file_with_file_url_parent() {
assert_eq!(
&final_url,
&format!(
"{file}{cwd}/src/tests/data/local-script.js",
"{file}{cwd}/src/tests/data/basic/local-script.js",
file = file_url_protocol,
cwd = cwd.to_str().unwrap()
)