Repository navigation
Expand file tree
/
Copy pathtar_extension_state.rs
More file actions
157 lines (157 loc) · 6.89 KB
/
Copy pathtar_extension_state.rs
File metadata and controls
157 lines (157 loc) · 6.89 KB
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
//! 有界 GNU/PAX 解释层;不恢复权限、所有者、时间或扩展属性。
use std::collections::BTreeSet;
/// 下一普通条目的局部扩展状态;消费后清空,不得扩展到其它文件。
#[derive(Default)]
pub(crate) struct TarExtensionState {
path: Option<String>,
size: Option<u64>,
gnu_seen: bool,
pax_seen: bool,
}
impl TarExtensionState {
/// 解析完整扩展数据;参数为原生类型及有界字节,返回固定原因。
pub(crate) fn observe(&mut self, kind: u8, bytes: &[u8]) -> Result<(), &'static str> {
if bytes.is_empty() || bytes.len() > 64 * 1024 {
return Err("archive_extension_invalid");
}
match kind {
b'L' => {
if self.gnu_seen || bytes.len() > 1025 || bytes.last() != Some(&0) {
return Err("archive_extension_invalid");
}
let path = std::str::from_utf8(&bytes[..bytes.len() - 1])
.map_err(|_| "archive_extension_invalid")?;
if path.is_empty() || path.chars().any(char::is_control) {
return Err("archive_extension_invalid");
}
self.merge_path(path)?;
self.gnu_seen = true;
}
b'x' | b'g' => {
let global = kind == b'g';
if !global && self.pax_seen {
return Err("archive_extension_conflict");
}
let mut seen = BTreeSet::new();
let mut offset = 0;
while offset < bytes.len() {
// PAX 是带长度的记录,不按换行分割后忽略坏尾部;所有记录必须完整消费。
let remaining = &bytes[offset..];
let space = remaining
.iter()
.position(|b| *b == b' ')
.ok_or("archive_extension_invalid")?;
if space == 0
|| space > 8
|| !remaining[..space].iter().all(u8::is_ascii_digit)
|| remaining[0] == b'0'
{
return Err("archive_extension_invalid");
}
let length = std::str::from_utf8(&remaining[..space])
.map_err(|_| "archive_extension_invalid")?
.parse::<usize>()
.map_err(|_| "archive_extension_invalid")?;
if length <= space + 2
|| length > remaining.len()
|| remaining[length - 1] != b'\n'
{
return Err("archive_extension_invalid");
}
let payload = &remaining[space + 1..length - 1];
let equals = payload
.iter()
.position(|b| *b == b'=')
.ok_or("archive_extension_invalid")?;
let key = std::str::from_utf8(&payload[..equals])
.map_err(|_| "archive_extension_invalid")?;
if key.is_empty()
|| !key
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-'))
|| !seen.insert(key)
{
return Err("archive_extension_invalid");
}
let value = &payload[equals + 1..];
if key == "linkpath"
|| key.starts_with("GNU.sparse.")
|| matches!(key, "SCHILY.filetype" | "SCHILY.realsize" | "hdrcharset")
{
return Err("archive_extension_unsupported");
}
match key {
"path" => {
if global {
return Err("archive_extension_unsupported");
}
let path = std::str::from_utf8(value)
.map_err(|_| "archive_extension_invalid")?;
if path.is_empty()
|| path.len() > 1024
|| path.chars().any(char::is_control)
{
return Err("archive_extension_invalid");
}
self.merge_path(path)?;
}
"size" => {
if global {
return Err("archive_extension_unsupported");
}
if value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
return Err("archive_extension_invalid");
}
self.size = Some(
std::str::from_utf8(value)
.map_err(|_| "archive_extension_invalid")?
.parse()
.map_err(|_| "archive_extension_invalid")?,
);
}
// 非路径/大小的描述字段仅消费,永远不保留到落盘权限或 xattr。
_ => {}
}
offset += length;
}
if !global {
self.pax_seen = true;
}
}
_ => return Err("archive_extension_unsupported"),
}
Ok(())
}
/// 为当前文件取得生效路径和大小后清空状态;覆盖路径不要求截断的原头仍为 UTF-8。
pub(crate) fn consume(
&mut self,
base_path: &[u8],
header_size: u64,
) -> Result<(String, u64), &'static str> {
let path = match self.path.take() {
Some(path) => path,
None => std::str::from_utf8(base_path)
.map_err(|_| "archive_path_invalid")?
.to_owned(),
};
let size = self.size.take().unwrap_or(header_size);
self.gnu_seen = false;
self.pax_seen = false;
Ok((path, size))
}
/// 原头路径是否被完整扩展路径替代;返回值只控制有效路径解析。
pub(crate) fn has_path_override(&self) -> bool {
self.path.is_some()
}
/// 判断尾部是否留下没有对应文件的局部扩展。
pub(crate) fn has_pending(&self) -> bool {
self.gnu_seen || self.pax_seen
}
fn merge_path(&mut self, path: &str) -> Result<(), &'static str> {
if self.path.as_deref().is_some_and(|old| old != path) {
return Err("archive_extension_conflict");
}
self.path = Some(path.to_owned());
Ok(())
}
}