Skip to content

Commit df13534

Browse files
committed
du: prevent panics from chrono
1 parent 6ea955d commit df13534

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/du/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ chrono = { workspace = true }
2121
# For the --exclude & --exclude-from options
2222
glob = { workspace = true }
2323
clap = { workspace = true }
24+
regex = { workspace = true }
2425
uucore = { workspace = true, features = ["format", "parser"] }
2526
thiserror = { workspace = true }
2627

2728
[target.'cfg(target_os = "windows")'.dependencies]
2829
windows-sys = { workspace = true, features = [
29-
"Win32_Storage_FileSystem",
30-
"Win32_Foundation",
30+
"Win32_Storage_FileSystem",
31+
"Win32_Foundation",
3132
] }
3233

3334
[[bin]]

src/uu/du/src/du.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use chrono::{DateTime, Local};
77
use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue};
88
use glob::Pattern;
9+
use regex::Regex;
10+
use std::borrow::Cow;
911
use std::collections::HashSet;
1012
use std::env;
1113
#[cfg(not(windows))]
@@ -20,7 +22,7 @@ use std::os::windows::fs::MetadataExt;
2022
use std::os::windows::io::AsRawHandle;
2123
use std::path::{Path, PathBuf};
2224
use std::str::FromStr;
23-
use std::sync::mpsc;
25+
use std::sync::{LazyLock, mpsc};
2426
use std::thread;
2527
use std::time::{Duration, UNIX_EPOCH};
2628
use thiserror::Error;
@@ -74,6 +76,13 @@ const ABOUT: &str = help_about!("du.md");
7476
const AFTER_HELP: &str = help_section!("after help", "du.md");
7577
const USAGE: &str = help_usage!("du.md");
7678

79+
static TIME_STYLE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
80+
Regex::new(
81+
r"(?<before>(?:^|[^%])(?:%%)*)%(?<after>[^YCyqmbBhdeaAwuUWGgVjDxFvHkIlPpMSfRTXrZzc+stn%.369:]|[-_0][^YCyqmdewuUWGgVjHkIlMSfs%]|\.(?:[^369f]|$)|\.[369](?:[^f]|$)|[369](?:[^f]|$)|:{1,2}(?:[^z:]|$)|:{3}[^z]|$)", // cspell:disable-line
82+
)
83+
.unwrap()
84+
});
85+
7786
struct TraversalOptions {
7887
all: bool,
7988
separate_dirs: bool,
@@ -806,8 +815,8 @@ fn get_time_secs(time: Time, stat: &Stat) -> Result<u64, DuError> {
806815
}
807816
}
808817

809-
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
810-
match s {
818+
fn parse_time_style(s: Option<&str>) -> UResult<Cow<'_, str>> {
819+
let s = match s {
811820
Some(s) => match s {
812821
"full-iso" => Ok("%Y-%m-%d %H:%M:%S.%f %z"),
813822
"long-iso" => Ok("%Y-%m-%d %H:%M"),
@@ -818,6 +827,26 @@ fn parse_time_style(s: Option<&str>) -> UResult<&str> {
818827
},
819828
},
820829
None => Ok("%Y-%m-%d %H:%M"),
830+
};
831+
match s {
832+
Ok(s) => {
833+
let mut s = s.to_owned();
834+
let mut start = 0;
835+
while start < s.len() {
836+
// FIXME: this should use let chains once they're stabilized
837+
// See https://github.com/rust-lang/rust/issues/53667
838+
if let Some(cap) = TIME_STYLE_REGEX.captures(&s[start..]) {
839+
let mat = cap.name("before").unwrap();
840+
let percent_idx = mat.end();
841+
s.replace_range(percent_idx + start..percent_idx + start + 1, "%%");
842+
start = percent_idx + 2;
843+
} else {
844+
break;
845+
}
846+
}
847+
Ok(Cow::Owned(s))
848+
}
849+
Err(e) => Err(e),
821850
}
822851
}
823852

tests/by-util/test_du.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,42 @@ fn test_valid_time_style(#[case] input: &str) {
11851185
.succeeds();
11861186
}
11871187

1188+
#[test]
1189+
fn test_time_style_escaping() {
1190+
let ts = TestScenario::new(util_name!());
1191+
let at = &ts.fixtures;
1192+
1193+
at.touch("date_test");
1194+
1195+
// printable characters (a little overkill, but better than missing something)
1196+
for c in '!'..='~' {
1197+
let f = format!("+%{c}");
1198+
ts.ucmd()
1199+
.arg("--time")
1200+
.arg("--time-style")
1201+
.arg(f)
1202+
.arg("date_test")
1203+
.succeeds();
1204+
for prefix in ".:#0-_".chars() {
1205+
let f = format!("+%{prefix}{c}");
1206+
ts.ucmd()
1207+
.arg("--time")
1208+
.arg("--time-style")
1209+
.arg(f)
1210+
.arg("date_test")
1211+
.succeeds();
1212+
}
1213+
}
1214+
1215+
let f = "+%%%";
1216+
ts.ucmd()
1217+
.arg("--time")
1218+
.arg("--time-style")
1219+
.arg(f)
1220+
.arg("date_test")
1221+
.succeeds();
1222+
}
1223+
11881224
#[test]
11891225
fn test_human_size() {
11901226
use std::fs::File;

0 commit comments

Comments
 (0)