Merge pull request #176 from snshn/img-srcset

IMG srcset
pull/177/head v2.2.6
Sunshine 4 years ago committed by GitHub
commit 3d678d80ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

2
Cargo.lock generated

@ -623,7 +623,7 @@ dependencies = [
[[package]] [[package]]
name = "monolith" name = "monolith"
version = "2.2.5" version = "2.2.6"
dependencies = [ dependencies = [
"assert_cmd 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",

@ -1,6 +1,6 @@
[package] [package]
name = "monolith" name = "monolith"
version = "2.2.5" version = "2.2.6"
edition = "2018" edition = "2018"
authors = [ authors = [
"Sunshine <sunshine@uberspace.net>", "Sunshine <sunshine@uberspace.net>",

@ -16,6 +16,11 @@ use sha2::{Digest, Sha256, Sha384, Sha512};
use std::collections::HashMap; use std::collections::HashMap;
use std::default::Default; use std::default::Default;
struct SrcSetItem<'a> {
path: &'a str,
descriptor: &'a str,
}
const ICON_VALUES: &[&str] = &[ const ICON_VALUES: &[&str] = &[
"icon", "icon",
"shortcut 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( pub fn walk_and_embed_assets(
cache: &mut HashMap<String, Vec<u8>>, cache: &mut HashMap<String, Vec<u8>>,
client: &Client, client: &Client,
@ -352,15 +421,18 @@ pub fn walk_and_embed_assets(
} }
"img" => { "img" => {
// Find source attribute(s) // Find source attribute(s)
let mut img_src: String = str!();
let mut img_data_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; let mut i = 0;
while i < attrs_mut.len() { while i < attrs_mut.len() {
let attr_name: &str = &attrs_mut[i].name.local; let attr_name: &str = &attrs_mut[i].name.local;
if attr_name.eq_ignore_ascii_case("src") { if attr_name.eq_ignore_ascii_case("data-src") {
img_src = str!(attrs_mut.remove(i).value.trim());
} else if attr_name.eq_ignore_ascii_case("data-src") {
img_data_src = str!(attrs_mut.remove(i).value.trim()); 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 { } else {
i += 1; 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" => { "svg" => {
if opt_no_images { if opt_no_images {

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

@ -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
);
}
}

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

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

Loading…
Cancel
Save