-
Notifications
You must be signed in to change notification settings - Fork 753
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #936 from aymanbagabas/more-packp
Respect pktline error-line errors
- Loading branch information
Showing
9 changed files
with
403 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package pktline | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"strings" | ||
) | ||
|
||
var ( | ||
// ErrInvalidErrorLine is returned by Decode when the packet line is not an | ||
// error line. | ||
ErrInvalidErrorLine = errors.New("expected an error-line") | ||
|
||
errPrefix = []byte("ERR ") | ||
) | ||
|
||
// ErrorLine is a packet line that contains an error message. | ||
// Once this packet is sent by client or server, the data transfer process is | ||
// terminated. | ||
// See https://git-scm.com/docs/pack-protocol#_pkt_line_format | ||
type ErrorLine struct { | ||
Text string | ||
} | ||
|
||
// Error implements the error interface. | ||
func (e *ErrorLine) Error() string { | ||
return e.Text | ||
} | ||
|
||
// Encode encodes the ErrorLine into a packet line. | ||
func (e *ErrorLine) Encode(w io.Writer) error { | ||
p := NewEncoder(w) | ||
return p.Encodef("%s%s\n", string(errPrefix), e.Text) | ||
} | ||
|
||
// Decode decodes a packet line into an ErrorLine. | ||
func (e *ErrorLine) Decode(r io.Reader) error { | ||
s := NewScanner(r) | ||
if !s.Scan() { | ||
return s.Err() | ||
} | ||
|
||
line := s.Bytes() | ||
if !bytes.HasPrefix(line, errPrefix) { | ||
return ErrInvalidErrorLine | ||
} | ||
|
||
e.Text = strings.TrimSpace(string(line[4:])) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package pktline | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"testing" | ||
) | ||
|
||
func TestEncodeEmptyErrorLine(t *testing.T) { | ||
e := &ErrorLine{} | ||
err := e.Encode(io.Discard) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestEncodeErrorLine(t *testing.T) { | ||
e := &ErrorLine{ | ||
Text: "something", | ||
} | ||
var buf bytes.Buffer | ||
err := e.Encode(&buf) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if buf.String() != "0012ERR something\n" { | ||
t.Fatalf("unexpected encoded error line: %q", buf.String()) | ||
} | ||
} | ||
|
||
func TestDecodeEmptyErrorLine(t *testing.T) { | ||
var buf bytes.Buffer | ||
e := &ErrorLine{} | ||
err := e.Decode(&buf) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if e.Text != "" { | ||
t.Fatalf("unexpected error line: %q", e.Text) | ||
} | ||
} | ||
|
||
func TestDecodeErrorLine(t *testing.T) { | ||
var buf bytes.Buffer | ||
buf.WriteString("000eERR foobar") | ||
var e *ErrorLine | ||
err := e.Decode(&buf) | ||
if !errors.As(err, &e) { | ||
t.Fatalf("expected error line, got: %T: %v", err, err) | ||
} | ||
if e.Text != "foobar" { | ||
t.Fatalf("unexpected error line: %q", e.Text) | ||
} | ||
} | ||
|
||
func TestDecodeErrorLineLn(t *testing.T) { | ||
var buf bytes.Buffer | ||
buf.WriteString("000fERR foobar\n") | ||
var e *ErrorLine | ||
err := e.Decode(&buf) | ||
if !errors.As(err, &e) { | ||
t.Fatalf("expected error line, got: %T: %v", err, err) | ||
} | ||
if e.Text != "foobar" { | ||
t.Fatalf("unexpected error line: %q", e.Text) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package packp | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/go-git/go-git/v5/plumbing/format/pktline" | ||
) | ||
|
||
var ( | ||
// ErrInvalidGitProtoRequest is returned by Decode if the input is not a | ||
// valid git protocol request. | ||
ErrInvalidGitProtoRequest = fmt.Errorf("invalid git protocol request") | ||
) | ||
|
||
// GitProtoRequest is a command request for the git protocol. | ||
// It is used to send the command, endpoint, and extra parameters to the | ||
// remote. | ||
// See https://git-scm.com/docs/pack-protocol#_git_transport | ||
type GitProtoRequest struct { | ||
RequestCommand string | ||
Pathname string | ||
|
||
// Optional | ||
Host string | ||
|
||
// Optional | ||
ExtraParams []string | ||
} | ||
|
||
// validate validates the request. | ||
func (g *GitProtoRequest) validate() error { | ||
if g.RequestCommand == "" { | ||
return fmt.Errorf("%w: empty request command", ErrInvalidGitProtoRequest) | ||
} | ||
|
||
if g.Pathname == "" { | ||
return fmt.Errorf("%w: empty pathname", ErrInvalidGitProtoRequest) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Encode encodes the request into the writer. | ||
func (g *GitProtoRequest) Encode(w io.Writer) error { | ||
if w == nil { | ||
return ErrNilWriter | ||
} | ||
|
||
if err := g.validate(); err != nil { | ||
return err | ||
} | ||
|
||
p := pktline.NewEncoder(w) | ||
req := fmt.Sprintf("%s %s\x00", g.RequestCommand, g.Pathname) | ||
if host := g.Host; host != "" { | ||
req += fmt.Sprintf("host=%s\x00", host) | ||
} | ||
|
||
if len(g.ExtraParams) > 0 { | ||
req += "\x00" | ||
for _, param := range g.ExtraParams { | ||
req += param + "\x00" | ||
} | ||
} | ||
|
||
if err := p.Encode([]byte(req)); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Decode decodes the request from the reader. | ||
func (g *GitProtoRequest) Decode(r io.Reader) error { | ||
s := pktline.NewScanner(r) | ||
if !s.Scan() { | ||
err := s.Err() | ||
if err == nil { | ||
return ErrInvalidGitProtoRequest | ||
} | ||
return err | ||
} | ||
|
||
line := string(s.Bytes()) | ||
if len(line) == 0 { | ||
return io.EOF | ||
} | ||
|
||
if line[len(line)-1] != 0 { | ||
return fmt.Errorf("%w: missing null terminator", ErrInvalidGitProtoRequest) | ||
} | ||
|
||
parts := strings.SplitN(line, " ", 2) | ||
if len(parts) != 2 { | ||
return fmt.Errorf("%w: short request", ErrInvalidGitProtoRequest) | ||
} | ||
|
||
g.RequestCommand = parts[0] | ||
params := strings.Split(parts[1], string(null)) | ||
if len(params) < 1 { | ||
return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest) | ||
} | ||
|
||
g.Pathname = params[0] | ||
if len(params) > 1 { | ||
g.Host = strings.TrimPrefix(params[1], "host=") | ||
} | ||
|
||
if len(params) > 2 { | ||
for _, param := range params[2:] { | ||
if param != "" { | ||
g.ExtraParams = append(g.ExtraParams, param) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package packp | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
) | ||
|
||
func TestEncodeEmptyGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
var p GitProtoRequest | ||
err := p.Encode(&buf) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} | ||
|
||
func TestEncodeGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
p := GitProtoRequest{ | ||
RequestCommand: "command", | ||
Pathname: "pathname", | ||
Host: "host", | ||
ExtraParams: []string{"param1", "param2"}, | ||
} | ||
err := p.Encode(&buf) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expected := "002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00" | ||
if buf.String() != expected { | ||
t.Fatalf("expected %q, got %q", expected, buf.String()) | ||
} | ||
} | ||
|
||
func TestEncodeInvalidGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
p := GitProtoRequest{ | ||
RequestCommand: "command", | ||
} | ||
err := p.Encode(&buf) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} | ||
|
||
func TestDecodeEmptyGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
var p GitProtoRequest | ||
err := p.Decode(&buf) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} | ||
|
||
func TestDecodeGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
buf.WriteString("002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00") | ||
var p GitProtoRequest | ||
err := p.Decode(&buf) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expected := GitProtoRequest{ | ||
RequestCommand: "command", | ||
Pathname: "pathname", | ||
Host: "host", | ||
ExtraParams: []string{"param1", "param2"}, | ||
} | ||
if p.RequestCommand != expected.RequestCommand { | ||
t.Fatalf("expected %q, got %q", expected.RequestCommand, p.RequestCommand) | ||
} | ||
if p.Pathname != expected.Pathname { | ||
t.Fatalf("expected %q, got %q", expected.Pathname, p.Pathname) | ||
} | ||
if p.Host != expected.Host { | ||
t.Fatalf("expected %q, got %q", expected.Host, p.Host) | ||
} | ||
if len(p.ExtraParams) != len(expected.ExtraParams) { | ||
t.Fatalf("expected %d, got %d", len(expected.ExtraParams), len(p.ExtraParams)) | ||
} | ||
} | ||
|
||
func TestDecodeInvalidGitProtoRequest(t *testing.T) { | ||
var buf bytes.Buffer | ||
buf.WriteString("0026command \x00host=host\x00\x00param1\x00param2") | ||
var p GitProtoRequest | ||
err := p.Decode(&buf) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} | ||
|
||
func TestValidateEmptyGitProtoRequest(t *testing.T) { | ||
var p GitProtoRequest | ||
err := p.validate() | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} |
Oops, something went wrong.