commit
3d678d80ee
10 changed files with 138 additions and 22 deletions
2
Cargo.lock
generated
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>",
|
||||||
|
|
97
src/html.rs
97
src/html.rs
|
@ -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();
|
||||||
|
|
||||||
|
|
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 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…
Reference in a new issue