| use std::io; |
| |
| use gix_transport::{client, Protocol}; |
| |
| use crate::fetch::{ |
| response, |
| response::{Acknowledgement, ShallowUpdate, WantedRef}, |
| Response, |
| }; |
| |
| fn parse_v2_section<T>( |
| line: &mut String, |
| reader: &mut impl client::ExtendedBufRead, |
| res: &mut Vec<T>, |
| parse: impl Fn(&str) -> Result<T, response::Error>, |
| ) -> Result<bool, response::Error> { |
| line.clear(); |
| while reader.readline_str(line)? != 0 { |
| res.push(parse(line)?); |
| line.clear(); |
| } |
| // End of message, or end of section? |
| Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) { |
| // try reading more sections |
| reader.reset(Protocol::V2); |
| false |
| } else { |
| // we are done, there is no pack |
| true |
| }) |
| } |
| |
| impl Response { |
| /// Parse a response of the given `version` of the protocol from `reader`. |
| /// |
| /// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`, |
| /// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done. |
| /// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier |
| /// and more localized without having to support all the cruft that there is. |
| pub fn from_line_reader( |
| version: Protocol, |
| reader: &mut impl client::ExtendedBufRead, |
| client_expects_pack: bool, |
| ) -> Result<Response, response::Error> { |
| match version { |
| Protocol::V0 | Protocol::V1 => { |
| let mut line = String::new(); |
| let mut acks = Vec::<Acknowledgement>::new(); |
| let mut shallows = Vec::<ShallowUpdate>::new(); |
| let mut saw_ready = false; |
| let has_pack = 'lines: loop { |
| line.clear(); |
| let peeked_line = match reader.peek_data_line() { |
| Some(Ok(Ok(line))) => String::from_utf8_lossy(line), |
| // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes |
| // Note that this might block forever in stateful connections as there it's not really clear |
| // if something will be following or not by just looking at the response. Instead you have to know |
| // [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594) |
| // to deal with this correctly. |
| // For now this is acceptable, as V2 can be used as a workaround, which also is the default. |
| Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false, |
| Some(Err(err)) => return Err(err.into()), |
| Some(Ok(Err(err))) => return Err(err.into()), |
| None => { |
| // maybe we saw a shallow flush packet, let's reset and retry |
| debug_assert_eq!( |
| reader.stopped_at(), |
| Some(client::MessageKind::Flush), |
| "If this isn't a flush packet, we don't know what's going on" |
| ); |
| reader.readline_str(&mut line)?; |
| reader.reset(Protocol::V1); |
| match reader.peek_data_line() { |
| Some(Ok(Ok(line))) => String::from_utf8_lossy(line), |
| Some(Err(err)) => return Err(err.into()), |
| Some(Ok(Err(err))) => return Err(err.into()), |
| None => break 'lines false, // EOF |
| } |
| } |
| }; |
| |
| if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) { |
| break 'lines true; |
| } |
| assert_ne!(reader.readline_str(&mut line)?, 0, "consuming a peeked line works"); |
| // When the server sends ready, we know there is going to be a pack so no need to stop early. |
| saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready)); |
| if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack && !saw_ready) { |
| break 'lines false; |
| } |
| }; |
| Ok(Response { |
| acks, |
| shallows, |
| wanted_refs: vec![], |
| has_pack, |
| }) |
| } |
| Protocol::V2 => { |
| // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features |
| let mut line = String::new(); |
| reader.reset(Protocol::V2); |
| let mut acks = Vec::<Acknowledgement>::new(); |
| let mut shallows = Vec::<ShallowUpdate>::new(); |
| let mut wanted_refs = Vec::<WantedRef>::new(); |
| let has_pack = 'section: loop { |
| line.clear(); |
| if reader.readline_str(&mut line)? == 0 { |
| return Err(response::Error::Io(io::Error::new( |
| io::ErrorKind::UnexpectedEof, |
| "Could not read message headline", |
| ))); |
| }; |
| |
| match line.trim_end() { |
| "acknowledgments" => { |
| if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line)? { |
| break 'section false; |
| } |
| } |
| "shallow-info" => { |
| if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line)? { |
| break 'section false; |
| } |
| } |
| "wanted-refs" => { |
| if parse_v2_section(&mut line, reader, &mut wanted_refs, WantedRef::from_line)? { |
| break 'section false; |
| } |
| } |
| "packfile" => { |
| // what follows is the packfile itself, which can be read with a sideband enabled reader |
| break 'section true; |
| } |
| _ => return Err(response::Error::UnknownSectionHeader { header: line }), |
| } |
| }; |
| Ok(Response { |
| acks, |
| shallows, |
| wanted_refs, |
| has_pack, |
| }) |
| } |
| } |
| } |
| } |