From 87e7008d888cdea2c9849cdccb5eb0dc4c29e09e Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Fri, 2 Apr 2021 23:25:41 +0200 Subject: [PATCH 1/4] decode percent encoding in gltf uris --- crates/bevy_gltf/Cargo.toml | 1 + crates/bevy_gltf/src/loader.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 5e8d1ad72b480..8fa4de3b66cc6 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -30,3 +30,4 @@ gltf = { version = "0.15.2", default-features = false, features = ["utils", "nam thiserror = "1.0" anyhow = "1.0" base64 = "0.13.0" +percent-encoding = "2.1" diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 25da841eb3664..f8882d8cd6ac4 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -222,8 +222,12 @@ async fn load_gltf<'a, 'b>( Texture::from_buffer(buffer, ImageType::MimeType(mime_type))? } gltf::image::Source::Uri { uri, mime_type } => { + let uri = percent_encoding::percent_decode_str(uri) + .decode_utf8() + .unwrap(); + let parent = load_context.path().parent().unwrap(); - let image_path = parent.join(uri); + let image_path = parent.join(uri.as_ref()); let bytes = load_context.read_asset_bytes(image_path.clone()).await?; Texture::from_buffer( &bytes, @@ -581,8 +585,12 @@ async fn load_buffers( .ok_or(GltfError::BufferFormatUnsupported)?, )?); } else { + let uri = percent_encoding::percent_decode_str(uri) + .decode_utf8() + .unwrap(); + // TODO: Remove this and add dep - let buffer_path = asset_path.parent().unwrap().join(uri); + let buffer_path = asset_path.parent().unwrap().join(uri.as_ref()); let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?; buffer_data.push(buffer_bytes); } From 28f0c33cf3c7536db511b4d5056d155296362a3f Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 5 Apr 2021 21:31:17 +0200 Subject: [PATCH 2/4] support data uri for gltf images --- crates/bevy_gltf/src/loader.rs | 52 ++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 25da841eb3664..7fe730954e3a3 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -222,16 +222,25 @@ async fn load_gltf<'a, 'b>( Texture::from_buffer(buffer, ImageType::MimeType(mime_type))? } gltf::image::Source::Uri { uri, mime_type } => { - let parent = load_context.path().parent().unwrap(); - let image_path = parent.join(uri); - let bytes = load_context.read_asset_bytes(image_path.clone()).await?; + let (bytes, image_type) = match DataUri::parse(uri) { + Ok(data_uri) => (data_uri.decode()?, ImageType::MimeType(data_uri.mime_type)), + Err(()) => { + let parent = load_context.path().parent().unwrap(); + let image_path = parent.join(uri); + let bytes = load_context.read_asset_bytes(image_path.clone()).await?; + + let extension = Path::new(uri).extension().unwrap().to_str().unwrap(); + let image_type = ImageType::Extension(extension); + + (bytes, image_type) + } + }; + Texture::from_buffer( &bytes, mime_type .map(|mt| ImageType::MimeType(mt)) - .unwrap_or_else(|| { - ImageType::Extension(image_path.extension().unwrap().to_str().unwrap()) - }), + .unwrap_or(image_type), )? } }; @@ -646,6 +655,37 @@ fn resolve_node_hierarchy( .collect() } +struct DataUri<'a> { + mime_type: &'a str, + base64: bool, + data: &'a str, +} + +impl<'a> DataUri<'a> { + fn parse(uri: &'a str) -> Result, ()> { + let uri = uri.strip_prefix("data:").ok_or(())?; + let (mime_type, data) = uri.split_once(',').ok_or(())?; + let (mime_type, base64) = match mime_type.strip_suffix(";base64") { + Some(mime_type) => (mime_type, true), + None => (mime_type, false), + }; + + Ok(DataUri { + mime_type, + base64, + data, + }) + } + + fn decode(&self) -> Result, base64::DecodeError> { + if self.base64 { + base64::decode(self.data) + } else { + Ok(self.data.as_bytes().to_owned()) + } + } +} + #[cfg(test)] mod test { use super::resolve_node_hierarchy; From 84d87abcf65f2a587b720147fbe74dbf126ad96d Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 5 Apr 2021 21:32:38 +0200 Subject: [PATCH 3/4] handle buffer URIs the same as image URIs --- crates/bevy_gltf/src/loader.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 7fe730954e3a3..5b073c41756a4 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -578,23 +578,23 @@ async fn load_buffers( load_context: &LoadContext<'_>, asset_path: &Path, ) -> Result>, GltfError> { - const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,"; + const OCTET_STREAM_URI: &str = "application/octet-stream"; let mut buffer_data = Vec::new(); for buffer in gltf.buffers() { match buffer.source() { gltf::buffer::Source::Uri(uri) => { - if uri.starts_with("data:") { - buffer_data.push(base64::decode( - uri.strip_prefix(OCTET_STREAM_URI) - .ok_or(GltfError::BufferFormatUnsupported)?, - )?); - } else { - // TODO: Remove this and add dep - let buffer_path = asset_path.parent().unwrap().join(uri); - let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?; - buffer_data.push(buffer_bytes); - } + let buffer_bytes = match DataUri::parse(uri) { + Ok(data_uri) if data_uri.mime_type == OCTET_STREAM_URI => data_uri.decode()?, + Ok(_) => return Err(GltfError::BufferFormatUnsupported), + Err(()) => { + // TODO: Remove this and add dep + let buffer_path = asset_path.parent().unwrap().join(uri); + let buffer_bytes = load_context.read_asset_bytes(buffer_path).await?; + buffer_bytes + } + }; + buffer_data.push(buffer_bytes); } gltf::buffer::Source::Bin => { if let Some(blob) = gltf.blob.as_deref() { From 99cada561047a3072474545b896c8656469cd3d5 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 5 Apr 2021 22:06:06 +0200 Subject: [PATCH 4/4] polyfill split_once --- crates/bevy_gltf/src/loader.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 326f147f6d184..ca1090e4404bb 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -669,10 +669,16 @@ struct DataUri<'a> { data: &'a str, } +fn split_once(input: &str, delimiter: char) -> Option<(&str, &str)> { + let mut iter = input.splitn(2, delimiter); + Some((iter.next()?, iter.next()?)) +} + impl<'a> DataUri<'a> { fn parse(uri: &'a str) -> Result, ()> { let uri = uri.strip_prefix("data:").ok_or(())?; - let (mime_type, data) = uri.split_once(',').ok_or(())?; + let (mime_type, data) = split_once(uri, ',').ok_or(())?; + let (mime_type, base64) = match mime_type.strip_suffix(";base64") { Some(mime_type) => (mime_type, true), None => (mime_type, false),