diff --git a/plumbing/protocol/packp/updreq_decode.go b/plumbing/protocol/packp/updreq_decode.go index 076de545f..412e39624 100644 --- a/plumbing/protocol/packp/updreq_decode.go +++ b/plumbing/protocol/packp/updreq_decode.go @@ -9,6 +9,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/pktline" + "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" ) var ( @@ -74,6 +75,11 @@ func errMalformedCommand(err error) error { "malformed command: %s", err.Error())) } +func errInvalidPushOption(err error) error { + return errMalformedRequest(fmt.Sprintf( + "invalid push option: %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 @@ -100,6 +106,7 @@ func (d *updReqDecoder) Decode(req *ReferenceUpdateRequest) error { d.decodeShallow, d.decodeCommandAndCapabilities, d.decodeCommands, + d.decodePushOptions, d.setPackfile, req.validate, } @@ -201,6 +208,35 @@ func (d *updReqDecoder) setPackfile() error { return nil } +func (d *updReqDecoder) decodePushOptions() error { + if !d.req.Capabilities.Supports(capability.PushOptions) { + return nil + } + + if ok := d.s.Scan(); !ok { + return d.s.Err() + } + + for { + b := d.s.Bytes() + + if bytes.Equal(b, pktline.Flush) { + return nil + } + + o, err := parsePushOption(b) + if err != nil { + return err + } + + d.req.Options = append(d.req.Options, o) + + if ok := d.s.Scan(); !ok { + return d.s.Err() + } + } +} + func parseCommand(b []byte) (*Command, error) { if len(b) < minCommandLength { return nil, errInvalidCommandLineLength(len(b)) @@ -247,3 +283,17 @@ func (d *updReqDecoder) scanErrorOr(origErr error) error { return origErr } + +func parsePushOption(b []byte) (*Option, error) { + i := bytes.IndexByte(b, '=') + if i == -1 { + return &Option{Key: string(b)}, nil + } + if i == 0 { + return nil, errInvalidPushOption(errors.New("empty option key")) + } + if i == len(b)-1 { + return &Option{Key: string(b[:i])}, nil + } + return &Option{Key: string(b[:i]), Value: string(b[i+1:])}, nil +} diff --git a/plumbing/protocol/packp/updreq_decode_test.go b/plumbing/protocol/packp/updreq_decode_test.go index bdcbdf503..781f12a91 100644 --- a/plumbing/protocol/packp/updreq_decode_test.go +++ b/plumbing/protocol/packp/updreq_decode_test.go @@ -260,6 +260,50 @@ func (s *UpdReqDecodeSuite) TestWithPackfile(c *C) { s.testDecodeOkRaw(c, expected, buf.Bytes()) } +func (s *UpdReqDecodeSuite) TestPushOptions(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + expected := NewReferenceUpdateRequest() + expected.Commands = []*Command{ + {Name: plumbing.ReferenceName("myref1"), Old: hash1, New: hash2}, + } + expected.Capabilities.Add("push-options") + expected.Options = append(expected.Options, &Option{Key: "key1", Value: "value1"}) + expected.Options = append(expected.Options, &Option{Key: "key2", Value: "value2"}) + expected.Options = append(expected.Options, &Option{Key: "key3", Value: ""}) + expected.Options = append(expected.Options, &Option{Key: "key4", Value: ""}) + expected.Packfile = io.NopCloser(bytes.NewReader([]byte{})) + + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00push-options", + pktline.FlushString, + "key1=value1", + "key2=value2", + "key3", + "key4=", + pktline.FlushString, + } + + s.testDecodeOkExpected(c, expected, payloads) +} + +func (s *UpdReqDecodeSuite) TestInvalidPushOptionMissingKey(c *C) { + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00push-options", + pktline.FlushString, + "=value", + pktline.FlushString, + } + + var buf bytes.Buffer + e := pktline.NewEncoder(&buf) + err := e.EncodeString(payloads...) + c.Assert(err, IsNil) + + s.testDecoderErrorMatches(c, &buf, "^malformed request: invalid push option: empty option key") +} + func (s *UpdReqDecodeSuite) testDecoderErrorMatches(c *C, input io.Reader, pattern string) { r := NewReferenceUpdateRequest() c.Assert(r.Decode(input), ErrorMatches, pattern) diff --git a/plumbing/transport/server/server.go b/plumbing/transport/server/server.go index cf5d6f43f..43c59534d 100644 --- a/plumbing/transport/server/server.go +++ b/plumbing/transport/server/server.go @@ -367,6 +367,10 @@ func (*rpSession) setSupportedCapabilities(c *capability.List) error { return err } + if err := c.Set(capability.PushOptions); err != nil { + return err + } + return c.Set(capability.ReportStatus) }