Skip to content

Commit 130df51

Browse files
committed
[0.31.1] Improve input qualification in FileSpec::try_from (fixes #194).
1 parent 2d3a19f commit 130df51

File tree

9 files changed

+107
-46
lines changed

9 files changed

+107
-46
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this
66
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.31.1] - 2025-06-22
9+
10+
Improve input qualification in `FileSpec::try_from` (fixes #194).
11+
812
## [0.31.0] - 2025-06-16
913

1014
Add format configuration capabilities to `flexi_logger::trc::setup_tracing`

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "flexi_logger"
3-
version = "0.31.0"
3+
version = "0.31.1"
44
authors = ["emabee <[email protected]>"]
55
categories = ["development-tools::debugging"]
66
description = """

src/flexi_error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ pub enum FlexiLoggerError {
2626
#[error("Log file cannot be written because the specified path is a directory")]
2727
OutputBadFile,
2828

29+
/// Invalid input for deriving `FileSpec`.
30+
#[error("Invalid input for deriving FileSpec")]
31+
BadFileSpec(&'static str),
32+
2933
/// Spawning the cleanup thread failed.
3034
///
3135
/// This error can safely be avoided with `Logger::cleanup_in_background_thread(false)`.

src/parameters/file_spec.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use crate::writers::file_log_writer::InfixFilter;
2-
use crate::{DeferredNow, FlexiLoggerError};
1+
use crate::{writers::file_log_writer::InfixFilter, DeferredNow, FlexiLoggerError};
32
use std::{
43
ffi::{OsStr, OsString},
54
ops::Add,
65
path::{Path, PathBuf},
76
};
7+
88
/// Builder object for specifying the name and path of the log output file.
99
///
1010
/// The filename is built from several partially components, using this pattern:
@@ -91,23 +91,56 @@ impl FileSpec {
9191
/// # Errors
9292
///
9393
/// [`FlexiLoggerError::OutputBadFile`] if the given path exists and is a folder.
94-
///
95-
/// # Panics
96-
///
97-
/// Panics if the basename of the given path has no filename
94+
/// [`FlexiLoggerError::BadFileSpec`] if deriving the `FileSpec` from the given path fails.
95+
#[allow(clippy::missing_panics_doc)]
9896
pub fn try_from<P: Into<PathBuf>>(p: P) -> Result<Self, FlexiLoggerError> {
99-
let p: PathBuf = p.into();
100-
if p.is_dir() {
101-
Err(FlexiLoggerError::OutputBadFile)
97+
let input: PathBuf = p.into();
98+
99+
if input.is_dir() {
100+
Err(FlexiLoggerError::BadFileSpec("File path is a directory"))
102101
} else {
103-
Ok(FileSpec {
104-
directory: p.parent().unwrap(/*cannot fail*/).to_path_buf(),
105-
basename: p.file_stem().unwrap(/*ok*/).to_string_lossy().to_string(),
106-
o_discriminant: None,
107-
o_suffix: p.extension().map(|s| s.to_string_lossy().to_string()),
108-
timestamp_cfg: TimestampCfg::No,
109-
use_utc: false,
110-
})
102+
let input_as_str = input.as_os_str().to_string_lossy();
103+
if input_as_str.is_empty() {
104+
Err(FlexiLoggerError::BadFileSpec("File path is empty"))
105+
} else if input_as_str.ends_with('/')
106+
|| input_as_str.ends_with("/.")
107+
|| input_as_str.ends_with("/..")
108+
{
109+
Err(FlexiLoggerError::BadFileSpec(
110+
"Path ends with '/' or '/.' or '/..'",
111+
))
112+
} else if input
113+
.file_name()
114+
.ok_or(FlexiLoggerError::OutputBadFile)?
115+
.to_string_lossy()
116+
.starts_with('.')
117+
&& input.extension().is_none()
118+
{
119+
Err(FlexiLoggerError::BadFileSpec(
120+
"File name cannot start with '.' without an extension",
121+
))
122+
} else {
123+
match input.parent() {
124+
None => Err(FlexiLoggerError::BadFileSpec(
125+
"File path has no parent directory",
126+
)),
127+
Some(parent) => {
128+
let filespec = FileSpec {
129+
directory: if parent.as_os_str().is_empty() {
130+
PathBuf::from(".")
131+
} else {
132+
parent.to_path_buf()
133+
},
134+
basename: input.file_stem().unwrap(/*ok*/).to_string_lossy().to_string(),
135+
o_discriminant: None,
136+
o_suffix: input.extension().map(|s| s.to_string_lossy().to_string()),
137+
timestamp_cfg: TimestampCfg::No,
138+
use_utc: false,
139+
};
140+
Ok(filespec)
141+
}
142+
}
143+
}
111144
}
112145
}
113146

@@ -473,6 +506,27 @@ mod test {
473506
assert_file_spec(&path, &PathBuf::from("."), true, "log");
474507
}
475508

509+
#[test]
510+
fn issue_194() {
511+
assert!(dbg!(FileSpec::try_from("")).is_err());
512+
assert!(dbg!(FileSpec::try_from(".")).is_err());
513+
assert!(dbg!(FileSpec::try_from("..")).is_err());
514+
assert!(dbg!(FileSpec::try_from("/Users")).is_err());
515+
assert!(dbg!(FileSpec::try_from("/Users/")).is_err());
516+
assert!(dbg!(FileSpec::try_from("./f/")).is_err());
517+
assert!(dbg!(FileSpec::try_from("./f/.")).is_err());
518+
assert!(dbg!(FileSpec::try_from("./f/..")).is_err());
519+
assert!(dbg!(FileSpec::try_from(".log")).is_err());
520+
assert!(dbg!(FileSpec::try_from("./.log")).is_err());
521+
assert!(dbg!(FileSpec::try_from("./f/.log")).is_err());
522+
523+
let filespec = FileSpec::try_from("test.log").unwrap();
524+
std::fs::create_dir_all(filespec.get_directory()).unwrap();
525+
assert!(std::fs::metadata(filespec.get_directory())
526+
.unwrap()
527+
.is_dir());
528+
}
529+
476530
// todo: does not support suppress_timestamp & suppress_basename & use discriminant
477531
fn assert_file_spec(path: &Path, folder: &Path, with_timestamp: bool, suffix: &str) {
478532
// check folder

src/writers/file_log_writer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
mod builder;
33
mod config;
44
mod infix_filter;
5+
mod rotation_config;
56
mod state;
67
mod state_handle;
78
mod threads;
@@ -10,7 +11,7 @@ pub use self::builder::{ArcFileLogWriter, FileLogWriterBuilder, FileLogWriterHan
1011
pub use self::config::FileLogWriterConfig;
1112
pub(crate) use infix_filter::InfixFilter;
1213

13-
use self::{config::RotationConfig, state::State, state_handle::StateHandle};
14+
use self::{rotation_config::RotationConfig, state::State, state_handle::StateHandle};
1415
use crate::{
1516
writers::LogWriter, DeferredNow, EffectiveWriteMode, FileSpec, FlexiLoggerError,
1617
FormatFunction, LogfileSelector,

src/writers/file_log_writer/builder.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use crate::flexi_error::FlexiLoggerError;
2-
use crate::formats::default_format;
3-
use crate::{Cleanup, Criterion, FileSpec, FormatFunction, Naming, WriteMode};
4-
use std::path::{Path, PathBuf};
5-
use std::sync::Arc;
1+
use crate::{
2+
default_format,
3+
writers::{FileLogWriter, FileLogWriterConfig, LogWriter},
4+
Cleanup, Criterion, FileSpec, FlexiLoggerError, FormatFunction, Naming, WriteMode,
5+
};
6+
use std::{path::PathBuf, sync::Arc};
67

7-
use super::{FileLogWriter, FileLogWriterConfig, LogWriter, RotationConfig, State};
8+
use super::{RotationConfig, State};
89

910
/// Builder for [`FileLogWriter`].
1011
#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)]
@@ -219,11 +220,10 @@ impl FileLogWriterBuilder {
219220
}
220221

221222
pub(super) fn try_build_state(&self) -> Result<State, FlexiLoggerError> {
222-
// make sure the folder exists or create it
223-
let dir = self.file_spec.get_directory();
224-
let p_directory = Path::new(&dir);
225-
std::fs::create_dir_all(p_directory)?;
226-
if !std::fs::metadata(p_directory)?.is_dir() {
223+
// make sure the folder exists, or create it
224+
let parent = self.file_spec.get_directory();
225+
std::fs::create_dir_all(&parent)?;
226+
if !std::fs::metadata(&parent)?.is_dir() {
227227
return Err(FlexiLoggerError::OutputBadDirectory);
228228
}
229229

src/writers/file_log_writer/config.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
1-
use crate::{Cleanup, Criterion, FileSpec, Naming, WriteMode};
1+
use crate::{FileSpec, WriteMode};
22
use std::path::PathBuf;
33

4-
/// Describes how rotation should work
5-
#[derive(Clone, Debug)]
6-
pub struct RotationConfig {
7-
// Defines if rotation should be based on size or date
8-
pub(crate) criterion: Criterion,
9-
// Defines if rotated files should be numbered or get a date-based name
10-
pub(crate) naming: Naming,
11-
// Defines the cleanup strategy
12-
pub(crate) cleanup: Cleanup,
13-
}
14-
154
/// Configuration of a `FileLogWriter`.
165
#[derive(Debug, Clone)]
176
pub struct FileLogWriterConfig {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::{Cleanup, Criterion, Naming};
2+
3+
// Describes how rotation should work
4+
#[derive(Clone, Debug)]
5+
pub(super) struct RotationConfig {
6+
// Defines if rotation should be based on size or date
7+
pub(crate) criterion: Criterion,
8+
// Defines if rotated files should be numbered or get a date-based name
9+
pub(crate) naming: Naming,
10+
// Defines the cleanup strategy
11+
pub(crate) cleanup: Cleanup,
12+
}

src/writers/file_log_writer/state.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ mod list_and_cleanup;
22
mod numbers;
33
mod timestamps;
44

5+
use super::{config::FileLogWriterConfig, rotation_config::RotationConfig, InfixFilter};
56
pub(crate) use timestamps::timestamp_from_ts_infix;
67

7-
use super::{
8-
config::{FileLogWriterConfig, RotationConfig},
9-
InfixFilter,
10-
};
118
#[cfg(feature = "async")]
129
use crate::util::eprint_msg;
1310
use crate::{

0 commit comments

Comments
 (0)