keyhog_core/winpath.rs
1//! Canonical Windows-path classification predicates.
2//!
3//! ONE PLACE for "does this string carry a Windows drive letter". Two callers
4//! previously hand-rolled a private `is_windows_absolute` with *different*
5//! semantics under the *same* name, a same-name-divergence trap:
6//!
7//! * the archive entry-name sanitizer (a security reject) wanted the BROAD
8//! sense: reject anything drive-letter-prefixed, including the drive-RELATIVE
9//! `C:evil` form, because on Windows `C:evil` still escapes the intended
10//! extraction root; and
11//! * the SARIF URI formatter wanted the STRICT sense: only a fully-qualified
12//! absolute path `C:\dir` / `C:/dir` is "absolute"; the drive-relative
13//! `C:rel` resolves against the drive's current directory and is NOT absolute.
14//!
15//! Both are correct for their caller, so they are two DISTINCT predicates with
16//! DISTINCT names, defined once here and imported where needed. No reader has to
17//! guess which `is_windows_absolute` a call meant.
18//!
19//! Tests live in `tests/unit/winpath.rs` (KH-GAP-004: no inline test modules
20//! in `src/`).
21
22/// True iff `s` begins with a Windows drive-letter prefix (`X:`), regardless of
23/// whether a path separator follows. This is the BROAD, security-oriented sense
24/// used to reject untrusted archive entry names: it catches the fully-qualified
25/// `C:\evil` *and* the drive-relative `C:evil`, both of which can escape an
26/// intended extraction root on Windows.
27#[must_use]
28pub fn has_windows_drive_prefix(s: &str) -> bool {
29 let b = s.as_bytes();
30 b.len() >= 2 && b[0].is_ascii_alphabetic() && b[1] == b':'
31}
32
33/// True iff `s` is a fully-qualified Windows absolute path: a drive letter, a
34/// colon, and a path separator (`C:\dir` or `C:/dir`). This is the STRICT sense
35/// used when "absolute" must mean root-anchored, e.g. deciding whether a path
36/// is already absolute for URI formatting. The drive-relative `C:rel` is NOT
37/// absolute and returns `false`.
38#[must_use]
39pub fn is_windows_absolute(s: &str) -> bool {
40 let b = s.as_bytes();
41 b.len() >= 3 && b[0].is_ascii_alphabetic() && b[1] == b':' && (b[2] == b'/' || b[2] == b'\\')
42}