implement support for embedding images within srcset
This commit is contained in:
parent
b6a44c64cf
commit
cbe3f9f554
8 changed files with 136 additions and 20 deletions
97
src/html.rs
97
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<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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
26
src/tests/html/embed_srcset.rs
Normal file
26
src/tests/html/embed_srcset.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod embed_srcset;
|
||||
mod get_node_name;
|
||||
mod has_proper_integrity;
|
||||
mod is_icon;
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue