Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,9 @@ pub fn create_monolithic_document_from_data(

match session.retrieve_asset(/*&target_url, */ &base_url, &favicon_ico_url) {
Ok((data, final_url, media_type, charset)) => {
let favicon_data_url: Url =
create_data_url(&media_type, &charset, &data, &final_url);
dom = add_favicon(&dom.document, favicon_data_url.to_string());
let favicon_data_url: String =
create_data_url(&media_type, &charset, data, &final_url, None);
dom = add_favicon(&dom.document, favicon_data_url);
}
Err(_) => {
// Failed to retrieve /favicon.ico
Expand Down
43 changes: 24 additions & 19 deletions src/css.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,21 +272,19 @@ pub fn process_css<'a>(
import_media_type,
import_charset,
)) => {
let mut import_data_url = create_data_url(
let import_data_url = create_data_url(
&import_media_type,
"utf-8",
embed_stylesheet(
session,
&import_final_url,
&import_contents,
&import_charset,
)
.as_bytes(),
),
&import_final_url,
import_full_url.fragment(),
);
import_data_url.set_fragment(import_full_url.fragment());
result
.push_str(format_quoted_string(import_data_url.as_ref()).as_str());
result.push_str(format_quoted_string(&import_data_url).as_str());
}
Err(_) => {
// Keep remote reference if unable to retrieve the asset
Expand Down Expand Up @@ -314,11 +312,14 @@ pub fn process_css<'a>(
match session.retrieve_asset(document_url, &resolved_url) {
Ok((data, final_url, media_type, charset)) => {
// TODO: if it's @font-face, exclude definitions of non-woff/woff-2 fonts (if woff/woff-2 are present)
let mut data_url =
create_data_url(&media_type, &charset, &data, &final_url);
data_url.set_fragment(resolved_url.fragment());
result
.push_str(format_quoted_string(data_url.as_ref()).as_str());
let data_url = create_data_url(
&media_type,
&charset,
data,
&final_url,
resolved_url.fragment(),
);
result.push_str(format_quoted_string(&data_url).as_str());
}
Err(_) => {
// Keep remote reference if unable to retrieve the asset
Expand Down Expand Up @@ -404,14 +405,14 @@ pub fn process_css<'a>(
let full_url: Url = resolve_url(document_url, value);
match session.retrieve_asset(document_url, &full_url) {
Ok((css, final_url, media_type, charset)) => {
let mut data_url = create_data_url(
let data_url = create_data_url(
&media_type,
"utf-8",
embed_stylesheet(session, &final_url, &css, &charset).as_bytes(),
embed_stylesheet(session, &final_url, &css, &charset),
&final_url,
full_url.fragment(),
);
data_url.set_fragment(full_url.fragment());
result.push_str(format_quoted_string(data_url.as_ref()).as_str());
result.push_str(format_quoted_string(&data_url).as_str());
}
Err(_) => {
// Keep remote reference if unable to retrieve the asset
Expand All @@ -428,10 +429,14 @@ pub fn process_css<'a>(
if session.options.output_format == MonolithOutputFormat::HTML {
match session.retrieve_asset(document_url, &full_url) {
Ok((data, final_url, media_type, charset)) => {
let mut data_url =
create_data_url(&media_type, &charset, &data, &final_url);
data_url.set_fragment(full_url.fragment());
result.push_str(format_quoted_string(data_url.as_ref()).as_str());
let data_url = create_data_url(
&media_type,
&charset,
data,
&final_url,
full_url.fragment(),
);
result.push_str(format_quoted_string(&data_url).as_str());
}
Err(_) => {
// Keep remote reference if unable to retrieve the asset
Expand Down
63 changes: 35 additions & 28 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,14 @@ pub fn embed_srcset(session: &mut Session, document_url: &Url, srcset: &str) ->
match session.retrieve_asset(document_url, &image_full_url) {
Ok((image_data, image_final_url, image_media_type, image_charset)) => {
if session.options.output_format == MonolithOutputFormat::HTML {
let mut image_data_url = create_data_url(
// Append retrieved asset as a data URL
result.push_str(&create_data_url(
&image_media_type,
&image_charset,
&image_data,
image_data,
&image_final_url,
);
// Append retrieved asset as a data URL
image_data_url.set_fragment(image_full_url.fragment());
result.push_str(image_data_url.as_ref());
image_full_url.fragment(),
));
} else {
// MHTML
result.push_str(image_full_url.as_ref());
Expand Down Expand Up @@ -808,9 +807,8 @@ pub fn retrieve_and_embed_asset(
let css: String = embed_stylesheet(session, &final_url, &data, &charset);

// Create and embed data URL (decoded stylesheets are always stored as UTF-8)
let css_data_url =
create_data_url(&media_type, "utf-8", css.as_bytes(), &final_url);
set_node_attr(node, attr_name, Some(css_data_url.to_string()));
let css_data_url = create_data_url(&media_type, "utf-8", css, &final_url, None);
set_node_attr(node, attr_name, Some(css_data_url));
} else if node_name == "frame" || node_name == "iframe" {
// (I)FRAMEs are also quite different from conventional resources
let mut frame_dom = html_to_dom(&data, charset.clone());
Expand Down Expand Up @@ -841,10 +839,14 @@ pub fn retrieve_and_embed_asset(
serialize(&mut frame_data, &serializable, SerializeOpts::default()).unwrap();

// Create and embed data URL (serialized frames are always UTF-8)
let mut frame_data_url =
create_data_url(&media_type, "utf-8", &frame_data, &final_url);
frame_data_url.set_fragment(resolved_url.fragment());
set_node_attr(node, attr_name, Some(frame_data_url.to_string()));
let frame_data_url = create_data_url(
&media_type,
"utf-8",
frame_data,
&final_url,
resolved_url.fragment(),
);
set_node_attr(node, attr_name, Some(frame_data_url));
} else {
// Every other type of element gets processed here

Expand Down Expand Up @@ -894,17 +896,25 @@ pub fn retrieve_and_embed_asset(
}
} else {
// Create and embed data URL
let mut data_url =
create_data_url(&script_media_type, &charset, &data, &final_url);
data_url.set_fragment(resolved_url.fragment());
set_node_attr(node, attr_name, Some(data_url.to_string()));
let data_url = create_data_url(
&script_media_type,
&charset,
data,
&final_url,
resolved_url.fragment(),
);
set_node_attr(node, attr_name, Some(data_url));
}
} else {
// Create and embed data URL
let mut data_url =
create_data_url(&media_type, &charset, &data, &final_url);
data_url.set_fragment(resolved_url.fragment());
set_node_attr(node, attr_name, Some(data_url.to_string()));
let data_url = create_data_url(
&media_type,
&charset,
data,
&final_url,
resolved_url.fragment(),
);
set_node_attr(node, attr_name, Some(data_url));
}
}
}
Expand Down Expand Up @@ -1230,17 +1240,14 @@ pub fn walk(session: &mut Session, document_url: &Url, node: &Handle) {
}
} else {
// It's likely a raster image; embed it as data URL
let image_asset_data: Url = create_data_url(
let image_asset_data: String = create_data_url(
&media_type,
&charset,
&data,
data,
&final_url,
None,
);
set_node_attr(
node,
attr_name,
Some(image_asset_data.to_string()),
);
set_node_attr(node, attr_name, Some(image_asset_data));
}
}
Err(_) => {
Expand Down
22 changes: 19 additions & 3 deletions src/url.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Write;

use dataurl::{DataEncoding, DataUrl};
pub use url::Url;

Expand All @@ -15,17 +17,28 @@ pub fn clean_url(url: Url) -> Url {
url
}

pub fn create_data_url(media_type: &str, charset: &str, data: &[u8], final_asset_url: &Url) -> Url {
/// Creates a data URL for an asset, with an optional fragment (already percent-encoded, as Url::fragment() returns it).
/// Passing data as a Vec<u8> moves it in without copying.
pub fn create_data_url(
media_type: &str,
charset: &str,
data: impl Into<Vec<u8>>,
final_asset_url: &Url,
fragment: Option<&str>,
) -> String {
let data: Vec<u8> = data.into();

// TODO: move this block out of this function
let media_type: String = if media_type.is_empty() {
detect_media_type(data, final_asset_url)
detect_media_type(&data, final_asset_url)
} else {
media_type.to_string()
};

let mut data_url = DataUrl::new();
data_url.set_data_encoding(DataEncoding::Base64);
data_url.set_data(data);
data_url.set_fragment(fragment);

// Invalid media types get omitted, which makes browsers treat the data as text/plain, same as they would anyway
if !media_type.is_empty() {
Expand All @@ -39,7 +52,10 @@ pub fn create_data_url(media_type: &str, charset: &str, data: &[u8], final_asset
let _ = data_url.set_parameter("charset", Some(charset));
}

Url::parse(&data_url.to_string()).unwrap()
// Allocating the exact size upfront avoids reallocations (and copies) while the string grows
let mut result = String::with_capacity(data_url.encoded_len());
write!(result, "{data_url}").unwrap();
result
}

pub fn domain_is_within_domain(domain: &str, domain_to_match_against: &str) -> bool {
Expand Down
14 changes: 11 additions & 3 deletions tests/css/embed_css.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ mod passing {
("", "p::before{content:'café'}".as_bytes()),
("", b"p::before{content:'caf\xe9'}".as_slice()),
] {
let asset = monolith::url::create_data_url("text/css", charset, bytes, &url);
let asset = monolith::url::create_data_url("text/css", charset, bytes, &url, None);

for source in [
format!("@import \"{}\";", asset),
Expand Down Expand Up @@ -68,6 +68,7 @@ mod passing {
"",
b"@charset \"windows-1252\";p::before{content:'caf\xe9'}",
&url,
None,
);
let mut session = Session::new(None, None, MonolithOptions::default());
let result = embed_css(&mut session, &url, &format!("@import \"{}\";", asset));
Expand All @@ -89,6 +90,7 @@ mod passing {
"",
b"p{font-family:'\x83\x81\x83\x43\x83\x8a\x83\x49'}",
&url,
None,
);
let mut session = Session::new(None, None, MonolithOptions::default());
session.environment_encoding = Some(encoding_rs::SHIFT_JIS);
Expand All @@ -109,13 +111,19 @@ mod passing {
fn nested_import_inherits_importing_stylesheet_encoding() {
let url = Url::parse("https://example.com/").unwrap();
// EUC-JP "両", which also happens to be valid UTF-8 ("ξ")
let inner =
monolith::url::create_data_url("text/css", "", b"p::before{content:'\xce\xbe'}", &url);
let inner = monolith::url::create_data_url(
"text/css",
"",
b"p::before{content:'\xce\xbe'}",
&url,
None,
);
let outer = monolith::url::create_data_url(
"text/css",
"",
format!("@charset \"EUC-JP\";@import \"{}\";", inner).as_bytes(),
&url,
None,
);
let mut session = Session::new(None, None, MonolithOptions::default());
session.environment_encoding = Some(encoding_rs::SHIFT_JIS);
Expand Down
8 changes: 4 additions & 4 deletions tests/html/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ mod passing {
"@charset \"UTF-8\";p::before{content:\"café\"}",
),
] {
let asset = monolith::url::create_data_url("text/css", charset, bytes, &url);
let asset = monolith::url::create_data_url("text/css", charset, bytes, &url, None);
let source = format!("<link rel=\"stylesheet\" href=\"{}\">", asset);
let dom = html::html_to_dom(&source.into_bytes(), "utf-8".to_string());
let mut session = Session::new(None, None, MonolithOptions::default());
Expand Down Expand Up @@ -80,7 +80,7 @@ mod passing {
#[test]
fn linked_stylesheet_environment_encoding() {
let url = Url::parse("https://example.com/").unwrap();
let asset = monolith::url::create_data_url("text/css", "", SHIFT_JIS_CSS, &url);
let asset = monolith::url::create_data_url("text/css", "", SHIFT_JIS_CSS, &url, None);
let source = format!("<link rel=\"stylesheet\" href=\"{}\">", asset);
let dom = html::html_to_dom(&source.into_bytes(), "utf-8".to_string());
let mut session = Session::new(None, None, MonolithOptions::default());
Expand All @@ -102,14 +102,14 @@ mod passing {
#[test]
fn frame_encoding() {
let url = Url::parse("https://example.com/").unwrap();
let css = monolith::url::create_data_url("text/css", "", SHIFT_JIS_CSS, &url);
let css = monolith::url::create_data_url("text/css", "", SHIFT_JIS_CSS, &url, None);
let mut frame_html: Vec<u8> = format!(
"<html><head><meta charset=\"Shift_JIS\"><link rel=\"stylesheet\" href=\"{}\"></head><body>",
css
)
.into_bytes();
frame_html.extend_from_slice(b"\x83\x81\x83\x43\x83\x8a\x83\x49</body></html>");
let frame = monolith::url::create_data_url("text/html", "", &frame_html, &url);
let frame = monolith::url::create_data_url("text/html", "", frame_html, &url, None);
let source = format!("<iframe src=\"{}\"></iframe>", frame);
let dom = html::html_to_dom(&source.into_bytes(), "utf-8".to_string());
let mut session = Session::new(None, None, MonolithOptions::default());
Expand Down
8 changes: 4 additions & 4 deletions tests/session/retrieve_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ mod passing {
assert_eq!(&media_type, "text/html");
assert_eq!(&charset, "");
assert_eq!(
url::create_data_url(&media_type, &charset, &data, &final_url),
Url::parse("data:text/html;base64,dGFyZ2V0").unwrap(),
url::create_data_url(&media_type, &charset, data, &final_url, None),
"data:text/html;base64,dGFyZ2V0",
);
assert_eq!(
final_url,
Expand Down Expand Up @@ -72,8 +72,8 @@ mod passing {
assert_eq!(&charset, "");
let data_url = "data:text/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==";
assert_eq!(
url::create_data_url(&media_type, &charset, &data, &final_url),
Url::parse(data_url).unwrap()
url::create_data_url(&media_type, &charset, data, &final_url, None),
data_url
);
assert_eq!(
final_url,
Expand Down
Loading
Loading