diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/disk_cache.rs | 2 | ||||
-rw-r--r-- | cli/file_fetcher.rs | 132 | ||||
-rw-r--r-- | cli/http_cache.rs | 2 | ||||
-rw-r--r-- | cli/main.rs | 2 | ||||
-rw-r--r-- | cli/program_state.rs | 8 | ||||
-rw-r--r-- | cli/source_maps.rs | 1 | ||||
-rw-r--r-- | cli/specifier_handler.rs | 6 | ||||
-rw-r--r-- | cli/standalone.rs | 3 | ||||
-rw-r--r-- | cli/tests/import_blob_url.ts | 13 | ||||
-rw-r--r-- | cli/tests/import_blob_url.ts.out | 3 | ||||
-rw-r--r-- | cli/tests/import_blob_url_error_stack.ts | 13 | ||||
-rw-r--r-- | cli/tests/import_blob_url_error_stack.ts.out | 6 | ||||
-rw-r--r-- | cli/tests/import_blob_url_import_relative.ts | 8 | ||||
-rw-r--r-- | cli/tests/import_blob_url_import_relative.ts.out | 4 | ||||
-rw-r--r-- | cli/tests/import_blob_url_imports.ts | 11 | ||||
-rw-r--r-- | cli/tests/import_blob_url_imports.ts.out | 1 | ||||
-rw-r--r-- | cli/tests/import_blob_url_jsx.ts | 16 | ||||
-rw-r--r-- | cli/tests/import_blob_url_jsx.ts.out | 1 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 28 | ||||
-rw-r--r-- | cli/tests/unsupported_dynamic_import_scheme.out | 1 | ||||
-rw-r--r-- | cli/tsc.rs | 48 |
21 files changed, 287 insertions, 22 deletions
diff --git a/cli/disk_cache.rs b/cli/disk_cache.rs index 2f2cc9e4f..0f5d16f98 100644 --- a/cli/disk_cache.rs +++ b/cli/disk_cache.rs @@ -67,7 +67,7 @@ impl DiskCache { out.push(path_seg); } } - "http" | "https" | "data" => out = url_to_filename(url)?, + "http" | "https" | "data" | "blob" => out = url_to_filename(url)?, "file" => { let path = match url.to_file_path() { Ok(path) => path, diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 1c97d7018..0c1d9519d 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -18,9 +18,11 @@ use deno_core::futures; use deno_core::futures::future::FutureExt; use deno_core::ModuleSpecifier; use deno_runtime::deno_fetch::reqwest; +use deno_runtime::deno_file::BlobUrlStore; use deno_runtime::permissions::Permissions; use log::debug; use log::info; +use std::borrow::Borrow; use std::collections::HashMap; use std::env; use std::fs; @@ -32,7 +34,8 @@ use std::sync::Arc; use std::sync::Mutex; static DENO_AUTH_TOKENS: &str = "DENO_AUTH_TOKENS"; -pub const SUPPORTED_SCHEMES: [&str; 4] = ["data", "file", "http", "https"]; +pub const SUPPORTED_SCHEMES: [&str; 5] = + ["data", "blob", "file", "http", "https"]; /// A structure representing a source file. #[derive(Debug, Clone, Eq, PartialEq)] @@ -317,6 +320,7 @@ pub struct FileFetcher { cache_setting: CacheSetting, http_cache: HttpCache, http_client: reqwest::Client, + blob_url_store: BlobUrlStore, } impl FileFetcher { @@ -325,6 +329,7 @@ impl FileFetcher { cache_setting: CacheSetting, allow_remote: bool, ca_data: Option<Vec<u8>>, + blob_url_store: BlobUrlStore, ) -> Result<Self, AnyError> { Ok(Self { auth_tokens: AuthTokens::new(env::var(DENO_AUTH_TOKENS).ok()), @@ -333,6 +338,7 @@ impl FileFetcher { cache_setting, http_cache, http_client: create_http_client(get_user_agent(), ca_data)?, + blob_url_store, }) } @@ -446,6 +452,62 @@ impl FileFetcher { }) } + /// Get a blob URL. + fn fetch_blob_url( + &self, + specifier: &ModuleSpecifier, + ) -> Result<File, AnyError> { + debug!("FileFetcher::fetch_blob_url() - specifier: {}", specifier); + match self.fetch_cached(specifier, 0) { + Ok(Some(file)) => return Ok(file), + Ok(None) => {} + Err(err) => return Err(err), + } + + if self.cache_setting == CacheSetting::Only { + return Err(custom_error( + "NotFound", + format!( + "Specifier not found in cache: \"{}\", --cached-only is specified.", + specifier + ), + )); + } + + let blob_url_storage = self.blob_url_store.borrow(); + let blob = blob_url_storage.get(specifier)?.ok_or_else(|| { + custom_error( + "NotFound", + format!("Blob URL not found: \"{}\".", specifier), + ) + })?; + + let content_type = blob.media_type; + + let (media_type, maybe_charset) = + map_content_type(specifier, Some(content_type.clone())); + let source = + strip_shebang(get_source_from_bytes(blob.data, maybe_charset)?); + + let local = + self + .http_cache + .get_cache_filename(specifier) + .ok_or_else(|| { + generic_error("Cannot convert specifier to cached filename.") + })?; + let mut headers = HashMap::new(); + headers.insert("content-type".to_string(), content_type); + self.http_cache.set(specifier, headers, source.as_bytes())?; + + Ok(File { + local, + maybe_types: None, + media_type, + source, + specifier: specifier.clone(), + }) + } /// Asynchronously fetch remote source file specified by the URL following /// redirects. /// @@ -555,6 +617,12 @@ impl FileFetcher { self.cache.insert(specifier.clone(), file.clone()); } result + } else if scheme == "blob" { + let result = self.fetch_blob_url(specifier); + if let Ok(file) = &result { + self.cache.insert(specifier.clone(), file.clone()); + } + result } else if !self.allow_remote { Err(custom_error( "NoRemote", @@ -604,6 +672,7 @@ mod tests { use deno_core::error::get_custom_error_class; use deno_core::resolve_url; use deno_core::resolve_url_or_path; + use deno_runtime::deno_file::Blob; use std::rc::Rc; use tempfile::TempDir; @@ -611,14 +680,29 @@ mod tests { cache_setting: CacheSetting, maybe_temp_dir: Option<Rc<TempDir>>, ) -> (FileFetcher, Rc<TempDir>) { + let (file_fetcher, temp_dir, _) = + setup_with_blob_url_store(cache_setting, maybe_temp_dir); + (file_fetcher, temp_dir) + } + + fn setup_with_blob_url_store( + cache_setting: CacheSetting, + maybe_temp_dir: Option<Rc<TempDir>>, + ) -> (FileFetcher, Rc<TempDir>, BlobUrlStore) { let temp_dir = maybe_temp_dir.unwrap_or_else(|| { Rc::new(TempDir::new().expect("failed to create temp directory")) }); let location = temp_dir.path().join("deps"); - let file_fetcher = - FileFetcher::new(HttpCache::new(&location), cache_setting, true, None) - .expect("setup failed"); - (file_fetcher, temp_dir) + let blob_url_store = BlobUrlStore::default(); + let file_fetcher = FileFetcher::new( + HttpCache::new(&location), + cache_setting, + true, + None, + blob_url_store.clone(), + ) + .expect("setup failed"); + (file_fetcher, temp_dir, blob_url_store) } macro_rules! file_url { @@ -989,6 +1073,36 @@ mod tests { } #[tokio::test] + async fn test_fetch_blob_url() { + let (file_fetcher, _, blob_url_store) = + setup_with_blob_url_store(CacheSetting::Use, None); + + let specifier = blob_url_store.insert( + Blob { + data: + "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n" + .as_bytes() + .to_vec(), + media_type: "application/typescript".to_string(), + }, + None, + ); + + let result = file_fetcher + .fetch(&specifier, &Permissions::allow_all()) + .await; + assert!(result.is_ok()); + let file = result.unwrap(); + assert_eq!( + file.source, + "export const a = \"a\";\n\nexport enum A {\n A,\n B,\n C,\n}\n" + ); + assert_eq!(file.media_type, MediaType::TypeScript); + assert_eq!(file.maybe_types, None); + assert_eq!(file.specifier, specifier); + } + + #[tokio::test] async fn test_fetch_complex() { let _http_server_guard = test_util::http_server(); let (file_fetcher, temp_dir) = setup(CacheSetting::Use, None); @@ -1061,6 +1175,7 @@ mod tests { CacheSetting::ReloadAll, true, None, + BlobUrlStore::default(), ) .expect("setup failed"); let result = file_fetcher @@ -1087,6 +1202,7 @@ mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let specifier = @@ -1114,6 +1230,7 @@ mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let result = file_fetcher_02 @@ -1274,6 +1391,7 @@ mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let specifier = @@ -1304,6 +1422,7 @@ mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let result = file_fetcher_02 @@ -1413,6 +1532,7 @@ mod tests { CacheSetting::Use, false, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let specifier = @@ -1439,6 +1559,7 @@ mod tests { CacheSetting::Only, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let file_fetcher_02 = FileFetcher::new( @@ -1446,6 +1567,7 @@ mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not create file fetcher"); let specifier = diff --git a/cli/http_cache.rs b/cli/http_cache.rs index 7eff2b9be..37da00983 100644 --- a/cli/http_cache.rs +++ b/cli/http_cache.rs @@ -39,7 +39,7 @@ fn base_url_to_filename(url: &Url) -> Option<PathBuf> { }; out.push(host_port); } - "data" => (), + "data" | "blob" => (), scheme => { error!("Don't know how to create cache name for scheme: {}", scheme); return None; diff --git a/cli/main.rs b/cli/main.rs index e9e29c9db..7146d8548 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -122,6 +122,7 @@ fn create_web_worker_callback( ts_version: version::TYPESCRIPT.to_string(), no_color: !colors::use_color(), get_error_class_fn: Some(&crate::errors::get_error_class_name), + blob_url_store: program_state.blob_url_store.clone(), }; let mut worker = WebWorker::from_options( @@ -199,6 +200,7 @@ pub fn create_main_worker( no_color: !colors::use_color(), get_error_class_fn: Some(&crate::errors::get_error_class_name), location: program_state.flags.location.clone(), + blob_url_store: program_state.blob_url_store.clone(), }; let mut worker = MainWorker::from_options(main_module, permissions, &options); diff --git a/cli/program_state.rs b/cli/program_state.rs index f47dcf0e9..a41acdec3 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -14,6 +14,7 @@ use crate::module_graph::TypeLib; use crate::source_maps::SourceMapGetter; use crate::specifier_handler::FetchHandler; use crate::version; +use deno_runtime::deno_file::BlobUrlStore; use deno_runtime::inspector::InspectorServer; use deno_runtime::permissions::Permissions; @@ -56,6 +57,7 @@ pub struct ProgramState { pub maybe_import_map: Option<ImportMap>, pub maybe_inspector_server: Option<Arc<InspectorServer>>, pub ca_data: Option<Vec<u8>>, + pub blob_url_store: BlobUrlStore, } impl ProgramState { @@ -80,11 +82,14 @@ impl ProgramState { CacheSetting::Use }; + let blob_url_store = BlobUrlStore::default(); + let file_fetcher = FileFetcher::new( http_cache, cache_usage, !flags.no_remote, ca_data.clone(), + blob_url_store.clone(), )?; let lockfile = if let Some(filename) = &flags.lock { @@ -131,6 +136,7 @@ impl ProgramState { maybe_import_map, maybe_inspector_server, ca_data, + blob_url_store, }; Ok(Arc::new(program_state)) } @@ -257,7 +263,7 @@ impl ProgramState { match url.scheme() { // we should only be looking for emits for schemes that denote external // modules, which the disk_cache supports - "wasm" | "file" | "http" | "https" | "data" => (), + "wasm" | "file" | "http" | "https" | "data" | "blob" => (), _ => { return None; } diff --git a/cli/source_maps.rs b/cli/source_maps.rs index 8bc070418..d62c0f3e0 100644 --- a/cli/source_maps.rs +++ b/cli/source_maps.rs @@ -142,6 +142,7 @@ pub fn get_orig_position<G: SourceMapGetter>( // around it. Use the `file_name` we get from V8 if // `source_file_name` does not parse as a URL. let file_name = match deno_core::resolve_url(source_file_name) { + Ok(m) if m.scheme() == "blob" => file_name, Ok(m) => m.to_string(), Err(_) => file_name, }; diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs index 9f329819e..58815e15c 100644 --- a/cli/specifier_handler.rs +++ b/cli/specifier_handler.rs @@ -306,7 +306,9 @@ impl SpecifierHandler for FetchHandler { } })?; let url = &source_file.specifier; - let is_remote = !(url.scheme() == "file" || url.scheme() == "data"); + let is_remote = !(url.scheme() == "file" + || url.scheme() == "data" + || url.scheme() == "blob"); let filename = disk_cache.get_cache_filename_with_extension(url, "meta"); let maybe_version = if let Some(filename) = filename { if let Ok(bytes) = disk_cache.get(&filename) { @@ -569,6 +571,7 @@ pub mod tests { use crate::file_fetcher::CacheSetting; use crate::http_cache::HttpCache; use deno_core::resolve_url_or_path; + use deno_runtime::deno_file::BlobUrlStore; use tempfile::TempDir; macro_rules! map ( @@ -593,6 +596,7 @@ pub mod tests { CacheSetting::Use, true, None, + BlobUrlStore::default(), ) .expect("could not setup"); let disk_cache = deno_dir.gen_cache; diff --git a/cli/standalone.rs b/cli/standalone.rs index e3fb6fe43..83879a163 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -15,6 +15,7 @@ use deno_core::v8_set_flags; use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::OpState; +use deno_runtime::deno_file::BlobUrlStore; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsOptions; use deno_runtime::worker::MainWorker; @@ -158,6 +159,7 @@ pub async fn run( ) -> Result<(), AnyError> { let main_module = resolve_url(SPECIFIER)?; let permissions = Permissions::from_options(&metadata.permissions); + let blob_url_store = BlobUrlStore::default(); let module_loader = Rc::new(EmbeddedModuleLoader(source_code)); let create_web_worker_cb = Arc::new(|_| { todo!("Worker are currently not supported in standalone binaries"); @@ -189,6 +191,7 @@ pub async fn run( no_color: !colors::use_color(), get_error_class_fn: Some(&get_error_class_name), location: metadata.location, + blob_url_store, }; let mut worker = MainWorker::from_options(main_module.clone(), permissions, &options); diff --git a/cli/tests/import_blob_url.ts b/cli/tests/import_blob_url.ts new file mode 100644 index 000000000..86bb634e1 --- /dev/null +++ b/cli/tests/import_blob_url.ts @@ -0,0 +1,13 @@ +const blob = new Blob( + ['export const a = "a";\n\nexport enum A {\n A,\n B,\n C,\n}\n'], + { + type: "application/typescript", + }, +); +const url = URL.createObjectURL(blob); + +const a = await import(url); + +console.log(a.a); +console.log(a.A); +console.log(a.A.A); diff --git a/cli/tests/import_blob_url.ts.out b/cli/tests/import_blob_url.ts.out new file mode 100644 index 000000000..bfa0b9d94 --- /dev/null +++ b/cli/tests/import_blob_url.ts.out @@ -0,0 +1,3 @@ +a +{ "0": "A", "1": "B", "2": "C", A: 0, B: 1, C: 2 } +0 diff --git a/cli/tests/import_blob_url_error_stack.ts b/cli/tests/import_blob_url_error_stack.ts new file mode 100644 index 000000000..f9c4f2e9d --- /dev/null +++ b/cli/tests/import_blob_url_error_stack.ts @@ -0,0 +1,13 @@ +const blob = new Blob( + [ + "enum A {\n A,\n B,\n C,\n }\n \n export function a() {\n throw new Error(`Hello ${A.C}`);\n }\n ", + ], + { + type: "application/typescript", + }, +); +const url = URL.createObjectURL(blob); + +const { a } = await import(url); + +a(); diff --git a/cli/tests/import_blob_url_error_stack.ts.out b/cli/tests/import_blob_url_error_stack.ts.out new file mode 100644 index 000000000..18ca023e3 --- /dev/null +++ b/cli/tests/import_blob_url_error_stack.ts.out @@ -0,0 +1,6 @@ +[WILDCARD]error: Uncaught (in promise) Error: Hello 2 + throw new Error(`Hello ${A.C}`); + ^ + at a (blob:null/[WILDCARD]:8:10) + at file:///[WILDCARD]/cli/tests/import_blob_url_error_stack.ts:13:1 +[WILDCARD] diff --git a/cli/tests/import_blob_url_import_relative.ts b/cli/tests/import_blob_url_import_relative.ts new file mode 100644 index 000000000..ad130bdac --- /dev/null +++ b/cli/tests/import_blob_url_import_relative.ts @@ -0,0 +1,8 @@ +const blob = new Blob(['export { a } from "./a.ts";'], { + type: "application/javascript", +}); +const url = URL.createObjectURL(blob); + +const a = await import(url); + +console.log(a); diff --git a/cli/tests/import_blob_url_import_relative.ts.out b/cli/tests/import_blob_url_import_relative.ts.out new file mode 100644 index 000000000..9d47ab719 --- /dev/null +++ b/cli/tests/import_blob_url_import_relative.ts.out @@ -0,0 +1,4 @@ +error: Uncaught (in promise) TypeError: invalid URL: relative URL with a cannot-be-a-base base +const a = await import(url); + ^ + at async file://[WILDCARD]/cli/tests/import_blob_url_import_relative.ts:6:11 diff --git a/cli/tests/import_blob_url_imports.ts b/cli/tests/import_blob_url_imports.ts new file mode 100644 index 000000000..7c2b10865 --- /dev/null +++ b/cli/tests/import_blob_url_imports.ts @@ -0,0 +1,11 @@ +const blob = new Blob( + [ + 'export { printHello } from "http://localhost:4545/cli/tests/subdir/mod2.ts"', + ], + { type: "application/javascript" }, +); +const url = URL.createObjectURL(blob); + +const { printHello } = await import(url); + +printHello(); diff --git a/cli/tests/import_blob_url_imports.ts.out b/cli/tests/import_blob_url_imports.ts.out new file mode 100644 index 000000000..e965047ad --- /dev/null +++ b/cli/tests/import_blob_url_imports.ts.out @@ -0,0 +1 @@ +Hello diff --git a/cli/tests/import_blob_url_jsx.ts b/cli/tests/import_blob_url_jsx.ts new file mode 100644 index 000000000..8d645796a --- /dev/null +++ b/cli/tests/import_blob_url_jsx.ts @@ -0,0 +1,16 @@ +const blob = new Blob( + ["export default function() {\n return <div>Hello Deno!</div>\n}\n"], + { type: "text/jsx" }, +); +const url = URL.createObjectURL(blob); + +const { default: render } = await import(url); + +// deno-lint-ignore no-explicit-any +(globalThis as any).React = { + createElement(...args: unknown[]) { + console.log(...args); + }, +}; + +render(); diff --git a/cli/tests/import_blob_url_jsx.ts.out b/cli/tests/import_blob_url_jsx.ts.out new file mode 100644 index 000000000..c1c85f250 --- /dev/null +++ b/cli/tests/import_blob_url_jsx.ts.out @@ -0,0 +1 @@ +div null Hello Deno! diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 5f8d5d403..73fdcc994 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -3683,6 +3683,34 @@ console.log("finish"); output: "import_dynamic_data_url.ts.out", }); + itest!(import_blob_url_error_stack { + args: "run --quiet --reload import_blob_url_error_stack.ts", + output: "import_blob_url_error_stack.ts.out", + exit_code: 1, + }); + + itest!(import_blob_url_import_relative { + args: "run --quiet --reload import_blob_url_import_relative.ts", + output: "import_blob_url_import_relative.ts.out", + exit_code: 1, + }); + + itest!(import_blob_url_imports { + args: "run --quiet --reload import_blob_url_imports.ts", + output: "import_blob_url_imports.ts.out", + http_server: true, + }); + + itest!(import_blob_url_jsx { + args: "run --quiet --reload import_blob_url_jsx.ts", + output: "import_blob_url_jsx.ts.out", + }); + + itest!(import_blob_url { + args: "run --quiet --reload import_blob_url.ts", + output: "import_blob_url.ts.out", + }); + itest!(import_file_with_colon { args: "run --quiet --reload import_file_with_colon.ts", output: "import_file_with_colon.ts.out", diff --git a/cli/tests/unsupported_dynamic_import_scheme.out b/cli/tests/unsupported_dynamic_import_scheme.out index 9160f935f..c708fced4 100644 --- a/cli/tests/unsupported_dynamic_import_scheme.out +++ b/cli/tests/unsupported_dynamic_import_scheme.out @@ -1,5 +1,6 @@ error: Uncaught (in promise) TypeError: Unsupported scheme "xxx" for module "xxx:". Supported schemes: [ "data", + "blob", "file", "http", "https", diff --git a/cli/tsc.rs b/cli/tsc.rs index 7c54905c5..834b0cc58 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -110,6 +110,19 @@ fn hash_data_url( format!("data:///{}{}", hash, media_type.as_ts_extension()) } +fn hash_blob_url( + specifier: &ModuleSpecifier, + media_type: &MediaType, +) -> String { + assert_eq!( + specifier.scheme(), + "blob", + "Specifier must be a blob: specifier." + ); + let hash = crate::checksum::gen(&[specifier.path().as_bytes()]); + format!("blob:///{}{}", hash, media_type.as_ts_extension()) +} + /// tsc only supports `.ts`, `.tsx`, `.d.ts`, `.js`, or `.jsx` as root modules /// and so we have to detect the apparent media type based on extensions it /// supports. @@ -378,15 +391,19 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> { resolved_specifier ) }; - let resolved_specifier_str = if resolved_specifier.scheme() == "data" - { - let specifier_str = hash_data_url(&resolved_specifier, &media_type); - state - .data_url_map - .insert(specifier_str.clone(), resolved_specifier); - specifier_str - } else { - resolved_specifier.to_string() + let resolved_specifier_str = match resolved_specifier.scheme() { + "data" | "blob" => { + let specifier_str = if resolved_specifier.scheme() == "data" { + hash_data_url(&resolved_specifier, &media_type) + } else { + hash_blob_url(&resolved_specifier, &media_type) + }; + state + .data_url_map + .insert(specifier_str.clone(), resolved_specifier); + specifier_str + } + _ => resolved_specifier.to_string(), }; resolved.push(( resolved_specifier_str, @@ -439,12 +456,17 @@ pub fn exec(request: Request) -> Result<Response, AnyError> { let root_names: Vec<String> = request .root_names .iter() - .map(|(s, mt)| { - if s.scheme() == "data" { - let specifier_str = hash_data_url(s, mt); + .map(|(s, mt)| match s.scheme() { + "data" | "blob" => { + let specifier_str = if s.scheme() == "data" { + hash_data_url(&s, &mt) + } else { + hash_blob_url(&s, &mt) + }; data_url_map.insert(specifier_str.clone(), s.clone()); specifier_str - } else { + } + _ => { let ext_media_type = get_tsc_media_type(s); if mt != &ext_media_type { let new_specifier = format!("{}{}", s, mt.as_ts_extension()); |