Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,4 @@ at your option.
### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

2 changes: 0 additions & 2 deletions static-serve-macro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ pub(crate) enum Error {
InvalidUnicodeInDirectoryName,
#[error("Cannot canonicalize ignore path")]
CannotCanonicalizeIgnorePath(#[source] io::Error),
#[error("Invalid unicode in directory name")]
InvalidUnicodeInEntryName,
#[error("Error while compressing with gzip")]
Gzip(#[from] GzipType),
#[error("Error while compressing with zstd")]
Expand Down
80 changes: 58 additions & 22 deletions static-serve-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,11 @@ impl ToTokens for OptionBytesSlice {
}
}

struct EmbeddedFileInfo<'a> {
struct EmbeddedFileInfo {
/// When creating a `Router`, we need the API path/route to the
/// target file. If creating a `Handler`, this is not needed since
/// the router is responsible for the file's path on the server.
entry_path: Option<&'a str>,
entry_path: Option<String>,
content_type: String,
etag_str: String,
lit_byte_str_contents: LitByteStr,
Expand All @@ -671,9 +671,9 @@ struct EmbeddedFileInfo<'a> {
cache_busted: bool,
}

impl<'a> EmbeddedFileInfo<'a> {
impl EmbeddedFileInfo {
fn from_path(
pathbuf: &'a PathBuf,
pathbuf: &PathBuf,
assets_dir_abs_str: Option<&str>,
should_compress: &LitBool,
should_strip_html_ext: &LitBool,
Expand All @@ -695,18 +695,18 @@ impl<'a> EmbeddedFileInfo<'a> {

// entry_path is only needed for the router (embed_assets!)
let entry_path = if let Some(dir) = assets_dir_abs_str {
if should_strip_html_ext.value && content_type == "text/html" {
Some(
strip_html_ext(pathbuf)?
.strip_prefix(dir)
.unwrap_or_default(),
)
let relative_entry = pathbuf
.strip_prefix(dir)
.ok()
.and_then(|p| p.to_str())
.unwrap_or_default();
let relative_path = if should_strip_html_ext.value && content_type == "text/html" {
strip_html_ext_relative(relative_entry)
} else {
pathbuf
.to_str()
.ok_or(Error::InvalidUnicodeInEntryName)?
.strip_prefix(dir)
}
relative_entry.to_owned()
};

Some(normalize_web_path(&relative_path))
} else {
None
};
Expand Down Expand Up @@ -820,21 +820,57 @@ fn etag(contents: &[u8]) -> String {
format!("\"{hash:016x}\"")
}

fn strip_html_ext(entry: &Path) -> Result<&str, Error> {
let entry_str = entry.to_str().ok_or(Error::InvalidUnicodeInEntryName)?;
let mut output = entry_str;
/// Normalize a relative asset path, strip `.html`/`.htm`, and map
/// `/index(.html|.htm)` to its directory route.
///
/// The input is normalized via `Path::components()` so separator style
/// differences across platforms do not affect route generation.
fn strip_html_ext_relative(entry: &str) -> String {
let mut output = Path::new(entry)
.components()
.filter_map(|component| match component {
std::path::Component::Normal(segment) => segment.to_str(),
std::path::Component::CurDir
| std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_) => None,
})
.collect::<Vec<_>>()
.join("/");

// Strip the extension
if let Some(prefix) = output.strip_suffix(".html") {
output = prefix;
output = prefix.to_owned();
} else if let Some(prefix) = output.strip_suffix(".htm") {
output = prefix;
output = prefix.to_owned();
}

// If it was `/index.html` or `/index.htm`, also remove `index`
if output.ends_with("/index") {
output = output.strip_suffix("index").unwrap_or("/");
output = output.strip_suffix("index").unwrap_or("/").to_owned();
} else if output == "index" {
output.clear();
}

Ok(output)
output
}

/// Convert a relative filesystem-style path into a rooted web route.
///
/// Path segments are normalized via `Path::components()`. The returned
/// route is always absolute (starts with `/`) and defaults to `/` for
/// empty input.
fn normalize_web_path(relative_path: &str) -> String {
let normalized = Path::new(relative_path)
.components()
.filter_map(|component| match component {
std::path::Component::Normal(segment) => segment.to_str(),
std::path::Component::CurDir
| std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_) => None,
})
.collect::<Vec<_>>()
.join("/");
format!("/{normalized}")
}