1use crate::{Error, Object, ObjectFormat, ObjectId, ObjectInfo, Result};
4
5pub const UPLOAD_CAPABILITIES: &[u8] =
8 b" multi_ack multi_ack_detailed side-band side-band-64k no-progress shallow deepen-relative filter ofs-delta include-tag";
9
10pub const MAX_REF_NAME_LENGTH: usize = crate::pktline::MAX_LENGTH - 4 - 40 - b" \n".len();
12
13pub const MAX_HEAD_REF_LENGTH: usize = head_ref_limit(ObjectFormat::Sha1);
15
16pub const MAX_UNBORN_REF_LENGTH: usize = unborn_ref_limit(ObjectFormat::Sha1);
18
19pub const MAX_PEELED_REF_LENGTH: usize = MAX_REF_NAME_LENGTH - 3;
21
22pub const fn ref_name_limit(format: ObjectFormat) -> usize {
23 crate::pktline::MAX_LENGTH - 4 - format.hex_length() - b" \n".len()
24}
25pub const fn head_ref_limit(format: ObjectFormat) -> usize {
26 ref_name_limit(format)
27 - b"HEAD\0symref=HEAD:".len()
28 - 1
29 - format.capability().len()
30 - UPLOAD_CAPABILITIES.len()
31}
32pub const fn unborn_ref_limit(format: ObjectFormat) -> usize {
33 ref_name_limit(format)
34 - b"capabilities^{}\0symref=HEAD:".len()
35 - 1
36 - format.capability().len()
37 - UPLOAD_CAPABILITIES.len()
38}
39
40#[derive(Clone, Copy, Debug)]
44pub struct Reference<'a> {
45 pub name: &'a str,
46 pub id: ObjectId,
47 pub symbolic_target: Option<&'a str>,
48 pub peeled: Option<ObjectId>,
49}
50
51impl<'a> Reference<'a> {
52 pub const fn direct(name: &'a str, id: ObjectId) -> Self {
53 Self {
54 name,
55 id,
56 symbolic_target: None,
57 peeled: None,
58 }
59 }
60}
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum Head<'a> {
65 Symbolic {
66 target: &'a str,
67 id: ObjectId,
68 },
69 Detached(ObjectId),
70 Unborn(&'a str),
72}
73
74impl<'a> Head<'a> {
75 pub fn id(self) -> Option<ObjectId> {
76 match self {
77 Self::Symbolic { id, .. } | Self::Detached(id) => Some(id),
78 Self::Unborn(_) => None,
79 }
80 }
81
82 pub fn target(self) -> Option<&'a str> {
83 match self {
84 Self::Symbolic { target, .. } | Self::Unborn(target) => Some(target),
85 Self::Detached(_) => None,
86 }
87 }
88}
89
90#[derive(Clone, Copy, Debug)]
92pub struct EmptyRepository<'a> {
93 target: &'a str,
94 format: ObjectFormat,
95}
96
97impl<'a> EmptyRepository<'a> {
98 pub fn new(target: &'a str) -> Result<Self> {
99 Self::new_with_format(target, ObjectFormat::Sha1)
100 }
101 pub fn new_with_format(target: &'a str, format: ObjectFormat) -> Result<Self> {
102 let repository = Self { target, format };
103 validate_refs(&repository)?;
104 Ok(repository)
105 }
106}
107
108impl Repository for EmptyRepository<'_> {
109 fn head(&self) -> ObjectId {
110 self.format.zero_id()
111 }
112 fn object_format(&self) -> ObjectFormat {
113 self.format
114 }
115 fn head_state(&self) -> Head<'_> {
116 Head::Unborn(self.target)
117 }
118 fn head_ref(&self) -> &str {
119 self.target
120 }
121 fn object_count(&self) -> usize {
122 0
123 }
124 fn reference_count(&self) -> usize {
125 0
126 }
127 fn reference_name(&self, _index: usize) -> Option<&str> {
128 None
129 }
130}
131
132pub trait Repository {
146 fn head(&self) -> ObjectId;
149 fn object_format(&self) -> ObjectFormat {
152 ObjectFormat::Sha1
153 }
154 fn head_state(&self) -> Head<'_> {
155 Head::Symbolic {
156 target: self.head_ref(),
157 id: self.head(),
158 }
159 }
160 fn object_count(&self) -> usize;
161 fn object(&self, _index: usize) -> Option<Object<'_>> {
164 None
165 }
166
167 fn object_info(&self, index: usize) -> Result<ObjectInfo> {
168 self.object(index)
169 .map(|object| object.info())
170 .ok_or(Error::ObjectNotFound)
171 }
172
173 fn read_object(&self, index: usize, offset: u64, storage: &mut [u8]) -> Result<usize> {
178 let object = self.object(index).ok_or(Error::ObjectNotFound)?;
179 let Some(bytes) = usize::try_from(offset)
180 .ok()
181 .and_then(|offset| object.data.get(offset..))
182 else {
183 return Ok(0);
184 };
185 let length = bytes.len().min(storage.len());
186 storage[..length].copy_from_slice(&bytes[..length]);
187 Ok(length)
188 }
189
190 fn object_id(&self, index: usize) -> Option<ObjectId> {
193 self.object(index)
194 .map(|object| object.id_with_format(self.object_format()))
195 }
196
197 fn head_ref(&self) -> &str {
198 "refs/heads/main"
199 }
200
201 fn reference_count(&self) -> usize {
202 1
203 }
204
205 fn reference_name(&self, index: usize) -> Option<&str> {
206 (index == 0).then(|| self.head_ref())
207 }
208
209 fn reference_id(&self, index: usize) -> Option<ObjectId> {
211 self.reference_name(index)?;
212 self.head_state().id()
213 }
214
215 fn reference_symbolic_target(&self, _index: usize) -> Option<&str> {
216 None
217 }
218
219 fn reference_peeled(&self, _index: usize) -> Option<ObjectId> {
220 None
221 }
222
223 fn find(&self, id: ObjectId) -> Option<Object<'_>> {
224 self.object(self.find_index(id)?)
225 }
226
227 fn find_index(&self, id: ObjectId) -> Option<usize> {
229 (0..self.object_count()).find(|&index| self.object_id(index) == Some(id))
230 }
231}
232
233pub struct RefRepository<'refs, 'repo, R: Repository + ?Sized> {
237 source: &'repo R,
238 head: Head<'refs>,
239 references: &'refs [Reference<'refs>],
240}
241
242impl<'refs, 'repo, R: Repository + ?Sized> RefRepository<'refs, 'repo, R> {
243 pub fn new(
244 source: &'repo R,
245 head: Head<'refs>,
246 references: &'refs [Reference<'refs>],
247 ) -> Result<Self> {
248 let repository = Self {
249 source,
250 head,
251 references,
252 };
253 validate_refs(&repository)?;
254 Ok(repository)
255 }
256}
257
258impl<R: Repository + ?Sized> Repository for RefRepository<'_, '_, R> {
259 fn head(&self) -> ObjectId {
260 self.head
261 .id()
262 .unwrap_or(self.source.object_format().zero_id())
263 }
264 fn object_format(&self) -> ObjectFormat {
265 self.source.object_format()
266 }
267 fn head_state(&self) -> Head<'_> {
268 self.head
269 }
270 fn head_ref(&self) -> &str {
271 self.head.target().unwrap_or("refs/heads/main")
272 }
273 fn object_count(&self) -> usize {
274 self.source.object_count()
275 }
276 fn object(&self, index: usize) -> Option<Object<'_>> {
277 self.source.object(index)
278 }
279 fn object_info(&self, index: usize) -> Result<ObjectInfo> {
280 self.source.object_info(index)
281 }
282 fn object_id(&self, index: usize) -> Option<ObjectId> {
283 self.source.object_id(index)
284 }
285 fn read_object(&self, index: usize, offset: u64, storage: &mut [u8]) -> Result<usize> {
286 self.source.read_object(index, offset, storage)
287 }
288 fn find_index(&self, id: ObjectId) -> Option<usize> {
289 self.source.find_index(id)
290 }
291 fn reference_count(&self) -> usize {
292 self.references.len()
293 }
294 fn reference_name(&self, index: usize) -> Option<&str> {
295 self.references.get(index).map(|reference| reference.name)
296 }
297 fn reference_id(&self, index: usize) -> Option<ObjectId> {
298 self.references.get(index).map(|reference| reference.id)
299 }
300 fn reference_symbolic_target(&self, index: usize) -> Option<&str> {
301 self.references.get(index)?.symbolic_target
302 }
303 fn reference_peeled(&self, index: usize) -> Option<ObjectId> {
304 self.references.get(index)?.peeled
305 }
306}
307
308#[derive(Clone, Copy, Debug)]
310pub struct ObjectMetadata<'a> {
311 pub id: ObjectId,
312 pub object: Object<'a>,
313}
314
315pub struct IndexedRepository<'index, 'objects, R: Repository + ?Sized> {
324 repository: &'objects R,
325 objects: &'index [Option<ObjectMetadata<'objects>>],
326 by_id: &'index [usize],
327}
328
329impl<'index, 'objects, R: Repository + ?Sized> IndexedRepository<'index, 'objects, R> {
330 pub fn new(
355 repository: &'objects R,
356 object_storage: &'index mut [Option<ObjectMetadata<'objects>>],
357 index_storage: &'index mut [usize],
358 ) -> Result<Self> {
359 let count = repository.object_count();
360 if object_storage.len() < count || index_storage.len() < count {
361 return Err(Error::BufferTooSmall);
362 }
363 let objects = &mut object_storage[..count];
364 let by_id = &mut index_storage[..count];
365 for (index, slot) in objects.iter_mut().enumerate() {
366 let object = repository.object(index).ok_or(Error::ObjectNotFound)?;
367 *slot = Some(ObjectMetadata {
368 id: object.id_with_format(repository.object_format()),
369 object,
370 });
371 by_id[index] = index;
372 }
373 by_id.sort_unstable_by_key(|&index| objects[index].expect("initialized metadata").id);
375 Ok(Self {
376 repository,
377 objects,
378 by_id,
379 })
380 }
381
382 pub fn metadata(&self, index: usize) -> Option<&ObjectMetadata<'objects>> {
384 self.objects.get(index)?.as_ref()
385 }
386}
387
388impl<R: Repository + ?Sized> Repository for IndexedRepository<'_, '_, R> {
389 fn object_format(&self) -> ObjectFormat {
390 self.repository.object_format()
391 }
392 fn head(&self) -> ObjectId {
393 self.repository.head()
394 }
395
396 fn head_state(&self) -> Head<'_> {
397 self.repository.head_state()
398 }
399
400 fn object_count(&self) -> usize {
401 self.objects.len()
402 }
403
404 fn object(&self, index: usize) -> Option<Object<'_>> {
405 self.metadata(index).map(|metadata| metadata.object)
406 }
407
408 fn object_id(&self, index: usize) -> Option<ObjectId> {
409 self.metadata(index).map(|metadata| metadata.id)
410 }
411
412 fn head_ref(&self) -> &str {
413 self.repository.head_ref()
414 }
415
416 fn reference_count(&self) -> usize {
417 self.repository.reference_count()
418 }
419
420 fn reference_name(&self, index: usize) -> Option<&str> {
421 self.repository.reference_name(index)
422 }
423
424 fn reference_id(&self, index: usize) -> Option<ObjectId> {
425 self.repository.reference_id(index)
426 }
427 fn reference_symbolic_target(&self, index: usize) -> Option<&str> {
428 self.repository.reference_symbolic_target(index)
429 }
430 fn reference_peeled(&self, index: usize) -> Option<ObjectId> {
431 self.repository.reference_peeled(index)
432 }
433
434 fn find_index(&self, id: ObjectId) -> Option<usize> {
435 let position = self
436 .by_id
437 .binary_search_by_key(&id, |&index| {
438 self.objects[index].expect("initialized metadata").id
439 })
440 .ok()?;
441 Some(self.by_id[position])
442 }
443}
444
445pub fn validate_ref_name(name: &str) -> crate::Result<()> {
448 validate_ref_name_bytes(name.as_bytes())
449}
450
451pub fn validate_ref_name_bytes(name: &[u8]) -> crate::Result<()> {
453 if name.len() > MAX_REF_NAME_LENGTH
454 || !name.starts_with(b"refs/")
455 || name.ends_with(b".")
456 || name.windows(2).any(|pair| matches!(pair, b".." | b"@{"))
457 || name.iter().any(|&byte| {
458 byte <= b' ' || matches!(byte, 127 | b'~' | b'^' | b':' | b'?' | b'*' | b'[' | b'\\')
459 })
460 || name
461 .split(|&byte| byte == b'/')
462 .any(|part| part.is_empty() || part.starts_with(b".") || part.ends_with(b".lock"))
463 {
464 return Err(crate::Error::InvalidReference);
465 }
466 Ok(())
467}
468
469pub fn validate_refs(repository: &(impl Repository + ?Sized)) -> Result<()> {
474 let head = repository.head_state();
475 let format = repository.object_format();
476 validate_head(repository, head, format)?;
477 let mut found = false;
478 for index in 0..repository.reference_count() {
479 let name = repository
480 .reference_name(index)
481 .ok_or(Error::InvalidReference)?;
482 validate_reference(repository, index, name, head, format)?;
483 found |= Some(name) == head.target();
484 validate_reference_order(repository, index, name)?;
485 }
486 if matches!(head, Head::Symbolic { .. }) && !found {
487 return Err(Error::InvalidReference);
488 }
489 if let Head::Symbolic { target, .. } = head {
490 let (target, _) = resolve_reference(repository, target)?;
491 if target.len() > head_ref_limit(format) {
492 return Err(Error::InvalidReference);
493 }
494 }
495 Ok(())
496}
497
498fn validate_reference_order(
500 repository: &(impl Repository + ?Sized),
501 index: usize,
502 name: &str,
503) -> Result<()> {
504 for previous in 0..index {
505 let previous = repository
506 .reference_name(previous)
507 .ok_or(Error::InvalidReference)?;
508 if previous >= name
509 || name
510 .strip_prefix(previous)
511 .is_some_and(|suffix| suffix.starts_with('/'))
512 {
513 return Err(Error::InvalidReference);
514 }
515 }
516 Ok(())
517}
518
519fn validate_head(
521 repository: &(impl Repository + ?Sized),
522 head: Head<'_>,
523 format: ObjectFormat,
524) -> Result<()> {
525 if let Some(target) = head.target() {
526 validate_ref_name(target)?;
527 let maximum = if head.id().is_some() {
528 head_ref_limit(format)
529 } else {
530 unborn_ref_limit(format)
531 };
532 if !target.starts_with("refs/heads/") || target.len() > maximum {
533 return Err(Error::InvalidReference);
534 }
535 }
536 match head.id() {
537 Some(id) if id != repository.head() || id.is_zero() || id.format() != format => {
538 return Err(Error::InvalidObject);
539 }
540 None if repository.head() != format.zero_id()
541 || (repository.reference_count() == 0 && repository.object_count() != 0) =>
542 {
543 return Err(Error::InvalidObject);
544 }
545 _ => {}
546 }
547 Ok(())
548}
549
550fn validate_reference(
552 repository: &(impl Repository + ?Sized),
553 index: usize,
554 name: &str,
555 head: Head<'_>,
556 format: ObjectFormat,
557) -> Result<()> {
558 let ref_limit = ref_name_limit(format);
559 validate_ref_name(name)?;
560 if name.len() > ref_limit {
561 return Err(Error::InvalidReference);
562 }
563 let id = repository.reference_id(index).ok_or(Error::InvalidObject)?;
564 if id.is_zero()
565 || id.format() != format
566 || repository
567 .reference_peeled(index)
568 .is_some_and(|peeled| peeled.is_zero() || peeled.format() != format)
569 {
570 return Err(Error::InvalidObject);
571 }
572 if repository.reference_peeled(index).is_some() && name.len() > ref_limit - 3 {
573 return Err(Error::InvalidReference);
574 }
575 if head.id().is_none()
576 && index == 0
577 && name.len() > ref_limit - 1 - format.capability().len() - UPLOAD_CAPABILITIES.len()
578 {
579 return Err(Error::InvalidReference);
580 }
581 resolve_reference(repository, name)?;
582 if Some(name) == head.target() && head.id() != Some(id) {
583 return Err(Error::InvalidReference);
584 }
585 Ok(())
586}
587
588pub fn resolve_reference<'a>(
590 repository: &'a (impl Repository + ?Sized),
591 name: &str,
592) -> Result<(&'a str, ObjectId)> {
593 let mut index = reference_index(repository, name).ok_or(Error::InvalidReference)?;
594 let id = repository.reference_id(index).ok_or(Error::InvalidObject)?;
595 if id.is_zero() || id.format() != repository.object_format() {
596 return Err(Error::InvalidObject);
597 }
598 for _ in 0..repository.reference_count() {
599 let name = repository
600 .reference_name(index)
601 .ok_or(Error::InvalidReference)?;
602 let Some(target) = repository.reference_symbolic_target(index) else {
603 return Ok((name, id));
604 };
605 validate_ref_name(target)?;
606 index = reference_index(repository, target).ok_or(Error::InvalidReference)?;
607 if repository.reference_id(index) != Some(id) {
608 return Err(Error::InvalidReference);
609 }
610 }
611 Err(Error::InvalidReference)
612}
613
614fn reference_index(repository: &(impl Repository + ?Sized), name: &str) -> Option<usize> {
615 (0..repository.reference_count()).find(|&index| repository.reference_name(index) == Some(name))
616}
617
618pub fn uses_only_head(repository: &(impl Repository + ?Sized)) -> bool {
620 (0..repository.reference_count())
621 .all(|index| repository.reference_id(index) == repository.head_state().id())
622}