| // 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 |
| } |