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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Turning a resolved path into a name a file system will take.
//!
//! Three things stand between a path a resolver gave and a file on disk: the
//! path may be one the extraction must refuse outright ([`is_evil`]), another
//! path of the same extraction may need it to be a directory
//! ([`DirectoryPaths`]), or the file system may refuse the name when the write
//! is tried ([`is_path_conflict`]). The first two are settled before anything
//! is written, which is what makes one archive and one hash table give one
//! output tree on every run.
//!
//! `docs/design/wad-extractor.md` records why, and what was measured.
use ;
use ;
use LeagueFileKind;
use crateWadChunk;
use hex_name;
/// Whether `path` is one an extraction must refuse to write.
///
/// A resolver's paths are untrusted, a hash table and name recovery alike;
/// the extractor module docs say why, under "Paths the extraction will not
/// write". A path is evil when joining it onto the output directory would not
/// give a plain file plainly under that directory:
///
/// - it starts at a root, a drive or a network share, so the join ignores the
/// output directory;
/// - a component is `..`, which reaches the directory above;
/// - a component holds a `:`, naming a Windows drive or an alternate data
/// stream instead of a file the directory lists;
/// - a component ends in a dot or a space, which Windows strips before it
/// looks the name up, so `notes.txt.` and `notes.txt` are one file under two
/// names and would walk past the check for two chunks claiming one path;
/// - or it names no file at all, holding nothing but separators and `.`.
///
/// The last two rules are Windows behaviour, applied wherever the extraction
/// runs. That is deliberate: one archive and one hash table then give one
/// output tree on every host, and a test on any of them catches a table that
/// would misbehave on Windows. It costs two conditions.
///
/// `path` is read as the raw string a resolver gave. Turning it into a
/// [`Utf8Path`] first would normalise away the very things this looks for, and
/// `/` and `\` both separate components whatever the host, so a table written
/// on Windows cannot escape on Linux or the other way round.
///
/// The check is lexical. It says the joined path cannot name a file outside the
/// output directory; it says nothing about a symlink an output tree already
/// holds.
pub
/// The directories a set of paths names, each a path in its own right.
///
/// A WAD is a flat map from path to bytes, so it can hold both `x` and `x/y`.
/// A file system holds one or the other, so one of the two has to move. Which
/// one is settled against this, built over the paths an extraction resolved
/// before it writes any of them. Settling it at the write instead would follow
/// whichever worker reached the file system first, and two runs of one archive
/// could give two trees.
pub ;
/// `path` with one `/` between its components and nothing else.
///
/// The components are the ones a join steps onto, so an empty one from `a//b`
/// or from a trailing separator is dropped, as is a `.`, and `\` separates as
/// `/` does whatever the host. Borrowed when `path` is in that form already,
/// which nearly every path a hash table holds is.
pub
/// Whether `error` says something already occupies `path`, rather than the
/// write having failed for a reason a different name would not mend.
///
/// Windows reports a directory standing in the way as `PermissionDenied`, and
/// reports a file it cannot open the same way, so `path.is_dir()` breaks the
/// tie between the two.
pub
/// `<hash>.<ext>`, the name a chunk takes when the file system refuses its own.
pub
/// `<name>.ltk`, the name a chunk takes when a directory holds its own.
///
/// The suffix is added and never substituted, so stripping a trailing `.ltk`
/// gives back the path the chunk was named for, whatever that path held. A
/// caller that hashes an extracted file's path back to its chunk needs exactly
/// that, and a name built from the file's stem could not give it: `foo.bin`
/// renamed to `foo.ltk.dds` says nothing about the `.bin` it came from.
pub
/// `path` with the suffix on the end of its file name.
///
/// The suffix goes on the end of the path, which is the same thing: a path's
/// file name is its tail. Going through
/// [`set_file_name`](Utf8PathBuf::set_file_name) would not be, because that
/// re-joins the path with the host's separator and would hand back
/// `assets\thing.ltk` on Windows where every un-renamed chunk reports
/// `assets/thing`. A caller stripping the suffix off that would hash a path
/// the archive was never built from.
pub