Skip to content

Make AssetPath::get_full_extension work for non-UTF-8 file names. #19974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions crates/bevy_asset/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,18 +465,17 @@ impl<'a> AssetPath<'a> {
/// Ex: Returns `"config.ron"` for `"my_asset.config.ron"`
///
/// Also strips out anything following a `?` to handle query parameters in URIs
pub fn get_full_extension(&self) -> Option<String> {
let file_name = self.path().file_name()?.to_str()?;
let index = file_name.find('.')?;
let mut extension = file_name[index + 1..].to_owned();
pub fn get_full_extension(&self) -> Option<&str> {
let file_name = self.path().file_name()?.as_encoded_bytes();
let index = file_name.iter().position(|b| *b == b'.')?;
let mut extension = &file_name[index + 1..];

// Strip off any query parameters
let query = extension.find('?');
if let Some(offset) = query {
extension.truncate(offset);
if let Some(offset) = extension.iter().position(|b| *b == b'?') {
extension = &extension[..offset];
}

Some(extension)
core::str::from_utf8(extension).ok()
}

pub(crate) fn iter_secondary_extensions(full_extension: &str) -> impl Iterator<Item = &str> {
Expand Down Expand Up @@ -688,7 +687,6 @@ pub(crate) fn normalize_path(path: &Path) -> PathBuf {
#[cfg(test)]
mod tests {
use crate::AssetPath;
use alloc::string::ToString;
use std::path::Path;

#[test]
Expand Down Expand Up @@ -1025,15 +1023,28 @@ mod tests {
#[test]
fn test_get_extension() {
let result = AssetPath::from("http://a.tar.gz#Foo");
assert_eq!(result.get_full_extension(), Some("tar.gz".to_string()));
assert_eq!(result.get_full_extension(), Some("tar.gz"));

let result = AssetPath::from("http://a#Foo");
assert_eq!(result.get_full_extension(), None);

let result = AssetPath::from("http://a.tar.bz2?foo=bar#Baz");
assert_eq!(result.get_full_extension(), Some("tar.bz2".to_string()));
assert_eq!(result.get_full_extension(), Some("tar.bz2"));

let result = AssetPath::from("asset.Custom");
assert_eq!(result.get_full_extension(), Some("Custom".to_string()));
assert_eq!(result.get_full_extension(), Some("Custom"));
}

// Regression test for non-UTF-8 file names. Creating an `OsStr` from bytes (without unsafe) is
// platform-dependent, so we just test Unix here.
#[test]
#[cfg(unix)]
fn test_get_full_extension_non_utf8() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;

let path = OsStr::from_bytes(b"imag\xe9.png");
let result = AssetPath::from_path(Path::new(path));
assert_eq!(result.get_full_extension(), Some("png"));
}
}
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl AssetProcessor {
let path = path.into();
let Some(processor) = path
.get_full_extension()
.and_then(|extension| self.get_default_processor(&extension))
.and_then(|extension| self.get_default_processor(extension))
else {
return self
.server
Expand Down Expand Up @@ -841,7 +841,7 @@ impl AssetProcessor {
Err(AssetReaderError::NotFound(_path)) => {
let (meta, processor) = if let Some(processor) = asset_path
.get_full_extension()
.and_then(|ext| self.get_default_processor(&ext))
.and_then(|ext| self.get_default_processor(ext))
{
let meta = processor.default_meta();
(meta, Some(processor))
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_asset/src/server/loaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,12 @@ impl AssetLoaders {

// Try extracting the extension from the path
if let Some(full_extension) = asset_path.and_then(AssetPath::get_full_extension) {
if let Some(&index) = try_extension(full_extension.as_str()) {
if let Some(&index) = try_extension(full_extension) {
return self.get_by_index(index);
}

// Try secondary extensions from the path
for extension in AssetPath::iter_secondary_extensions(&full_extension) {
for extension in AssetPath::iter_secondary_extensions(full_extension) {
if let Some(&index) = try_extension(extension) {
return self.get_by_index(index);
}
Expand Down Expand Up @@ -279,8 +279,8 @@ impl AssetLoaders {
pub(crate) fn get_by_path(&self, path: &AssetPath<'_>) -> Option<MaybeAssetLoader> {
let extension = path.get_full_extension()?;

let result = core::iter::once(extension.as_str())
.chain(AssetPath::iter_secondary_extensions(&extension))
let result = core::iter::once(extension)
.chain(AssetPath::iter_secondary_extensions(extension))
.filter_map(|extension| self.extension_to_loaders.get(extension)?.last().copied())
.find_map(|index| self.get_by_index(index))?;

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ impl AssetServer {
};
};

let mut extensions = vec![full_extension.clone()];
let mut extensions = vec![full_extension.to_owned()];
extensions.extend(
AssetPath::iter_secondary_extensions(&full_extension).map(ToString::to_string),
AssetPath::iter_secondary_extensions(full_extension).map(ToString::to_string),
);

MissingAssetLoaderForExtensionError { extensions }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: `AssetPath::get_full_extension` returns `&str`
pull_requests: [19974]
---

`AssetPath::get_full_extension` now works on non-UTF-8 file names and returns `&str` instead of `String`.