Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor commit signature parser #30228

Merged
merged 2 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions modules/git/commit.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ type Commit struct {
Author *Signature Author *Signature
Committer *Signature Committer *Signature
CommitMessage string CommitMessage string
Signature *CommitGPGSignature Signature *CommitSignature


Parents []ObjectID // ID strings Parents []ObjectID // ID strings
submoduleCache *ObjectCache submoduleCache *ObjectCache
} }


// CommitGPGSignature represents a git commit signature part. // CommitSignature represents a git commit signature part.
type CommitGPGSignature struct { type CommitSignature struct {
Signature string Signature string
Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
} }
Expand Down
4 changes: 2 additions & 2 deletions modules/git/commit_convert_gogit.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
) )


func convertPGPSignature(c *object.Commit) *CommitGPGSignature { func convertPGPSignature(c *object.Commit) *CommitSignature {
if c.PGPSignature == "" { if c.PGPSignature == "" {
return nil return nil
} }
Expand Down Expand Up @@ -57,7 +57,7 @@ func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
return nil return nil
} }


return &CommitGPGSignature{ return &CommitSignature{
Signature: c.PGPSignature, Signature: c.PGPSignature,
Payload: w.String(), Payload: w.String(),
} }
Expand Down
2 changes: 1 addition & 1 deletion modules/git/commit_reader.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ readLoop:
} }
} }
commit.CommitMessage = messageSB.String() commit.CommitMessage = messageSB.String()
commit.Signature = &CommitGPGSignature{ commit.Signature = &CommitSignature{
Signature: signatureSB.String(), Signature: signatureSB.String(),
Payload: payloadSB.String(), Payload: payloadSB.String(),
} }
Expand Down
10 changes: 4 additions & 6 deletions modules/git/repo_tag.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -185,17 +185,15 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {


tag.Tagger = parseSignatureFromCommitLine(ref["creator"]) tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
tag.Message = ref["contents"] tag.Message = ref["contents"]
// strip PGP signature if present in contents field
pgpStart := strings.Index(tag.Message, beginpgp) // strip any signature if present in contents field
if pgpStart >= 0 { _, tag.Message, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.Message), 0)
tag.Message = tag.Message[0:pgpStart]
}


// annotated tag with GPG signature // annotated tag with GPG signature
if tag.Type == "tag" && ref["contents:signature"] != "" { if tag.Type == "tag" && ref["contents:signature"] != "" {
payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n", payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n",
tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message)) tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message))
tag.Signature = &CommitGPGSignature{ tag.Signature = &CommitSignature{
Signature: ref["contents:signature"], Signature: ref["contents:signature"],
Payload: payload, Payload: payload,
} }
Expand Down
2 changes: 1 addition & 1 deletion modules/git/repo_tag_test.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ qbHDASXl
Type: "tag", Type: "tag",
Tagger: parseSignatureFromCommitLine("Foo Bar <[email protected]> 1565789218 +0300"), Tagger: parseSignatureFromCommitLine("Foo Bar <[email protected]> 1565789218 +0300"),
Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md",
Signature: &CommitGPGSignature{ Signature: &CommitSignature{
Signature: `-----BEGIN PGP SIGNATURE----- Signature: `-----BEGIN PGP SIGNATURE-----


aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3
Expand Down
90 changes: 52 additions & 38 deletions modules/git/tag.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@ package git
import ( import (
"bytes" "bytes"
"sort" "sort"
"strings"


"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )


const (
beginpgp = "\n-----BEGIN PGP SIGNATURE-----\n"
endpgp = "\n-----END PGP SIGNATURE-----"
)

// Tag represents a Git tag. // Tag represents a Git tag.
type Tag struct { type Tag struct {
Name string Name string
Expand All @@ -24,14 +18,44 @@ type Tag struct {
Type string Type string
Tagger *Signature Tagger *Signature
Message string Message string
Signature *CommitGPGSignature Signature *CommitSignature
} }


// Commit return the commit of the tag reference // Commit return the commit of the tag reference
func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) { func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) {
return gitRepo.getCommit(tag.Object) return gitRepo.getCommit(tag.Object)
} }


func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) {
pos := messageStart
signStart, signEnd := -1, -1
for {
eol := bytes.IndexByte(data[pos:], '\n')
if eol < 0 {
break
}
line := data[pos : pos+eol]
signType, hasPrefix := bytes.CutPrefix(line, []byte("-----BEGIN "))
signType, hasSuffix := bytes.CutSuffix(signType, []byte(" SIGNATURE-----"))
if hasPrefix && hasSuffix {
signEndBytes := append([]byte("\n-----END "), signType...)
signEndBytes = append(signEndBytes, []byte(" SIGNATURE-----")...)
signEnd = bytes.Index(data[pos:], signEndBytes)
if signEnd != -1 {
signStart = pos
signEnd = pos + signEnd + len(signEndBytes)
}
}
pos += eol + 1
}

if signStart != -1 && signEnd != -1 {
msgEnd := max(messageStart, signStart-1)
return string(data[:msgEnd]), string(data[messageStart:msgEnd]), string(data[signStart:signEnd])
}
return string(data), string(data[messageStart:]), ""
}

// Parse commit information from the (uncompressed) raw // Parse commit information from the (uncompressed) raw
// data from the commit object. // data from the commit object.
// \n\n separate headers from message // \n\n separate headers from message
Expand All @@ -40,47 +64,37 @@ func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) {
tag.ID = objectFormat.EmptyObjectID() tag.ID = objectFormat.EmptyObjectID()
tag.Object = objectFormat.EmptyObjectID() tag.Object = objectFormat.EmptyObjectID()
tag.Tagger = &Signature{} tag.Tagger = &Signature{}
// we now have the contents of the commit object. Let's investigate...
nextline := 0 pos := 0
l:
for { for {
eol := bytes.IndexByte(data[nextline:], '\n') eol := bytes.IndexByte(data[pos:], '\n')
switch { if eol == -1 {
case eol > 0: break // shouldn't happen, but could just tolerate it
line := data[nextline : nextline+eol] }
spacepos := bytes.IndexByte(line, ' ') if eol == 0 {
reftype := line[:spacepos] pos++
switch string(reftype) { break // end of headers
}
line := data[pos : pos+eol]
key, val, _ := bytes.Cut(line, []byte(" "))
switch string(key) {
case "object": case "object":
id, err := NewIDFromString(string(line[spacepos+1:])) id, err := NewIDFromString(string(val))
if err != nil { if err != nil {
return nil, err return nil, err
} }
tag.Object = id tag.Object = id
case "type": case "type":
// A commit can have one or more parents tag.Type = string(val) // A commit can have one or more parents
tag.Type = string(line[spacepos+1:])
case "tagger": case "tagger":
tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:])) tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(val))
}
nextline += eol + 1
case eol == 0:
tag.Message = string(data[nextline+1:])
break l
default:
break l
}
}
idx := strings.LastIndex(tag.Message, beginpgp)
if idx > 0 {
endSigIdx := strings.Index(tag.Message[idx:], endpgp)
if endSigIdx > 0 {
tag.Signature = &CommitGPGSignature{
Signature: tag.Message[idx+1 : idx+endSigIdx+len(endpgp)],
Payload: string(data[:bytes.LastIndex(data, []byte(beginpgp))+1]),
} }
tag.Message = tag.Message[:idx+1] pos += eol + 1
} }
payload, msg, sign := parsePayloadSignature(data, pos)
tag.Message = msg
if len(sign) > 0 {
tag.Signature = &CommitSignature{Signature: sign, Payload: payload}
} }
return tag, nil return tag, nil
} }
Expand Down
86 changes: 55 additions & 31 deletions modules/git/tag_test.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,62 +12,86 @@ import (


func Test_parseTagData(t *testing.T) { func Test_parseTagData(t *testing.T) {
testData := []struct { testData := []struct {
data []byte data string
tag Tag expected Tag
}{ }{
{data: []byte(`object 3b114ab800c6432ad42387ccf6bc8d4388a2885a {
data: `object 3b114ab800c6432ad42387ccf6bc8d4388a2885a
type commit type commit
tag 1.22.0 tag 1.22.0
tagger Lucas Michot <[email protected]> 1484491741 +0100 tagger Lucas Michot <[email protected]> 1484491741 +0100


`), tag: Tag{ `,
expected: Tag{
Name: "", Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(), ID: Sha1ObjectFormat.EmptyObjectID(),
Object: &Sha1Hash{0x3b, 0x11, 0x4a, 0xb8, 0x0, 0xc6, 0x43, 0x2a, 0xd4, 0x23, 0x87, 0xcc, 0xf6, 0xbc, 0x8d, 0x43, 0x88, 0xa2, 0x88, 0x5a}, Object: MustIDFromString("3b114ab800c6432ad42387ccf6bc8d4388a2885a"),
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "[email protected]", When: time.Unix(1484491741, 0)}, Tagger: &Signature{Name: "Lucas Michot", Email: "[email protected]", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "", Message: "",
Signature: nil, Signature: nil,
}}, },
{data: []byte(`object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc },
{
data: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc
type commit type commit
tag 1.22.1 tag 1.22.1
tagger Lucas Michot <[email protected]> 1484553735 +0100 tagger Lucas Michot <[email protected]> 1484553735 +0100


test message test message
o o


ono`), tag: Tag{ ono`,
expected: Tag{
Name: "", Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(), ID: Sha1ObjectFormat.EmptyObjectID(),
Object: &Sha1Hash{0x7c, 0xdf, 0x42, 0xc0, 0xb1, 0xcc, 0x76, 0x3a, 0xb7, 0xe4, 0xc3, 0x3c, 0x47, 0xa2, 0x4e, 0x27, 0xc6, 0x6b, 0xfc, 0xcc}, Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc"),
Type: "commit", Type: "commit",
Tagger: &Signature{Name: "Lucas Michot", Email: "[email protected]", When: time.Unix(1484553735, 0)}, Tagger: &Signature{Name: "Lucas Michot", Email: "[email protected]", When: time.Unix(1484553735, 0).In(time.FixedZone("", 3600))},
Message: "test message\no\n\nono", Message: "test message\no\n\nono",
Signature: nil, Signature: nil,
}}, },
},
{
data: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa
type commit
tag v0
tagger dummy user <[email protected]> 1484491741 +0100

dummy message
-----BEGIN SSH SIGNATURE-----
dummy signature
-----END SSH SIGNATURE-----
`,
expected: Tag{
Name: "",
ID: Sha1ObjectFormat.EmptyObjectID(),
Object: MustIDFromString("7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa"),
Type: "commit",
Tagger: &Signature{Name: "dummy user", Email: "[email protected]", When: time.Unix(1484491741, 0).In(time.FixedZone("", 3600))},
Message: "dummy message",
Signature: &CommitSignature{
Signature: `-----BEGIN SSH SIGNATURE-----
dummy signature
-----END SSH SIGNATURE-----`,
Payload: `object 7cdf42c0b1cc763ab7e4c33c47a24e27c66bfaaa
type commit
tag v0
tagger dummy user <[email protected]> 1484491741 +0100

dummy message`,
},
},
},
} }


for _, test := range testData { for _, test := range testData {
tag, err := parseTagData(Sha1ObjectFormat, test.data) tag, err := parseTagData(Sha1ObjectFormat, []byte(test.data))
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, test.tag.ID, tag.ID) assert.Equal(t, test.expected, *tag)
assert.EqualValues(t, test.tag.Object, tag.Object)
assert.EqualValues(t, test.tag.Name, tag.Name)
assert.EqualValues(t, test.tag.Message, tag.Message)
assert.EqualValues(t, test.tag.Type, tag.Type)
if test.tag.Signature != nil && assert.NotNil(t, tag.Signature) {
assert.EqualValues(t, test.tag.Signature.Signature, tag.Signature.Signature)
assert.EqualValues(t, test.tag.Signature.Payload, tag.Signature.Payload)
} else {
assert.Nil(t, tag.Signature)
}
if test.tag.Tagger != nil && assert.NotNil(t, tag.Tagger) {
assert.EqualValues(t, test.tag.Tagger.Name, tag.Tagger.Name)
assert.EqualValues(t, test.tag.Tagger.Email, tag.Tagger.Email)
assert.EqualValues(t, test.tag.Tagger.When.Unix(), tag.Tagger.When.Unix())
} else {
assert.Nil(t, tag.Tagger)
}
} }

tag, err := parseTagData(Sha1ObjectFormat, []byte("type commit\n\nfoo\n-----BEGIN SSH SIGNATURE-----\ncorrupted..."))
assert.NoError(t, err)
assert.Equal(t, "foo\n-----BEGIN SSH SIGNATURE-----\ncorrupted...", tag.Message)
} }