1use alloc::vec::Vec;
8
9use crate::Odb;
10use crate::err::{Error, Result};
11use crate::object::{Kind, TreeIter};
12use crate::oid::Oid;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum EntryKind {
17 File,
18 Executable,
19 Symlink,
21 Gitlink,
23}
24
25pub type Visit<'a> = dyn FnMut(&[u8], EntryKind, &[u8]) -> Result<()> + 'a;
27
28pub fn walk<O: Odb>(odb: &O, tree: Oid, visit: &mut Visit<'_>) -> Result<()> {
31 let root = read_tree(odb, &tree)?;
32 let mut stack: Vec<(Vec<u8>, Vec<u8>, usize)> = alloc::vec![(Vec::new(), root, 0)];
34
35 loop {
36 let Some((prefix, body, pos)) = stack.last() else {
39 return Ok(());
40 };
41 let Some(entry) = TreeIter::new(&body[*pos..]).next() else {
42 stack.pop();
43 continue;
44 };
45 let entry = entry?;
46 if entry.name.is_empty()
49 || entry.name == b"."
50 || entry.name == b".."
51 || entry.name.contains(&b'/')
52 {
53 return Err(Error::Corrupt("tree entry name"));
54 }
55 let consumed = entry.mode.len() + 1 + entry.name.len() + 1 + 20;
57
58 let mut path = prefix.clone();
59 if !path.is_empty() {
60 path.push(b'/');
61 }
62 path.extend_from_slice(entry.name);
63 let (mode, oid) = (entry.mode.to_vec(), entry.oid);
64
65 let top = stack.last_mut().expect("stack is non-empty");
66 top.2 += consumed;
67
68 match mode.as_slice() {
69 b"40000" | b"040000" => {
70 if stack.len() >= 256 {
71 return Err(Error::Corrupt("tree nesting too deep"));
72 }
73 let child = read_tree(odb, &oid)?;
74 stack.push((path, child, 0));
75 }
76 b"160000" => visit(&path, EntryKind::Gitlink, oid.as_bytes())?,
77 _ => {
78 let kind = match mode.as_slice() {
79 b"100644" | b"100664" => EntryKind::File,
80 b"100755" => EntryKind::Executable,
81 b"120000" => EntryKind::Symlink,
82 _ => return Err(Error::Corrupt("tree entry mode")),
83 };
84 let Some((Kind::Blob, content)) = odb.read(&oid) else {
85 return Err(Error::MissingBase);
86 };
87 visit(&path, kind, &content)?;
88 }
89 }
90 }
91}
92
93fn read_tree<O: Odb>(odb: &O, oid: &Oid) -> Result<Vec<u8>> {
94 match odb.read(oid) {
95 Some((Kind::Tree, body)) => Ok(body),
96 Some(_) => Err(Error::Corrupt("not a tree")),
97 None => Err(Error::MissingBase),
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::object::compute_oid;
105 use alloc::vec;
106
107 struct MemOdb(Vec<(Oid, Kind, Vec<u8>)>);
108
109 impl MemOdb {
110 fn put(&mut self, kind: Kind, body: Vec<u8>) -> Oid {
111 let oid = compute_oid(kind, &body);
112 self.0.push((oid, kind, body));
113 oid
114 }
115 }
116
117 impl Odb for MemOdb {
118 fn read(&self, oid: &Oid) -> Option<(Kind, Vec<u8>)> {
119 self.0
120 .iter()
121 .find(|(o, _, _)| o == oid)
122 .map(|(_, k, b)| (*k, b.clone()))
123 }
124 }
125
126 fn tree_body(entries: &[(&[u8], &[u8], Oid)]) -> Vec<u8> {
127 let mut body = Vec::new();
128 for (mode, name, oid) in entries {
129 body.extend_from_slice(mode);
130 body.push(b' ');
131 body.extend_from_slice(name);
132 body.push(0);
133 body.extend_from_slice(oid.as_bytes());
134 }
135 body
136 }
137
138 #[test]
139 fn walks_nested_trees_in_order() {
140 let mut odb = MemOdb(vec![]);
141 let blob_a = odb.put(Kind::Blob, b"A".to_vec());
142 let blob_b = odb.put(Kind::Blob, b"B".to_vec());
143 let sub = odb.put(Kind::Tree, tree_body(&[(b"100755", b"run.sh", blob_b)]));
144 let root = odb.put(
145 Kind::Tree,
146 tree_body(&[(b"100644", b"a.txt", blob_a), (b"40000", b"dir", sub)]),
147 );
148
149 let mut seen: Vec<(Vec<u8>, EntryKind, Vec<u8>)> = Vec::new();
150 walk(&odb, root, &mut |path, kind, content| {
151 seen.push((path.to_vec(), kind, content.to_vec()));
152 Ok(())
153 })
154 .unwrap();
155
156 assert_eq!(
157 seen,
158 vec![
159 (b"a.txt".to_vec(), EntryKind::File, b"A".to_vec()),
160 (b"dir/run.sh".to_vec(), EntryKind::Executable, b"B".to_vec()),
161 ]
162 );
163 }
164
165 #[test]
167 fn malicious_names_rejected() {
168 for name in [&b".."[..], b".", b"", b"a/b"] {
169 let mut odb = MemOdb(vec![]);
170 let blob = odb.put(Kind::Blob, b"x".to_vec());
171 let root = odb.put(Kind::Tree, tree_body(&[(b"100644", name, blob)]));
172 assert_eq!(
173 walk(&odb, root, &mut |_, _, _| Ok(())).unwrap_err(),
174 Error::Corrupt("tree entry name"),
175 "name={name:?}"
176 );
177 }
178 }
179
180 #[test]
181 fn missing_blob_is_error() {
182 let mut odb = MemOdb(vec![]);
183 let ghost = compute_oid(Kind::Blob, b"ghost");
184 let root = odb.put(Kind::Tree, tree_body(&[(b"100644", b"x", ghost)]));
185 assert_eq!(
186 walk(&odb, root, &mut |_, _, _| Ok(())).unwrap_err(),
187 Error::MissingBase
188 );
189 }
190}