Skip to content

Commit f1f0ba5

Browse files
committed
feat: add path::component_is_windows_device()
That way it's easy to determine if a component contains a windows device name
1 parent 5f86e6b commit f1f0ba5

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

gix-validate/src/path.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,25 @@ pub fn component(
124124
Ok(input)
125125
}
126126

127-
fn check_win_devices_and_illegal_characters(input: &BStr) -> Option<component::Error> {
128-
let in3 = input.get(..3)?;
127+
/// Return `true` if the path component at `input` looks like a Windows device, like `CON`
128+
/// or `LPT1` (case-insensitively).
129+
///
130+
/// This is relevant only on Windows, where one may be tricked into reading or writing to such devices.
131+
/// When reading from `CON`, a console-program may block until the user provided input.
132+
pub fn component_is_windows_device(input: &BStr) -> bool {
133+
is_win_device(input)
134+
}
135+
136+
fn is_win_device(input: &BStr) -> bool {
137+
let Some(in3) = input.get(..3) else { return false };
129138
if in3.eq_ignore_ascii_case(b"AUX") && is_done_windows(input.get(3..)) {
130-
return Some(component::Error::WindowsReservedName);
139+
return true;
131140
}
132141
if in3.eq_ignore_ascii_case(b"NUL") && is_done_windows(input.get(3..)) {
133-
return Some(component::Error::WindowsReservedName);
142+
return true;
134143
}
135144
if in3.eq_ignore_ascii_case(b"PRN") && is_done_windows(input.get(3..)) {
136-
return Some(component::Error::WindowsReservedName);
145+
return true;
137146
}
138147
// Note that the following allows `COM0`, even though `LPT0` is not allowed.
139148
// Even though tests seem to indicate that neither `LPT0` nor `COM0` are valid
@@ -145,19 +154,26 @@ fn check_win_devices_and_illegal_characters(input: &BStr) -> Option<component::E
145154
&& input.get(3).map_or(false, |n| *n >= b'1' && *n <= b'9')
146155
&& is_done_windows(input.get(4..))
147156
{
148-
return Some(component::Error::WindowsReservedName);
157+
return true;
149158
}
150159
if in3.eq_ignore_ascii_case(b"LPT")
151160
&& input.get(3).map_or(false, u8::is_ascii_digit)
152161
&& is_done_windows(input.get(4..))
153162
{
154-
return Some(component::Error::WindowsReservedName);
163+
return true;
155164
}
156165
if in3.eq_ignore_ascii_case(b"CON")
157166
&& (is_done_windows(input.get(3..))
158167
|| (input.get(3..6).map_or(false, |n| n.eq_ignore_ascii_case(b"IN$")) && is_done_windows(input.get(6..)))
159168
|| (input.get(3..7).map_or(false, |n| n.eq_ignore_ascii_case(b"OUT$")) && is_done_windows(input.get(7..))))
160169
{
170+
return true;
171+
}
172+
false
173+
}
174+
175+
fn check_win_devices_and_illegal_characters(input: &BStr) -> Option<component::Error> {
176+
if is_win_device(input) {
161177
return Some(component::Error::WindowsReservedName);
162178
}
163179
if input.iter().any(|b| *b < 0x20 || b":<>\"|?*".contains(b)) {

gix-validate/tests/path/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
#[test]
2+
fn component_is_windows_device() {
3+
for device in ["con", "CONIN$", "lpt1.txt", "AUX", "Prn", "NUL", "COM9"] {
4+
assert!(gix_validate::path::component_is_windows_device(device.into()));
5+
}
6+
for not_device in ["coni", "CONIN", "lpt", "AUXi", "aPrn", "NULl", "COM"] {
7+
assert!(!gix_validate::path::component_is_windows_device(not_device.into()));
8+
}
9+
}
10+
111
mod component {
212
use gix_validate::path::component;
313

0 commit comments

Comments
 (0)