Imported Upstream version 2.5.1
[scm/test.git] / git / pkt_line.go
1 package git
2
3 import (
4         "bufio"
5         "errors"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "strconv"
10         "strings"
11 )
12
13 const (
14         // MaxPacketLength is the maximum total (header+payload) length
15         // encode-able within one packet using Git's pkt-line protocol.
16         MaxPacketLength = 65516
17 )
18
19 type pktline struct {
20         r *bufio.Reader
21         w *bufio.Writer
22 }
23
24 func newPktline(r io.Reader, w io.Writer) *pktline {
25         return &pktline{
26                 r: bufio.NewReader(r),
27                 w: bufio.NewWriter(w),
28         }
29 }
30
31 // readPacket reads a single packet entirely and returns the data encoded within
32 // it. Errors can occur in several cases, as described below.
33 //
34 // 1) If no data was present in the reader, and no more data could be read (the
35 //    pipe was closed, etc) than an io.EOF will be returned.
36 // 2) If there was some data to be read, but the pipe or reader was closed
37 //    before an entire packet (or header) could be ingested, an
38 //    io.ErrShortBuffer error will be returned.
39 // 3) If there was a valid header, but no body associated with the packet, an
40 //    "Invalid packet length." error will be returned.
41 // 4) If the data in the header could not be parsed as a hexadecimal length in
42 //    the Git pktline format, the parse error will be returned.
43 //
44 // If none of the above cases fit the state of the data on the wire, the packet
45 // is returned along with a nil error.
46 func (p *pktline) readPacket() ([]byte, error) {
47         var pktLenHex [4]byte
48         if n, err := io.ReadFull(p.r, pktLenHex[:]); err != nil {
49                 return nil, err
50         } else if n != 4 {
51                 return nil, io.ErrShortBuffer
52         }
53
54         pktLen, err := strconv.ParseInt(string(pktLenHex[:]), 16, 0)
55         if err != nil {
56                 return nil, err
57         }
58
59         // pktLen==0: flush packet
60         if pktLen == 0 {
61                 return nil, nil
62         }
63         if pktLen <= 4 {
64                 return nil, errors.New("Invalid packet length.")
65         }
66
67         payload, err := ioutil.ReadAll(io.LimitReader(p.r, pktLen-4))
68         return payload, err
69 }
70
71 // readPacketText follows identical semantics to the `readPacket()` function,
72 // but additionally removes the trailing `\n` LF from the end of the packet, if
73 // present.
74 func (p *pktline) readPacketText() (string, error) {
75         data, err := p.readPacket()
76         return strings.TrimSuffix(string(data), "\n"), err
77 }
78
79 // readPacketList reads as many packets as possible using the `readPacketText`
80 // function before encountering a flush packet. It returns a slice of all the
81 // packets it read, or an error if one was encountered.
82 func (p *pktline) readPacketList() ([]string, error) {
83         var list []string
84         for {
85                 data, err := p.readPacketText()
86                 if err != nil {
87                         return nil, err
88                 }
89
90                 if len(data) == 0 {
91                         break
92                 }
93
94                 list = append(list, data)
95         }
96
97         return list, nil
98 }
99
100 // writePacket writes the given data in "data" to the underlying data stream
101 // using Git's `pkt-line` format.
102 //
103 // If the data was longer than MaxPacketLength, an error will be returned. If
104 // there was any error encountered while writing any component of the packet
105 // (hdr, payload), it will be returned.
106 //
107 // NB: writePacket does _not_ flush the underlying buffered writer. See instead:
108 // `writeFlush()`.
109 func (p *pktline) writePacket(data []byte) error {
110         if len(data) > MaxPacketLength {
111                 return errors.New("Packet length exceeds maximal length")
112         }
113
114         if _, err := p.w.WriteString(fmt.Sprintf("%04x", len(data)+4)); err != nil {
115                 return err
116         }
117
118         if _, err := p.w.Write(data); err != nil {
119                 return err
120         }
121
122         return nil
123 }
124
125 // writeFlush writes the terminating "flush" packet and then flushes the
126 // underlying buffered writer.
127 //
128 // If any error was encountered along the way, it will be returned immediately
129 func (p *pktline) writeFlush() error {
130         if _, err := p.w.WriteString(fmt.Sprintf("%04x", 0)); err != nil {
131                 return err
132         }
133
134         if err := p.w.Flush(); err != nil {
135                 return err
136         }
137
138         return nil
139 }
140
141 // writePacketText follows the same semantics as `writePacket`, but appends a
142 // trailing "\n" LF character to the end of the data.
143 func (p *pktline) writePacketText(data string) error {
144         return p.writePacket([]byte(data + "\n"))
145 }
146
147 // writePacketList writes a slice of strings using the semantics of
148 // and then writes a terminating flush sequence afterwords.
149 //
150 // If any error was encountered, it will be returned immediately.
151 func (p *pktline) writePacketList(list []string) error {
152         for _, i := range list {
153                 if err := p.writePacketText(i); err != nil {
154                         return err
155                 }
156         }
157
158         return p.writeFlush()
159 }