diff --git a/src/css.rs b/src/css.rs index 3357a56..560c163 100644 --- a/src/css.rs +++ b/src/css.rs @@ -2,7 +2,9 @@ use cssparser::{ParseError, Parser, ParserInput, SourcePosition, Token}; use reqwest::blocking::Client; use std::collections::HashMap; -use crate::utils::{data_to_data_url, get_url_fragment, is_http_url, resolve_url, retrieve_asset}; +use crate::utils::{ + data_to_data_url, get_url_fragment, is_http_url, resolve_url, retrieve_asset, url_with_fragment, +}; const CSS_PROPS_WITH_IMAGE_URLS: &[&str] = &[ // Universal @@ -173,32 +175,34 @@ pub fn process_css<'a>( let import_url_fragment = get_url_fragment(import_full_url.clone()); match retrieve_asset(cache, client, &parent_url, &import_full_url, opt_silent) { Ok((import_contents, import_final_url, _import_media_type)) => { - result.push_str( - enquote( - data_to_data_url( - "text/css", - embed_css( - cache, - client, - &import_final_url, - &String::from_utf8_lossy(&import_contents), - opt_no_fonts, - opt_no_images, - opt_silent, - ) - .as_bytes(), - &import_final_url, - &import_url_fragment, - ), - false, + let import_data_url = data_to_data_url( + "text/css", + embed_css( + cache, + client, + &import_final_url, + &String::from_utf8_lossy(&import_contents), + opt_no_fonts, + opt_no_images, + opt_silent, ) - .as_str(), + .as_bytes(), + &import_final_url, + ); + let assembled_url: String = url_with_fragment( + import_data_url.as_str(), + import_url_fragment.as_str(), ); + result.push_str(enquote(assembled_url, false).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(import_full_url.clone()) { - result.push_str(enquote(import_full_url, false).as_str()); + let assembled_url: String = url_with_fragment( + import_full_url.as_str(), + import_url_fragment.as_str(), + ); + result.push_str(enquote(assembled_url, false).as_str()); } } } @@ -222,18 +226,19 @@ pub fn process_css<'a>( opt_silent, ) { Ok((data, final_url, media_type)) => { - let data_url = data_to_data_url( - &media_type, - &data, - &final_url, - &url_fragment, - ); - result.push_str(enquote(data_url, false).as_str()); + let data_url = data_to_data_url(&media_type, &data, &final_url); + let assembled_url: String = + url_with_fragment(data_url.as_str(), url_fragment.as_str()); + result.push_str(enquote(assembled_url, false).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(resolved_url.clone()) { - result.push_str(enquote(resolved_url, false).as_str()); + let assembled_url: String = url_with_fragment( + resolved_url.as_str(), + url_fragment.as_str(), + ); + result.push_str(enquote(assembled_url, false).as_str()); } } } @@ -320,14 +325,17 @@ pub fn process_css<'a>( ) .as_bytes(), &final_url, - &url_fragment, ); - result.push_str(enquote(data_url, false).as_str()); + let assembled_url: String = + url_with_fragment(data_url.as_str(), url_fragment.as_str()); + result.push_str(enquote(assembled_url, false).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(full_url.clone()) { - result.push_str(enquote(full_url, false).as_str()); + let assembled_url: String = + url_with_fragment(full_url.as_str(), url_fragment.as_str()); + result.push_str(enquote(assembled_url, false).as_str()); } } } @@ -339,14 +347,17 @@ pub fn process_css<'a>( let url_fragment = get_url_fragment(full_url.clone()); match retrieve_asset(cache, client, &parent_url, &full_url, opt_silent) { Ok((data, final_url, media_type)) => { - let data_url = - data_to_data_url(&media_type, &data, &final_url, &url_fragment); - result.push_str(enquote(data_url, false).as_str()); + let data_url = data_to_data_url(&media_type, &data, &final_url); + let assembled_url: String = + url_with_fragment(data_url.as_str(), url_fragment.as_str()); + result.push_str(enquote(assembled_url, false).as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(full_url.clone()) { - result.push_str(enquote(full_url, false).as_str()); + let assembled_url: String = + url_with_fragment(full_url.as_str(), url_fragment.as_str()); + result.push_str(enquote(assembled_url, false).as_str()); } } } diff --git a/src/html.rs b/src/html.rs index 8590655..13a5f01 100644 --- a/src/html.rs +++ b/src/html.rs @@ -2,6 +2,7 @@ use crate::css::embed_css; use crate::js::attr_is_event_handler; use crate::utils::{ data_to_data_url, get_url_fragment, is_http_url, resolve_url, retrieve_asset, url_has_protocol, + url_with_fragment, }; use base64; use html5ever::interface::QualName; @@ -91,19 +92,19 @@ pub fn embed_srcset( 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, - ); + let image_data_url = + data_to_data_url(&image_media_type, &image_data, &image_final_url); // Append retreved asset as a data URL - result.push_str(image_data_url.as_ref()); + let assembled_url: String = + url_with_fragment(image_data_url.as_str(), image_url_fragment.as_str()); + result.push_str(assembled_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()); + let assembled_url: String = + url_with_fragment(image_full_url.as_str(), image_url_fragment.as_str()); + result.push_str(assembled_url.as_ref()); } else { // Avoid breaking the structure in case if not an HTTP(S) URL result.push_str(empty_image!()); @@ -246,33 +247,36 @@ pub fn walk_and_embed_assets( &link_href_media_type, &link_href_data, &link_href_final_url, - &link_href_url_fragment, ); // Add new data URL href attribute + let assembled_url: String = url_with_fragment( + link_href_data_url.as_str(), + link_href_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new( None, ns!(), local_name!("href"), ), - value: Tendril::from_slice( - link_href_data_url.as_ref(), - ), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(link_href_full_url.clone()) { + let assembled_url: String = url_with_fragment( + link_href_full_url.as_str(), + link_href_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new( None, ns!(), local_name!("href"), ), - value: Tendril::from_slice( - link_href_full_url.as_ref(), - ), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } @@ -324,7 +328,6 @@ pub fn walk_and_embed_assets( "text/css", css.as_bytes(), &link_href_final_url, - "", ); // Add new data URL href attribute attrs_mut.push(Attribute { @@ -399,20 +402,27 @@ pub fn walk_and_embed_assets( &background_media_type, &background_data, &background_final_url, - &background_url_fragment, ); // Add new data URL background attribute + let assembled_url: String = url_with_fragment( + background_data_url.as_str(), + background_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("background")), - value: Tendril::from_slice(background_data_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(background_full_url.clone()) { + let assembled_url: String = url_with_fragment( + background_full_url.as_str(), + background_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("background")), - value: Tendril::from_slice(background_full_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } @@ -469,19 +479,26 @@ pub fn walk_and_embed_assets( &img_media_type, &img_data, &img_final_url, - &img_url_fragment, + ); + let assembled_url: String = url_with_fragment( + img_data_url.as_str(), + img_url_fragment.as_str(), ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("src")), - value: Tendril::from_slice(img_data_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(img_full_url.clone()) { + let assembled_url: String = url_with_fragment( + img_full_url.as_str(), + img_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("src")), - value: Tendril::from_slice(img_full_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } @@ -563,22 +580,27 @@ pub fn walk_and_embed_assets( &input_image_media_type, &input_image_data, &input_image_final_url, - &input_image_url_fragment, ); // Add data URL src attribute + let assembled_url: String = url_with_fragment( + input_image_data_url.as_str(), + input_image_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("src")), - value: Tendril::from_slice(input_image_data_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(input_image_full_url.clone()) { + let assembled_url: String = url_with_fragment( + input_image_full_url.as_str(), + input_image_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("src")), - value: Tendril::from_slice( - input_image_full_url.as_ref(), - ), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } @@ -610,20 +632,27 @@ pub fn walk_and_embed_assets( &image_media_type, &image_data, &image_final_url, - &image_url_fragment, ); // Add new data URL href attribute + let assembled_url: String = url_with_fragment( + image_data_url.as_str(), + image_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("href")), - value: Tendril::from_slice(image_data_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(image_full_url.clone()) { + let assembled_url: String = url_with_fragment( + image_full_url.as_str(), + image_url_fragment.as_str(), + ); attrs_mut.push(Attribute { name: QualName::new(None, ns!(), local_name!("href")), - value: Tendril::from_slice(image_full_url.as_ref()), + value: Tendril::from_slice(assembled_url.as_ref()), }); } } @@ -661,21 +690,23 @@ pub fn walk_and_embed_assets( &srcset_media_type, &srcset_data, &srcset_final_url, - &srcset_url_fragment, ); attr.value.clear(); - attr.value.push_slice(srcset_data_url.as_str()); + let assembled_url: String = url_with_fragment( + srcset_data_url.as_str(), + srcset_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(srcset_full_url.clone()) { attr.value.clear(); - attr.value.push_slice(srcset_full_url.as_str()); - if !srcset_url_fragment.is_empty() { - attr.value.push_slice("#"); - attr.value - .push_slice(srcset_url_fragment.as_str()); - } + let assembled_url: String = url_with_fragment( + srcset_full_url.as_str(), + srcset_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } } } @@ -739,7 +770,6 @@ pub fn walk_and_embed_assets( "application/javascript", &script_data, &script_final_url, - "", ); // Add new data URL src attribute attrs_mut.push(Attribute { @@ -844,16 +874,23 @@ pub fn walk_and_embed_assets( &frame_media_type, &frame_data, &frame_final_url, - &frame_url_fragment, ); attr.value.clear(); - attr.value.push_slice(frame_data_url.as_str()); + let assembled_url: String = url_with_fragment( + frame_data_url.as_str(), + frame_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(frame_full_url.clone()) { attr.value.clear(); - attr.value.push_slice(frame_full_url.as_str()); + let assembled_url: String = url_with_fragment( + frame_full_url.as_str(), + frame_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } } } @@ -896,16 +933,23 @@ pub fn walk_and_embed_assets( &video_poster_media_type, &video_poster_data, &video_poster_final_url, - &video_poster_url_fragment, ); attr.value.clear(); - attr.value.push_slice(video_poster_data_url.as_str()); + let assembled_url: String = url_with_fragment( + video_poster_data_url.as_str(), + video_poster_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } Err(_) => { // Keep remote reference if unable to retrieve the asset if is_http_url(video_poster_full_url.clone()) { attr.value.clear(); - attr.value.push_slice(video_poster_full_url.as_str()); + let assembled_url: String = url_with_fragment( + video_poster_full_url.as_str(), + video_poster_url_fragment.as_str(), + ); + attr.value.push_slice(assembled_url.as_str()); } } } diff --git a/src/tests/utils/data_to_data_url.rs b/src/tests/utils/data_to_data_url.rs index 9b4b4f6..2bd63c1 100644 --- a/src/tests/utils/data_to_data_url.rs +++ b/src/tests/utils/data_to_data_url.rs @@ -13,7 +13,7 @@ mod passing { fn encode_string_with_specific_media_type() { let mime = "application/javascript"; let data = "var word = 'hello';\nalert(word);\n"; - let data_url = utils::data_to_data_url(mime, data.as_bytes(), "", ""); + let data_url = utils::data_to_data_url(mime, data.as_bytes(), ""); assert_eq!( &data_url, @@ -24,8 +24,8 @@ mod passing { #[test] fn encode_append_fragment() { let data = "\n"; - let data_url = utils::data_to_data_url("text/css", data.as_bytes(), "", "fragment"); + let data_url = utils::data_to_data_url("image/svg+xml", data.as_bytes(), ""); - assert_eq!(&data_url, "data:text/css;base64,PHN2Zz48L3N2Zz4K#fragment"); + assert_eq!(&data_url, "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4K"); } } diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs index 8fc7e47..90378a7 100644 --- a/src/tests/utils/mod.rs +++ b/src/tests/utils/mod.rs @@ -11,3 +11,4 @@ mod is_http_url; mod resolve_url; mod retrieve_asset; mod url_has_protocol; +mod url_with_fragment; diff --git a/src/tests/utils/retrieve_asset.rs b/src/tests/utils/retrieve_asset.rs index 4aeeb5b..b04c63e 100644 --- a/src/tests/utils/retrieve_asset.rs +++ b/src/tests/utils/retrieve_asset.rs @@ -28,12 +28,12 @@ mod passing { ) .unwrap(); assert_eq!( - utils::data_to_data_url(&media_type, &data, &final_url, ""), - utils::data_to_data_url("text/html", "target".as_bytes(), "", "") + utils::data_to_data_url(&media_type, &data, &final_url), + utils::data_to_data_url("text/html", "target".as_bytes(), "") ); assert_eq!( final_url, - utils::data_to_data_url("text/html", "target".as_bytes(), "", "") + utils::data_to_data_url("text/html", "target".as_bytes(), "") ); assert_eq!(&media_type, "text/html"); } @@ -63,7 +63,7 @@ mod passing { false, ) .unwrap(); - assert_eq!(utils::data_to_data_url("application/javascript", &data, &final_url, ""), "data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg=="); + assert_eq!(utils::data_to_data_url("application/javascript", &data, &final_url), "data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg=="); assert_eq!( &final_url, &format!( diff --git a/src/tests/utils/url_with_fragment.rs b/src/tests/utils/url_with_fragment.rs new file mode 100644 index 0000000..50a51f9 --- /dev/null +++ b/src/tests/utils/url_with_fragment.rs @@ -0,0 +1,40 @@ +// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗ +// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝ +// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗ +// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║ +// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝ +// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + +#[cfg(test)] +mod passing { + use crate::utils; + + #[test] + fn url_with_fragment_url() { + let url = "https://localhost.localdomain/path/"; + let fragment = "test"; + let assembled_url = utils::url_with_fragment(url, fragment); + + assert_eq!(&assembled_url, "https://localhost.localdomain/path/#test"); + } + #[test] + fn url_with_fragment_empty_url() { + let url = "https://localhost.localdomain/path/"; + let fragment = ""; + let assembled_url = utils::url_with_fragment(url, fragment); + + assert_eq!(&assembled_url, "https://localhost.localdomain/path/"); + } + + #[test] + fn url_with_fragment_data_url() { + let url = "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4K"; + let fragment = "fragment"; + let assembled_url = utils::url_with_fragment(url, fragment); + + assert_eq!( + &assembled_url, + "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4K#fragment" + ); + } +} diff --git a/src/utils.rs b/src/utils.rs index 11aecb2..a628c04 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,24 +38,14 @@ const PLAINTEXT_MEDIA_TYPES: &[&str] = &[ "text/plain", ]; -pub fn data_to_data_url(media_type: &str, data: &[u8], url: &str, fragment: &str) -> String { +pub fn data_to_data_url(media_type: &str, data: &[u8], url: &str) -> String { let media_type: String = if media_type.is_empty() { detect_media_type(data, &url) } else { media_type.to_string() }; - let hash: String = if fragment != "" { - format!("#{}", fragment) - } else { - str!() - }; - format!( - "data:{};base64,{}{}", - media_type, - base64::encode(data), - hash - ) + format!("data:{};base64,{}", media_type, base64::encode(data)) } pub fn detect_media_type(data: &[u8], url: &str) -> String { @@ -301,3 +291,14 @@ pub fn retrieve_asset( } } } + +pub fn url_with_fragment(url: &str, fragment: &str) -> String { + let mut result = str!(&url); + + if !fragment.is_empty() { + result += "#"; + result += fragment; + } + + result +}