ref: f84c576b415dc35c104f757959f7a9e4f88e14ee
pkg/go-git-http/pktparser.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
package githttp import ( "encoding/hex" "errors" "fmt" ) // pktLineParser is a parser for git pkt-line Format, // as documented in https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt. // A zero value of pktLineParser is valid to use as a parser in ready state. // Output should be read from Lines and Error after Step returns finished true. // pktLineParser reads until a terminating "0000" flush-pkt. It's good for a single use only. type pktLineParser struct { // Lines contains all pkt-lines. Lines []string // Error contains the first error encountered while parsing, or nil otherwise. Error error // Internal state machine. state state next int // next is the number of bytes that need to be written to buf before its contents should be processed by the state machine. buf []byte } // Feed accumulates and parses data. // It will return early if it reaches end of pkt-line data (indicated by a flush-pkt "0000"), // or if it encounters a parsing error. // It must not be called when state is done. // When done, all of pkt-lines will be available in Lines, and Error will be set if any error occurred. func (p *pktLineParser) Feed(data []byte) { for { // If not enough data to reach next state, append it to buf and return. if len(data) < p.next { p.buf = append(p.buf, data...) p.next -= len(data) return } // There's enough data to reach next state. Take from data only what's needed. b := data[:p.next] data = data[p.next:] p.buf = append(p.buf, b...) p.next = 0 // Take a step to next state. err := p.step() if err != nil { p.state = done p.Error = err return } // Break out once reached done state. if p.state == done { return } } } const ( // pkt-len = 4*(HEXDIG) pktLenSize = 4 ) type state uint8 const ( ready state = iota readingLen readingPayload done ) // step moves the state machine to the next state. // buf must contain all the data ready for consumption for current state. // It must not be called when state is done. func (p *pktLineParser) step() error { switch p.state { case ready: p.state = readingLen p.next = pktLenSize return nil case readingLen: // len(p.buf) is 4. pktLen, err := parsePktLen(p.buf) if err != nil { return err } switch { case pktLen == 0: p.state = done p.next = 0 p.buf = nil return nil default: p.state = readingPayload p.next = pktLen - pktLenSize // (pkt-len - 4)*(OCTET) p.buf = p.buf[:0] return nil } case readingPayload: p.state = readingLen p.next = pktLenSize p.Lines = append(p.Lines, string(p.buf)) p.buf = p.buf[:0] return nil default: panic(fmt.Errorf("unreachable: %v", p.state)) } } // parsePktLen parses a pkt-len segment. // len(b) must be 4. func parsePktLen(b []byte) (int, error) { pktLen, err := parseHex(b) switch { case err != nil: return 0, err case 1 <= pktLen && pktLen < pktLenSize: return 0, fmt.Errorf("invalid pkt-len: %v", pktLen) case pktLen > 65524: // The maximum length of a pkt-line is 65524 bytes (65520 bytes of payload + 4 bytes of length data). return 0, fmt.Errorf("invalid pkt-len: %v", pktLen) } return int(pktLen), nil } // parseHex parses a 4-byte hex number. // len(h) must be 4. func parseHex(h []byte) (uint16, error) { var b [2]uint8 n, err := hex.Decode(b[:], h) switch { case err != nil: return 0, err case n != 2: return 0, errors.New("short output") } return uint16(b[0])<<8 | uint16(b[1]), nil } |