1use super::extensions;
3use git_zero_core::{
4 Error, ObjectFormat, ObjectId, ObjectKind, Repository, Result,
5 io::Write,
6 pktline::{self, Packet},
7};
8
9pub(crate) fn capabilities(selection: bool) -> &'static [u8] {
10 if selection {
11 git_zero_core::repository::UPLOAD_CAPABILITIES
12 } else {
13 b" multi_ack multi_ack_detailed side-band side-band-64k no-progress"
14 }
15}
16
17#[derive(Clone, Copy, Default)]
18struct Capabilities(u16);
19
20const CAPABILITY_FLAGS: &[(&[u8], u16)] = &[
21 (b"multi_ack", 1),
22 (b"multi_ack_detailed", 2),
23 (b"side-band", 4),
24 (b"side-band-64k", 8),
25 (b"no-progress", 16),
26 (b"shallow", 64),
27 (b"filter", 128),
28 (b"deepen-relative", 256),
29 (b"ofs-delta", 512),
30 (b"include-tag", 1024),
31 (b"deepen-since", 2048),
32 (b"deepen-not", 4096),
33];
34
35fn capability_flag(capability: &[u8], format: ObjectFormat) -> Result<u16> {
36 if capability == format.capability() {
37 return Ok(32);
38 }
39 if matches!(
40 capability,
41 b"object-format=sha1" | b"object-format=sha256" | b""
42 ) {
43 return Err(Error::InvalidRequest);
44 }
45 CAPABILITY_FLAGS
46 .iter()
47 .find_map(|&(name, flag)| (name == capability).then_some(flag))
48 .ok_or(Error::Unsupported)
49}
50
51impl Capabilities {
52 fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
53 let mut seen = 0_u16;
54 for capability in bytes
56 .split(|&byte| byte == b' ')
57 .filter(|part| !part.is_empty())
58 {
59 let flag = capability_flag(capability, format)?;
60 if seen & flag != 0 {
61 return Err(Error::InvalidRequest);
62 }
63 seen |= flag;
64 }
65 if seen & 12 == 12 {
66 return Err(Error::InvalidRequest);
67 }
68 Ok(Self(seen))
69 }
70
71 fn want(&mut self, value: &[u8], first: bool, format: ObjectFormat) -> Result<ObjectId> {
72 let mut hex = value;
73 if let Some(split) = value.iter().position(|&byte| byte == b' ') {
74 if !first {
75 return Err(Error::InvalidRequest);
76 }
77 *self = Self::parse(&value[split + 1..], format)?;
78 hex = &value[..split];
79 }
80 id(hex, format)
81 }
82
83 fn ack(self) -> u8 {
84 if self.0 & 2 != 0 {
86 2
87 } else {
88 (self.0 & 1) as u8
89 }
90 }
91 fn band(self) -> u8 {
92 ((self.0 >> 2) & 3) as u8
93 }
94}
95
96pub(crate) struct UploadRequest<'a> {
97 pub done: bool,
98 capabilities: Capabilities,
99 haves: &'a [u8],
100 v2: bool,
101 #[cfg(feature = "read")]
102 shallow_body: &'a [u8],
103 pub extensions: extensions::Extensions,
104 #[cfg(feature = "read")]
105 include_tags: bool,
106}
107
108pub(crate) fn id(bytes: &[u8], format: ObjectFormat) -> Result<ObjectId> {
109 let id = ObjectId::from_hex(bytes)?;
110 if id.format() != format || id.is_zero() {
111 return Err(Error::InvalidRequest);
112 }
113 Ok(id)
114}
115
116fn request_line(line: &[u8]) -> &[u8] {
117 line.strip_suffix(b"\n").unwrap_or(line)
118}
119
120fn unexpected_command(line: &[u8]) -> Error {
121 if line.starts_with(b"want ") || line.starts_with(b"have ") || line == b"done" {
122 Error::InvalidRequest
123 } else {
124 Error::Unsupported
125 }
126}
127
128fn parse_wants(
129 bytes: &mut &[u8],
130 format: ObjectFormat,
131 mut want: impl FnMut(ObjectId) -> Result<()>,
132 extensions: &mut extensions::Extensions,
133) -> Result<Capabilities> {
134 let mut wanted = false;
135 let mut extended = false;
136 let mut capabilities = Capabilities::default();
137 while let Some(packet) = pktline::read(bytes)? {
138 let Packet::Data(line) = packet else {
139 if packet != Packet::Flush {
140 return Err(Error::InvalidRequest);
141 }
142 extensions.options.deepen_relative |= capabilities.0 & 256 != 0;
145 extensions.require_filter_capability(capabilities.0 & 128 != 0)?;
146 return wanted.then_some(capabilities).ok_or(Error::InvalidRequest);
147 };
148 let line = request_line(line);
149 if let Some(value) = line.strip_prefix(b"want ") {
150 if extended {
151 return Err(Error::InvalidRequest);
152 }
153 let id = capabilities.want(value, !wanted, format)?;
154 if !extensions.revisions_enabled && capabilities.0 & 6144 != 0 {
155 return Err(Error::Unsupported);
156 }
157 want(id)?;
158 wanted = true;
159 } else if wanted {
160 extensions.argument(line, format)?;
161 extended = true;
162 } else {
163 return Err(unexpected_command(line));
164 }
165 }
166 Err(Error::InvalidRequest)
167}
168
169fn parse_haves(mut bytes: &[u8], format: ObjectFormat) -> Result<bool> {
170 let empty = bytes.is_empty();
171 while let Some(packet) = pktline::read(&mut bytes)? {
172 let Packet::Data(line) = packet else {
173 return bytes
174 .is_empty()
175 .then_some(false)
176 .ok_or(Error::InvalidRequest);
177 };
178 let line = request_line(line);
179 if line == b"done" && bytes.is_empty() {
180 return Ok(true);
181 }
182 let value = line
183 .strip_prefix(b"have ")
184 .ok_or_else(|| unexpected_command(line))?;
185 id(value, format)?;
186 }
187 empty.then_some(false).ok_or(Error::InvalidRequest)
188}
189
190impl<'a> UploadRequest<'a> {
191 pub fn parse(
192 bytes: &'a [u8],
193 format: ObjectFormat,
194 version: crate::route::Version,
195 want: impl FnMut(ObjectId) -> Result<()>,
196 ) -> Result<Self> {
197 Self::parse_mode(bytes, format, version, false, want)
198 }
199
200 fn parse_mode(
201 mut bytes: &'a [u8],
202 format: ObjectFormat,
203 version: crate::route::Version,
204 revisions_enabled: bool,
205 want: impl FnMut(ObjectId) -> Result<()>,
206 ) -> Result<Self> {
207 let mut extensions = extensions::Extensions::new(revisions_enabled);
208 #[cfg(feature = "read")]
209 let shallow_body = bytes;
210 if version == crate::route::Version::V2 {
211 let command = crate::v2::Command::parse(bytes, format)?.ok_or(Error::InvalidRequest)?;
212 if command.name != b"fetch" {
213 return Err(Error::InvalidRequest);
214 }
215 let fetch = crate::v2::Fetch::parse(command.args, format, want, &mut extensions)?;
216 extensions.validate()?;
217 return Ok(Self {
218 done: fetch.done,
219 capabilities: Capabilities(u16::from(fetch.ofs_delta) * 512),
220 haves: command.args,
221 v2: true,
222 #[cfg(feature = "read")]
223 shallow_body: command.args,
224 extensions,
225 #[cfg(feature = "read")]
226 include_tags: fetch.include_tags,
227 });
228 }
229 let capabilities = parse_wants(&mut bytes, format, want, &mut extensions)?;
230 extensions.validate()?;
231 Ok(Self {
232 done: parse_haves(bytes, format)?,
233 capabilities,
234 haves: bytes,
235 v2: false,
236 #[cfg(feature = "read")]
237 shallow_body,
238 extensions,
239 #[cfg(feature = "read")]
240 include_tags: capabilities.0 & 1024 != 0,
241 })
242 }
243
244 #[cfg(feature = "read")]
245 pub fn ofs_delta(&self) -> bool {
246 self.capabilities.0 & 512 != 0
247 }
248
249 pub fn require_complete(&self) -> Result<()> {
250 if self.extensions.active() || self.capabilities.0 & (192 | 1024 | 6144) != 0 {
253 return Err(Error::Unsupported);
254 }
255 Ok(())
256 }
257
258 #[cfg(feature = "read")]
259 pub(crate) fn exclusion_ids<'r>(
260 &'r self,
261 repository: &'r impl Repository,
262 ) -> impl Iterator<Item = Result<ObjectId>> + 'r {
263 packet_values(self.shallow_body, b"deepen-not ")
264 .map(move |name| exclusion_ref(repository, name))
265 }
266
267 #[cfg(feature = "read")]
268 pub(crate) fn revision_info(
269 &self,
270 format: ObjectFormat,
271 changes: impl Iterator<Item = git_zero_core::ShallowChange>,
272 output: &mut impl Write,
273 ) -> Result<()> {
274 let mut updates = ShallowUpdates::new(self, format);
275 if updates.enabled {
276 for change in changes {
277 let (id, unshallow) = match change {
278 git_zero_core::ShallowChange::Shallow(id) => (id, false),
279 git_zero_core::ShallowChange::Unshallow(id) => (id, true),
280 };
281 updates.change(id, unshallow, output)?;
282 }
283 }
284 updates.finish(output)
285 }
286
287 #[cfg(feature = "read")]
288 pub fn shallow_info(
289 &self,
290 format: ObjectFormat,
291 objects: &[Option<git_zero_core::ValidationMetadata>],
292 output: &mut impl Write,
293 ) -> Result<()> {
294 self.revision_info(
295 format,
296 objects.iter().flatten().filter_map(shallow_change),
297 output,
298 )
299 }
300
301 #[cfg(feature = "read")]
302 pub(crate) fn parse_selected<R: Repository + ?Sized>(
303 repository: &git_zero_core::ValidatedRepository<'_, '_, R>,
304 body: &'a [u8],
305 version: crate::route::Version,
306 wants: &mut [ObjectId],
307 options: crate::WantOptions,
308 revisions_enabled: bool,
309 ) -> Result<(Self, usize)> {
310 let mut count = 0;
311 let mut remaining = options.max_want_lines;
312 let upload = Self::parse_mode(
313 body,
314 repository.object_format(),
315 version,
316 revisions_enabled,
317 |id| {
318 remaining = remaining.checked_sub(1).ok_or(Error::LimitExceeded)?;
319 let allowed = match options.policy {
320 crate::WantPolicy::AdvertisedOnly => super::advertised(repository, id),
321 crate::WantPolicy::AllVisibleReachable => repository.find_index(id).is_some(),
322 };
323 if !allowed {
324 return Err(Error::InvalidRequest);
325 }
326 if !wants[..count].contains(&id) {
327 *wants.get_mut(count).ok_or(Error::BufferTooSmall)? = id;
328 count += 1;
329 }
330 Ok(())
331 },
332 )?;
333 Ok((upload, count))
334 }
335
336 pub fn common<'r>(
339 &'r self,
340 repository: &'r impl Repository,
341 ) -> impl Iterator<Item = Result<ObjectId>> + 'r {
342 packet_values(self.haves, b"have ")
343 .filter_map(|hex| ObjectId::from_hex(hex).ok())
344 .filter_map(move |id| common_commit(repository, id).transpose())
345 }
346
347 #[cfg(feature = "read")]
348 pub fn shallow(&self) -> impl Iterator<Item = Result<ObjectId>> + '_ {
349 packet_values(self.shallow_body, b"shallow ").map(ObjectId::from_hex)
350 }
351
352 #[cfg(feature = "read")]
353 pub fn validate_shallow(&self, repository: &impl Repository) -> Result<()> {
354 for id in self.shallow() {
355 if common_commit(repository, id?)?.is_none() {
356 return Err(Error::InvalidRequest);
357 }
358 }
359 Ok(())
360 }
361
362 #[cfg(feature = "read")]
363 pub fn include_tags(&self) -> bool {
364 self.include_tags
365 }
366
367 pub fn acknowledge(&self, repository: &impl Repository, output: &mut impl Write) -> Result<()> {
368 let mut acknowledgments = Acknowledgments::new(self);
369 acknowledgments.start(output)?;
370 for id in self.common(repository) {
371 acknowledgments.common(id?, output)?;
372 }
373 acknowledgments.finish(output)
374 }
375
376 pub fn output<'w, W: Write>(&self, output: &'w mut W) -> PackOutput<'w, W> {
377 PackOutput {
378 output,
379 band: self.band(),
380 buffer: [0; 1024],
381 length: 0,
382 }
383 }
384
385 pub(crate) fn band(&self) -> u8 {
386 if self.v2 { 2 } else { self.capabilities.band() }
388 }
389}
390
391#[cfg(feature = "read")]
392fn exclusion_matches(full: &[u8], name: &[u8]) -> bool {
393 [
394 (b"".as_slice(), b"".as_slice()),
395 (b"refs/", b""),
396 (b"refs/tags/", b""),
397 (b"refs/heads/", b""),
398 (b"refs/remotes/", b""),
399 (b"refs/remotes/", b"/HEAD"),
400 ]
401 .iter()
402 .any(|&(prefix, suffix)| {
403 full.strip_prefix(prefix)
404 .and_then(|rest| rest.strip_suffix(suffix))
405 == Some(name)
406 })
407}
408
409#[cfg(feature = "read")]
410fn exclusion_ref(repository: &impl Repository, name: &[u8]) -> Result<ObjectId> {
411 let mut found = if name == b"HEAD" {
412 repository.head_state().id()
413 } else {
414 None
415 };
416 for index in 0..repository.reference_count() {
417 let full = repository
418 .reference_name(index)
419 .ok_or(Error::InvalidReference)?
420 .as_bytes();
421 if !exclusion_matches(full, name) {
422 continue;
423 }
424 if found.is_some() {
425 return Err(Error::InvalidRequest);
426 }
427 found = Some(
429 repository
430 .reference_id(index)
431 .ok_or(Error::InvalidReference)?,
432 );
433 }
434 found.ok_or(Error::InvalidRequest)
435}
436
437#[cfg(feature = "read")]
438fn shallow_change(
439 object: &git_zero_core::ValidationMetadata,
440) -> Option<git_zero_core::ShallowChange> {
441 if object.is_shallow() {
442 Some(git_zero_core::ShallowChange::Shallow(object.id))
443 } else if object.is_unshallow() {
444 Some(git_zero_core::ShallowChange::Unshallow(object.id))
445 } else {
446 None
447 }
448}
449
450fn packet_values<'a>(mut bytes: &'a [u8], prefix: &'static [u8]) -> impl Iterator<Item = &'a [u8]> {
453 core::iter::from_fn(move || pktline::read(&mut bytes).ok().flatten()).filter_map(
454 move |packet| {
455 let Packet::Data(line) = packet else {
456 return None;
457 };
458 request_line(line).strip_prefix(prefix)
459 },
460 )
461}
462
463pub(crate) fn common_commit(
464 repository: &impl Repository,
465 id: ObjectId,
466) -> Result<Option<ObjectId>> {
467 let Some(index) = repository.find_index(id) else {
468 return Ok(None);
469 };
470 Ok((repository.object_info(index)?.kind == ObjectKind::Commit).then_some(id))
471}
472
473pub struct PackOutput<'a, W: Write> {
478 output: &'a mut W,
479 band: u8,
480 buffer: [u8; 1024],
481 length: usize,
482}
483
484impl<W: Write> PackOutput<'_, W> {
485 fn flush(&mut self) -> Result<()> {
486 if self.length != 0 {
487 pktline::write_parts(self.output, &[b"\x01", &self.buffer[..self.length]])?;
488 self.length = 0;
489 }
490 Ok(())
491 }
492
493 pub(crate) fn finish(&mut self) -> Result<()> {
494 if self.band != 0 {
495 self.flush()?;
496 self.output.write_all(b"0000")?;
497 }
498 Ok(())
499 }
500}
501
502impl<W: Write> Write for PackOutput<'_, W> {
503 fn write_all(&mut self, mut bytes: &[u8]) -> Result<()> {
504 if self.band == 0 {
505 return self.output.write_all(bytes);
506 }
507 let capacity = if self.band == 1 {
508 995
509 } else {
510 self.buffer.len()
511 };
512 while !bytes.is_empty() {
513 let count = bytes.len().min(capacity - self.length);
514 self.buffer[self.length..self.length + count].copy_from_slice(&bytes[..count]);
515 self.length += count;
516 bytes = &bytes[count..];
517 if self.length == capacity {
518 self.flush()?;
519 }
520 }
521 Ok(())
522 }
523}
524
525pub fn write_upload_error(error: Error, output: &mut impl Write) -> Result<()> {
529 let message: &[u8] = match error {
530 Error::BufferTooSmall | Error::LimitExceeded => b"request exceeds resource limits",
531 Error::Backend => b"repository unavailable",
532 Error::Unsupported => b"unsupported upload request",
533 _ => b"invalid upload request",
534 };
535 pktline::write_parts(output, &[b"ERR ", message, b"\n"])
536}
537
538#[derive(Clone, Copy, Eq, PartialEq)]
541enum Phase {
542 Initial,
543 Started,
544 Finished,
545}
546
547#[cfg(feature = "read")]
550pub struct ShallowUpdates {
551 format: ObjectFormat,
552 v2: bool,
553 enabled: bool,
554 phase: Phase,
555 failure: Option<Error>,
556}
557#[cfg(feature = "read")]
558impl ShallowUpdates {
559 pub(crate) fn new(request: &UploadRequest<'_>, format: ObjectFormat) -> Self {
560 Self {
561 format,
562 v2: request.v2,
563 enabled: request.extensions.history() && (!request.v2 || request.done),
564 phase: Phase::Initial,
565 failure: None,
566 }
567 }
568 pub const fn is_enabled(&self) -> bool {
570 self.enabled
571 }
572 pub fn change(&mut self, id: ObjectId, unshallow: bool, output: &mut impl Write) -> Result<()> {
573 let result = self.write_change(id, unshallow, output);
574 self.record(result)
575 }
576 fn write_change(
577 &mut self,
578 id: ObjectId,
579 unshallow: bool,
580 output: &mut impl Write,
581 ) -> Result<()> {
582 self.ready()?;
583 if self.phase == Phase::Finished || !self.enabled {
584 return Err(Error::InvalidRequest);
585 }
586 if id.is_zero() || id.format() != self.format {
587 return Err(Error::InvalidObjectId);
588 }
589 if self.phase == Phase::Initial && self.v2 {
590 pktline::write(output, b"shallow-info\n")?;
591 }
592 self.phase = Phase::Started;
593 let marker: &[u8] = if unshallow {
594 b"unshallow "
595 } else {
596 b"shallow "
597 };
598 pktline::write_parts(output, &[marker, id.to_hex().as_bytes(), b"\n"])
599 }
600 pub fn finish(&mut self, output: &mut impl Write) -> Result<()> {
601 let result = self.write_finish(output);
602 self.record(result)
603 }
604 fn write_finish(&mut self, output: &mut impl Write) -> Result<()> {
605 self.ready()?;
606 if self.phase == Phase::Finished {
607 return Err(Error::InvalidRequest);
608 }
609 let started = self.phase == Phase::Started;
610 self.phase = Phase::Finished;
611 if self.enabled && (!self.v2 || started) {
612 output.write_all(if self.v2 { b"0001" } else { b"0000" })?;
613 }
614 Ok(())
615 }
616 fn ready(&self) -> Result<()> {
617 self.failure.map_or(Ok(()), Err)
618 }
619 fn record(&mut self, result: Result<()>) -> Result<()> {
620 if let Err(error) = result {
621 self.failure.get_or_insert(error);
622 }
623 result
624 }
625}
626
627pub struct Acknowledgments {
630 v2: bool,
631 done: bool,
632 mode: u8,
633 suppress: bool,
634 last: Option<ObjectId>,
635 phase: Phase,
636}
637impl Acknowledgments {
638 pub(crate) fn new(request: &UploadRequest<'_>) -> Self {
639 Self {
640 v2: request.v2,
641 done: request.done,
642 mode: request.capabilities.ack(),
643 suppress: !request.v2 && request.extensions.history() && request.haves.is_empty(),
644 last: None,
645 phase: Phase::Initial,
646 }
647 }
648 pub fn start(&mut self, output: &mut impl Write) -> Result<()> {
649 if self.phase != Phase::Initial {
650 return Err(Error::InvalidRequest);
651 }
652 self.phase = Phase::Started;
653 if self.v2 {
654 pktline::write(
655 output,
656 if self.done {
657 b"packfile\n"
658 } else {
659 b"acknowledgments\n"
660 },
661 )?;
662 }
663 Ok(())
664 }
665 pub fn common(&mut self, id: ObjectId, output: &mut impl Write) -> Result<()> {
666 if self.phase != Phase::Started || id.is_zero() {
667 return Err(Error::InvalidRequest);
668 }
669 if self.suppress
670 || (self.v2 && self.done)
671 || (!self.v2 && self.mode == 0 && self.last.is_some())
672 {
673 return Ok(());
674 }
675 let suffix: &[u8] = if self.v2 || self.mode == 0 {
676 b"\n"
677 } else if self.mode == 1 {
678 b" continue\n"
679 } else {
680 b" common\n"
681 };
682 pktline::write_parts(output, &[b"ACK ", id.to_hex().as_bytes(), suffix])?;
683 self.last = Some(id);
684 Ok(())
685 }
686 pub fn finish(&mut self, output: &mut impl Write) -> Result<()> {
687 if self.phase != Phase::Started {
688 return Err(Error::InvalidRequest);
689 }
690 self.phase = Phase::Finished;
691 if self.v2 {
692 if !self.done {
693 if self.last.is_none() {
694 pktline::write(output, b"NAK\n")?;
695 }
696 output.write_all(b"0000")?;
697 }
698 } else if !self.suppress && !(self.mode == 0 && self.last.is_some()) {
699 if let Some(id) = self.last.filter(|_| self.done) {
700 pktline::write_parts(output, &[b"ACK ", id.to_hex().as_bytes(), b"\n"])?;
701 } else {
702 pktline::write(output, b"NAK\n")?;
703 }
704 }
705 Ok(())
706 }
707}