1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! One rule, asked by every door here that builds a path out of a caller's
//! string: is that string ONE plain path component?
//!
//! Two doors take a caller-controlled string, append a fixed suffix, and join
//! the result to a directory the caller configured earlier —
//! [`ResultWriter::write`](crate::audio::whisper::result::writer::ResultWriter::write)'s
//! `file_stem` under the writer's `output_dir` (#114), and
//! [`detect_model_url`](crate::audio::whisper::model::detect_model_url)'s
//! `name` under its `folder` (#120). A join is not a concatenation:
//! `dir.join("../x")` names a file in `dir`'s PARENT and `dir.join("/x")`
//! discards `dir` altogether, so a string carrying path syntax silently
//! redirects the write, or the read, to a directory other than the one the
//! caller configured.
//!
//! [`single_path_component`] refuses exactly the spellings that change WHICH
//! directory a join resolves into, and nothing else: no Unicode
//! normalisation, no length cap, no reserved-name list, no case folding.
//! Whether the surviving name is one this filesystem will actually accept —
//! too long, already taken, on a read-only mount — is the filesystem's
//! business, reported by the `std::fs` call that runs into it.
//!
//! `.` and `..` are refused as the components they are, even though both call
//! sites append a suffix before joining (`..` in fact reaches
//! `folder/...mlmodelc`, an ordinary file, and escapes nothing). The rule is a
//! property of the caller's STRING, not of one caller's suffix: a door that
//! ever joins the string bare must get the same answer from it.
//!
//! `\` is refused alongside `/` though macOS treats it as an ordinary
//! filename byte. A transcript stem and a model name both travel — into an
//! archive entry, onto an SMB share, to a Windows client — and are read as
//! two components wherever they land; neither of these two doors has a caller
//! that wants one, so refusing it costs nothing here.
/// Why a caller's string is not one plain path component.
///
/// Crate-private on purpose: each door maps this into ITS own public error
/// ([`WriteError::FileStem`](crate::audio::whisper::result::writer::WriteError::FileStem),
/// [`ModelError::ModelName`](crate::audio::whisper::error::ModelError::ModelName)),
/// which is what a caller matches on. [`Self::reason`] is the phrase those
/// errors render.
pub
/// `s` unchanged when it is one plain path component; its defect otherwise.
///
/// Accepted iff `s` is non-empty, holds no `/`, no `\` and no NUL byte, and is
/// neither `.` nor `..`. That is the whole rule — see this module's doc for
/// why nothing else belongs in it.
///
/// # Errors
/// The [`PathComponentDefect`] the string tripped, whose
/// [`reason`](PathComponentDefect::reason) each caller renders into its own
/// error type.
pub