1use sley_core::{Capability, GitError, ObjectFormat, ObjectId, Result};
2use std::io::{Read, Write};
3
4use crate::pktline::{
5 PKT_LINE_MAX_LEN, PktLineFrame, line_from_str, parse_oid_argument, parse_pkt_len,
6 parse_protocol_v2_line_text, read_pkt_line_frame, read_pkt_line_frames_until_flush,
7 trace_packet_read_payload, trim_trailing_lf, validate_capability_field,
8 validate_protocol_v2_token, write_pkt_line_payload,
9};
10use crate::receive_pack::reject_capability_value;
11use crate::sideband::{
12 SideBandDemux, SideBandPacket, demux_sideband_packets, encode_sideband_packet,
13 parse_sideband_packet, write_sideband_packet,
14};
15use crate::v0::{encode_capabilities, parse_capabilities};
16use crate::v2::{
17 ProtocolV2FetchShallowInfo, parse_fetch_shallow_info, parse_u32_argument, parse_u64_argument,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq, Default)]
21pub struct UploadPackRequest {
22 pub wants: Vec<ObjectId>,
23 pub capabilities: Vec<Capability>,
24 pub shallow: Vec<ObjectId>,
25 pub deepen: Option<u32>,
26 pub deepen_since: Option<u64>,
27 pub deepen_not: Vec<String>,
28 pub filter: Option<String>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Default)]
32pub struct UploadPackFeatures {
33 pub multi_ack: bool,
34 pub multi_ack_detailed: bool,
35 pub no_done: bool,
36 pub thin_pack: bool,
37 pub side_band: bool,
38 pub side_band_64k: bool,
39 pub ofs_delta: bool,
40 pub shallow: bool,
41 pub deepen_since: bool,
42 pub deepen_not: bool,
43 pub include_tag: bool,
44 pub no_progress: bool,
45 pub allow_tip_sha1_in_want: bool,
46 pub allow_reachable_sha1_in_want: bool,
47 pub filter: bool,
48 pub agent: Option<String>,
49 pub object_format: Option<ObjectFormat>,
50 pub symrefs: Vec<String>,
51 pub unknown: Vec<Capability>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Default)]
55pub struct UploadPackNegotiationRequest {
56 pub haves: Vec<ObjectId>,
57 pub done: bool,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum UploadPackAckStatus {
62 Continue,
63 Common,
64 Ready,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum UploadPackAcknowledgment {
69 Nak,
70 Ack {
71 oid: ObjectId,
72 status: Option<UploadPackAckStatus>,
73 },
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Default)]
77pub struct UploadPackPackfileResponse {
78 pub acknowledgments: Vec<UploadPackAcknowledgment>,
79 pub sideband: Vec<SideBandPacket>,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Default)]
83pub struct UploadPackRawPackfileResponse {
84 pub acknowledgments: Vec<UploadPackAcknowledgment>,
85 pub packfile: Vec<u8>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Default)]
89pub struct UploadPackRawPackfileResponseHeader {
90 pub acknowledgments: Vec<UploadPackAcknowledgment>,
91 pub pack_prefix: Vec<u8>,
92}
93pub fn parse_upload_pack_request(
94 format: ObjectFormat,
95 frames: &[PktLineFrame],
96) -> Result<Option<UploadPackRequest>> {
97 if matches!(frames, [PktLineFrame::Flush]) {
98 return Ok(None);
99 }
100
101 let mut request = UploadPackRequest::default();
102 let mut in_options = false;
103 let mut saw_flush = false;
104 for (idx, frame) in frames.iter().enumerate() {
105 match frame {
106 PktLineFrame::Data(payload) if !saw_flush => {
107 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
108 if let Some(value) = text.strip_prefix("want ") {
109 if in_options {
110 return Err(GitError::InvalidFormat(
111 "upload-pack request has want after options".into(),
112 ));
113 }
114 let (oid, capabilities) = if request.wants.is_empty() {
115 value
116 .split_once(' ')
117 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
118 } else {
119 if value.contains(' ') {
120 return Err(GitError::InvalidFormat(
121 "additional upload-pack want has capabilities".into(),
122 ));
123 }
124 (value, None)
125 };
126 validate_protocol_v2_token("upload-pack want", oid)?;
127 request.wants.push(ObjectId::from_hex(format, oid)?);
128 if let Some(capabilities) = capabilities {
129 request.capabilities = parse_capabilities(capabilities)?;
130 }
131 continue;
132 }
133
134 if request.wants.is_empty() {
135 return Err(GitError::InvalidFormat(
136 "upload-pack request must start with want".into(),
137 ));
138 }
139 in_options = true;
140 if text.starts_with("shallow ") {
141 request.shallow.push(parse_oid_argument(
142 format,
143 "upload-pack shallow",
144 text,
145 "shallow ",
146 )?);
147 } else if text.starts_with("deepen ") {
148 if request.deepen.is_some() {
149 return Err(GitError::InvalidFormat(
150 "upload-pack request has duplicate deepen".into(),
151 ));
152 }
153 request.deepen =
154 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
155 } else if text.starts_with("deepen-since ") {
156 if request.deepen_since.is_some() {
157 return Err(GitError::InvalidFormat(
158 "upload-pack request has duplicate deepen-since".into(),
159 ));
160 }
161 request.deepen_since = Some(parse_u64_argument(
162 "upload-pack deepen-since",
163 text,
164 "deepen-since ",
165 )?);
166 } else if let Some(name) = text.strip_prefix("deepen-not ") {
167 validate_protocol_v2_token("upload-pack deepen-not", name)?;
168 request.deepen_not.push(name.to_string());
169 } else if let Some(filter) = text.strip_prefix("filter ") {
170 if request.filter.is_some() {
171 return Err(GitError::InvalidFormat(
172 "upload-pack request has duplicate filter".into(),
173 ));
174 }
175 validate_protocol_v2_token("upload-pack filter", filter)?;
176 request.filter = Some(filter.to_string());
177 } else {
178 return Err(GitError::InvalidFormat(format!(
179 "unsupported upload-pack request line {text}"
180 )));
181 }
182 }
183 PktLineFrame::Data(_) => {
184 return Err(GitError::InvalidFormat(
185 "upload-pack request has data after flush".into(),
186 ));
187 }
188 PktLineFrame::Flush => {
189 saw_flush = true;
190 if idx + 1 != frames.len() {
191 return Err(GitError::InvalidFormat(
192 "upload-pack request has frames after flush".into(),
193 ));
194 }
195 }
196 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
197 return Err(GitError::InvalidFormat(
198 "upload-pack request contains a non-flush control packet".into(),
199 ));
200 }
201 }
202 }
203 if !saw_flush {
204 return Err(GitError::InvalidFormat(
205 "upload-pack request missing flush".into(),
206 ));
207 }
208 if request.wants.is_empty() {
209 return Err(GitError::InvalidFormat(
210 "upload-pack request missing want".into(),
211 ));
212 }
213 Ok(Some(request))
214}
215
216pub fn encode_upload_pack_request(
217 request: Option<&UploadPackRequest>,
218) -> Result<Vec<PktLineFrame>> {
219 let Some(request) = request else {
220 return Ok(vec![PktLineFrame::Flush]);
221 };
222 if request.wants.is_empty() {
223 return Err(GitError::InvalidFormat(
224 "upload-pack request missing want".into(),
225 ));
226 }
227
228 let mut frames = Vec::new();
229 for (idx, oid) in request.wants.iter().enumerate() {
230 let mut line = format!("want {oid}");
231 if idx == 0 && !request.capabilities.is_empty() {
232 line.push(' ');
233 line.push_str(
234 &String::from_utf8(encode_capabilities(&request.capabilities)?)
235 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
236 );
237 }
238 frames.push(PktLineFrame::data(line_from_str(&line))?);
239 }
240 for oid in &request.shallow {
241 frames.push(PktLineFrame::data(line_from_str(&format!(
242 "shallow {oid}"
243 )))?);
244 }
245 if let Some(deepen) = request.deepen {
246 if deepen == 0 {
247 return Err(GitError::InvalidFormat(
248 "upload-pack deepen must be positive".into(),
249 ));
250 }
251 frames.push(PktLineFrame::data(line_from_str(&format!(
252 "deepen {deepen}"
253 )))?);
254 }
255 if let Some(deepen_since) = request.deepen_since {
256 frames.push(PktLineFrame::data(line_from_str(&format!(
257 "deepen-since {deepen_since}"
258 )))?);
259 }
260 for name in &request.deepen_not {
261 validate_protocol_v2_token("upload-pack deepen-not", name)?;
262 frames.push(PktLineFrame::data(line_from_str(&format!(
263 "deepen-not {name}"
264 )))?);
265 }
266 if let Some(filter) = &request.filter {
267 validate_protocol_v2_token("upload-pack filter", filter)?;
268 frames.push(PktLineFrame::data(line_from_str(&format!(
269 "filter {filter}"
270 )))?);
271 }
272 frames.push(PktLineFrame::Flush);
273 Ok(frames)
274}
275
276pub fn read_upload_pack_request(
277 format: ObjectFormat,
278 reader: &mut impl Read,
279) -> Result<Option<UploadPackRequest>> {
280 let frames = read_pkt_line_frames_until_flush(reader)?;
281 parse_upload_pack_request(format, &frames)
282}
283
284pub fn write_upload_pack_request(
285 writer: &mut impl Write,
286 request: Option<&UploadPackRequest>,
287) -> Result<()> {
288 let Some(request) = request else {
289 writer.write_all(b"0000")?;
290 return Ok(());
291 };
292 if request.wants.is_empty() {
293 return Err(GitError::InvalidFormat(
294 "upload-pack request missing want".into(),
295 ));
296 }
297
298 for (idx, oid) in request.wants.iter().enumerate() {
299 let mut line = format!("want {oid}");
300 if idx == 0 && !request.capabilities.is_empty() {
301 line.push(' ');
302 line.push_str(
303 &String::from_utf8(encode_capabilities(&request.capabilities)?)
304 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
305 );
306 }
307 write_pkt_line_payload(writer, &line_from_str(&line))?;
308 }
309 for oid in &request.shallow {
310 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
311 }
312 if let Some(deepen) = request.deepen {
313 if deepen == 0 {
314 return Err(GitError::InvalidFormat(
315 "upload-pack deepen must be positive".into(),
316 ));
317 }
318 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
319 }
320 if let Some(deepen_since) = request.deepen_since {
321 write_pkt_line_payload(
322 writer,
323 &line_from_str(&format!("deepen-since {deepen_since}")),
324 )?;
325 }
326 for name in &request.deepen_not {
327 validate_protocol_v2_token("upload-pack deepen-not", name)?;
328 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
329 }
330 if let Some(filter) = &request.filter {
331 validate_protocol_v2_token("upload-pack filter", filter)?;
332 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
333 }
334 writer.write_all(b"0000")?;
335 Ok(())
336}
337
338pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
339 let mut features = UploadPackFeatures::default();
340 for capability in capabilities {
341 match capability.name.as_str() {
342 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
343 "multi_ack_detailed" => {
344 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
345 }
346 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
347 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
348 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
349 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
350 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
351 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
352 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
353 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
354 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
355 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
356 "allow-tip-sha1-in-want" => {
357 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
358 }
359 "allow-reachable-sha1-in-want" => {
360 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
361 }
362 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
363 "agent" => {
364 let Some(agent) = &capability.value else {
365 return Err(GitError::InvalidFormat(
366 "upload-pack agent capability is missing value".into(),
367 ));
368 };
369 if features.agent.is_some() {
370 return Err(GitError::InvalidFormat(
371 "upload-pack has duplicate agent capability".into(),
372 ));
373 }
374 validate_capability_field("upload-pack agent", agent)?;
375 features.agent = Some(agent.clone());
376 }
377 "object-format" => {
378 let Some(format) = &capability.value else {
379 return Err(GitError::InvalidFormat(
380 "upload-pack object-format capability is missing value".into(),
381 ));
382 };
383 if features.object_format.is_some() {
384 return Err(GitError::InvalidFormat(
385 "upload-pack has duplicate object-format capability".into(),
386 ));
387 }
388 validate_capability_field("upload-pack object-format", format)?;
389 features.object_format = Some(format.parse()?);
390 }
391 "symref" => {
392 let Some(symref) = &capability.value else {
393 continue;
394 };
395 validate_capability_field("upload-pack symref", symref)?;
396 features.symrefs.push(symref.clone());
397 }
398 _ => {
399 encode_capabilities(std::slice::from_ref(capability))?;
400 if features
401 .unknown
402 .iter()
403 .any(|known| known.name == capability.name)
404 {
405 return Err(GitError::InvalidFormat(format!(
406 "upload-pack has duplicate {} capability",
407 capability.name
408 )));
409 }
410 features.unknown.push(capability.clone());
411 }
412 }
413 }
414 Ok(features)
415}
416
417pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
418 let mut capabilities = Vec::new();
419 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
420 push_upload_pack_flag(
421 &mut capabilities,
422 "multi_ack_detailed",
423 features.multi_ack_detailed,
424 );
425 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
426 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
427 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
428 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
429 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
430 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
431 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
432 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
433 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
434 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
435 push_upload_pack_flag(
436 &mut capabilities,
437 "allow-tip-sha1-in-want",
438 features.allow_tip_sha1_in_want,
439 );
440 push_upload_pack_flag(
441 &mut capabilities,
442 "allow-reachable-sha1-in-want",
443 features.allow_reachable_sha1_in_want,
444 );
445 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
446 if let Some(agent) = &features.agent {
447 validate_capability_field("upload-pack agent", agent)?;
448 capabilities.push(Capability {
449 name: "agent".into(),
450 value: Some(agent.clone()),
451 });
452 }
453 if let Some(format) = features.object_format {
454 capabilities.push(Capability {
455 name: "object-format".into(),
456 value: Some(format.name().into()),
457 });
458 }
459 for symref in &features.symrefs {
460 validate_capability_field("upload-pack symref", symref)?;
461 capabilities.push(Capability {
462 name: "symref".into(),
463 value: Some(symref.clone()),
464 });
465 }
466 for capability in &features.unknown {
467 if is_known_upload_pack_capability(&capability.name) {
468 return Err(GitError::InvalidFormat(format!(
469 "upload-pack unknown capability duplicates known capability {}",
470 capability.name
471 )));
472 }
473 encode_capabilities(std::slice::from_ref(capability))?;
474 capabilities.push(capability.clone());
475 }
476 Ok(capabilities)
477}
478
479pub fn validate_upload_pack_request_features(
480 features: &UploadPackFeatures,
481 request: &UploadPackRequest,
482) -> Result<()> {
483 for capability in &request.capabilities {
484 if is_upload_pack_flag_capability(&capability.name) {
485 reject_capability_value("upload-pack request capability", capability)?;
486 }
487 match capability.name.as_str() {
488 "multi_ack" if !features.multi_ack => {
489 return Err(GitError::InvalidFormat(
490 "upload-pack request uses multi_ack without advertised capability".into(),
491 ));
492 }
493 "multi_ack_detailed" if !features.multi_ack_detailed => {
494 return Err(GitError::InvalidFormat(
495 "upload-pack request uses multi_ack_detailed without advertised capability"
496 .into(),
497 ));
498 }
499 "no-done" if !features.no_done => {
500 return Err(GitError::InvalidFormat(
501 "upload-pack request uses no-done without advertised capability".into(),
502 ));
503 }
504 "thin-pack" if !features.thin_pack => {
505 return Err(GitError::InvalidFormat(
506 "upload-pack request uses thin-pack without advertised capability".into(),
507 ));
508 }
509 "side-band" if !features.side_band => {
510 return Err(GitError::InvalidFormat(
511 "upload-pack request uses side-band without advertised capability".into(),
512 ));
513 }
514 "side-band-64k" if !features.side_band_64k => {
515 return Err(GitError::InvalidFormat(
516 "upload-pack request uses side-band-64k without advertised capability".into(),
517 ));
518 }
519 "ofs-delta" if !features.ofs_delta => {
520 return Err(GitError::InvalidFormat(
521 "upload-pack request uses ofs-delta without advertised capability".into(),
522 ));
523 }
524 "include-tag" if !features.include_tag => {
525 return Err(GitError::InvalidFormat(
526 "upload-pack request uses include-tag without advertised capability".into(),
527 ));
528 }
529 "no-progress" if !features.no_progress => {
530 return Err(GitError::InvalidFormat(
531 "upload-pack request uses no-progress without advertised capability".into(),
532 ));
533 }
534 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
535 return Err(GitError::InvalidFormat(
536 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
537 .into(),
538 ));
539 }
540 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
541 return Err(GitError::InvalidFormat(
542 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
543 .into(),
544 ));
545 }
546 "filter" if !features.filter => {
547 return Err(GitError::InvalidFormat(
548 "upload-pack request uses filter capability without advertised capability"
549 .into(),
550 ));
551 }
552 "agent" => {
553 let Some(agent) = &capability.value else {
554 return Err(GitError::InvalidFormat(
555 "upload-pack request agent capability is missing value".into(),
556 ));
557 };
558 validate_capability_field("upload-pack request agent", agent)?;
559 }
560 "object-format" => {
561 let Some(format) = &capability.value else {
562 return Err(GitError::InvalidFormat(
563 "upload-pack request object-format capability is missing value".into(),
564 ));
565 };
566 let requested_format: ObjectFormat = format.parse()?;
567 if features.object_format != Some(requested_format) {
568 return Err(GitError::InvalidFormat(
569 "upload-pack request object-format was not advertised".into(),
570 ));
571 }
572 }
573 name if is_known_upload_pack_capability(name) => {}
574 _ => {
575 if !features
576 .unknown
577 .iter()
578 .any(|advertised| advertised.name == capability.name)
579 {
580 return Err(GitError::InvalidFormat(format!(
581 "upload-pack request uses unadvertised capability {}",
582 capability.name
583 )));
584 }
585 }
586 }
587 }
588
589 let sideband = request
590 .capabilities
591 .iter()
592 .any(|capability| capability.name == "side-band");
593 let sideband_64k = request
594 .capabilities
595 .iter()
596 .any(|capability| capability.name == "side-band-64k");
597 if sideband && sideband_64k {
598 return Err(GitError::InvalidFormat(
599 "upload-pack request must not request both side-band and side-band-64k".into(),
600 ));
601 }
602
603 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
604 return Err(GitError::InvalidFormat(
605 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
606 ));
607 }
608 if !features.deepen_since && request.deepen_since.is_some() {
609 return Err(GitError::InvalidFormat(
610 "upload-pack request uses deepen-since without advertised capability".into(),
611 ));
612 }
613 if !features.deepen_not && !request.deepen_not.is_empty() {
614 return Err(GitError::InvalidFormat(
615 "upload-pack request uses deepen-not without advertised capability".into(),
616 ));
617 }
618 if !features.filter && request.filter.is_some() {
619 return Err(GitError::InvalidFormat(
620 "upload-pack request uses filter without advertised capability".into(),
621 ));
622 }
623 Ok(())
624}
625
626pub fn build_upload_pack_raw_packfile_response<C, B>(
627 features: &UploadPackFeatures,
628 request: UploadPackRequest,
629 haves: impl IntoIterator<Item = ObjectId>,
630 mut contains_object: C,
631 mut build_pack: B,
632) -> Result<UploadPackRawPackfileResponse>
633where
634 C: FnMut(&ObjectId) -> Result<bool>,
635 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
636{
637 validate_upload_pack_request_features(features, &request)?;
638 for want in &request.wants {
639 if !contains_object(want)? {
640 return Err(GitError::InvalidObject(format!(
641 "upload-pack requested missing object {want}"
642 )));
643 }
644 }
645 let known_haves = haves
646 .into_iter()
647 .filter_map(|oid| match contains_object(&oid) {
648 Ok(true) => Some(Ok(oid)),
649 Ok(false) => None,
650 Err(err) => Some(Err(err)),
651 })
652 .collect::<Result<Vec<_>>>()?;
653 let packfile = build_pack(request.wants, known_haves)?
654 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
655 Ok(UploadPackRawPackfileResponse {
656 acknowledgments: vec![UploadPackAcknowledgment::Nak],
657 packfile,
658 })
659}
660
661pub fn parse_upload_pack_shallow_update(
662 format: ObjectFormat,
663 frames: &[PktLineFrame],
664) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
665 let mut entries = Vec::new();
666 let mut saw_flush = false;
667 for (idx, frame) in frames.iter().enumerate() {
668 match frame {
669 PktLineFrame::Data(payload) if !saw_flush => {
670 entries.push(parse_fetch_shallow_info(format, payload)?);
671 }
672 PktLineFrame::Data(_) => {
673 return Err(GitError::InvalidFormat(
674 "upload-pack shallow update has data after flush".into(),
675 ));
676 }
677 PktLineFrame::Flush => {
678 saw_flush = true;
679 if idx + 1 != frames.len() {
680 return Err(GitError::InvalidFormat(
681 "upload-pack shallow update has frames after flush".into(),
682 ));
683 }
684 }
685 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
686 return Err(GitError::InvalidFormat(
687 "upload-pack shallow update contains a non-flush control packet".into(),
688 ));
689 }
690 }
691 }
692 if !saw_flush {
693 return Err(GitError::InvalidFormat(
694 "upload-pack shallow update missing flush".into(),
695 ));
696 }
697 Ok(entries)
698}
699
700pub fn encode_upload_pack_shallow_update(
701 entries: &[ProtocolV2FetchShallowInfo],
702) -> Result<Vec<PktLineFrame>> {
703 let mut frames = Vec::new();
704 for entry in entries {
705 let line = match entry {
706 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
707 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
708 };
709 frames.push(PktLineFrame::data(line_from_str(&line))?);
710 }
711 frames.push(PktLineFrame::Flush);
712 Ok(frames)
713}
714
715pub fn read_upload_pack_shallow_update(
716 format: ObjectFormat,
717 reader: &mut impl Read,
718) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
719 let frames = read_pkt_line_frames_until_flush(reader)?;
720 parse_upload_pack_shallow_update(format, &frames)
721}
722
723pub fn write_upload_pack_shallow_update(
724 writer: &mut impl Write,
725 entries: &[ProtocolV2FetchShallowInfo],
726) -> Result<()> {
727 for entry in entries {
728 let line = match entry {
729 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
730 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
731 };
732 write_pkt_line_payload(writer, &line_from_str(&line))?;
733 }
734 writer.write_all(b"0000")?;
735 Ok(())
736}
737
738pub fn parse_upload_pack_negotiation_request(
739 format: ObjectFormat,
740 frames: &[PktLineFrame],
741) -> Result<UploadPackNegotiationRequest> {
742 let mut request = UploadPackNegotiationRequest::default();
743 let mut terminated = false;
744 for (idx, frame) in frames.iter().enumerate() {
745 match frame {
746 PktLineFrame::Data(payload) if !terminated => {
747 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
748 if text == "done" {
749 request.done = true;
750 terminated = true;
751 if idx + 1 != frames.len() {
752 return Err(GitError::InvalidFormat(
753 "upload-pack negotiation has frames after done".into(),
754 ));
755 }
756 } else if text.starts_with("have ") {
757 request.haves.push(parse_oid_argument(
758 format,
759 "upload-pack have",
760 text,
761 "have ",
762 )?);
763 } else {
764 return Err(GitError::InvalidFormat(format!(
765 "unsupported upload-pack negotiation line {text}"
766 )));
767 }
768 }
769 PktLineFrame::Data(_) => {
770 return Err(GitError::InvalidFormat(
771 "upload-pack negotiation has data after terminator".into(),
772 ));
773 }
774 PktLineFrame::Flush => {
775 terminated = true;
776 if idx + 1 != frames.len() {
777 return Err(GitError::InvalidFormat(
778 "upload-pack negotiation has frames after flush".into(),
779 ));
780 }
781 }
782 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
783 return Err(GitError::InvalidFormat(
784 "upload-pack negotiation contains a non-flush control packet".into(),
785 ));
786 }
787 }
788 }
789 if !terminated {
790 return Err(GitError::InvalidFormat(
791 "upload-pack negotiation missing terminator".into(),
792 ));
793 }
794 Ok(request)
795}
796
797pub fn encode_upload_pack_negotiation_request(
798 request: &UploadPackNegotiationRequest,
799) -> Result<Vec<PktLineFrame>> {
800 let mut frames = Vec::new();
801 for oid in &request.haves {
802 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
803 }
804 if request.done {
805 frames.push(PktLineFrame::data(line_from_str("done"))?);
806 } else {
807 frames.push(PktLineFrame::Flush);
808 }
809 Ok(frames)
810}
811
812pub fn read_upload_pack_negotiation_request(
813 format: ObjectFormat,
814 reader: &mut impl Read,
815) -> Result<UploadPackNegotiationRequest> {
816 let mut frames = Vec::new();
817 loop {
818 let Some(frame) = read_pkt_line_frame(reader)? else {
819 return Err(GitError::InvalidFormat(
820 "pkt-line stream ended before upload-pack negotiation terminator".into(),
821 ));
822 };
823 let done = match &frame {
824 PktLineFrame::Flush => true,
825 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
826 _ => false,
827 };
828 frames.push(frame);
829 if done {
830 return parse_upload_pack_negotiation_request(format, &frames);
831 }
832 }
833}
834
835pub fn write_upload_pack_negotiation_request(
836 writer: &mut impl Write,
837 request: &UploadPackNegotiationRequest,
838) -> Result<()> {
839 for oid in &request.haves {
840 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
841 }
842 if request.done {
843 write_pkt_line_payload(writer, b"done\n")?;
844 } else {
845 writer.write_all(b"0000")?;
846 }
847 Ok(())
848}
849
850pub fn parse_upload_pack_acknowledgment(
851 format: ObjectFormat,
852 payload: &[u8],
853) -> Result<UploadPackAcknowledgment> {
854 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
855 if text == "NAK" {
856 return Ok(UploadPackAcknowledgment::Nak);
857 }
858 let Some(rest) = text.strip_prefix("ACK ") else {
859 return Err(GitError::InvalidFormat(format!(
860 "unsupported upload-pack acknowledgment {text}"
861 )));
862 };
863 let mut fields = rest.split(' ');
864 let oid = fields
865 .next()
866 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
867 validate_protocol_v2_token("upload-pack ACK", oid)?;
868 let status = match fields.next() {
869 None => None,
870 Some("continue") => Some(UploadPackAckStatus::Continue),
871 Some("common") => Some(UploadPackAckStatus::Common),
872 Some("ready") => Some(UploadPackAckStatus::Ready),
873 Some(other) => {
874 return Err(GitError::InvalidFormat(format!(
875 "unsupported upload-pack ACK status {other}"
876 )));
877 }
878 };
879 if fields.next().is_some() {
880 return Err(GitError::InvalidFormat(
881 "upload-pack ACK has too many fields".into(),
882 ));
883 }
884 Ok(UploadPackAcknowledgment::Ack {
885 oid: ObjectId::from_hex(format, oid)?,
886 status,
887 })
888}
889
890pub fn encode_upload_pack_acknowledgment(
891 acknowledgment: &UploadPackAcknowledgment,
892) -> Result<Vec<u8>> {
893 let line = match acknowledgment {
894 UploadPackAcknowledgment::Nak => "NAK".to_string(),
895 UploadPackAcknowledgment::Ack { oid, status } => {
896 let mut line = format!("ACK {oid}");
897 if let Some(status) = status {
898 line.push(' ');
899 line.push_str(match status {
900 UploadPackAckStatus::Continue => "continue",
901 UploadPackAckStatus::Common => "common",
902 UploadPackAckStatus::Ready => "ready",
903 });
904 }
905 line
906 }
907 };
908 Ok(line_from_str(&line))
909}
910
911pub fn read_upload_pack_acknowledgment(
912 format: ObjectFormat,
913 reader: &mut impl Read,
914) -> Result<UploadPackAcknowledgment> {
915 let Some(frame) = read_pkt_line_frame(reader)? else {
916 return Err(GitError::InvalidFormat(
917 "pkt-line stream ended before upload-pack acknowledgment".into(),
918 ));
919 };
920 match frame {
921 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
922 _ => Err(GitError::InvalidFormat(
923 "upload-pack acknowledgment must be a data packet".into(),
924 )),
925 }
926}
927
928pub fn write_upload_pack_acknowledgment(
929 writer: &mut impl Write,
930 acknowledgment: &UploadPackAcknowledgment,
931) -> Result<()> {
932 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
933}
934
935pub fn parse_upload_pack_packfile_response(
936 format: ObjectFormat,
937 frames: &[PktLineFrame],
938) -> Result<UploadPackPackfileResponse> {
939 let mut response = UploadPackPackfileResponse::default();
940 let mut in_sideband = false;
941 let mut saw_flush = false;
942 for (idx, frame) in frames.iter().enumerate() {
943 match frame {
944 PktLineFrame::Data(payload) if !saw_flush => {
945 if !in_sideband
946 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
947 {
948 response
949 .acknowledgments
950 .push(parse_upload_pack_acknowledgment(format, payload)?);
951 continue;
952 }
953 in_sideband = true;
954 response.sideband.push(parse_sideband_packet(payload)?);
955 }
956 PktLineFrame::Data(_) => {
957 return Err(GitError::InvalidFormat(
958 "upload-pack packfile response has data after flush".into(),
959 ));
960 }
961 PktLineFrame::Flush => {
962 saw_flush = true;
963 if idx + 1 != frames.len() {
964 return Err(GitError::InvalidFormat(
965 "upload-pack packfile response has frames after flush".into(),
966 ));
967 }
968 }
969 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
970 return Err(GitError::InvalidFormat(
971 "upload-pack packfile response contains a non-flush control packet".into(),
972 ));
973 }
974 }
975 }
976 if !saw_flush {
977 return Err(GitError::InvalidFormat(
978 "upload-pack packfile response missing flush".into(),
979 ));
980 }
981 Ok(response)
982}
983
984pub fn encode_upload_pack_packfile_response(
985 response: &UploadPackPackfileResponse,
986) -> Result<Vec<PktLineFrame>> {
987 let mut frames = Vec::new();
988 for acknowledgment in &response.acknowledgments {
989 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
990 acknowledgment,
991 )?)?);
992 }
993 for packet in &response.sideband {
994 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
995 }
996 frames.push(PktLineFrame::Flush);
997 Ok(frames)
998}
999
1000pub fn read_upload_pack_packfile_response(
1001 format: ObjectFormat,
1002 reader: &mut impl Read,
1003) -> Result<UploadPackPackfileResponse> {
1004 let frames = read_pkt_line_frames_until_flush(reader)?;
1005 parse_upload_pack_packfile_response(format, &frames)
1006}
1007
1008pub fn write_upload_pack_packfile_response(
1009 writer: &mut impl Write,
1010 response: &UploadPackPackfileResponse,
1011) -> Result<()> {
1012 for acknowledgment in &response.acknowledgments {
1013 write_upload_pack_acknowledgment(writer, acknowledgment)?;
1014 }
1015 for packet in &response.sideband {
1016 write_sideband_packet(writer, packet)?;
1017 }
1018 writer.write_all(b"0000")?;
1019 Ok(())
1020}
1021
1022pub fn demux_upload_pack_packfile_response(
1023 response: &UploadPackPackfileResponse,
1024) -> Result<SideBandDemux> {
1025 demux_sideband_packets(&response.sideband)
1026}
1027
1028pub fn parse_upload_pack_raw_packfile_response(
1029 format: ObjectFormat,
1030 input: &[u8],
1031) -> Result<UploadPackRawPackfileResponse> {
1032 let mut response = UploadPackRawPackfileResponse::default();
1033 let mut offset = 0usize;
1034 while offset < input.len() {
1035 match PktLineFrame::parse(&input[offset..]) {
1036 Ok((PktLineFrame::Data(payload), consumed)) => {
1037 let trimmed = trim_trailing_lf(&payload);
1038 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
1039 response
1040 .acknowledgments
1041 .push(parse_upload_pack_acknowledgment(format, &payload)?);
1042 offset += consumed;
1043 continue;
1044 }
1045 return Err(GitError::InvalidFormat(
1046 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
1047 ));
1048 }
1049 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
1050 return Err(GitError::InvalidFormat(
1051 "upload-pack raw packfile response contains a control packet".into(),
1052 ));
1053 }
1054 Err(_) if input[offset..].starts_with(b"PACK") => break,
1055 Err(err) => return Err(err),
1056 }
1057 }
1058 response.packfile = input[offset..].to_vec();
1059 if response.packfile.is_empty() {
1060 return Err(GitError::InvalidFormat(
1061 "upload-pack raw packfile response missing packfile".into(),
1062 ));
1063 }
1064 if !response.packfile.starts_with(b"PACK") {
1065 return Err(GitError::InvalidFormat(
1066 "upload-pack raw packfile response packfile must start with PACK".into(),
1067 ));
1068 }
1069 Ok(response)
1070}
1071
1072pub fn encode_upload_pack_raw_packfile_response(
1073 response: &UploadPackRawPackfileResponse,
1074) -> Result<Vec<u8>> {
1075 if response.packfile.is_empty() {
1076 return Err(GitError::InvalidFormat(
1077 "upload-pack raw packfile response missing packfile".into(),
1078 ));
1079 }
1080 if !response.packfile.starts_with(b"PACK") {
1081 return Err(GitError::InvalidFormat(
1082 "upload-pack raw packfile response packfile must start with PACK".into(),
1083 ));
1084 }
1085 let mut out = Vec::new();
1086 for acknowledgment in &response.acknowledgments {
1087 write_pkt_line_payload(
1088 &mut out,
1089 &encode_upload_pack_acknowledgment(acknowledgment)?,
1090 )?;
1091 }
1092 out.extend_from_slice(&response.packfile);
1093 Ok(out)
1094}
1095
1096pub fn read_upload_pack_raw_packfile_response(
1097 format: ObjectFormat,
1098 reader: &mut impl Read,
1099) -> Result<UploadPackRawPackfileResponse> {
1100 let mut input = Vec::new();
1101 reader.read_to_end(&mut input)?;
1102 parse_upload_pack_raw_packfile_response(format, &input)
1103}
1104
1105pub fn read_upload_pack_raw_packfile_response_header(
1106 format: ObjectFormat,
1107 reader: &mut impl Read,
1108) -> Result<UploadPackRawPackfileResponseHeader> {
1109 let mut acknowledgments = Vec::new();
1110 loop {
1111 let mut header = [0u8; 4];
1112 reader.read_exact(&mut header)?;
1113 if &header == b"PACK" {
1114 return Ok(UploadPackRawPackfileResponseHeader {
1115 acknowledgments,
1116 pack_prefix: header.to_vec(),
1117 });
1118 }
1119 let len = parse_pkt_len(&header)?;
1120 let payload = match len {
1121 0..=2 => {
1122 return Err(GitError::InvalidFormat(
1123 "upload-pack raw packfile response contains a control packet".into(),
1124 ));
1125 }
1126 3 => {
1127 return Err(GitError::InvalidFormat(
1128 "reserved pkt-line length 0003".into(),
1129 ));
1130 }
1131 4..=PKT_LINE_MAX_LEN => {
1132 let mut payload = vec![0; len - 4];
1133 reader.read_exact(&mut payload)?;
1134 payload
1135 }
1136 _ => {
1137 return Err(GitError::InvalidFormat(format!(
1138 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
1139 )));
1140 }
1141 };
1142 trace_packet_read_payload(&payload);
1143 let trimmed = trim_trailing_lf(&payload);
1144 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
1145 acknowledgments.push(parse_upload_pack_acknowledgment(format, &payload)?);
1146 continue;
1147 }
1148 return Err(GitError::InvalidFormat(
1149 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
1150 ));
1151 }
1152}
1153
1154pub fn read_upload_pack_shallow_info_section(
1155 format: ObjectFormat,
1156 reader: &mut impl Read,
1157) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
1158 let mut entries = Vec::new();
1159 loop {
1160 let Some(frame) = read_pkt_line_frame(reader)? else {
1161 return Err(GitError::InvalidFormat(
1162 "upload-pack shallow-info section ended before flush".into(),
1163 ));
1164 };
1165 match frame {
1166 PktLineFrame::Data(payload) => {
1167 entries.push(parse_fetch_shallow_info(format, &payload)?)
1168 }
1169 PktLineFrame::Flush => return Ok(entries),
1170 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1171 return Err(GitError::InvalidFormat(
1172 "upload-pack shallow-info section contains a non-flush control packet".into(),
1173 ));
1174 }
1175 }
1176 }
1177}
1178
1179pub fn read_upload_pack_shallow_info_and_raw_packfile_response_header(
1180 format: ObjectFormat,
1181 reader: &mut impl Read,
1182) -> Result<(
1183 Vec<ProtocolV2FetchShallowInfo>,
1184 UploadPackRawPackfileResponseHeader,
1185)> {
1186 let shallow = read_upload_pack_shallow_info_section(format, reader)?;
1187 let raw = read_upload_pack_raw_packfile_response_header(format, reader)?;
1188 Ok((shallow, raw))
1189}
1190
1191pub fn write_upload_pack_raw_packfile_response(
1192 writer: &mut impl Write,
1193 response: &UploadPackRawPackfileResponse,
1194) -> Result<()> {
1195 if response.packfile.is_empty() {
1196 return Err(GitError::InvalidFormat(
1197 "upload-pack raw packfile response missing packfile".into(),
1198 ));
1199 }
1200 if !response.packfile.starts_with(b"PACK") {
1201 return Err(GitError::InvalidFormat(
1202 "upload-pack raw packfile response packfile must start with PACK".into(),
1203 ));
1204 }
1205 for acknowledgment in &response.acknowledgments {
1206 write_upload_pack_acknowledgment(writer, acknowledgment)?;
1207 }
1208 writer.write_all(&response.packfile)?;
1209 Ok(())
1210}
1211
1212pub fn parse_upload_pack_shallow_info_section(
1223 format: ObjectFormat,
1224 input: &[u8],
1225) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
1226 let mut entries = Vec::new();
1227 let mut offset = 0usize;
1228 loop {
1229 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
1230 offset += consumed;
1231 match frame {
1232 PktLineFrame::Data(payload) => {
1233 entries.push(parse_fetch_shallow_info(format, &payload)?)
1234 }
1235 PktLineFrame::Flush => return Ok((entries, offset)),
1236 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1237 return Err(GitError::InvalidFormat(
1238 "upload-pack shallow-info section contains a non-flush control packet".into(),
1239 ));
1240 }
1241 }
1242 }
1243}
1244
1245pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
1256 format: ObjectFormat,
1257 input: &[u8],
1258) -> Result<(
1259 Vec<ProtocolV2FetchShallowInfo>,
1260 UploadPackRawPackfileResponse,
1261)> {
1262 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
1263 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
1264 Ok((shallow, response))
1265}
1266
1267pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
1275 format: ObjectFormat,
1276 reader: &mut impl Read,
1277) -> Result<(
1278 Vec<ProtocolV2FetchShallowInfo>,
1279 UploadPackRawPackfileResponse,
1280)> {
1281 let (shallow, header) =
1282 read_upload_pack_shallow_info_and_raw_packfile_response_header(format, reader)?;
1283 let mut packfile = header.pack_prefix;
1284 reader.read_to_end(&mut packfile)?;
1285 Ok((
1286 shallow,
1287 UploadPackRawPackfileResponse {
1288 acknowledgments: header.acknowledgments,
1289 packfile,
1290 },
1291 ))
1292}
1293fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
1294 reject_capability_value("upload-pack capability", capability)?;
1295 if *value {
1296 return Err(GitError::InvalidFormat(format!(
1297 "upload-pack has duplicate {} capability",
1298 capability.name
1299 )));
1300 }
1301 *value = true;
1302 Ok(())
1303}
1304
1305fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
1306 if enabled {
1307 capabilities.push(Capability {
1308 name: name.into(),
1309 value: None,
1310 });
1311 }
1312}
1313
1314fn is_known_upload_pack_capability(name: &str) -> bool {
1315 matches!(
1316 name,
1317 "multi_ack"
1318 | "multi_ack_detailed"
1319 | "no-done"
1320 | "thin-pack"
1321 | "side-band"
1322 | "side-band-64k"
1323 | "ofs-delta"
1324 | "shallow"
1325 | "deepen-since"
1326 | "deepen-not"
1327 | "include-tag"
1328 | "no-progress"
1329 | "allow-tip-sha1-in-want"
1330 | "allow-reachable-sha1-in-want"
1331 | "filter"
1332 | "agent"
1333 | "object-format"
1334 | "symref"
1335 )
1336}
1337
1338fn is_upload_pack_flag_capability(name: &str) -> bool {
1339 matches!(
1340 name,
1341 "multi_ack"
1342 | "multi_ack_detailed"
1343 | "no-done"
1344 | "thin-pack"
1345 | "side-band"
1346 | "side-band-64k"
1347 | "ofs-delta"
1348 | "shallow"
1349 | "deepen-since"
1350 | "deepen-not"
1351 | "include-tag"
1352 | "no-progress"
1353 | "allow-tip-sha1-in-want"
1354 | "allow-reachable-sha1-in-want"
1355 | "filter"
1356 )
1357}