You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
5.0 KiB
Go
251 lines
5.0 KiB
Go
package packp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
|
|
)
|
|
|
|
var (
|
|
shallowLineLength = len(shallow) + hashSize
|
|
minCommandLength = hashSize*2 + 2 + 1
|
|
minCommandAndCapsLenth = minCommandLength + 1
|
|
)
|
|
|
|
var (
|
|
ErrEmpty = errors.New("empty update-request message")
|
|
errNoCommands = errors.New("unexpected EOF before any command")
|
|
errMissingCapabilitiesDelimiter = errors.New("capabilities delimiter not found")
|
|
)
|
|
|
|
func errMalformedRequest(reason string) error {
|
|
return fmt.Errorf("malformed request: %s", reason)
|
|
}
|
|
|
|
func errInvalidHashSize(got int) error {
|
|
return fmt.Errorf("invalid hash size: expected %d, got %d",
|
|
hashSize, got)
|
|
}
|
|
|
|
func errInvalidHash(err error) error {
|
|
return fmt.Errorf("invalid hash: %s", err.Error())
|
|
}
|
|
|
|
func errInvalidShallowLineLength(got int) error {
|
|
return errMalformedRequest(fmt.Sprintf(
|
|
"invalid shallow line length: expected %d, got %d",
|
|
shallowLineLength, got))
|
|
}
|
|
|
|
func errInvalidCommandCapabilitiesLineLength(got int) error {
|
|
return errMalformedRequest(fmt.Sprintf(
|
|
"invalid command and capabilities line length: expected at least %d, got %d",
|
|
minCommandAndCapsLenth, got))
|
|
}
|
|
|
|
func errInvalidCommandLineLength(got int) error {
|
|
return errMalformedRequest(fmt.Sprintf(
|
|
"invalid command line length: expected at least %d, got %d",
|
|
minCommandLength, got))
|
|
}
|
|
|
|
func errInvalidShallowObjId(err error) error {
|
|
return errMalformedRequest(
|
|
fmt.Sprintf("invalid shallow object id: %s", err.Error()))
|
|
}
|
|
|
|
func errInvalidOldObjId(err error) error {
|
|
return errMalformedRequest(
|
|
fmt.Sprintf("invalid old object id: %s", err.Error()))
|
|
}
|
|
|
|
func errInvalidNewObjId(err error) error {
|
|
return errMalformedRequest(
|
|
fmt.Sprintf("invalid new object id: %s", err.Error()))
|
|
}
|
|
|
|
func errMalformedCommand(err error) error {
|
|
return errMalformedRequest(fmt.Sprintf(
|
|
"malformed command: %s", err.Error()))
|
|
}
|
|
|
|
// Decode reads the next update-request message form the reader and wr
|
|
func (req *ReferenceUpdateRequest) Decode(r io.Reader) error {
|
|
var rc io.ReadCloser
|
|
var ok bool
|
|
rc, ok = r.(io.ReadCloser)
|
|
if !ok {
|
|
rc = ioutil.NopCloser(r)
|
|
}
|
|
|
|
d := &updReqDecoder{r: rc, s: pktline.NewScanner(r)}
|
|
return d.Decode(req)
|
|
}
|
|
|
|
type updReqDecoder struct {
|
|
r io.ReadCloser
|
|
s *pktline.Scanner
|
|
req *ReferenceUpdateRequest
|
|
}
|
|
|
|
func (d *updReqDecoder) Decode(req *ReferenceUpdateRequest) error {
|
|
d.req = req
|
|
funcs := []func() error{
|
|
d.scanLine,
|
|
d.decodeShallow,
|
|
d.decodeCommandAndCapabilities,
|
|
d.decodeCommands,
|
|
d.setPackfile,
|
|
req.validate,
|
|
}
|
|
|
|
for _, f := range funcs {
|
|
if err := f(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *updReqDecoder) scanLine() error {
|
|
if ok := d.s.Scan(); !ok {
|
|
return d.scanErrorOr(ErrEmpty)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *updReqDecoder) decodeShallow() error {
|
|
b := d.s.Bytes()
|
|
|
|
if !bytes.HasPrefix(b, shallowNoSp) {
|
|
return nil
|
|
}
|
|
|
|
if len(b) != shallowLineLength {
|
|
return errInvalidShallowLineLength(len(b))
|
|
}
|
|
|
|
h, err := parseHash(string(b[len(shallow):]))
|
|
if err != nil {
|
|
return errInvalidShallowObjId(err)
|
|
}
|
|
|
|
if ok := d.s.Scan(); !ok {
|
|
return d.scanErrorOr(errNoCommands)
|
|
}
|
|
|
|
d.req.Shallow = &h
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *updReqDecoder) decodeCommands() error {
|
|
for {
|
|
b := d.s.Bytes()
|
|
if bytes.Equal(b, pktline.Flush) {
|
|
return nil
|
|
}
|
|
|
|
c, err := parseCommand(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.req.Commands = append(d.req.Commands, c)
|
|
|
|
if ok := d.s.Scan(); !ok {
|
|
return d.s.Err()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *updReqDecoder) decodeCommandAndCapabilities() error {
|
|
b := d.s.Bytes()
|
|
i := bytes.IndexByte(b, 0)
|
|
if i == -1 {
|
|
return errMissingCapabilitiesDelimiter
|
|
}
|
|
|
|
if len(b) < minCommandAndCapsLenth {
|
|
return errInvalidCommandCapabilitiesLineLength(len(b))
|
|
}
|
|
|
|
cmd, err := parseCommand(b[:i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.req.Commands = append(d.req.Commands, cmd)
|
|
|
|
if err := d.req.Capabilities.Decode(b[i+1:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := d.scanLine(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *updReqDecoder) setPackfile() error {
|
|
d.req.Packfile = d.r
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseCommand(b []byte) (*Command, error) {
|
|
if len(b) < minCommandLength {
|
|
return nil, errInvalidCommandLineLength(len(b))
|
|
}
|
|
|
|
var (
|
|
os, ns string
|
|
n plumbing.ReferenceName
|
|
)
|
|
if _, err := fmt.Sscanf(string(b), "%s %s %s", &os, &ns, &n); err != nil {
|
|
return nil, errMalformedCommand(err)
|
|
}
|
|
|
|
oh, err := parseHash(os)
|
|
if err != nil {
|
|
return nil, errInvalidOldObjId(err)
|
|
}
|
|
|
|
nh, err := parseHash(ns)
|
|
if err != nil {
|
|
return nil, errInvalidNewObjId(err)
|
|
}
|
|
|
|
return &Command{Old: oh, New: nh, Name: plumbing.ReferenceName(n)}, nil
|
|
}
|
|
|
|
func parseHash(s string) (plumbing.Hash, error) {
|
|
if len(s) != hashSize {
|
|
return plumbing.ZeroHash, errInvalidHashSize(len(s))
|
|
}
|
|
|
|
if _, err := hex.DecodeString(s); err != nil {
|
|
return plumbing.ZeroHash, errInvalidHash(err)
|
|
}
|
|
|
|
h := plumbing.NewHash(s)
|
|
return h, nil
|
|
}
|
|
|
|
func (d *updReqDecoder) scanErrorOr(origErr error) error {
|
|
if err := d.s.Err(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return origErr
|
|
}
|