1use core::cmp::Ordering;
5
6use crate::{
7 BlobFilter, Error, FetchOptions, Head, HistoryPolicy as History, Object, ObjectFormat,
8 ObjectHasher, ObjectId, ObjectInfo, ObjectKind, ObjectReader, Repository, Result,
9 names::{cmp_tree_names, validate_name},
10 repository::validate_refs,
11};
12
13mod revision;
14pub use revision::{RevisionLimits, RevisionOptions, RevisionState, ShallowChange};
15
16#[derive(Clone, Copy, Debug)]
18pub struct ValidationMetadata {
19 pub id: ObjectId,
20 pub info: ObjectInfo,
21 reached: bool,
22 checked: bool,
23 excluded: bool,
24 forced: bool,
25 wanted: bool,
26 depth: u32,
28 client_shallow: bool,
29 shallow: bool,
30}
31
32impl ValidationMetadata {
33 fn reset_walk(&mut self) {
34 self.reached = false;
35 self.checked = false;
36 self.depth = u32::MAX;
37 self.shallow = false;
38 self.forced = false;
39 self.wanted = false;
40 }
41
42 pub fn is_shallow(&self) -> bool {
44 self.reached && self.shallow
45 }
46
47 pub fn is_unshallow(&self) -> bool {
49 self.reached && self.client_shallow && !self.shallow
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
55pub struct SelectionOptions {
56 pub graph_read_limit: u64,
59 pub include_tags: bool,
64}
65
66pub struct ValidatedRepository<'index, 'repo, R: Repository + ?Sized> {
82 repository: &'repo R,
83 objects: &'index [Option<ValidationMetadata>],
84 by_id: &'index [usize],
85 head: ObjectId,
86}
87
88impl<'index, 'repo, R: Repository + ?Sized> ValidatedRepository<'index, 'repo, R> {
89 pub fn new(
107 repository: &'repo R,
108 object_storage: &'index mut [Option<ValidationMetadata>],
109 index_storage: &'index mut [usize],
110 read_scratch: &mut [u8],
111 parse_scratch: &mut [u8],
112 ) -> Result<Self> {
113 let count = repository.object_count();
114 if object_storage.len() < count || index_storage.len() < count {
115 return Err(Error::BufferTooSmall);
116 }
117 validate_refs(repository)?;
118 let objects = &mut object_storage[..count];
119 let by_id = &mut index_storage[..count];
120 initialize_index(repository, objects, by_id, read_scratch)?;
121 let head = repository.head();
122 if let Some(id) = repository.head_state().id() {
123 reach(objects, by_id, id, ObjectKind::Commit)?;
124 }
125 for index in 0..repository.reference_count() {
126 validate_reference_target(
127 repository,
128 index,
129 objects,
130 by_id,
131 read_scratch,
132 parse_scratch,
133 )?;
134 }
135 walk(
136 repository,
137 objects,
138 by_id,
139 (read_scratch, parse_scratch),
140 None,
141 History::default(),
142 BlobFilter::All,
143 )?;
144 if objects
145 .iter()
146 .any(|slot| !slot.expect("initialized metadata").reached)
147 {
148 return Err(Error::InvalidObject);
149 }
150 Ok(Self {
151 repository,
152 objects,
153 by_id,
154 head,
155 })
156 }
157
158 pub fn metadata(&self, index: usize) -> Option<&ValidationMetadata> {
159 self.objects.get(index)?.as_ref()
160 }
161
162 pub fn select_objects<'selected>(
175 &self,
176 roots: &[ObjectId],
177 work: &mut [Option<ValidationMetadata>],
178 selected: &'selected mut [usize],
179 read_scratch: &mut [u8],
180 parse_scratch: &mut [u8],
181 graph_read_limit: u64,
182 ) -> Result<&'selected [usize]> {
183 self.select_missing_objects(
184 roots,
185 core::iter::empty(),
186 work,
187 selected,
188 (read_scratch, parse_scratch),
189 graph_read_limit,
190 )
191 }
192
193 pub fn select_missing_objects<'selected>(
200 &self,
201 roots: &[ObjectId],
202 common: impl IntoIterator<Item = Result<ObjectId>>,
203 work: &mut [Option<ValidationMetadata>],
204 selected: &'selected mut [usize],
205 scratch: (&mut [u8], &mut [u8]),
206 graph_read_limit: u64,
207 ) -> Result<&'selected [usize]> {
208 self.select_fetch_objects(
209 roots,
210 common,
211 work,
212 selected,
213 scratch,
214 SelectionOptions {
215 graph_read_limit,
216 include_tags: false,
217 },
218 )
219 }
220
221 pub fn select_fetch_objects<'selected>(
225 &self,
226 roots: &[ObjectId],
227 common: impl IntoIterator<Item = Result<ObjectId>>,
228 work: &mut [Option<ValidationMetadata>],
229 selected: &'selected mut [usize],
230 scratch: (&mut [u8], &mut [u8]),
231 options: SelectionOptions,
232 ) -> Result<&'selected [usize]> {
233 self.select_bounded_fetch(
234 roots,
235 (common, core::iter::empty()),
236 work,
237 selected,
238 scratch,
239 (options, FetchOptions::default()),
240 )
241 }
242
243 pub fn select_bounded_fetch<'selected>(
301 &self,
302 roots: &[ObjectId],
303 history: (
304 impl IntoIterator<Item = Result<ObjectId>>,
305 impl IntoIterator<Item = Result<ObjectId>>,
306 ),
307 work: &mut [Option<ValidationMetadata>],
308 selected: &'selected mut [usize],
309 scratch: (&mut [u8], &mut [u8]),
310 options: (SelectionOptions, FetchOptions),
311 ) -> Result<&'selected [usize]> {
312 let (options, fetch) = options;
313 if fetch
314 .depth
315 .is_some_and(|depth| depth == 0 || depth > i32::MAX as u32)
316 {
317 return Err(Error::InvalidRequest);
318 }
319 let SelectionOptions {
320 graph_read_limit,
321 include_tags,
322 } = options;
323 let (read_scratch, parse_scratch) = scratch;
324 let (common, shallow) = history;
325 if roots.is_empty() {
326 return Err(Error::InvalidRequest);
327 }
328 let count = self.objects.len();
329 if work.len() < count || selected.len() < count {
330 return Err(Error::BufferTooSmall);
331 }
332 let mut selection = Selection {
333 repository: self,
334 work: &mut work[..count],
335 scratch: (read_scratch, parse_scratch),
336 remaining: graph_read_limit,
337 filter: BlobFilter::All,
338 };
339 let has_shallow = selection.prepare(shallow)?;
340 let deepening = fetch.deepens_history(has_shallow);
343 selection.exclude_common(common, deepening)?;
344 let nearest = match fetch.depth.filter(|_| deepening) {
345 Some(_) if fetch.deepen_relative => selection.nearest_boundary_depth(roots)?,
346 _ => None,
347 };
348 let history = fetch.history(has_shallow, nearest)?;
349 selection.filter = fetch.filter;
350 selection.walk_wants(roots, history)?;
351 filter_objects(selection.work, fetch.filter);
352 if include_tags {
353 selection.include_matching_tags()?;
354 selection.walk(history)?;
355 filter_objects(selection.work, fetch.filter);
356 }
357 let length = selected_indices(selection.work, selected);
358 Ok(&selected[..length])
359 }
360}
361
362fn selected_indices(objects: &[Option<ValidationMetadata>], selected: &mut [usize]) -> usize {
364 let mut length = 0;
365 for (index, slot) in objects.iter().enumerate() {
366 if slot.expect("initialized metadata").reached {
367 selected[length] = index;
368 length += 1;
369 }
370 }
371 length
372}
373
374struct Selection<'request, 'index, 'repo, R: Repository + ?Sized> {
377 repository: &'request ValidatedRepository<'index, 'repo, R>,
378 work: &'request mut [Option<ValidationMetadata>],
379 scratch: (&'request mut [u8], &'request mut [u8]),
380 remaining: u64,
381 filter: BlobFilter,
382}
383
384impl<R: Repository + ?Sized> Selection<'_, '_, '_, R> {
385 fn walk(&mut self, history: History) -> Result<()> {
386 walk(
387 self.repository,
388 self.work,
389 self.repository.by_id,
390 (&mut *self.scratch.0, &mut *self.scratch.1),
391 Some(&mut self.remaining),
392 history,
393 self.filter,
394 )
395 }
396
397 fn prepare(&mut self, shallow: impl IntoIterator<Item = Result<ObjectId>>) -> Result<bool> {
398 let work = &mut *self.work;
399 work.copy_from_slice(self.repository.objects);
400 for slot in work.iter_mut() {
401 let object = slot.as_mut().expect("validated metadata");
402 object.reset_walk();
403 object.client_shallow = false;
404 }
405 let mut has_shallow = false;
406 for id in shallow {
407 let index = find(work, self.repository.by_id, id?).ok_or(Error::InvalidRequest)?;
408 let object = metadata_mut(work, index);
409 if object.info.kind != ObjectKind::Commit {
410 return Err(Error::InvalidRequest);
411 }
412 object.client_shallow = true;
413 has_shallow = true;
414 }
415 Ok(has_shallow)
416 }
417
418 fn exclude_common(
419 &mut self,
420 common: impl IntoIterator<Item = Result<ObjectId>>,
421 deepening: bool,
422 ) -> Result<()> {
423 let work = &mut *self.work;
424 for id in common {
425 let id = id?;
426 let index = find(work, self.repository.by_id, id).ok_or(Error::ObjectNotFound)?;
427 if !deepening {
428 reach(
429 work,
430 self.repository.by_id,
431 id,
432 metadata(work, index).info.kind,
433 )?;
434 }
435 }
436 self.walk(History {
437 limit: None,
438 client: true,
439 distance: false,
440 })?;
441 for slot in self.work.iter_mut() {
442 let object = slot.as_mut().expect("validated metadata");
443 object.excluded = object.reached;
444 object.reset_walk();
445 }
446 Ok(())
447 }
448
449 fn nearest_boundary_depth(&mut self, roots: &[ObjectId]) -> Result<Option<u32>> {
450 self.walk_wants(roots, History::boundary_probe())?;
453 let limit = self
456 .work
457 .iter()
458 .flatten()
459 .filter(|object| object.reached && object.client_shallow)
460 .map(|object| object.depth)
461 .min();
462 for slot in self.work.iter_mut() {
463 slot.as_mut().expect("validated metadata").reset_walk();
464 }
465 Ok(limit)
466 }
467
468 fn walk_wants(&mut self, roots: &[ObjectId], history: History) -> Result<()> {
469 let work = &mut *self.work;
470 for &id in roots {
471 let index = find(work, self.repository.by_id, id).ok_or(Error::ObjectNotFound)?;
472 metadata_mut(work, index).wanted = true;
473 reach_at(
474 work,
475 self.repository.by_id,
476 id,
477 metadata(work, index).info.kind,
478 1,
479 true,
480 )?;
481 }
482 self.walk(history)
483 }
484
485 fn include_matching_tags(&mut self) -> Result<()> {
486 let work = &mut *self.work;
487 for reference in 0..self.repository.reference_count() {
488 let Some(peeled) = self.repository.reference_peeled(reference) else {
489 continue;
490 };
491 let index = find(work, self.repository.by_id, peeled).ok_or(Error::ObjectNotFound)?;
492 if metadata(work, index).reached {
493 let id = self
494 .repository
495 .reference_id(reference)
496 .ok_or(Error::InvalidObject)?;
497 let tag = find(work, self.repository.by_id, id).ok_or(Error::ObjectNotFound)?;
500 metadata_mut(work, tag).wanted = true;
501 reach_at(
502 work,
503 self.repository.by_id,
504 id,
505 ObjectKind::Tag,
506 metadata(work, index).depth,
507 false,
508 )?;
509 }
510 }
511 Ok(())
512 }
513}
514
515fn filter_objects(objects: &mut [Option<ValidationMetadata>], filter: BlobFilter) {
516 if matches!(filter, BlobFilter::All) {
517 return;
518 }
519 for slot in objects {
520 let object = slot.as_mut().expect("validated metadata");
521 if !filter.includes_at(object.info, object.wanted, object.depth.saturating_sub(1)) {
522 object.reached = false;
523 }
524 }
525}
526
527impl<R: Repository + ?Sized> Repository for ValidatedRepository<'_, '_, R> {
528 fn object_format(&self) -> ObjectFormat {
529 self.repository.object_format()
530 }
531 fn head(&self) -> ObjectId {
532 self.head
533 }
534 fn head_state(&self) -> Head<'_> {
535 self.repository.head_state()
536 }
537 fn object_count(&self) -> usize {
538 self.objects.len()
539 }
540 fn object(&self, index: usize) -> Option<Object<'_>> {
541 self.metadata(index)?;
542 self.repository.object(index)
543 }
544 fn object_info(&self, index: usize) -> Result<ObjectInfo> {
545 self.metadata(index)
546 .map(|object| object.info)
547 .ok_or(Error::ObjectNotFound)
548 }
549 fn object_id(&self, index: usize) -> Option<ObjectId> {
550 self.metadata(index).map(|object| object.id)
551 }
552 fn read_object(&self, index: usize, offset: u64, storage: &mut [u8]) -> Result<usize> {
553 self.object_info(index)?;
554 self.repository.read_object(index, offset, storage)
555 }
556 fn find_index(&self, id: ObjectId) -> Option<usize> {
557 find(self.objects, self.by_id, id)
558 }
559 fn head_ref(&self) -> &str {
560 self.repository.head_ref()
561 }
562 fn reference_count(&self) -> usize {
563 self.repository.reference_count()
564 }
565 fn reference_name(&self, index: usize) -> Option<&str> {
566 self.repository.reference_name(index)
567 }
568 fn reference_id(&self, index: usize) -> Option<ObjectId> {
569 self.repository.reference_id(index)
570 }
571 fn reference_symbolic_target(&self, index: usize) -> Option<&str> {
572 self.repository.reference_symbolic_target(index)
573 }
574 fn reference_peeled(&self, index: usize) -> Option<ObjectId> {
575 self.repository.reference_peeled(index)
576 }
577}
578
579fn initialize_index(
581 repository: &(impl Repository + ?Sized),
582 objects: &mut [Option<ValidationMetadata>],
583 by_id: &mut [usize],
584 read_scratch: &mut [u8],
585) -> Result<()> {
586 for (index, slot) in objects.iter_mut().enumerate() {
587 *slot = Some(authenticated_metadata(repository, index, read_scratch)?);
588 by_id[index] = index;
589 }
590 by_id.sort_unstable_by_key(|&index| metadata(objects, index).id);
591 if by_id
592 .windows(2)
593 .any(|pair| metadata(objects, pair[0]).id == metadata(objects, pair[1]).id)
594 {
595 return Err(Error::InvalidObject);
596 }
597 Ok(())
598}
599
600fn authenticated_metadata(
602 repository: &(impl Repository + ?Sized),
603 index: usize,
604 read_scratch: &mut [u8],
605) -> Result<ValidationMetadata> {
606 let info = repository.object_info(index)?;
607 let id = repository.object_id(index).ok_or(Error::ObjectNotFound)?;
608 if ObjectHasher::new(repository, index)?.finish(read_scratch)? != id
609 || repository.object(index).is_some_and(|object| {
610 object.info() != info || object.id_with_format(repository.object_format()) != id
611 })
612 {
613 return Err(Error::InvalidObject);
614 }
615 Ok(ValidationMetadata {
616 id,
617 info,
618 reached: false,
619 checked: false,
620 excluded: false,
621 forced: false,
622 wanted: false,
623 depth: u32::MAX,
624 client_shallow: false,
625 shallow: false,
626 })
627}
628
629fn walk(
630 repository: &(impl Repository + ?Sized),
631 objects: &mut [Option<ValidationMetadata>],
632 by_id: &[usize],
633 scratch: (&mut [u8], &mut [u8]),
634 mut budget: Option<&mut u64>,
635 history: History,
636 filter: BlobFilter,
637) -> Result<()> {
638 let (read_scratch, parse_scratch) = scratch;
639 while let Some(index) = objects.iter().position(|slot| {
640 let object = slot.expect("initialized metadata");
641 object.reached && !object.checked
642 }) {
643 metadata_mut(objects, index).checked = true;
644 let object = *metadata(objects, index);
645 if object.info.kind == ObjectKind::Tree
646 && !filter.follows_tree(object.depth.saturating_sub(1))
647 {
648 continue;
649 }
650 let mut body = Body::budgeted(repository, index, read_scratch, budget.as_deref_mut())?;
651 validate_walk_object(
652 &mut body,
653 index,
654 objects,
655 by_id,
656 parse_scratch,
657 history,
658 filter,
659 )?;
660 }
661 Ok(())
662}
663
664fn validate_walk_object(
665 body: &mut Body<'_, '_, impl Repository + ?Sized>,
666 index: usize,
667 objects: &mut [Option<ValidationMetadata>],
668 by_id: &[usize],
669 scratch: &mut [u8],
670 history: History,
671 filter: BlobFilter,
672) -> Result<()> {
673 let object = *metadata(objects, index);
674 match object.info.kind {
675 ObjectKind::Commit => {
676 let stop = !history.follows_parents(object.depth, object.client_shallow);
677 validate_commit(
678 body,
679 scratch,
680 objects,
681 by_id,
682 (!stop).then_some(history.parent_depth(object.depth)),
683 )?;
684 metadata_mut(objects, index).shallow = stop;
685 Ok(())
686 }
687 ObjectKind::Tree => {
688 let child_depth = if matches!(filter, BlobFilter::TreeDepth(_))
689 && !(object.wanted && object.forced)
690 {
691 object.depth.saturating_add(1)
692 } else {
693 1
694 };
695 validate_tree(body, scratch, objects, by_id, object.forced, child_depth)
696 }
697 ObjectKind::Blob => Ok(()),
698 ObjectKind::Tag => validate_tag(
699 body,
700 scratch,
701 objects,
702 by_id,
703 object.depth,
704 object.forced,
705 object.wanted,
706 ),
707 }
708}
709
710fn validate_reference_target(
711 repository: &(impl Repository + ?Sized),
712 index: usize,
713 objects: &mut [Option<ValidationMetadata>],
714 by_id: &[usize],
715 read_scratch: &mut [u8],
716 parse_scratch: &mut [u8],
717) -> Result<()> {
718 let id = repository.reference_id(index).ok_or(Error::InvalidObject)?;
719 let position = find(objects, by_id, id).ok_or(Error::ObjectNotFound)?;
720 let kind = metadata(objects, position).info.kind;
721 let name = repository
722 .reference_name(index)
723 .ok_or(Error::InvalidReference)?;
724 if name.starts_with("refs/heads/") && kind != ObjectKind::Commit {
725 return Err(Error::InvalidObject);
726 }
727 reach(objects, by_id, id, kind)?;
728 let expected = if kind == ObjectKind::Tag {
729 Some(peel(
730 repository,
731 objects,
732 by_id,
733 id,
734 read_scratch,
735 parse_scratch,
736 )?)
737 } else {
738 None
739 };
740 if repository.reference_peeled(index) != expected {
741 return Err(Error::InvalidObject);
742 }
743 Ok(())
744}
745
746fn peel(
747 repository: &(impl Repository + ?Sized),
748 objects: &[Option<ValidationMetadata>],
749 by_id: &[usize],
750 mut id: ObjectId,
751 read_scratch: &mut [u8],
752 parse_scratch: &mut [u8],
753) -> Result<ObjectId> {
754 for _ in 0..objects.len() {
755 let index = find(objects, by_id, id).ok_or(Error::ObjectNotFound)?;
756 if metadata(objects, index).info.kind != ObjectKind::Tag {
757 return Ok(id);
758 }
759 let (target, kind) = tag_target(
760 &mut Body::new(repository, index, read_scratch)?,
761 parse_scratch,
762 )?;
763 let target_index = find(objects, by_id, target).ok_or(Error::ObjectNotFound)?;
764 if metadata(objects, target_index).info.kind != kind {
765 return Err(Error::InvalidObject);
766 }
767 id = target;
768 }
769 Err(Error::InvalidObject)
770}
771
772fn metadata(objects: &[Option<ValidationMetadata>], index: usize) -> &ValidationMetadata {
773 objects[index].as_ref().expect("initialized metadata")
774}
775
776fn metadata_mut(
777 objects: &mut [Option<ValidationMetadata>],
778 index: usize,
779) -> &mut ValidationMetadata {
780 objects[index].as_mut().expect("initialized metadata")
781}
782
783fn find(objects: &[Option<ValidationMetadata>], by_id: &[usize], id: ObjectId) -> Option<usize> {
784 let position = by_id
785 .binary_search_by_key(&id, |&index| metadata(objects, index).id)
786 .ok()?;
787 Some(by_id[position])
788}
789
790fn reach(
791 objects: &mut [Option<ValidationMetadata>],
792 by_id: &[usize],
793 id: ObjectId,
794 kind: ObjectKind,
795) -> Result<()> {
796 reach_at(objects, by_id, id, kind, 1, false)
797}
798
799fn reach_at(
800 objects: &mut [Option<ValidationMetadata>],
801 by_id: &[usize],
802 id: ObjectId,
803 kind: ObjectKind,
804 depth: u32,
805 forced: bool,
806) -> Result<()> {
807 if id.is_zero() {
808 return Err(Error::InvalidObject);
809 }
810 let index = find(objects, by_id, id).ok_or(Error::ObjectNotFound)?;
811 let object = metadata_mut(objects, index);
812 if object.info.kind != kind {
813 return Err(Error::InvalidObject);
814 }
815 let forced = forced && kind != ObjectKind::Commit;
818 let newly_forced = forced && !object.forced;
819 object.forced |= forced;
820 if forced {
821 object.excluded = false;
822 }
823 if !object.excluded {
824 object.reached = true;
825 if depth < object.depth || newly_forced {
826 object.depth = object.depth.min(depth);
827 object.checked = false;
828 }
829 }
830 Ok(())
831}
832
833struct Body<'scratch, 'repo, R: Repository + ?Sized> {
836 reader: ObjectReader<'repo, R>,
837 scratch: &'scratch mut [u8],
838 position: usize,
839 length: usize,
840 budget: Option<&'scratch mut u64>,
841}
842
843impl<'scratch, 'repo, R: Repository + ?Sized> Body<'scratch, 'repo, R> {
844 fn new(repository: &'repo R, index: usize, scratch: &'scratch mut [u8]) -> Result<Self> {
845 Self::budgeted(repository, index, scratch, None)
846 }
847 fn budgeted(
848 repository: &'repo R,
849 index: usize,
850 scratch: &'scratch mut [u8],
851 budget: Option<&'scratch mut u64>,
852 ) -> Result<Self> {
853 Ok(Self {
854 reader: ObjectReader::new(repository, index)?,
855 scratch,
856 position: 0,
857 length: 0,
858 budget,
859 })
860 }
861 fn offset(&self) -> u64 {
862 self.reader.position() - (self.length - self.position) as u64
863 }
864 fn seek(&mut self, offset: u64) -> Result<()> {
865 self.reader.seek(offset)?;
866 self.position = 0;
867 self.length = 0;
868 Ok(())
869 }
870 fn byte(&mut self) -> Result<Option<u8>> {
871 if self.position == self.length {
872 self.refill()?;
873 if self.length == 0 {
874 return Ok(None);
875 }
876 }
877 let byte = self.scratch[self.position];
878 self.position += 1;
879 Ok(Some(byte))
880 }
881 fn refill(&mut self) -> Result<()> {
883 if self.budget.as_deref() == Some(&0) && self.reader.remaining() != 0 {
884 return Err(Error::LimitExceeded);
885 }
886 let capacity = self
887 .budget
888 .as_deref()
889 .map_or(self.scratch.len(), |remaining| {
890 self.scratch
891 .len()
892 .min(usize::try_from(*remaining).unwrap_or(usize::MAX))
893 });
894 self.length = self.reader.read(&mut self.scratch[..capacity])?;
895 if let Some(remaining) = self.budget.as_deref_mut() {
896 *remaining -= self.length as u64;
897 }
898 self.position = 0;
899 Ok(())
900 }
901 fn until<'a>(&mut self, delimiter: u8, output: &'a mut [u8]) -> Result<&'a [u8]> {
902 let mut length = 0;
903 loop {
904 let byte = self.byte()?.ok_or(Error::InvalidObject)?;
905 if byte == delimiter {
906 return Ok(&output[..length]);
907 }
908 *output.get_mut(length).ok_or(Error::BufferTooSmall)? = byte;
909 length += 1;
910 }
911 }
912 fn id(&mut self) -> Result<ObjectId> {
913 let format = self.reader.object_format();
914 let mut bytes = [0; 32];
915 for byte in &mut bytes[..format.id_length()] {
916 *byte = self.byte()?.ok_or(Error::InvalidObject)?;
917 }
918 let id = ObjectId::from_bytes(format, &bytes[..format.id_length()])?;
919 if id.is_zero() {
920 return Err(Error::InvalidObject);
921 }
922 Ok(id)
923 }
924 fn line<'a>(&mut self, scratch: &'a mut [u8]) -> Result<&'a [u8]> {
925 let line = self.until(b'\n', scratch)?;
926 if line.contains(&0) {
927 return Err(Error::InvalidObject);
928 }
929 Ok(line)
930 }
931}
932
933fn header_value<'a>(line: &'a [u8], prefix: &[u8]) -> Result<&'a [u8]> {
935 line.strip_prefix(prefix)
936 .filter(|value| !value.is_empty())
937 .ok_or(Error::InvalidObject)
938}
939
940fn link(line: &[u8], prefix: &[u8], format: ObjectFormat) -> Result<ObjectId> {
941 let id = ObjectId::from_hex(header_value(line, prefix)?).map_err(|_| Error::InvalidObject)?;
942 if id.format() != format {
943 return Err(Error::InvalidObject);
944 }
945 Ok(id)
946}
947
948fn validate_commit(
949 body: &mut Body<'_, '_, impl Repository + ?Sized>,
950 scratch: &mut [u8],
951 objects: &mut [Option<ValidationMetadata>],
952 by_id: &[usize],
953 parent_depth: Option<u32>,
954) -> Result<()> {
955 let tree = commit_tree(body, scratch)?;
956 reach(objects, by_id, tree, ObjectKind::Tree)?;
957 commit_parents(body, scratch, |id| match parent_depth {
958 Some(depth) => reach_at(objects, by_id, id, ObjectKind::Commit, depth, false),
959 None => Ok(()),
960 })?;
961 validate_extra_headers(
962 body,
963 scratch,
964 &[b"tree", b"parent", b"author", b"committer"],
965 )
966}
967
968fn commit_tree(
969 body: &mut Body<'_, '_, impl Repository + ?Sized>,
970 scratch: &mut [u8],
971) -> Result<ObjectId> {
972 let format = body.reader.object_format();
973 link(body.line(scratch)?, b"tree ", format)
974}
975
976fn commit_parents<'a>(
979 body: &mut Body<'_, '_, impl Repository + ?Sized>,
980 scratch: &'a mut [u8],
981 mut visit: impl FnMut(ObjectId) -> Result<()>,
982) -> Result<&'a [u8]> {
983 let format = body.reader.object_format();
984 let mut line = body.line(scratch)?;
985 while line.starts_with(b"parent ") {
986 visit(link(line, b"parent ", format)?)?;
987 line = body.line(scratch)?;
988 }
989 header_value(line, b"author ")?;
990 let line = body.line(scratch)?;
991 header_value(line, b"committer ")?;
992 Ok(line)
993}
994
995fn tag_target(
996 body: &mut Body<'_, '_, impl Repository + ?Sized>,
997 scratch: &mut [u8],
998) -> Result<(ObjectId, ObjectKind)> {
999 let format = body.reader.object_format();
1000 let id = link(body.line(scratch)?, b"object ", format)?;
1001 if id.is_zero() {
1002 return Err(Error::InvalidObject);
1003 }
1004 let kind = ObjectKind::from_tag_header(body.line(scratch)?)?;
1005 Ok((id, kind))
1006}
1007
1008fn validate_tag(
1009 body: &mut Body<'_, '_, impl Repository + ?Sized>,
1010 scratch: &mut [u8],
1011 objects: &mut [Option<ValidationMetadata>],
1012 by_id: &[usize],
1013 depth: u32,
1014 forced: bool,
1015 wanted: bool,
1016) -> Result<()> {
1017 let (id, kind) = tag_target(body, scratch)?;
1018 reach_at(objects, by_id, id, kind, depth, forced)?;
1019 if wanted {
1020 let index = find(objects, by_id, id).ok_or(Error::ObjectNotFound)?;
1021 metadata_mut(objects, index).wanted = true;
1022 }
1023 header_value(body.line(scratch)?, b"tag ")?;
1024 validate_extra_headers(body, scratch, &[b"object", b"type", b"tag"])
1025}
1026
1027fn validate_extra_headers(
1028 body: &mut Body<'_, '_, impl Repository + ?Sized>,
1029 scratch: &mut [u8],
1030 reserved: &[&[u8]],
1031) -> Result<()> {
1032 loop {
1033 let line = body.line(scratch)?;
1034 if line.is_empty() {
1035 return Ok(());
1036 }
1037 if line.starts_with(b" ") {
1038 continue;
1039 }
1040 let space = line
1041 .iter()
1042 .position(|&byte| byte == b' ')
1043 .ok_or(Error::InvalidObject)?;
1044 let name = &line[..space];
1045 if name.is_empty()
1046 || name.iter().any(|byte| byte.is_ascii_control())
1047 || reserved.contains(&name)
1048 {
1049 return Err(Error::InvalidObject);
1050 }
1051 }
1052}
1053
1054fn tree_entry<'a>(
1055 body: &mut Body<'_, '_, impl Repository + ?Sized>,
1056 scratch: &'a mut [u8],
1057) -> Result<(ObjectKind, &'a [u8], ObjectId)> {
1058 let mut mode = [0; 6];
1059 let kind = match body.until(b' ', &mut mode).map_err(|error| {
1060 if error == Error::BufferTooSmall {
1061 Error::InvalidObject
1062 } else {
1063 error
1064 }
1065 })? {
1066 b"40000" => ObjectKind::Tree,
1067 b"100644" | b"100755" | b"120000" => ObjectKind::Blob,
1068 b"160000" => ObjectKind::Commit, _ => return Err(Error::InvalidObject),
1070 };
1071 let name = body.until(0, scratch)?;
1072 validate_name(name)?;
1073 Ok((kind, name, body.id()?))
1074}
1075
1076fn check_duplicate_name(
1077 body: &mut Body<'_, '_, impl Repository + ?Sized>,
1078 scratch: &mut [u8],
1079 name: &[u8],
1080 before: u64,
1081) -> Result<()> {
1082 let resume = body.offset();
1083 body.seek(0)?;
1084 while body.offset() < before {
1085 let (_, previous, _) = tree_entry(body, scratch)?;
1086 if previous == name {
1087 return Err(Error::UnsortedFiles);
1088 }
1089 }
1090 body.seek(resume)
1091}
1092
1093fn check_tree_order(
1094 previous: &[u8],
1095 previous_kind: ObjectKind,
1096 name: &[u8],
1097 kind: ObjectKind,
1098) -> Result<()> {
1099 if previous == name
1100 || cmp_tree_names(
1101 previous,
1102 previous_kind == ObjectKind::Tree,
1103 name,
1104 kind == ObjectKind::Tree,
1105 ) != Ordering::Less
1106 {
1107 return Err(Error::UnsortedFiles);
1108 }
1109 Ok(())
1110}
1111
1112fn validate_tree(
1113 body: &mut Body<'_, '_, impl Repository + ?Sized>,
1114 scratch: &mut [u8],
1115 objects: &mut [Option<ValidationMetadata>],
1116 by_id: &[usize],
1117 forced: bool,
1118 child_depth: u32,
1119) -> Result<()> {
1120 let capacity = scratch.len() / 2;
1121 let (previous, rest) = scratch.split_at_mut(capacity);
1122 let current = &mut rest[..capacity];
1123 let mut previous_length = 0;
1124 let mut previous_kind = ObjectKind::Blob;
1125 while body.offset() < body.reader.info().size {
1126 let start = body.offset();
1127 let (kind, name, id) = tree_entry(body, current)?;
1128 check_tree_order(&previous[..previous_length], previous_kind, name, kind)?;
1130 if kind != ObjectKind::Commit {
1131 reach_at(objects, by_id, id, kind, child_depth, forced)?;
1132 }
1133 if kind == ObjectKind::Tree && start != 0 {
1137 check_duplicate_name(body, previous, name, start)?;
1138 }
1139 previous[..name.len()].copy_from_slice(name);
1140 previous_length = name.len();
1141 previous_kind = kind;
1142 }
1143 Ok(())
1144}