1use std::io::Read as _;
2
3use amiss_wire::controls::{GitMode, ResourceName};
4use amiss_wire::model::{ObjectFormat, Oid};
5use flate2::bufread::ZlibDecoder;
6use sha1_checked::Digest as _;
7use sha2::Digest as _;
8
9use crate::Error;
10use crate::resources::ValueCap;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum ObjectKind {
14 Blob,
15 Commit,
16 Tag,
17 Tree,
18}
19
20impl ObjectKind {
21 #[must_use]
22 pub const fn as_str(self) -> &'static str {
23 match self {
24 Self::Blob => "blob",
25 Self::Commit => "commit",
26 Self::Tag => "tag",
27 Self::Tree => "tree",
28 }
29 }
30
31 pub(crate) const fn from_pack_type(code: u8) -> Option<Self> {
32 match code {
33 1 => Some(Self::Commit),
34 2 => Some(Self::Tree),
35 3 => Some(Self::Blob),
36 4 => Some(Self::Tag),
37 _ => None,
38 }
39 }
40
41 fn from_token(token: &[u8]) -> Option<Self> {
42 match token {
43 b"blob" => Some(Self::Blob),
44 b"commit" => Some(Self::Commit),
45 b"tag" => Some(Self::Tag),
46 b"tree" => Some(Self::Tree),
47 _ => None,
48 }
49 }
50}
51
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Object {
54 pub kind: ObjectKind,
55 pub body: Vec<u8>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct TreeEntry {
60 pub mode: GitMode,
61 pub name: Vec<u8>,
62 pub oid: Oid,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Commit {
67 pub tree: Oid,
68 pub parents: Vec<Oid>,
69}
70
71pub fn decode_loose(
78 compressed: &[u8],
79 object_format: ObjectFormat,
80 oid: &Oid,
81 inflated_cap: u64,
82 value_cap: Option<&ValueCap>,
83) -> Result<Object, Error> {
84 let mut decoder = ZlibDecoder::new(compressed);
85 let header = read_header(&mut decoder, inflated_cap, value_cap)?;
86 let mut body = vec![0_u8; header.size];
87 fill_exact(&mut decoder, &mut body)?;
88 let mut probe = [0_u8; 1];
89 match decoder.read(&mut probe) {
90 Ok(0) => {}
91 Ok(_) | Err(_) => return Err(Error::ObjectUnreadable),
92 }
93 if decoder.total_in() != u64::try_from(compressed.len()).map_err(discard_to_unreadable)? {
94 return Err(Error::ObjectUnreadable);
95 }
96 verify_oid(object_format, oid, &header.raw, &body)?;
97 Ok(Object {
98 kind: header.kind,
99 body,
100 })
101}
102
103struct Header {
104 kind: ObjectKind,
105 size: usize,
106 raw: Vec<u8>,
107}
108
109fn read_header(
110 decoder: &mut ZlibDecoder<&[u8]>,
111 inflated_cap: u64,
112 value_cap: Option<&ValueCap>,
113) -> Result<Header, Error> {
114 const MAX_SAFE: u64 = 9_007_199_254_740_991;
115 let mut raw: Vec<u8> = Vec::new();
116 let mut token: Vec<u8> = Vec::new();
117 let kind = loop {
118 let byte = next_byte(decoder)?;
119 raw.push(byte);
120 if byte == b' ' {
121 break ObjectKind::from_token(&token).ok_or(Error::ObjectUnreadable)?;
122 }
123 token.push(byte);
124 if token.len() > 6 {
125 return Err(Error::ObjectUnreadable);
126 }
127 };
128
129 let mut value: u64 = 0;
130 let mut digits: usize = 0;
131 let mut leading_zero = false;
132 let mut saturated = false;
133 loop {
134 let byte = next_byte(decoder)?;
135 raw.push(byte);
136 if byte == 0 {
137 break;
138 }
139 if !byte.is_ascii_digit() || leading_zero {
140 return Err(Error::ObjectUnreadable);
141 }
142 if digits == 0 && byte == b'0' {
143 leading_zero = true;
144 }
145 digits = digits.saturating_add(1);
146 value = value
147 .saturating_mul(10)
148 .saturating_add(u64::from(byte.wrapping_sub(b'0')));
149 if value > MAX_SAFE {
150 value = MAX_SAFE;
151 saturated = true;
152 break;
153 }
154 }
155 if digits == 0 && !saturated {
156 return Err(Error::ObjectUnreadable);
157 }
158 if let Some(cap) = value_cap
159 && value > cap.limit
160 {
161 return Err(Error::ResourceLimit {
162 resource: cap.resource,
163 configured_limit: cap.limit,
164 observed_lower_bound: value,
165 });
166 }
167 if value > inflated_cap {
168 return Err(Error::ResourceLimit {
169 resource: ResourceName::GitObjectBytes,
170 configured_limit: inflated_cap,
171 observed_lower_bound: value,
172 });
173 }
174 let size = usize::try_from(value).map_err(discard_to_unreadable)?;
175 Ok(Header { kind, size, raw })
176}
177
178fn next_byte(decoder: &mut ZlibDecoder<&[u8]>) -> Result<u8, Error> {
179 let mut buf = [0_u8; 1];
180 match decoder.read(&mut buf) {
181 Ok(1) => buf.first().copied().ok_or(Error::ObjectUnreadable),
182 Ok(_) | Err(_) => Err(Error::ObjectUnreadable),
183 }
184}
185
186fn fill_exact(decoder: &mut ZlibDecoder<&[u8]>, body: &mut [u8]) -> Result<(), Error> {
187 let mut filled = 0_usize;
188 while filled < body.len() {
189 let target = body.get_mut(filled..).ok_or(Error::ObjectUnreadable)?;
190 match decoder.read(target) {
191 Ok(0) | Err(_) => return Err(Error::ObjectUnreadable),
192 Ok(n) => filled = filled.saturating_add(n),
193 }
194 }
195 Ok(())
196}
197
198pub(crate) fn discard_to_unreadable<T>(_defect: T) -> Error {
199 Error::ObjectUnreadable
200}
201
202pub(crate) fn ordinary_digest(object_format: ObjectFormat, data: &[u8]) -> Vec<u8> {
203 match object_format {
204 ObjectFormat::Sha1 => {
205 let mut hasher = sha1_checked::Sha1::builder()
206 .detect_collision(false)
207 .build();
208 hasher.update(data);
209 hasher.try_finalize().hash().to_vec()
210 }
211 ObjectFormat::Sha256 => {
212 let mut hasher = sha2::Sha256::new();
213 hasher.update(data);
214 hasher.finalize().to_vec()
215 }
216 }
217}
218
219pub(crate) fn verify_oid(
220 object_format: ObjectFormat,
221 oid: &Oid,
222 raw_header: &[u8],
223 body: &[u8],
224) -> Result<(), Error> {
225 let actual = match object_format {
226 ObjectFormat::Sha1 => {
227 let mut hasher = sha1_checked::Sha1::builder()
228 .detect_collision(true)
229 .safe_hash(false)
230 .use_ubc(true)
231 .build();
232 hasher.update(raw_header);
233 hasher.update(body);
234 let result = hasher.try_finalize();
235 if result.has_collision() {
236 return Err(Error::ObjectUnreadable);
237 }
238 hex(result.hash().as_slice())
239 }
240 ObjectFormat::Sha256 => {
241 let mut hasher = sha2::Sha256::new();
242 hasher.update(raw_header);
243 hasher.update(body);
244 hex(&hasher.finalize())
245 }
246 };
247 if actual == oid.as_str() {
248 Ok(())
249 } else {
250 Err(Error::ObjectUnreadable)
251 }
252}
253
254pub(crate) fn hex(bytes: &[u8]) -> String {
255 let mut out = String::new();
256 for byte in bytes {
257 out.push(hex_digit(u32::from(byte.wrapping_shr(4))));
258 out.push(hex_digit(u32::from(byte & 0xF)));
259 }
260 out
261}
262
263fn hex_digit(value: u32) -> char {
264 char::from_digit(value, 16).unwrap_or('0')
265}
266
267pub fn parse_tree(object_format: ObjectFormat, body: &[u8]) -> Result<Vec<TreeEntry>, Error> {
273 let oid_width = match object_format {
274 ObjectFormat::Sha1 => 20_usize,
275 ObjectFormat::Sha256 => 32_usize,
276 };
277 let mut entries: Vec<TreeEntry> = Vec::new();
278 let mut previous_key: Option<Vec<u8>> = None;
279 let mut pos = 0_usize;
280 while pos < body.len() {
281 let rest = body.get(pos..).ok_or(Error::ObjectUnreadable)?;
282 let space = rest
283 .iter()
284 .position(|&b| b == b' ')
285 .ok_or(Error::ObjectUnreadable)?;
286 let mode_bytes = rest.get(..space).ok_or(Error::ObjectUnreadable)?;
287 let mode = tree_mode(mode_bytes)?;
288 let after_mode = rest
289 .get(space.saturating_add(1)..)
290 .ok_or(Error::ObjectUnreadable)?;
291 let nul = after_mode
292 .iter()
293 .position(|&b| b == 0)
294 .ok_or(Error::ObjectUnreadable)?;
295 let name = after_mode.get(..nul).ok_or(Error::ObjectUnreadable)?;
296 if name.is_empty() || name.contains(&b'/') || name == b"." || name == b".." {
297 return Err(Error::ObjectUnreadable);
298 }
299 let oid_start = nul.saturating_add(1);
300 let oid_end = oid_start.saturating_add(oid_width);
301 let raw_oid = after_mode
302 .get(oid_start..oid_end)
303 .ok_or(Error::ObjectUnreadable)?;
304 let oid = Oid::new(object_format, hex(raw_oid)).ok_or(Error::ObjectUnreadable)?;
305
306 let mut key = name.to_vec();
307 if mode == GitMode::Tree {
308 key.push(b'/');
309 }
310 if let Some(previous) = &previous_key
311 && *previous >= key
312 {
313 return Err(Error::ObjectUnreadable);
314 }
315 previous_key = Some(key);
316
317 entries.push(TreeEntry {
318 mode,
319 name: name.to_vec(),
320 oid,
321 });
322 pos = pos
323 .saturating_add(space)
324 .saturating_add(1)
325 .saturating_add(oid_end);
326 }
327 Ok(entries)
328}
329
330fn tree_mode(bytes: &[u8]) -> Result<GitMode, Error> {
331 match bytes {
332 b"40000" => Ok(GitMode::Tree),
333 b"100644" => Ok(GitMode::RegularFile),
334 b"100755" => Ok(GitMode::ExecutableFile),
335 b"120000" => Ok(GitMode::Symlink),
336 b"160000" => Ok(GitMode::Gitlink),
337 _ => Err(Error::ObjectUnreadable),
338 }
339}
340
341pub fn parse_commit(object_format: ObjectFormat, body: &[u8]) -> Result<Commit, Error> {
349 let mut lines = HeaderLines { body, pos: 0 };
350
351 let first = lines.next_line()?;
352 let tree_hex = first
353 .strip_prefix(b"tree ")
354 .ok_or(Error::ObjectUnreadable)?;
355 let tree = header_oid(object_format, tree_hex)?;
356
357 let mut parents: Vec<Oid> = Vec::new();
358 let mut line = lines.next_line()?;
359 while let Some(parent_hex) = line.strip_prefix(b"parent ") {
360 parents.push(header_oid(object_format, parent_hex)?);
361 line = lines.next_line()?;
362 }
363
364 let author = line
365 .strip_prefix(b"author ")
366 .ok_or(Error::ObjectUnreadable)?;
367 if author.is_empty() {
368 return Err(Error::ObjectUnreadable);
369 }
370 let committer_line = lines.next_line()?;
371 let committer = committer_line
372 .strip_prefix(b"committer ")
373 .ok_or(Error::ObjectUnreadable)?;
374 if committer.is_empty() {
375 return Err(Error::ObjectUnreadable);
376 }
377
378 let mut seen_extension = false;
379 loop {
380 let line = lines.next_line()?;
381 if line.is_empty() {
382 break;
383 }
384 if line.first() == Some(&b' ') {
385 if !seen_extension {
386 return Err(Error::ObjectUnreadable);
387 }
388 continue;
389 }
390 let space = line
391 .iter()
392 .position(|&b| b == b' ')
393 .ok_or(Error::ObjectUnreadable)?;
394 let key = line.get(..space).ok_or(Error::ObjectUnreadable)?;
395 let key_ok = !key.is_empty()
396 && key
397 .iter()
398 .all(|&b| b.is_ascii() && !b.is_ascii_control() && b != b' ')
399 && !matches!(key, b"tree" | b"parent" | b"author" | b"committer");
400 if !key_ok {
401 return Err(Error::ObjectUnreadable);
402 }
403 seen_extension = true;
404 }
405
406 Ok(Commit { tree, parents })
407}
408
409struct HeaderLines<'a> {
410 body: &'a [u8],
411 pos: usize,
412}
413
414impl HeaderLines<'_> {
415 fn next_line(&mut self) -> Result<&[u8], Error> {
416 let rest = self.body.get(self.pos..).ok_or(Error::ObjectUnreadable)?;
417 let lf = rest
418 .iter()
419 .position(|&b| b == b'\n')
420 .ok_or(Error::ObjectUnreadable)?;
421 let line = rest.get(..lf).ok_or(Error::ObjectUnreadable)?;
422 if line.iter().any(|&b| b == 0 || b == b'\r') {
423 return Err(Error::ObjectUnreadable);
424 }
425 self.pos = self.pos.saturating_add(lf).saturating_add(1);
426 Ok(line)
427 }
428}
429
430fn header_oid(object_format: ObjectFormat, hex_bytes: &[u8]) -> Result<Oid, Error> {
431 let text = std::str::from_utf8(hex_bytes).map_err(discard_to_unreadable)?;
432 Oid::new(object_format, text.to_owned()).ok_or(Error::ObjectUnreadable)
433}