Skip to content
Merged
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
3 changes: 2 additions & 1 deletion book/src/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ This ensures that file paths assembled from path components work on Windows and
Unix and cannot escape the package root. Symbolic links are not supported.

The filenames `manifest.filepack` and `.filepack`, with any case, are reserved
in the package root.
in the package root. Case variations of `metadata.filemeta`, other than
lowercase, are reserved in the package root.

Signatures
----------
Expand Down
20 changes: 17 additions & 3 deletions src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,21 @@ impl Component {
}

pub(crate) fn is_reserved(&self) -> bool {
Self::RESERVED
.iter()
.any(|name| self.0.eq_ignore_ascii_case(name))
let component = self.as_str();

for reserved in Self::RESERVED {
if component.eq_ignore_ascii_case(reserved) {
return true;
}
}

if component.eq_ignore_ascii_case(Metadata::DECO_FILENAME)
&& component != Metadata::DECO_FILENAME
{
return true;
}

false
}

pub(crate) fn new(s: &str) -> Result<&Component, ComponentError> {
Expand Down Expand Up @@ -148,6 +160,8 @@ mod tests {
case("manifest.filepack", true);
case(".filepack", true);
case("MANIFEST.FILEPACK", true);
case("METADATA.FILEMETA", true);
case("metadata.filemeta", false);
case("foo", false);
}

Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ pub enum Error {
backtrace: Option<Backtrace>,
hash: Hash,
},
#[snafu(display("expected `{expected}` but found case mismatch `{actual}`"))]
FilenameCase {
actual: String,
backtrace: Option<Backtrace>,
expected: &'static str,
},
#[snafu(transparent)]
Filesystem {
backtrace: Option<Backtrace>,
Expand Down
20 changes: 20 additions & 0 deletions src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ use {
::image::{DynamicImage, ImageBuffer, Rgb, Rgba},
};

pub(crate) fn check_case(dir: &Utf8Path, names: &[&'static str]) -> Result {
for entry in fs::read_dir(dir).context(filesystem_error::Io { path: dir })? {
let actual = entry
.context(filesystem_error::Io { path: dir })?
.file_name();

for &expected in names {
ensure! {
!actual.as_encoded_bytes().eq_ignore_ascii_case(expected.as_bytes()) || actual == expected,
error::FilenameCase {
actual: actual.to_str().unwrap(),
expected,
}
}
}
}

Ok(())
}

pub(crate) fn current_dir() -> Result<Utf8PathBuf> {
Utf8PathBuf::from_path_buf(env::current_dir().context(error::CurrentDir)?)
.map_err(|path| error::PathUnicode { path }.build())
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ use {
float_ext::FloatExt,
format::Format,
functions::{
current_dir, decode_path, default, format_size, ignore, now, parse_number, transfer_tempfile,
check_case, current_dir, decode_path, default, format_size, ignore, now, parse_number,
transfer_tempfile,
},
hashing_writer::HashingWriter,
head::Head,
Expand Down
11 changes: 9 additions & 2 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ impl Loader {
pub fn load_with_options(options: DecodeOptions, path: Option<&Utf8Path>) -> Result<Self> {
let path = if let Some(path) = path {
if path.is_dir() {
path.join(Manifest::FILENAME)
path.join_case_sensitive(Manifest::FILENAME)?
} else {
path.into()
}
} else {
Utf8Path::new(".").join_case_sensitive(Manifest::FILENAME)?;
Manifest::FILENAME.into()
};

Expand Down Expand Up @@ -58,7 +59,13 @@ impl Loader {
}

pub(crate) fn root(&self) -> &Utf8Path {
self.path.parent().unwrap()
let parent = self.path.parent().unwrap();

if parent.is_empty() {
".".into()
} else {
parent
}
}

pub(crate) fn unpack(&self) -> Result<Manifest> {
Expand Down
10 changes: 3 additions & 7 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ impl State {
const FILENAME: &'static str = "state.json";

pub fn load(root: &Utf8Path) -> Result<Self> {
let path = Self::path(root);
let path = root.join_case_sensitive(Self::DIR)?.join(Self::FILENAME);

let Some(json) = filesystem::read_to_string_opt(&path)? else {
return Ok(Self::default());
Expand All @@ -20,16 +20,12 @@ impl State {
serde_json::from_str(&json).context(error::DeserializeState { path })
}

fn path(root: &Utf8Path) -> Utf8PathBuf {
root.join(Self::DIR).join(Self::FILENAME)
}

pub(crate) fn save(&self, root: &Utf8Path) -> Result {
let dir = root.join(Self::DIR);
let dir = root.join_case_sensitive(Self::DIR)?;

filesystem::create_dir_all(&dir)?;

let path = Self::path(root);
let path = dir.join(Self::FILENAME);

let mut json = serde_json::to_string_pretty(self).unwrap();

Expand Down
10 changes: 10 additions & 0 deletions src/subcommand/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ impl Create {
error::PackageRootDirectory { path: root },
}

check_case(
&root,
&[
Manifest::FILENAME,
Metadata::DECO_FILENAME,
Metadata::YAML_FILENAME,
State::DIR,
],
)?;

let manifest_path = if let Some(path) = self.manifest {
path
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/subcommand/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ impl Metadata {
pub(crate) fn run(self) -> Result {
let path = if let Some(path) = self.path {
if path.is_dir() {
path.join(crate::Metadata::DECO_FILENAME)
path.join_case_sensitive(crate::Metadata::DECO_FILENAME)?
} else {
path
}
} else {
Utf8Path::new(".").join_case_sensitive(crate::Metadata::DECO_FILENAME)?;
crate::Metadata::DECO_FILENAME.into()
};

Expand Down
5 changes: 5 additions & 0 deletions src/subcommand/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ impl Verify {
error::PackageRootDirectory { path: root },
}

check_case(
&root,
&[Manifest::FILENAME, Metadata::DECO_FILENAME, State::DIR],
)?;

if self.manifest.is_some() {
ensure! {
!filesystem::exists(&root.join(Manifest::FILENAME))?,
Expand Down
13 changes: 13 additions & 0 deletions src/utf8_path_ext.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
use super::*;

pub(crate) trait Utf8PathExt {
fn is_empty(&self) -> bool;

fn join_case_sensitive(&self, name: &'static str) -> Result<Utf8PathBuf>;

fn lexiclean(&self) -> Utf8PathBuf;
}

impl Utf8PathExt for Utf8Path {
fn is_empty(&self) -> bool {
self.as_str().is_empty()
}

fn join_case_sensitive(&self, name: &'static str) -> Result<Utf8PathBuf> {
check_case(self, &[name])?;
Ok(self.join(name))
}

fn lexiclean(&self) -> Utf8PathBuf {
Utf8PathBuf::from_path_buf(self.as_std_path().lexiclean()).unwrap()
}
Expand Down
18 changes: 18 additions & 0 deletions tests/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ fn backslash_error() {
.failure();
}

#[test]
fn case_variant_error() {
for (actual, expected) in [
("MANIFEST.FILEPACK", "manifest.filepack"),
("METADATA.FILEMETA", "metadata.filemeta"),
("METADATA.YAML", "metadata.yaml"),
(".FILEPACK", ".filepack"),
] {
Test::new()
.touch(actual)
.arg("create")
.stderr(&format!(
"error: expected `{expected}` but found case mismatch `{actual}`\n"
))
.failure();
}
}

#[test]
fn create_rejects_invalid_artwork() {
Test::new()
Expand Down
12 changes: 12 additions & 0 deletions tests/fingerprint.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
use super::*;

#[test]
fn case_variant_error() {
Test::new()
.touch("MANIFEST.FILEPACK")
.arg("fingerprint")
.stderr("error: expected `manifest.filepack` but found case mismatch `MANIFEST.FILEPACK`\n")
.failure()
.args(["fingerprint", "."])
.stderr("error: expected `manifest.filepack` but found case mismatch `MANIFEST.FILEPACK`\n")
.failure();
}

#[test]
fn fingerprint() {
let test = Test::new()
Expand Down
12 changes: 12 additions & 0 deletions tests/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,18 @@ fn metadata_deco_force() {
.success();
}

#[test]
fn metadata_subcommand_case_variant_error() {
Test::new()
.touch("METADATA.FILEMETA")
.arg("metadata")
.stderr("error: expected `metadata.filemeta` but found case mismatch `METADATA.FILEMETA`\n")
.failure()
.args(["metadata", "."])
.stderr("error: expected `metadata.filemeta` but found case mismatch `METADATA.FILEMETA`\n")
.failure();
}

#[test]
fn metadata_subcommand_default() {
Test::new()
Expand Down
11 changes: 11 additions & 0 deletions tests/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,17 @@ fn upload_package_skips_files_already_on_server() {
server.terminate().success();
}

#[test]
fn upload_package_state_case_variant_error() {
Test::new()
.arg("create")
.success()
.create_dir(".FILEPACK")
.args(["upload", "--server", "http://127.0.0.1:1"])
.stderr("error: expected `.filepack` but found case mismatch `.FILEPACK`\n")
.failure();
}

#[test]
fn upload_package_uploads_files() {
let server = Test::new()
Expand Down
17 changes: 17 additions & 0 deletions tests/verify.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
use super::*;

#[test]
fn case_variant_error() {
for (actual, expected) in [
("MANIFEST.FILEPACK", "manifest.filepack"),
("METADATA.FILEMETA", "metadata.filemeta"),
(".FILEPACK", ".filepack"),
] {
Test::new()
.touch(actual)
.arg("verify")
.stderr(&format!(
"error: expected `{expected}` but found case mismatch `{actual}`\n"
))
.failure();
}
}

#[test]
fn duplicate_key_literal() {
Test::new()
Expand Down
Loading