diff --git a/Cargo.lock b/Cargo.lock index 4826a4edb8..a27cce7189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,6 +516,7 @@ dependencies = [ "byteorder", "chrono", "clap", + "data-url", "deno_core", "deno_doc", "deno_lint", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ea1c5b9637..909ec15370 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -43,6 +43,7 @@ atty = "0.2.14" base64 = "0.13.0" byteorder = "1.4.3" clap = "2.33.3" +data-url = "0.1.0" dissimilar = "1.0.2" dprint-plugin-json = "0.10.1" dprint-plugin-markdown = "0.6.2" diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index b5cc1c838d..471873195f 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -10,6 +10,7 @@ use crate::http_util::FetchOnceResult; use crate::media_type::MediaType; use crate::text_encoding; use crate::version::get_user_agent; +use data_url::DataUrl; use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::uri_error; @@ -153,40 +154,6 @@ pub fn get_source_from_bytes( Ok(source) } -fn get_source_from_data_url( - specifier: &ModuleSpecifier, -) -> Result<(String, MediaType, String), AnyError> { - if specifier.scheme() != "data" { - return Err(custom_error( - "BadScheme", - format!("Unexpected scheme of \"{}\"", specifier.scheme()), - )); - } - let path = specifier.path(); - let mut parts = path.splitn(2, ','); - let media_type_part = - percent_encoding::percent_decode_str(parts.next().unwrap()) - .decode_utf8()?; - let data_part = if let Some(data) = parts.next() { - data - } else { - return Err(custom_error( - "BadUrl", - "The data URL is badly formed, missing a comma.", - )); - }; - let (media_type, maybe_charset) = - map_content_type(specifier, Some(media_type_part.to_string())); - let is_base64 = media_type_part.rsplit(';').any(|p| p == "base64"); - let bytes = if is_base64 { - base64::decode(data_part)? - } else { - percent_encoding::percent_decode_str(data_part).collect() - }; - let source = strip_shebang(get_source_from_bytes(bytes, maybe_charset)?); - Ok((source, media_type, media_type_part.to_string())) -} - /// Return a validated scheme for a given module specifier. fn get_validated_scheme( specifier: &ModuleSpecifier, @@ -430,8 +397,18 @@ impl FileFetcher { )); } - let (source, media_type, content_type) = - get_source_from_data_url(specifier)?; + let data_url = DataUrl::process(specifier.as_str()) + .map_err(|e| uri_error(format!("{:?}", e)))?; + let mime = data_url.mime_type(); + let charset = mime.get_parameter("charset").map(|v| v.to_string()); + let (bytes, _) = data_url + .decode_to_vec() + .map_err(|e| uri_error(format!("{:?}", e)))?; + let source = strip_shebang(get_source_from_bytes(bytes, charset)?); + let content_type = format!("{}", mime); + let (media_type, _) = + map_content_type(specifier, Some(content_type.clone())); + let local = self .http_cache @@ -762,39 +739,6 @@ mod tests { assert_eq!(file.source, expected); } - #[test] - fn test_get_source_from_data_url() { - let fixtures = vec![ - ("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=", true, MediaType::TypeScript, "application/typescript;base64", "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n"), - ("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=?a=b&b=c", true, MediaType::TypeScript, "application/typescript;base64", "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n"), - ("data:text/plain,Hello%2C%20Deno!", true, MediaType::Unknown, "text/plain", "Hello, Deno!"), - ("data:,Hello%2C%20Deno!", true, MediaType::Unknown, "", "Hello, Deno!"), - ("data:application/javascript,console.log(\"Hello, Deno!\");%0A", true, MediaType::JavaScript, "application/javascript", "console.log(\"Hello, Deno!\");\n"), - ("data:text/jsx;base64,ZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24oKSB7CiAgcmV0dXJuIDxkaXY+SGVsbG8gRGVubyE8L2Rpdj4KfQo=", true, MediaType::Jsx, "text/jsx;base64", "export default function() {\n return
Hello Deno!
\n}\n"), - ("data:text/tsx;base64,ZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24oKSB7CiAgcmV0dXJuIDxkaXY+SGVsbG8gRGVubyE8L2Rpdj4KfQo=", true, MediaType::Tsx, "text/tsx;base64", "export default function() {\n return
Hello Deno!
\n}\n"), - ]; - - for ( - url_str, - expected_ok, - expected_media_type, - expected_media_type_str, - expected, - ) in fixtures - { - let specifier = resolve_url(url_str).unwrap(); - let actual = get_source_from_data_url(&specifier); - assert_eq!(actual.is_ok(), expected_ok); - if expected_ok { - let (actual, actual_media_type, actual_media_type_str) = - actual.unwrap(); - assert_eq!(actual, expected); - assert_eq!(actual_media_type, expected_media_type); - assert_eq!(actual_media_type_str, expected_media_type_str); - } - } - } - #[test] fn test_get_validated_scheme() { let fixtures = vec![ diff --git a/cli/lsp/urls.rs b/cli/lsp/urls.rs index 770dd98c3b..559f1652ae 100644 --- a/cli/lsp/urls.rs +++ b/cli/lsp/urls.rs @@ -3,6 +3,8 @@ use crate::file_fetcher::map_content_type; use crate::media_type::MediaType; +use data_url::DataUrl; +use deno_core::error::uri_error; use deno_core::error::AnyError; use deno_core::url::Position; use deno_core::url::Url; @@ -49,17 +51,6 @@ fn hash_data_specifier(specifier: &ModuleSpecifier) -> String { crate::checksum::gen(&[file_name_str.as_bytes()]) } -fn data_url_media_type(specifier: &ModuleSpecifier) -> MediaType { - let path = specifier.path(); - let mut parts = path.splitn(2, ','); - let media_type_part = - percent_encoding::percent_decode_str(parts.next().unwrap()) - .decode_utf8_lossy(); - let (media_type, _) = - map_content_type(specifier, Some(media_type_part.into())); - media_type -} - /// A bi-directional map of URLs sent to the LSP client and internal module /// specifiers. We need to map internal specifiers into `deno:` schema URLs /// to allow the Deno language server to manage these as virtual documents. @@ -96,7 +87,11 @@ impl LspUrlMap { specifier.clone() } else { let specifier_str = if specifier.scheme() == "data" { - let media_type = data_url_media_type(specifier); + let data_url = DataUrl::process(specifier.as_str()) + .map_err(|e| uri_error(format!("{:?}", e)))?; + let mime = data_url.mime_type(); + let (media_type, _) = + map_content_type(specifier, Some(format!("{}", mime))); let extension = if media_type == MediaType::Unknown { "" } else { @@ -159,21 +154,6 @@ mod tests { ); } - #[test] - fn test_data_url_media_type() { - let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); - let actual = data_url_media_type(&fixture); - assert_eq!(actual, MediaType::TypeScript); - - let fixture = resolve_url("data:application/javascript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); - let actual = data_url_media_type(&fixture); - assert_eq!(actual, MediaType::JavaScript); - - let fixture = resolve_url("data:text/plain;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); - let actual = data_url_media_type(&fixture); - assert_eq!(actual, MediaType::Unknown); - } - #[test] fn test_lsp_url_map() { let mut map = LspUrlMap::default();