128 lines
2.7 KiB
Go
128 lines
2.7 KiB
Go
|
package packp
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
|
||
|
)
|
||
|
|
||
|
const ackLineLen = 44
|
||
|
|
||
|
// ServerResponse object acknowledgement from upload-pack service
|
||
|
type ServerResponse struct {
|
||
|
ACKs []plumbing.Hash
|
||
|
}
|
||
|
|
||
|
// Decode decodes the response into the struct, isMultiACK should be true, if
|
||
|
// the request was done with multi_ack or multi_ack_detailed capabilities.
|
||
|
func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error {
|
||
|
// TODO: implement support for multi_ack or multi_ack_detailed responses
|
||
|
if isMultiACK {
|
||
|
return errors.New("multi_ack and multi_ack_detailed are not supported")
|
||
|
}
|
||
|
|
||
|
s := pktline.NewScanner(reader)
|
||
|
|
||
|
for s.Scan() {
|
||
|
line := s.Bytes()
|
||
|
|
||
|
if err := r.decodeLine(line); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// we need to detect when the end of a response header and the beginning
|
||
|
// of a packfile header happened, some requests to the git daemon
|
||
|
// produces a duplicate ACK header even when multi_ack is not supported.
|
||
|
stop, err := r.stopReading(reader)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if stop {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return s.Err()
|
||
|
}
|
||
|
|
||
|
// stopReading detects when a valid command such as ACK or NAK is found to be
|
||
|
// read in the buffer without moving the read pointer.
|
||
|
func (r *ServerResponse) stopReading(reader *bufio.Reader) (bool, error) {
|
||
|
ahead, err := reader.Peek(7)
|
||
|
if err == io.EOF {
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
if len(ahead) > 4 && r.isValidCommand(ahead[0:3]) {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
if len(ahead) == 7 && r.isValidCommand(ahead[4:]) {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
func (r *ServerResponse) isValidCommand(b []byte) bool {
|
||
|
commands := [][]byte{ack, nak}
|
||
|
for _, c := range commands {
|
||
|
if bytes.Equal(b, c) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (r *ServerResponse) decodeLine(line []byte) error {
|
||
|
if len(line) == 0 {
|
||
|
return fmt.Errorf("unexpected flush")
|
||
|
}
|
||
|
|
||
|
if bytes.Equal(line[0:3], ack) {
|
||
|
return r.decodeACKLine(line)
|
||
|
}
|
||
|
|
||
|
if bytes.Equal(line[0:3], nak) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return fmt.Errorf("unexpected content %q", string(line))
|
||
|
}
|
||
|
|
||
|
func (r *ServerResponse) decodeACKLine(line []byte) error {
|
||
|
if len(line) < ackLineLen {
|
||
|
return fmt.Errorf("malformed ACK %q", line)
|
||
|
}
|
||
|
|
||
|
sp := bytes.Index(line, []byte(" "))
|
||
|
h := plumbing.NewHash(string(line[sp+1 : sp+41]))
|
||
|
r.ACKs = append(r.ACKs, h)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Encode encodes the ServerResponse into a writer.
|
||
|
func (r *ServerResponse) Encode(w io.Writer) error {
|
||
|
if len(r.ACKs) > 1 {
|
||
|
return errors.New("multi_ack and multi_ack_detailed are not supported")
|
||
|
}
|
||
|
|
||
|
e := pktline.NewEncoder(w)
|
||
|
if len(r.ACKs) == 0 {
|
||
|
return e.Encodef("%s\n", nak)
|
||
|
}
|
||
|
|
||
|
return e.Encodef("%s %s\n", ack, r.ACKs[0].String())
|
||
|
}
|