1use std::{
2 fs,
3 sync::{Arc, MutexGuard},
4 time::SystemTime,
5};
6
7use crate::{GitError, ObjectId, Repository, Result, error::invalid};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct IndexEntry {
11 pub ctime_seconds: u32,
12 pub mtime_seconds: u32,
13 pub mode: u32,
14 pub size: u32,
15 pub id: ObjectId,
16 pub stage: u8,
17 pub assume_valid: bool,
18 pub skip_worktree: bool,
19 pub intent_to_add: bool,
20 pub path: Vec<u8>,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Index {
25 version: u32,
26 entries: Vec<IndexEntry>,
27}
28
29pub(crate) struct CachedIndex {
30 len: u64,
31 modified: Option<SystemTime>,
32 index: Arc<Index>,
33}
34
35impl Index {
36 #[must_use]
37 pub const fn version(&self) -> u32 {
38 self.version
39 }
40
41 #[must_use]
42 pub fn entries(&self) -> &[IndexEntry] {
43 &self.entries
44 }
45
46 fn parse(data: &[u8], repository: &Repository) -> Result<Self> {
47 if data.get(..4) != Some(b"DIRC") {
48 return Err(invalid("invalid index signature"));
49 }
50 let version = read_u32(data, 4)?;
51 if !(2..=4).contains(&version) {
52 return Err(GitError::Unsupported(format!("index version {version}")));
53 }
54 let count = usize::try_from(read_u32(data, 8)?)
55 .map_err(|_| invalid("index entry count overflow"))?;
56 if count > repository.limits().max_index_entries {
57 return Err(GitError::LimitExceeded {
58 resource: "index entries",
59 limit: repository.limits().max_index_entries,
60 });
61 }
62 let trailer = repository.hash_kind().bytes();
63 if data.len() < 12 + trailer {
64 return Err(invalid("truncated index"));
65 }
66 let content_end = data.len() - trailer;
67 let mut cursor = 12;
68 let mut entries = Vec::with_capacity(count);
69 let mut previous_path = Vec::new();
70 for _ in 0..count {
71 let entry = parse_entry(
72 data,
73 &mut cursor,
74 content_end,
75 version,
76 repository.hash_kind(),
77 &previous_path,
78 )?;
79 previous_path.clone_from(&entry.path);
80 entries.push(entry);
81 }
82 if !entries.windows(2).all(|pair| {
83 (pair[0].path.as_slice(), pair[0].stage) < (pair[1].path.as_slice(), pair[1].stage)
84 }) {
85 return Err(invalid("index entries are not sorted"));
86 }
87 parse_extensions(data, cursor, content_end)?;
88 Ok(Self { version, entries })
89 }
90}
91
92impl Repository {
93 pub fn index(&self) -> Result<Index> {
94 Ok((*self.index_shared()?).clone())
95 }
96
97 pub fn index_shared(&self) -> Result<Arc<Index>> {
98 let path = self.git_dir().join("index");
99 let metadata = fs::metadata(&path)?;
100 let modified = metadata.modified().ok();
101 let mut cache = self
102 .index_cache
103 .lock()
104 .unwrap_or_else(std::sync::PoisonError::into_inner);
105 if let Some(index) = cached(&cache, metadata.len(), modified) {
106 return Ok(index);
107 }
108 let index = Arc::new(Index::parse(&fs::read(path)?, self)?);
109 *cache = Some(CachedIndex {
110 len: metadata.len(),
111 modified,
112 index: Arc::clone(&index),
113 });
114 Ok(index)
115 }
116}
117
118fn cached(
119 cache: &MutexGuard<'_, Option<CachedIndex>>,
120 len: u64,
121 modified: Option<SystemTime>,
122) -> Option<Arc<Index>> {
123 cache
124 .as_ref()
125 .filter(|cached| cached.len == len && cached.modified == modified)
126 .map(|cached| Arc::clone(&cached.index))
127}
128
129fn parse_entry(
130 data: &[u8],
131 cursor: &mut usize,
132 end: usize,
133 version: u32,
134 hash: crate::HashKind,
135 previous_path: &[u8],
136) -> Result<IndexEntry> {
137 let start = *cursor;
138 let fixed = 40_usize
139 .checked_add(hash.bytes())
140 .and_then(|value| value.checked_add(2))
141 .ok_or_else(|| invalid("index entry length overflow"))?;
142 if start.saturating_add(fixed) > end {
143 return Err(invalid("truncated index entry"));
144 }
145 let ctime_seconds = read_u32(data, start)?;
146 let mtime_seconds = read_u32(data, start + 8)?;
147 let mode = read_u32(data, start + 24)?;
148 let size = read_u32(data, start + 36)?;
149 let oid_start = start + 40;
150 let id = ObjectId::from_bytes(
151 data.get(oid_start..oid_start + hash.bytes())
152 .ok_or_else(|| invalid("truncated index object id"))?,
153 )?;
154 *cursor = oid_start + hash.bytes();
155 let flags = read_u16(data, *cursor)?;
156 *cursor += 2;
157 let extended = flags & 0x4000 != 0;
158 if version == 2 && extended {
159 return Err(invalid("index v2 entry has extended flags"));
160 }
161 let extended_flags = if extended {
162 let value = read_u16(data, *cursor)?;
163 *cursor += 2;
164 if value & 0x1fff != 0 {
165 return Err(invalid("index entry has reserved extended flags"));
166 }
167 value
168 } else {
169 0
170 };
171 let path = if version == 4 {
172 parse_v4_path(data, cursor, end, previous_path)?
173 } else {
174 let path = take_path(data, cursor, end)?;
175 let entry_len = cursor
176 .checked_sub(start)
177 .ok_or_else(|| invalid("index entry cursor underflow"))?;
178 *cursor = start
179 .checked_add(entry_len.div_ceil(8) * 8)
180 .ok_or_else(|| invalid("index entry padding overflow"))?;
181 path
182 };
183 validate_path(&path)?;
184 Ok(IndexEntry {
185 ctime_seconds,
186 mtime_seconds,
187 mode,
188 size,
189 id,
190 stage: u8::try_from((flags >> 12) & 3).expect("two bits fit u8"),
191 assume_valid: flags & 0x8000 != 0,
192 skip_worktree: extended_flags & 0x4000 != 0,
193 intent_to_add: extended_flags & 0x2000 != 0,
194 path,
195 })
196}
197
198fn parse_v4_path(data: &[u8], cursor: &mut usize, end: usize, previous: &[u8]) -> Result<Vec<u8>> {
199 let remove = variable_width(data, cursor, end)?;
200 if remove > previous.len() {
201 return Err(invalid("index v4 path prefix is out of bounds"));
202 }
203 let suffix = take_path(data, cursor, end)?;
204 let mut path = previous[..previous.len() - remove].to_vec();
205 path.extend(suffix);
206 Ok(path)
207}
208
209fn variable_width(data: &[u8], cursor: &mut usize, end: usize) -> Result<usize> {
210 let mut byte = take(data, cursor, end)?;
211 let mut value = usize::from(byte & 0x7f);
212 while byte & 0x80 != 0 {
213 byte = take(data, cursor, end)?;
214 value = value
215 .checked_add(1)
216 .and_then(|value| value.checked_shl(7))
217 .and_then(|value| value.checked_add(usize::from(byte & 0x7f)))
218 .ok_or_else(|| invalid("index v4 path prefix overflow"))?;
219 }
220 Ok(value)
221}
222
223fn take_path(data: &[u8], cursor: &mut usize, end: usize) -> Result<Vec<u8>> {
224 let nul = data
225 .get(*cursor..end)
226 .and_then(|bytes| bytes.iter().position(|byte| *byte == 0))
227 .map(|offset| *cursor + offset)
228 .ok_or_else(|| invalid("index path has no terminator"))?;
229 let path = data[*cursor..nul].to_vec();
230 *cursor = nul + 1;
231 Ok(path)
232}
233
234fn validate_path(path: &[u8]) -> Result<()> {
235 if path.is_empty() || path.first() == Some(&b'/') || path.last() == Some(&b'/') {
236 return Err(invalid("invalid index path"));
237 }
238 if path
239 .split(|byte| *byte == b'/')
240 .any(|part| matches!(part, b"." | b".." | b".git"))
241 {
242 return Err(invalid("unsafe index path component"));
243 }
244 Ok(())
245}
246
247fn parse_extensions(data: &[u8], mut cursor: usize, end: usize) -> Result<()> {
248 while cursor < end {
249 if cursor.saturating_add(8) > end {
250 return Err(invalid("truncated index extension"));
251 }
252 let signature = &data[cursor..cursor + 4];
253 let length = usize::try_from(read_u32(data, cursor + 4)?)
254 .map_err(|_| invalid("index extension length overflow"))?;
255 if signature[0].is_ascii_lowercase() {
256 return Err(GitError::Unsupported(format!(
257 "mandatory index extension {}",
258 String::from_utf8_lossy(signature)
259 )));
260 }
261 cursor = cursor
262 .checked_add(8 + length)
263 .ok_or_else(|| invalid("index extension overflow"))?;
264 if cursor > end {
265 return Err(invalid("index extension is out of bounds"));
266 }
267 }
268 Ok(())
269}
270
271fn take(data: &[u8], cursor: &mut usize, end: usize) -> Result<u8> {
272 if *cursor >= end {
273 return Err(invalid("truncated index data"));
274 }
275 let value = data[*cursor];
276 *cursor += 1;
277 Ok(value)
278}
279
280fn read_u16(input: &[u8], offset: usize) -> Result<u16> {
281 let bytes = input
282 .get(offset..offset + 2)
283 .ok_or_else(|| invalid("truncated index integer"))?;
284 Ok(u16::from_be_bytes(bytes.try_into().expect("two bytes")))
285}
286
287fn read_u32(input: &[u8], offset: usize) -> Result<u32> {
288 let bytes = input
289 .get(offset..offset + 4)
290 .ok_or_else(|| invalid("truncated index integer"))?;
291 Ok(u32::from_be_bytes(bytes.try_into().expect("four bytes")))
292}