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