diff --git a/src/html.rs b/src/html.rs
index 26ec0f6..9b2d97b 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -17,7 +17,8 @@ use crate::css::{embed_css, embed_stylesheet};
use crate::js::{attr_is_event_handler, escape_script_end_tag};
use crate::session::Session;
use crate::url::{
- EMPTY_IMAGE_DATA_URL, Url, clean_url, create_data_url, is_url_and_has_protocol, resolve_url,
+ EMPTY_IMAGE_DATA_URL, Url, clean_url, create_data_url, create_srcset_data_url,
+ is_url_and_has_protocol, resolve_url,
};
const FAVICON_VALUES: &[&str] = &["icon", "shortcut icon"];
@@ -160,7 +161,7 @@ pub fn embed_srcset(session: &mut Session, document_url: &Url, srcset: &str) ->
Ok((image_data, image_final_url, image_media_type, image_charset)) => {
if session.options.output_format == MonolithOutputFormat::HTML {
// Append retrieved asset as a data URL
- result.push_str(&create_data_url(
+ result.push_str(&create_srcset_data_url(
&image_media_type,
&image_charset,
image_data,
diff --git a/src/url.rs b/src/url.rs
index f12c4fa..9265633 100644
--- a/src/url.rs
+++ b/src/url.rs
@@ -1,6 +1,6 @@
use std::fmt::Write;
-use dataurl::{DataEncoding, DataUrl};
+use dataurl::{DataEncoding, DataUrl, Escaping};
pub use url::Url;
use crate::core::detect_media_type;
@@ -19,6 +19,11 @@ pub fn clean_url(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 moves it in without copying.
+///
+/// The data gets percent-encoded or base64-encoded, whichever is shorter, and percent-encoding only escapes
+/// what has to be, which keeps text assets like SVG and CSS small and readable. That's only safe inside
+/// double-quoted HTML attributes and CSS strings, which is where monolith puts data URLs; srcset is the exception,
+/// see create_srcset_data_url().
pub fn create_data_url(
media_type: &str,
charset: &str,
@@ -26,8 +31,33 @@ pub fn create_data_url(
final_asset_url: &Url,
fragment: Option<&str>,
) -> String {
- let data: Vec = data.into();
+ let mut data_url = new_data_url(media_type, charset, data.into(), final_asset_url, fragment);
+ data_url.set_data_encoding(DataEncoding::Shortest);
+ data_url.set_escaping(Escaping::Minimal);
+ serialize_data_url(&data_url)
+}
+/// Same as create_data_url(), but always uses base64, since srcset gets split on whitespace
+/// and has trailing commas removed from URLs, which percent-encoded data could contain.
+pub fn create_srcset_data_url(
+ media_type: &str,
+ charset: &str,
+ data: impl Into>,
+ final_asset_url: &Url,
+ fragment: Option<&str>,
+) -> String {
+ let mut data_url = new_data_url(media_type, charset, data.into(), final_asset_url, fragment);
+ data_url.set_data_encoding(DataEncoding::Base64);
+ serialize_data_url(&data_url)
+}
+
+fn new_data_url(
+ media_type: &str,
+ charset: &str,
+ data: Vec,
+ final_asset_url: &Url,
+ fragment: Option<&str>,
+) -> DataUrl {
// TODO: move this block out of this function
let media_type: String = if media_type.is_empty() {
detect_media_type(&data, final_asset_url)
@@ -36,7 +66,6 @@ pub fn create_data_url(
};
let mut data_url = DataUrl::new();
- data_url.set_data_encoding(DataEncoding::Base64);
data_url.set_data(data);
data_url.set_fragment(fragment);
@@ -52,6 +81,10 @@ pub fn create_data_url(
let _ = data_url.set_parameter("charset", Some(charset));
}
+ data_url
+}
+
+fn serialize_data_url(data_url: &DataUrl) -> String {
// 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();
diff --git a/tests/cli/basic.rs b/tests/cli/basic.rs
index d1ca8c1..d6ea385 100644
--- a/tests/cli/basic.rs
+++ b/tests/cli/basic.rs
@@ -119,11 +119,11 @@ mod passing {
@charset "UTF-8";
- @import "data:text/css;charset=utf-8;base64,Ym9keXtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7Y29sb3I6I2ZmZn0K";
+ @import "data:text/css;charset=utf-8,body{background-color:%23000;color:%23fff}%0A";
- @import url("data:text/css;charset=utf-8;base64,Ym9keXtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7Y29sb3I6I2ZmZn0K");
+ @import url("data:text/css;charset=utf-8,body{background-color:%23000;color:%23fff}%0A");
- @import url("data:text/css;charset=utf-8;base64,Ym9keXtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7Y29sb3I6I2ZmZn0K");
+ @import url("data:text/css;charset=utf-8,body{background-color:%23000;color:%23fff}%0A");