package gitclient import ( "bytes" "errors" "fmt" "io" "strconv" ) type pktKind int const ( pktData pktKind = iota pktFlush pktDelim pktResponseEnd ) const maxPktLen = 65520 func writePkt(w io.Writer, line string) error { if len(line)+4 > maxPktLen { return fmt.Errorf("pkt-line too long: %d bytes", len(line)) } _, err := fmt.Fprintf(w, "%04x%s", len(line)+4, line) return err } func writeFlush(w io.Writer) error { _, err := io.WriteString(w, "0000") return err } func writeDelim(w io.Writer) error { _, err := io.WriteString(w, "0001") return err } type pktReader struct { r io.Reader buf []byte } func newPktReader(r io.Reader) *pktReader { return &pktReader{r: r, buf: make([]byte, maxPktLen)} } // payload is only valid until the next call func (p *pktReader) next() (pktKind, []byte, error) { if _, err := io.ReadFull(p.r, p.buf[:4]); err != nil { if errors.Is(err, io.EOF) { err = io.ErrUnexpectedEOF } return 0, nil, err } n, err := strconv.ParseUint(string(p.buf[:4]), 16, 16) if err != nil { return 0, nil, fmt.Errorf("bad pkt-line length %q", p.buf[:4]) } switch { case n == 0: return pktFlush, nil, nil case n == 1: return pktDelim, nil, nil case n == 2: return pktResponseEnd, nil, nil case n < 4 || n > maxPktLen: return 0, nil, fmt.Errorf("bad pkt-line length %d", n) } payload := p.buf[:n-4] if _, err := io.ReadFull(p.r, payload); err != nil { return 0, nil, err } return pktData, payload, nil } // skips acknowledgments/shallow-info; the reader yields band 1 of the // packfile section, band 3 becomes an error func packfileSection(p *pktReader) (io.Reader, error) { for { kind, line, err := p.next() if err != nil { return nil, err } if kind == pktFlush || kind == pktResponseEnd { return nil, errors.New("fetch response has no packfile section") } if msg, ok := bytes.CutPrefix(line, []byte("ERR ")); ok { return nil, fmt.Errorf("remote: %s", bytes.TrimSpace(msg)) } if string(line) == "packfile\n" { return &sideband{p: p}, nil } } } type sideband struct { p *pktReader data []byte done bool } func (s *sideband) Read(b []byte) (int, error) { for len(s.data) == 0 { if s.done { return 0, io.EOF } kind, line, err := s.p.next() if err != nil { return 0, err } if kind != pktData { s.done = true continue } if len(line) == 0 { return 0, errors.New("empty sideband pkt-line") } switch line[0] { case 1: s.data = line[1:] case 2: case 3: return 0, fmt.Errorf("remote: %s", bytes.TrimSpace(line[1:])) default: return 0, fmt.Errorf("bad sideband %d", line[0]) } } n := copy(b, s.data) s.data = s.data[n:] return n, nil } // errors with ErrTooLarge once more than n bytes come through type capReader struct { r io.Reader n int64 } func (c *capReader) Read(b []byte) (int, error) { n, err := c.r.Read(b) c.n -= int64(n) if c.n < 0 { return 0, ErrTooLarge } return n, err }