blob: d9be1d9d8affd8f3750cf6d81365c7e217f0f921 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitfs
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// A pktLineReader reads Git pkt-line-formatted packets.
//
// Each n-byte packet is preceded by a 4-digit hexadecimal length
// encoding n+4 (the length counts its own bytes), like "0006a\n" for "a\n".
//
// A packet starting with 0000 is a so-called flush packet.
// A packet starting with 0001 is a delimiting marker,
// which usually marks the end of a sequence in the stream.
//
// See https://git-scm.com/docs/protocol-common#_pkt_line_format
// for the official documentation, although it fails to mention the 0001 packets.
type pktLineReader struct {
b *bufio.Reader
size [4]byte
}
// newPktLineReader returns a new pktLineReader reading from r.
func newPktLineReader(r io.Reader) *pktLineReader {
return &pktLineReader{b: bufio.NewReader(r)}
}
// Next returns the payload of the next packet from the stream.
// If the next packet is a flush packet (length 0000), Next returns nil, io.EOF.
// If the next packet is a delimiter packet (length 0001), Next returns nil, nil.
// If the data stream has ended, Next returns nil, io.ErrUnexpectedEOF.
func (r *pktLineReader) Next() ([]byte, error) {
_, err := io.ReadFull(r.b, r.size[:])
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return nil, err
}
n, err := strconv.ParseUint(string(r.size[:]), 16, 0)
if err != nil || n == 2 || n == 3 {
return nil, fmt.Errorf("malformed pkt-line")
}
if n == 1 {
return nil, nil // delimiter
}
if n == 0 {
return nil, io.EOF
}
buf := make([]byte, n-4)
_, err = io.ReadFull(r.b, buf)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return nil, err
}
return buf, nil
}
// Lines reads packets from r until a flush packet.
// It returns a string for each packet, with any trailing newline trimmed.
func (r *pktLineReader) Lines() ([]string, error) {
var lines []string
for {
line, err := r.Next()
if err != nil {
if err == io.EOF {
err = nil
}
return lines, err
}
lines = append(lines, strings.TrimSuffix(string(line), "\n"))
}
}
// A pktLineWriter writes Git pkt-line-formatted packets.
// See pktLineReader for a description of the packet format.
type pktLineWriter struct {
b *bufio.Writer
size [4]byte
}
// newPktLineWriter returns a new pktLineWriter writing to w.
func newPktLineWriter(w io.Writer) *pktLineWriter {
return &pktLineWriter{b: bufio.NewWriter(w)}
}
// writeSize writes a four-digit hexadecimal length packet for n.
// Typically n is len(data)+4.
func (w *pktLineWriter) writeSize(n int) {
hex := "0123456789abcdef"
w.size[0] = hex[n>>12]
w.size[1] = hex[(n>>8)&0xf]
w.size[2] = hex[(n>>4)&0xf]
w.size[3] = hex[(n>>0)&0xf]
w.b.Write(w.size[:])
}
// Write writes b as a single packet.
func (w *pktLineWriter) Write(b []byte) (int, error) {
n := len(b)
if n+4 > 0xffff {
return 0, fmt.Errorf("write too large")
}
w.writeSize(n + 4)
w.b.Write(b)
return n, nil
}
// WriteString writes s as a single packet.
func (w *pktLineWriter) WriteString(s string) (int, error) {
n := len(s)
if n+4 > 0xffff {
return 0, fmt.Errorf("write too large")
}
w.writeSize(n + 4)
w.b.WriteString(s)
return n, nil
}
// Close writes a terminating flush packet
// and flushes buffered data to the underlying writer.
func (w *pktLineWriter) Close() error {
w.b.WriteString("0000")
w.b.Flush()
return nil
}
// Delim writes a delimiter packet.
func (w *pktLineWriter) Delim() {
w.b.WriteString("0001")
}
// cut looks for sep in s.
// If sep is present, cut returns the text before and after sep, with ok = true.
// If sep is missing, cut returns s, "", false.
func cut(s, sep string) (before, after string, ok bool) {
if i := strings.Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, "", false
}