add support for image inputs

This commit is contained in:
Sunshine 2020-04-03 03:30:52 -04:00
parent 5ba6e33fa8
commit 29836d979a
No known key found for this signature in database
GPG Key ID: B80CA68703CD8AB1
6 changed files with 119 additions and 46 deletions

View File

@ -18,9 +18,6 @@ const CSS_PROPS_WITH_IMAGE_URLS: &[&str] = &[
"mask-image", "mask-image",
]; ];
const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
pub fn is_image_url_prop(prop_name: &str) -> bool { pub fn is_image_url_prop(prop_name: &str) -> bool {
CSS_PROPS_WITH_IMAGE_URLS CSS_PROPS_WITH_IMAGE_URLS
.iter() .iter()
@ -185,7 +182,7 @@ pub fn process_css<'a>(
} }
if opt_no_images && is_image_url_prop(curr_prop.as_str()) { if opt_no_images && is_image_url_prop(curr_prop.as_str()) {
result.push_str(enquote(str!(TRANSPARENT_PIXEL), false).as_str()); result.push_str(enquote(str!(empty_image!()), false).as_str());
} else { } else {
let resolved_url = resolve_url(&parent_url, value).unwrap_or_default(); let resolved_url = resolve_url(&parent_url, value).unwrap_or_default();
let (data_url, _final_url) = retrieve_asset( let (data_url, _final_url) = retrieve_asset(
@ -294,7 +291,7 @@ pub fn process_css<'a>(
); );
} else { } else {
if opt_no_images && is_image_url_prop(curr_prop.as_str()) { if opt_no_images && is_image_url_prop(curr_prop.as_str()) {
result.push_str(enquote(str!(TRANSPARENT_PIXEL), false).as_str()); result.push_str(enquote(str!(empty_image!()), false).as_str());
} else { } else {
let full_url = resolve_url(&parent_url, value).unwrap_or_default(); let full_url = resolve_url(&parent_url, value).unwrap_or_default();
let (data_url, _final_url) = retrieve_asset( let (data_url, _final_url) = retrieve_asset(

View File

@ -20,9 +20,6 @@ const ICON_VALUES: &[&str] = &[
"fluid-icon", "fluid-icon",
]; ];
const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
pub fn get_parent_node(node: &Handle) -> Handle { pub fn get_parent_node(node: &Handle) -> Handle {
let parent = node.parent.take().clone(); let parent = node.parent.take().clone();
parent.and_then(|node| node.upgrade()).unwrap() parent.and_then(|node| node.upgrade()).unwrap()
@ -270,7 +267,7 @@ pub fn walk_and_embed_assets(
if opt_no_images { if opt_no_images {
attrs_mut.push(Attribute { attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")), name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(TRANSPARENT_PIXEL), value: Tendril::from_slice(empty_image!()),
}); });
} else if let Some((data_url, _)) = found_datasrc } else if let Some((data_url, _)) = found_datasrc
.iter() .iter()
@ -297,6 +294,58 @@ pub fn walk_and_embed_assets(
}); });
} }
} }
"input" => {
let mut is_image: bool = false;
for attr in attrs_mut.iter_mut() {
let attr_name: &str = &attr.name.local;
if attr_name == "type" {
is_image = attr.value.to_string().eq_ignore_ascii_case("image");
}
}
if is_image {
let mut found_src: Option<Attribute> = None;
let mut i = 0;
while i < attrs_mut.len() {
let attr_name = attrs_mut[i].name.local.as_ref();
if attr_name.eq_ignore_ascii_case("src") {
found_src = Some(attrs_mut.remove(i));
} else {
i += 1;
}
}
// If images are disabled, clear both sources
if opt_no_images {
attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(empty_image!()),
});
} else if let Some((data_url, _)) = found_src
.iter()
.map(|attr| attr.value.trim())
.filter(|src| !src.is_empty()) // Skip if empty
.next()
.and_then(|src| resolve_url(&url, src).ok()) // Make absolute
.and_then(|abs_src| // Download and convert to data_url
retrieve_asset(
cache,
client,
&url,
&abs_src,
true,
"",
opt_silent,
).ok())
{
// Add new data_url src attribute
attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(data_url.as_ref()),
});
}
}
}
"source" => { "source" => {
for attr in attrs_mut.iter_mut() { for attr in attrs_mut.iter_mut() {
let attr_name: &str = &attr.name.local; let attr_name: &str = &attr.name.local;
@ -310,7 +359,7 @@ pub fn walk_and_embed_assets(
if get_node_name(&get_parent_node(&node)) == Some("picture") { if get_node_name(&get_parent_node(&node)) == Some("picture") {
if opt_no_images { if opt_no_images {
attr.value.clear(); attr.value.clear();
attr.value.push_slice(TRANSPARENT_PIXEL); attr.value.push_slice(empty_image!());
} else { } else {
let srcset_full_url = let srcset_full_url =
resolve_url(&url, attr.value.trim()).unwrap_or_default(); resolve_url(&url, attr.value.trim()).unwrap_or_default();

View File

@ -7,3 +7,11 @@ macro_rules! str {
ToString::to_string(&$val) ToString::to_string(&$val)
}; };
} }
#[macro_export]
macro_rules! empty_image {
() => {
"data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAQAAADY4iz3AAAAEUlEQVR42mNkwAkYR6UolgIACvgADsuK6xYAAAAASUVORK5CYII="
};
}

View File

@ -162,15 +162,18 @@ fn passing_remove_images_from_data_url() -> Result<(), Box<dyn std::error::Error
// STDOUT should contain HTML with no images // STDOUT should contain HTML with no images
assert_eq!( assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(), std::str::from_utf8(&out.stdout).unwrap(),
"<html>\ format!(
"<html>\
<head>\ <head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"img-src data:;\"></meta>\ <meta http-equiv=\"Content-Security-Policy\" content=\"img-src data:;\"></meta>\
</head>\ </head>\
<body>\ <body>\
<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\ <img src=\"{empty_image}\">\
Hi\ Hi\
</body>\ </body>\
</html>\n" </html>\n",
empty_image = empty_image!()
)
); );
// STDERR should be empty // STDERR should be empty
@ -229,7 +232,8 @@ fn passing_local_file_target_input() -> Result<(), Box<dyn std::error::Error>> {
// STDOUT should contain HTML from the local file // STDOUT should contain HTML from the local file
assert_eq!( assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(), std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\n \ "\
<!DOCTYPE html><html lang=\"en\"><head>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \ <title>Local HTML file</title>\n \
<link href=\"data:text/css;base64,Ym9keSB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgY29sb3I6ICNmZmY7Cn0K\" rel=\"stylesheet\" type=\"text/css\">\n \ <link href=\"data:text/css;base64,Ym9keSB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgY29sb3I6ICNmZmY7Cn0K\" rel=\"stylesheet\" type=\"text/css\">\n \
@ -238,16 +242,19 @@ fn passing_local_file_target_input() -> Result<(), Box<dyn std::error::Error>> {
<a href=\"file://local-file.html/\">Tricky href</a>\n \ <a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \ <a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==\"></script>\n\n\n\n\ <script src=\"data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==\"></script>\n\n\n\n\
</body></html>\n" </body></html>\n\
"
); );
// STDERR should contain list of retrieved file URLs // STDERR should contain list of retrieved file URLs
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/local-file.html\n\
{file}{cwd}/src/tests/data/local-style.css\n\ {file}{cwd}/src/tests/data/local-style.css\n\
{file}{cwd}/src/tests/data/local-script.js\n", {file}{cwd}/src/tests/data/local-script.js\n\
",
file = file_url_protocol, file = file_url_protocol,
cwd = cwd_normalized cwd = cwd_normalized
) )
@ -286,17 +293,22 @@ fn passing_local_file_target_input_absolute_target_path() -> Result<(), Box<dyn
// STDOUT should contain HTML from the local file // STDOUT should contain HTML from the local file
assert_eq!( assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(), std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\ format!(
"\
<!DOCTYPE html><html lang=\"en\"><head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'unsafe-inline' data:; style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \ <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'unsafe-inline' data:; style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \ <title>Local HTML file</title>\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \ <link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \ <link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \
<img alt=\"\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\n \ <img alt=\"\" src=\"{empty_image}\">\n \
<a href=\"file://local-file.html/\">Tricky href</a>\n \ <a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \ <a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"\"></script>\n\n\n\n\ <script src=\"\"></script>\n\n\n\n\
</body></html>\n" </body></html>\n\
",
empty_image = empty_image!()
)
); );
// STDERR should contain only the target file // STDERR should contain only the target file
@ -342,17 +354,22 @@ fn passing_local_file_url_target_input() -> Result<(), Box<dyn std::error::Error
// STDOUT should contain HTML from the local file // STDOUT should contain HTML from the local file
assert_eq!( assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(), std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\ format!(
"\
<!DOCTYPE html><html lang=\"en\"><head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \ <meta http-equiv=\"Content-Security-Policy\" content=\"style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \ <title>Local HTML file</title>\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \ <link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \ <link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \
<img alt=\"\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\n \ <img alt=\"\" src=\"{empty_image}\">\n \
<a href=\"file://local-file.html/\">Tricky href</a>\n \ <a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \ <a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"\"></script>\n\n\n\n\ <script src=\"\"></script>\n\n\n\n\
</body></html>\n" </body></html>\n\
",
empty_image = empty_image!()
)
); );
// STDERR should contain list of retrieved file URLs // STDERR should contain list of retrieved file URLs
@ -458,7 +475,8 @@ fn passing_css_import_string() -> Result<(), Box<dyn std::error::Error>> {
let mut file_html = NamedTempFile::new()?; let mut file_html = NamedTempFile::new()?;
writeln!( writeln!(
file_html, file_html,
"<style>\n\ "\
<style>\n\
@charset 'UTF-8';\n\ @charset 'UTF-8';\n\
\n\ \n\
@import '{file}{css_path}';\n\ @import '{file}{css_path}';\n\
@ -466,7 +484,8 @@ fn passing_css_import_string() -> Result<(), Box<dyn std::error::Error>> {
@import url({file}{css_path});\n\ @import url({file}{css_path});\n\
\n\ \n\
@import url('{file}{css_path}')\n\ @import url('{file}{css_path}')\n\
</style>\n", </style>\n\
",
file = file_url_prefix, file = file_url_prefix,
css_path = str!(file_css.path().to_str().unwrap()).replace("\\", "/"), css_path = str!(file_css.path().to_str().unwrap()).replace("\\", "/"),
)?; )?;

View File

@ -40,13 +40,16 @@ height: calc(100vh - 10pt)";
true, true,
true, true,
), ),
"/* border: none;*/\ format!(
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); \ "/* border: none;*/\
list-style: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');\ background-image: url('{empty_image}'); \
list-style: url('{empty_image}');\
width:99.998%; \ width:99.998%; \
margin-top: -20px; \ margin-top: -20px; \
line-height: -1; \ line-height: -1; \
height: calc(100vh - 10pt)" height: calc(100vh - 10pt)",
empty_image = empty_image!()
)
); );
} }
@ -64,21 +67,17 @@ line-height: -1; \
height: calc(100vh - 10pt)"; height: calc(100vh - 10pt)";
assert_eq!( assert_eq!(
css::embed_css( css::embed_css(cache, &client, "", &STYLE, true, true,),
cache, format!(
&client, "/* border: none;*/\
"", background-image: url('{empty_image}'); \
&STYLE, list-style: url('{empty_image}');\
true,
true,
),
"/* border: none;*/\
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); \
list-style: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');\
width:99.998%; \ width:99.998%; \
margin-top: -20px; \ margin-top: -20px; \
line-height: -1; \ line-height: -1; \
height: calc(100vh - 10pt)" height: calc(100vh - 10pt)",
empty_image = empty_image!()
)
); );
} }

View File

@ -197,18 +197,19 @@ fn passing_no_images() {
assert_eq!( assert_eq!(
buf.iter().map(|&c| c as char).collect::<String>(), buf.iter().map(|&c| c as char).collect::<String>(),
"<html>\ format!(
"<html>\
<head>\ <head>\
<link rel=\"icon\" href=\"\">\ <link rel=\"icon\" href=\"\">\
</head>\ </head>\
<body>\ <body>\
<div>\ <div>\
<img src=\"data:image/png;base64,\ <img src=\"{empty_image}\">\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0\
lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\
</div>\ </div>\
</body>\ </body>\
</html>" </html>",
empty_image = empty_image!()
)
); );
} }