| package packp |
| |
| import ( |
| "bytes" |
| "encoding/hex" |
| "fmt" |
| "io" |
| "strconv" |
| "time" |
| |
| "gopkg.in/src-d/go-git.v4/plumbing" |
| "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" |
| ) |
| |
| // Decode reads the next upload-request form its input and |
| // stores it in the UploadRequest. |
| func (u *UploadRequest) Decode(r io.Reader) error { |
| d := newUlReqDecoder(r) |
| return d.Decode(u) |
| } |
| |
| type ulReqDecoder struct { |
| s *pktline.Scanner // a pkt-line scanner from the input stream |
| line []byte // current pkt-line contents, use parser.nextLine() to make it advance |
| nLine int // current pkt-line number for debugging, begins at 1 |
| err error // sticky error, use the parser.error() method to fill this out |
| data *UploadRequest // parsed data is stored here |
| } |
| |
| func newUlReqDecoder(r io.Reader) *ulReqDecoder { |
| return &ulReqDecoder{ |
| s: pktline.NewScanner(r), |
| } |
| } |
| |
| func (d *ulReqDecoder) Decode(v *UploadRequest) error { |
| d.data = v |
| |
| for state := d.decodeFirstWant; state != nil; { |
| state = state() |
| } |
| |
| return d.err |
| } |
| |
| // fills out the parser stiky error |
| func (d *ulReqDecoder) error(format string, a ...interface{}) { |
| msg := fmt.Sprintf( |
| "pkt-line %d: %s", d.nLine, |
| fmt.Sprintf(format, a...), |
| ) |
| |
| d.err = NewErrUnexpectedData(msg, d.line) |
| } |
| |
| // Reads a new pkt-line from the scanner, makes its payload available as |
| // p.line and increments p.nLine. A successful invocation returns true, |
| // otherwise, false is returned and the sticky error is filled out |
| // accordingly. Trims eols at the end of the payloads. |
| func (d *ulReqDecoder) nextLine() bool { |
| d.nLine++ |
| |
| if !d.s.Scan() { |
| if d.err = d.s.Err(); d.err != nil { |
| return false |
| } |
| |
| d.error("EOF") |
| return false |
| } |
| |
| d.line = d.s.Bytes() |
| d.line = bytes.TrimSuffix(d.line, eol) |
| |
| return true |
| } |
| |
| // Expected format: want <hash>[ capabilities] |
| func (d *ulReqDecoder) decodeFirstWant() stateFn { |
| if ok := d.nextLine(); !ok { |
| return nil |
| } |
| |
| if !bytes.HasPrefix(d.line, want) { |
| d.error("missing 'want ' prefix") |
| return nil |
| } |
| d.line = bytes.TrimPrefix(d.line, want) |
| |
| hash, ok := d.readHash() |
| if !ok { |
| return nil |
| } |
| d.data.Wants = append(d.data.Wants, hash) |
| |
| return d.decodeCaps |
| } |
| |
| func (d *ulReqDecoder) readHash() (plumbing.Hash, bool) { |
| if len(d.line) < hashSize { |
| d.err = fmt.Errorf("malformed hash: %v", d.line) |
| return plumbing.ZeroHash, false |
| } |
| |
| var hash plumbing.Hash |
| if _, err := hex.Decode(hash[:], d.line[:hashSize]); err != nil { |
| d.error("invalid hash text: %s", err) |
| return plumbing.ZeroHash, false |
| } |
| d.line = d.line[hashSize:] |
| |
| return hash, true |
| } |
| |
| // Expected format: sp cap1 sp cap2 sp cap3... |
| func (d *ulReqDecoder) decodeCaps() stateFn { |
| d.line = bytes.TrimPrefix(d.line, sp) |
| if err := d.data.Capabilities.Decode(d.line); err != nil { |
| d.error("invalid capabilities: %s", err) |
| } |
| |
| return d.decodeOtherWants |
| } |
| |
| // Expected format: want <hash> |
| func (d *ulReqDecoder) decodeOtherWants() stateFn { |
| if ok := d.nextLine(); !ok { |
| return nil |
| } |
| |
| if bytes.HasPrefix(d.line, shallow) { |
| return d.decodeShallow |
| } |
| |
| if bytes.HasPrefix(d.line, deepen) { |
| return d.decodeDeepen |
| } |
| |
| if len(d.line) == 0 { |
| return nil |
| } |
| |
| if !bytes.HasPrefix(d.line, want) { |
| d.error("unexpected payload while expecting a want: %q", d.line) |
| return nil |
| } |
| d.line = bytes.TrimPrefix(d.line, want) |
| |
| hash, ok := d.readHash() |
| if !ok { |
| return nil |
| } |
| d.data.Wants = append(d.data.Wants, hash) |
| |
| return d.decodeOtherWants |
| } |
| |
| // Expected format: shallow <hash> |
| func (d *ulReqDecoder) decodeShallow() stateFn { |
| if bytes.HasPrefix(d.line, deepen) { |
| return d.decodeDeepen |
| } |
| |
| if len(d.line) == 0 { |
| return nil |
| } |
| |
| if !bytes.HasPrefix(d.line, shallow) { |
| d.error("unexpected payload while expecting a shallow: %q", d.line) |
| return nil |
| } |
| d.line = bytes.TrimPrefix(d.line, shallow) |
| |
| hash, ok := d.readHash() |
| if !ok { |
| return nil |
| } |
| d.data.Shallows = append(d.data.Shallows, hash) |
| |
| if ok := d.nextLine(); !ok { |
| return nil |
| } |
| |
| return d.decodeShallow |
| } |
| |
| // Expected format: deepen <n> / deepen-since <ul> / deepen-not <ref> |
| func (d *ulReqDecoder) decodeDeepen() stateFn { |
| if bytes.HasPrefix(d.line, deepenCommits) { |
| return d.decodeDeepenCommits |
| } |
| |
| if bytes.HasPrefix(d.line, deepenSince) { |
| return d.decodeDeepenSince |
| } |
| |
| if bytes.HasPrefix(d.line, deepenReference) { |
| return d.decodeDeepenReference |
| } |
| |
| if len(d.line) == 0 { |
| return nil |
| } |
| |
| d.error("unexpected deepen specification: %q", d.line) |
| return nil |
| } |
| |
| func (d *ulReqDecoder) decodeDeepenCommits() stateFn { |
| d.line = bytes.TrimPrefix(d.line, deepenCommits) |
| |
| var n int |
| if n, d.err = strconv.Atoi(string(d.line)); d.err != nil { |
| return nil |
| } |
| if n < 0 { |
| d.err = fmt.Errorf("negative depth") |
| return nil |
| } |
| d.data.Depth = DepthCommits(n) |
| |
| return d.decodeFlush |
| } |
| |
| func (d *ulReqDecoder) decodeDeepenSince() stateFn { |
| d.line = bytes.TrimPrefix(d.line, deepenSince) |
| |
| var secs int64 |
| secs, d.err = strconv.ParseInt(string(d.line), 10, 64) |
| if d.err != nil { |
| return nil |
| } |
| t := time.Unix(secs, 0).UTC() |
| d.data.Depth = DepthSince(t) |
| |
| return d.decodeFlush |
| } |
| |
| func (d *ulReqDecoder) decodeDeepenReference() stateFn { |
| d.line = bytes.TrimPrefix(d.line, deepenReference) |
| |
| d.data.Depth = DepthReference(string(d.line)) |
| |
| return d.decodeFlush |
| } |
| |
| func (d *ulReqDecoder) decodeFlush() stateFn { |
| if ok := d.nextLine(); !ok { |
| return nil |
| } |
| |
| if len(d.line) != 0 { |
| d.err = fmt.Errorf("unexpected payload while expecting a flush-pkt: %q", d.line) |
| } |
| |
| return nil |
| } |