123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- 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
- }
|