1package packp 2 3import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 10 "github.com/go-git/go-git/v5/plumbing" 11 "github.com/go-git/go-git/v5/plumbing/format/pktline" 12) 13 14const ackLineLen = 44 15 16// ServerResponse object acknowledgement from upload-pack service 17type ServerResponse struct { 18 ACKs []plumbing.Hash 19} 20 21// Decode decodes the response into the struct, isMultiACK should be true, if 22// the request was done with multi_ack or multi_ack_detailed capabilities. 23func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error { 24 // TODO: implement support for multi_ack or multi_ack_detailed responses 25 if isMultiACK { 26 return errors.New("multi_ack and multi_ack_detailed are not supported") 27 } 28 29 s := pktline.NewScanner(reader) 30 31 for s.Scan() { 32 line := s.Bytes() 33 34 if err := r.decodeLine(line); err != nil { 35 return err 36 } 37 38 // we need to detect when the end of a response header and the beginning 39 // of a packfile header happened, some requests to the git daemon 40 // produces a duplicate ACK header even when multi_ack is not supported. 41 stop, err := r.stopReading(reader) 42 if err != nil { 43 return err 44 } 45 46 if stop { 47 break 48 } 49 } 50 51 return s.Err() 52} 53 54// stopReading detects when a valid command such as ACK or NAK is found to be 55// read in the buffer without moving the read pointer. 56func (r *ServerResponse) stopReading(reader *bufio.Reader) (bool, error) { 57 ahead, err := reader.Peek(7) 58 if err == io.EOF { 59 return true, nil 60 } 61 62 if err != nil { 63 return false, err 64 } 65 66 if len(ahead) > 4 && r.isValidCommand(ahead[0:3]) { 67 return false, nil 68 } 69 70 if len(ahead) == 7 && r.isValidCommand(ahead[4:]) { 71 return false, nil 72 } 73 74 return true, nil 75} 76 77func (r *ServerResponse) isValidCommand(b []byte) bool { 78 commands := [][]byte{ack, nak} 79 for _, c := range commands { 80 if bytes.Equal(b, c) { 81 return true 82 } 83 } 84 85 return false 86} 87 88func (r *ServerResponse) decodeLine(line []byte) error { 89 if len(line) == 0 { 90 return fmt.Errorf("unexpected flush") 91 } 92 93 if bytes.Equal(line[0:3], ack) { 94 return r.decodeACKLine(line) 95 } 96 97 if bytes.Equal(line[0:3], nak) { 98 return nil 99 } 100 101 return fmt.Errorf("unexpected content %q", string(line)) 102} 103 104func (r *ServerResponse) decodeACKLine(line []byte) error { 105 if len(line) < ackLineLen { 106 return fmt.Errorf("malformed ACK %q", line) 107 } 108 109 sp := bytes.Index(line, []byte(" ")) 110 h := plumbing.NewHash(string(line[sp+1 : sp+41])) 111 r.ACKs = append(r.ACKs, h) 112 return nil 113} 114 115// Encode encodes the ServerResponse into a writer. 116func (r *ServerResponse) Encode(w io.Writer) error { 117 if len(r.ACKs) > 1 { 118 return errors.New("multi_ack and multi_ack_detailed are not supported") 119 } 120 121 e := pktline.NewEncoder(w) 122 if len(r.ACKs) == 0 { 123 return e.Encodef("%s\n", nak) 124 } 125 126 return e.Encodef("%s %s\n", ack, r.ACKs[0].String()) 127} 128