| 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()) |
| } |