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