git_policy_cli/
repo_read_object.rs1use super::repo_read_layout::ObjectFormat;
13use miniz_oxide::inflate::TINFLStatus;
14use miniz_oxide::inflate::core::{DecompressorOxide, decompress, inflate_flags};
15
16pub const OBJECT_SIZE_LIMIT: usize = 256 << 20;
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum ObjectKind {
22 Commit,
24 Tree,
26 Blob,
28 Tag,
30}
31
32impl ObjectKind {
33 pub fn word(self) -> &'static str {
35 return match self {
36 ObjectKind::Commit => "commit",
37 ObjectKind::Tree => "tree",
38 ObjectKind::Blob => "blob",
39 ObjectKind::Tag => "tag",
40 };
41 }
42
43 pub fn from_word(word: &[u8]) -> Option<ObjectKind> {
45 return match word {
46 b"commit" => Some(ObjectKind::Commit),
47 b"tree" => Some(ObjectKind::Tree),
48 b"blob" => Some(ObjectKind::Blob),
49 b"tag" => Some(ObjectKind::Tag),
50 _ => None,
51 };
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct RawObject {
58 pub kind: ObjectKind,
60 pub bytes: Vec<u8>,
62}
63
64pub fn name_length(format: ObjectFormat) -> usize {
66 return format.hex_length() / 2;
67}
68
69pub fn to_hex(name: &[u8]) -> String {
71 let mut text: String = String::with_capacity(name.len() * 2);
72 for byte in name {
73 text.push(char::from(b"0123456789abcdef"[usize::from(byte >> 4)]));
74 text.push(char::from(b"0123456789abcdef"[usize::from(byte & 15)]));
75 }
76 return text;
77}
78
79pub fn from_hex(text: &[u8], format: ObjectFormat) -> Option<Vec<u8>> {
81 if text.len() != format.hex_length() {
82 return None;
83 }
84 let digit = |byte: u8| -> Option<u8> {
85 return match byte {
86 b'0'..=b'9' => Some(byte - b'0'),
87 b'a'..=b'f' => Some(byte - b'a' + 10),
88 _ => None,
89 };
90 };
91 let mut name: Vec<u8> = Vec::with_capacity(text.len() / 2);
92 for pair in text.chunks(2) {
93 name.push(digit(pair[0])? << 4 | digit(pair[1])?);
94 }
95 return Some(name);
96}
97
98pub const INFLATE_INPUT_LIMIT: usize = 512 << 10;
103
104const FIRST_OUTPUT_BYTES: usize = 64;
106
107pub fn inflate_zlib(whole_input: &[u8], limit: usize) -> Option<(Vec<u8>, usize)> {
119 let input: &[u8] = &whole_input[..whole_input.len().min(INFLATE_INPUT_LIMIT)];
120 let flags: u32 = inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
121 let mut output: Vec<u8> = vec![0; input.len().saturating_mul(2).min(limit)];
122 let mut state: Box<DecompressorOxide> = Box::<DecompressorOxide>::default();
123 let mut produced: usize = 0;
124 let mut used: usize = 0;
125 loop {
126 let (status, consumed, written) = decompress(&mut state, input.get(used..)?, &mut output, produced, flags);
127 used = used.checked_add(consumed)?;
128 produced = produced.checked_add(written)?;
129 match status {
130 TINFLStatus::Done => {
131 output.truncate(produced);
132 return Some((output, used));
133 }
134 TINFLStatus::HasMoreOutput => {
135 if output.len() >= limit {
136 return None;
137 }
138 let grown: usize = output.len().saturating_mul(2).max(FIRST_OUTPUT_BYTES).min(limit);
139 output.resize(grown, 0);
140 }
141 _ => return None,
142 }
143 }
144}
145
146pub fn parse_loose(file_bytes: &[u8]) -> Option<RawObject> {
157 let (whole, consumed): (Vec<u8>, usize) = inflate_zlib(file_bytes, OBJECT_SIZE_LIMIT + 32)?;
158 if consumed != file_bytes.len() {
160 return None;
161 }
162 let nul: usize = whole.iter().position(|byte| { return *byte == 0; })?;
163 let header: &[u8] = &whole[..nul];
164 let space: usize = header.iter().position(|byte| { return *byte == b' '; })?;
165 let kind: ObjectKind = ObjectKind::from_word(&header[..space])?;
166 let size_text: &str = std::str::from_utf8(&header[space + 1..]).ok()?;
167 if size_text.is_empty() || (size_text.len() > 1 && size_text.starts_with('0')) || !size_text.bytes().all(|byte| { return byte.is_ascii_digit(); }) {
168 return None;
169 }
170 let size: usize = size_text.parse::<usize>().ok()?;
171 let content: &[u8] = &whole[nul + 1..];
172 if content.len() != size {
173 return None;
174 }
175 return Some(RawObject { kind, bytes: content.to_vec() });
176}
177
178pub fn inflate_exact(window: &[u8], expected: usize) -> Option<Vec<u8>> {
188 if expected > OBJECT_SIZE_LIMIT {
189 return None;
190 }
191 let (inflated, _): (Vec<u8>, usize) = inflate_zlib(window, expected)?;
192 if inflated.len() != expected {
193 return None;
194 }
195 return Some(inflated);
196}
197
198#[cfg(test)]
199#[path = "repo_read_object_tests.rs"]
200mod tests;