git_policy_cli/
repo_read_object.rs1use super::repo_read_layout::ObjectFormat;
13use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit;
14
15pub const OBJECT_SIZE_LIMIT: usize = 256 << 20;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum ObjectKind {
21 Commit,
23 Tree,
25 Blob,
27 Tag,
29}
30
31impl ObjectKind {
32 pub fn word(self) -> &'static str {
34 return match self {
35 ObjectKind::Commit => "commit",
36 ObjectKind::Tree => "tree",
37 ObjectKind::Blob => "blob",
38 ObjectKind::Tag => "tag",
39 };
40 }
41
42 pub fn from_word(word: &[u8]) -> Option<ObjectKind> {
44 return match word {
45 b"commit" => Some(ObjectKind::Commit),
46 b"tree" => Some(ObjectKind::Tree),
47 b"blob" => Some(ObjectKind::Blob),
48 b"tag" => Some(ObjectKind::Tag),
49 _ => None,
50 };
51 }
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct RawObject {
57 pub kind: ObjectKind,
59 pub bytes: Vec<u8>,
61}
62
63pub fn name_length(format: ObjectFormat) -> usize {
65 return format.hex_length() / 2;
66}
67
68pub fn to_hex(name: &[u8]) -> String {
70 let mut text: String = String::with_capacity(name.len() * 2);
71 for byte in name {
72 text.push(char::from(b"0123456789abcdef"[usize::from(byte >> 4)]));
73 text.push(char::from(b"0123456789abcdef"[usize::from(byte & 15)]));
74 }
75 return text;
76}
77
78pub fn from_hex(text: &[u8], format: ObjectFormat) -> Option<Vec<u8>> {
80 if text.len() != format.hex_length() {
81 return None;
82 }
83 let digit = |byte: u8| -> Option<u8> {
84 return match byte {
85 b'0'..=b'9' => Some(byte - b'0'),
86 b'a'..=b'f' => Some(byte - b'a' + 10),
87 _ => None,
88 };
89 };
90 let mut name: Vec<u8> = Vec::with_capacity(text.len() / 2);
91 for pair in text.chunks(2) {
92 name.push(digit(pair[0])? << 4 | digit(pair[1])?);
93 }
94 return Some(name);
95}
96
97pub fn parse_loose(file_bytes: &[u8]) -> Option<RawObject> {
108 let whole: Vec<u8> = decompress_to_vec_zlib_with_limit(file_bytes, OBJECT_SIZE_LIMIT + 32).ok()?;
109 let nul: usize = whole.iter().position(|byte| { return *byte == 0; })?;
110 let header: &[u8] = &whole[..nul];
111 let space: usize = header.iter().position(|byte| { return *byte == b' '; })?;
112 let kind: ObjectKind = ObjectKind::from_word(&header[..space])?;
113 let size_text: &str = std::str::from_utf8(&header[space + 1..]).ok()?;
114 if size_text.is_empty() || (size_text.len() > 1 && size_text.starts_with('0')) || !size_text.bytes().all(|byte| { return byte.is_ascii_digit(); }) {
115 return None;
116 }
117 let size: usize = size_text.parse::<usize>().ok()?;
118 let content: &[u8] = &whole[nul + 1..];
119 if content.len() != size {
120 return None;
121 }
122 return Some(RawObject { kind, bytes: content.to_vec() });
123}
124
125pub fn inflate_exact(window: &[u8], expected: usize) -> Option<Vec<u8>> {
135 if expected > OBJECT_SIZE_LIMIT {
136 return None;
137 }
138 let inflated: Vec<u8> = decompress_to_vec_zlib_with_limit(window, expected).ok()?;
139 if inflated.len() != expected {
140 return None;
141 }
142 return Some(inflated);
143}