diff --git a/age/i18n/en-US/age.ftl b/age/i18n/en-US/age.ftl index f512f214..f1b07ed3 100644 --- a/age/i18n/en-US/age.ftl +++ b/age/i18n/en-US/age.ftl @@ -46,6 +46,10 @@ rec-deny-binary-output = Did you mean to use {-flag-armor}? {rec-detected-binary err-deny-overwrite-file = refusing to overwrite existing file '{$filename}'. +err-invalid-filename = invalid filename '{$filename}'. + +err-missing-directory = directory '{$path}' does not exist. + ## Identity file errors err-failed-to-write-output = Failed to write to output: {$err} diff --git a/age/src/cli_common/file_io.rs b/age/src/cli_common/file_io.rs index b5758dc7..7275df83 100644 --- a/age/src/cli_common/file_io.rs +++ b/age/src/cli_common/file_io.rs @@ -20,6 +20,8 @@ enum FileError { DenyBinaryOutput, DenyOverwriteFile(String), DetectedBinaryOutput, + InvalidFilename(String), + MissingDirectory(String), } impl fmt::Display for FileError { @@ -36,6 +38,10 @@ impl fmt::Display for FileError { wlnfl!(f, "err-detected-binary")?; wfl!(f, "rec-detected-binary") } + Self::InvalidFilename(filename) => { + wfl!(f, "err-invalid-filename", filename = filename.as_str()) + } + Self::MissingDirectory(path) => wfl!(f, "err-missing-directory", path = path.as_str()), } } } @@ -345,10 +351,29 @@ impl OutputWriter { // Respect the Unix convention that "-" as an output filename // parameter is an explicit request to use standard output. if filename != "-" { + let file_path = Path::new(&filename); + + // Provide a better error if the filename is invalid, or the directory + // containing the file does not exist (we don't automatically create + // directories). + if let Some(dir_path) = file_path.parent() { + if !(dir_path == Path::new("") || dir_path.exists()) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + FileError::MissingDirectory(dir_path.display().to_string()), + )); + } + } else { + return Err(io::Error::new( + io::ErrorKind::NotFound, + FileError::InvalidFilename(filename), + )); + } + // We open the file lazily, but as we don't want the caller to assume // this, we eagerly confirm that the file does not exist if we can't // overwrite it. - if !allow_overwrite && Path::new(&filename).exists() { + if !allow_overwrite && file_path.exists() { return Err(io::Error::new( io::ErrorKind::AlreadyExists, FileError::DenyOverwriteFile(filename), diff --git a/rage/tests/cmd/rage-keygen/gen-output-invalid-filename.toml b/rage/tests/cmd/rage-keygen/gen-output-invalid-filename.toml new file mode 100644 index 00000000..d8b01cd4 --- /dev/null +++ b/rage/tests/cmd/rage-keygen/gen-output-invalid-filename.toml @@ -0,0 +1,10 @@ +bin.name = "rage-keygen" +args = "-o ''" +status = "failed" +stdout = "" +stderr = """ +Error: Failed to open output: invalid filename ''. + +[ Did rage not do what you expected? Could an error be more useful? ] +[ Tell us: https://str4d.xyz/rage/report ] +""" diff --git a/rage/tests/cmd/rage-keygen/gen-output-missing-directory.toml b/rage/tests/cmd/rage-keygen/gen-output-missing-directory.toml new file mode 100644 index 00000000..e05627b6 --- /dev/null +++ b/rage/tests/cmd/rage-keygen/gen-output-missing-directory.toml @@ -0,0 +1,10 @@ +bin.name = "rage-keygen" +args = "-o does-not-exist/key.txt" +status = "failed" +stdout = "" +stderr = """ +Error: Failed to open output: directory 'does-not-exist' does not exist. + +[ Did rage not do what you expected? Could an error be more useful? ] +[ Tell us: https://str4d.xyz/rage/report ] +"""