mirror of https://github.com/go-gitea/gitea
Abstract hash function usage (#28138)
Refactor Hash interfaces and centralize hash function. This will allow easier introduction of different hash function later on. This forms the "no-op" part of the SHA256 enablement patch.pull/28392/head^2
parent
064f05204c
commit
cbf923e87b
@ -0,0 +1,103 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/sha1" |
||||||
|
"fmt" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type ObjectFormatID int |
||||||
|
|
||||||
|
const ( |
||||||
|
Sha1 ObjectFormatID = iota |
||||||
|
) |
||||||
|
|
||||||
|
// sha1Pattern can be used to determine if a string is an valid sha
|
||||||
|
var sha1Pattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) |
||||||
|
|
||||||
|
type ObjectFormat interface { |
||||||
|
ID() ObjectFormatID |
||||||
|
String() string |
||||||
|
|
||||||
|
// Empty is the hash of empty git
|
||||||
|
Empty() ObjectID |
||||||
|
// EmptyTree is the hash of an empty tree
|
||||||
|
EmptyTree() ObjectID |
||||||
|
// FullLength is the length of the hash's hex string
|
||||||
|
FullLength() int |
||||||
|
|
||||||
|
IsValid(input string) bool |
||||||
|
MustID(b []byte) ObjectID |
||||||
|
MustIDFromString(s string) ObjectID |
||||||
|
NewID(b []byte) (ObjectID, error) |
||||||
|
NewIDFromString(s string) (ObjectID, error) |
||||||
|
NewEmptyID() ObjectID |
||||||
|
|
||||||
|
NewHasher() HasherInterface |
||||||
|
} |
||||||
|
|
||||||
|
/* SHA1 Type */ |
||||||
|
type Sha1ObjectFormat struct{} |
||||||
|
|
||||||
|
func (*Sha1ObjectFormat) ID() ObjectFormatID { return Sha1 } |
||||||
|
func (*Sha1ObjectFormat) String() string { return "sha1" } |
||||||
|
func (*Sha1ObjectFormat) Empty() ObjectID { return &Sha1Hash{} } |
||||||
|
func (*Sha1ObjectFormat) EmptyTree() ObjectID { |
||||||
|
return &Sha1Hash{ |
||||||
|
0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, |
||||||
|
0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04, |
||||||
|
} |
||||||
|
} |
||||||
|
func (*Sha1ObjectFormat) FullLength() int { return 40 } |
||||||
|
func (*Sha1ObjectFormat) IsValid(input string) bool { |
||||||
|
return sha1Pattern.MatchString(input) |
||||||
|
} |
||||||
|
|
||||||
|
func (*Sha1ObjectFormat) MustID(b []byte) ObjectID { |
||||||
|
var id Sha1Hash |
||||||
|
copy(id[0:20], b) |
||||||
|
return &id |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Sha1ObjectFormat) MustIDFromString(s string) ObjectID { |
||||||
|
return MustIDFromString(h, s) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Sha1ObjectFormat) NewID(b []byte) (ObjectID, error) { |
||||||
|
return IDFromRaw(h, b) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Sha1ObjectFormat) NewIDFromString(s string) (ObjectID, error) { |
||||||
|
return genericIDFromString(h, s) |
||||||
|
} |
||||||
|
|
||||||
|
func (*Sha1ObjectFormat) NewEmptyID() ObjectID { |
||||||
|
return NewSha1() |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Sha1ObjectFormat) NewHasher() HasherInterface { |
||||||
|
return &Sha1Hasher{sha1.New()} |
||||||
|
} |
||||||
|
|
||||||
|
// utils
|
||||||
|
func ObjectFormatFromID(id ObjectFormatID) ObjectFormat { |
||||||
|
switch id { |
||||||
|
case Sha1: |
||||||
|
return &Sha1ObjectFormat{} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func ObjectFormatFromString(hash string) (ObjectFormat, error) { |
||||||
|
switch strings.ToLower(hash) { |
||||||
|
case "sha1": |
||||||
|
return &Sha1ObjectFormat{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown hash type: %s", hash) |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/hex" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"hash" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type ObjectID interface { |
||||||
|
String() string |
||||||
|
IsZero() bool |
||||||
|
RawValue() []byte |
||||||
|
Type() ObjectFormat |
||||||
|
} |
||||||
|
|
||||||
|
/* SHA1 */ |
||||||
|
type Sha1Hash [20]byte |
||||||
|
|
||||||
|
func (h *Sha1Hash) String() string { |
||||||
|
return hex.EncodeToString(h[:]) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Sha1Hash) IsZero() bool { |
||||||
|
empty := Sha1Hash{} |
||||||
|
return bytes.Equal(empty[:], h[:]) |
||||||
|
} |
||||||
|
func (h *Sha1Hash) RawValue() []byte { return h[:] } |
||||||
|
func (*Sha1Hash) Type() ObjectFormat { return &Sha1ObjectFormat{} } |
||||||
|
|
||||||
|
func NewSha1() *Sha1Hash { |
||||||
|
return &Sha1Hash{} |
||||||
|
} |
||||||
|
|
||||||
|
// generic implementations
|
||||||
|
func NewHash(hash string) (ObjectID, error) { |
||||||
|
hash = strings.ToLower(hash) |
||||||
|
switch hash { |
||||||
|
case "sha1": |
||||||
|
return &Sha1Hash{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
return nil, errors.New("unsupported hash type") |
||||||
|
} |
||||||
|
|
||||||
|
func IDFromRaw(h ObjectFormat, b []byte) (ObjectID, error) { |
||||||
|
if len(b) != h.FullLength()/2 { |
||||||
|
return h.Empty(), fmt.Errorf("length must be %d: %v", h.FullLength(), b) |
||||||
|
} |
||||||
|
return h.MustID(b), nil |
||||||
|
} |
||||||
|
|
||||||
|
func MustIDFromString(h ObjectFormat, s string) ObjectID { |
||||||
|
b, _ := hex.DecodeString(s) |
||||||
|
return h.MustID(b) |
||||||
|
} |
||||||
|
|
||||||
|
func genericIDFromString(h ObjectFormat, s string) (ObjectID, error) { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
if len(s) != h.FullLength() { |
||||||
|
return h.Empty(), fmt.Errorf("length must be %d: %s", h.FullLength(), s) |
||||||
|
} |
||||||
|
b, err := hex.DecodeString(s) |
||||||
|
if err != nil { |
||||||
|
return h.Empty(), err |
||||||
|
} |
||||||
|
return h.NewID(b) |
||||||
|
} |
||||||
|
|
||||||
|
// utils
|
||||||
|
func IDFromString(hexHash string) (ObjectID, error) { |
||||||
|
switch len(hexHash) { |
||||||
|
case 40: |
||||||
|
hashType := Sha1ObjectFormat{} |
||||||
|
h, err := hashType.NewIDFromString(hexHash) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return h, nil |
||||||
|
} |
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid hash hex string: '%s' len: %d", hexHash, len(hexHash)) |
||||||
|
} |
||||||
|
|
||||||
|
func IsEmptyCommitID(commitID string) bool { |
||||||
|
if commitID == "" { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
id, err := IDFromString(commitID) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return id.IsZero() |
||||||
|
} |
||||||
|
|
||||||
|
// HashInterface is a struct that will generate a Hash
|
||||||
|
type HasherInterface interface { |
||||||
|
hash.Hash |
||||||
|
|
||||||
|
HashSum() ObjectID |
||||||
|
} |
||||||
|
|
||||||
|
type Sha1Hasher struct { |
||||||
|
hash.Hash |
||||||
|
} |
||||||
|
|
||||||
|
// ComputeBlobHash compute the hash for a given blob content
|
||||||
|
func ComputeBlobHash(hashType ObjectFormat, content []byte) ObjectID { |
||||||
|
return ComputeHash(hashType, ObjectBlob, content) |
||||||
|
} |
||||||
|
|
||||||
|
// ComputeHash compute the hash for a given ObjectType and content
|
||||||
|
func ComputeHash(hashType ObjectFormat, t ObjectType, content []byte) ObjectID { |
||||||
|
h := hashType.NewHasher() |
||||||
|
_, _ = h.Write(t.Bytes()) |
||||||
|
_, _ = h.Write([]byte(" ")) |
||||||
|
_, _ = h.Write([]byte(strconv.FormatInt(int64(len(content)), 10))) |
||||||
|
_, _ = h.Write([]byte{0}) |
||||||
|
return h.HashSum() |
||||||
|
} |
||||||
|
|
||||||
|
// Sum generates a SHA1 for the provided hash
|
||||||
|
func (h *Sha1Hasher) HashSum() ObjectID { |
||||||
|
var sha1 Sha1Hash |
||||||
|
copy(sha1[:], h.Hash.Sum(nil)) |
||||||
|
return &sha1 |
||||||
|
} |
||||||
|
|
||||||
|
type ErrInvalidSHA struct { |
||||||
|
SHA string |
||||||
|
} |
||||||
|
|
||||||
|
func (err ErrInvalidSHA) Error() string { |
||||||
|
return fmt.Sprintf("invalid sha: %s", err.SHA) |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//go:build gogit
|
||||||
|
|
||||||
|
package git |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/go-git/go-git/v5/plumbing" |
||||||
|
"github.com/go-git/go-git/v5/plumbing/hash" |
||||||
|
) |
||||||
|
|
||||||
|
func ParseGogitHash(h plumbing.Hash) ObjectID { |
||||||
|
switch hash.Size { |
||||||
|
case 20: |
||||||
|
return ObjectFormatFromID(Sha1).MustID(h[:]) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func ParseGogitHashArray(objectIDs []plumbing.Hash) []ObjectID { |
||||||
|
ret := make([]ObjectID, len(objectIDs)) |
||||||
|
for i, h := range objectIDs { |
||||||
|
ret[i] = ParseGogitHash(h) |
||||||
|
} |
||||||
|
|
||||||
|
return ret |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
) |
||||||
|
|
||||||
|
func TestIsValidSHAPattern(t *testing.T) { |
||||||
|
h := NewSha1().Type() |
||||||
|
assert.True(t, h.IsValid("fee1")) |
||||||
|
assert.True(t, h.IsValid("abc000")) |
||||||
|
assert.True(t, h.IsValid("9023902390239023902390239023902390239023")) |
||||||
|
assert.False(t, h.IsValid("90239023902390239023902390239023902390239023")) |
||||||
|
assert.False(t, h.IsValid("abc")) |
||||||
|
assert.False(t, h.IsValid("123g")) |
||||||
|
assert.False(t, h.IsValid("some random text")) |
||||||
|
} |
@ -1,72 +0,0 @@ |
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/hex" |
|
||||||
"fmt" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// EmptySHA defines empty git SHA (undefined, non-existent)
|
|
||||||
const EmptySHA = "0000000000000000000000000000000000000000" |
|
||||||
|
|
||||||
// EmptyTreeSHA is the SHA of an empty tree, the root of all git repositories
|
|
||||||
const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" |
|
||||||
|
|
||||||
// SHAFullLength is the full length of a git SHA
|
|
||||||
const SHAFullLength = 40 |
|
||||||
|
|
||||||
// SHAPattern can be used to determine if a string is an valid sha
|
|
||||||
var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`) |
|
||||||
|
|
||||||
// IsValidSHAPattern will check if the provided string matches the SHA Pattern
|
|
||||||
func IsValidSHAPattern(sha string) bool { |
|
||||||
return shaPattern.MatchString(sha) |
|
||||||
} |
|
||||||
|
|
||||||
type ErrInvalidSHA struct { |
|
||||||
SHA string |
|
||||||
} |
|
||||||
|
|
||||||
func (err ErrInvalidSHA) Error() string { |
|
||||||
return fmt.Sprintf("invalid sha: %s", err.SHA) |
|
||||||
} |
|
||||||
|
|
||||||
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
|
|
||||||
func MustID(b []byte) SHA1 { |
|
||||||
var id SHA1 |
|
||||||
copy(id[:], b) |
|
||||||
return id |
|
||||||
} |
|
||||||
|
|
||||||
// NewID creates a new SHA1 from a [20]byte array.
|
|
||||||
func NewID(b []byte) (SHA1, error) { |
|
||||||
if len(b) != 20 { |
|
||||||
return SHA1{}, fmt.Errorf("Length must be 20: %v", b) |
|
||||||
} |
|
||||||
return MustID(b), nil |
|
||||||
} |
|
||||||
|
|
||||||
// MustIDFromString always creates a new sha from a ID with no validation of input.
|
|
||||||
func MustIDFromString(s string) SHA1 { |
|
||||||
b, _ := hex.DecodeString(s) |
|
||||||
return MustID(b) |
|
||||||
} |
|
||||||
|
|
||||||
// NewIDFromString creates a new SHA1 from a ID string of length 40.
|
|
||||||
func NewIDFromString(s string) (SHA1, error) { |
|
||||||
var id SHA1 |
|
||||||
s = strings.TrimSpace(s) |
|
||||||
if len(s) != SHAFullLength { |
|
||||||
return id, fmt.Errorf("Length must be 40: %s", s) |
|
||||||
} |
|
||||||
b, err := hex.DecodeString(s) |
|
||||||
if err != nil { |
|
||||||
return id, err |
|
||||||
} |
|
||||||
return NewID(b) |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build gogit
|
|
||||||
|
|
||||||
package git |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/go-git/go-git/v5/plumbing" |
|
||||||
) |
|
||||||
|
|
||||||
// SHA1 a git commit name
|
|
||||||
type SHA1 = plumbing.Hash |
|
||||||
|
|
||||||
// ComputeBlobHash compute the hash for a given blob content
|
|
||||||
func ComputeBlobHash(content []byte) SHA1 { |
|
||||||
return plumbing.ComputeHash(plumbing.BlobObject, content) |
|
||||||
} |
|
@ -1,61 +0,0 @@ |
|||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
//go:build !gogit
|
|
||||||
|
|
||||||
package git |
|
||||||
|
|
||||||
import ( |
|
||||||
"crypto/sha1" |
|
||||||
"encoding/hex" |
|
||||||
"hash" |
|
||||||
"strconv" |
|
||||||
) |
|
||||||
|
|
||||||
// SHA1 a git commit name
|
|
||||||
type SHA1 [20]byte |
|
||||||
|
|
||||||
// String returns a string representation of the SHA
|
|
||||||
func (s SHA1) String() string { |
|
||||||
return hex.EncodeToString(s[:]) |
|
||||||
} |
|
||||||
|
|
||||||
// IsZero returns whether this SHA1 is all zeroes
|
|
||||||
func (s SHA1) IsZero() bool { |
|
||||||
var empty SHA1 |
|
||||||
return s == empty |
|
||||||
} |
|
||||||
|
|
||||||
// ComputeBlobHash compute the hash for a given blob content
|
|
||||||
func ComputeBlobHash(content []byte) SHA1 { |
|
||||||
return ComputeHash(ObjectBlob, content) |
|
||||||
} |
|
||||||
|
|
||||||
// ComputeHash compute the hash for a given ObjectType and content
|
|
||||||
func ComputeHash(t ObjectType, content []byte) SHA1 { |
|
||||||
h := NewHasher(t, int64(len(content))) |
|
||||||
_, _ = h.Write(content) |
|
||||||
return h.Sum() |
|
||||||
} |
|
||||||
|
|
||||||
// Hasher is a struct that will generate a SHA1
|
|
||||||
type Hasher struct { |
|
||||||
hash.Hash |
|
||||||
} |
|
||||||
|
|
||||||
// NewHasher takes an object type and size and creates a hasher to generate a SHA
|
|
||||||
func NewHasher(t ObjectType, size int64) Hasher { |
|
||||||
h := Hasher{sha1.New()} |
|
||||||
_, _ = h.Write(t.Bytes()) |
|
||||||
_, _ = h.Write([]byte(" ")) |
|
||||||
_, _ = h.Write([]byte(strconv.FormatInt(size, 10))) |
|
||||||
_, _ = h.Write([]byte{0}) |
|
||||||
return h |
|
||||||
} |
|
||||||
|
|
||||||
// Sum generates a SHA1 for the provided hash
|
|
||||||
func (h Hasher) Sum() (sha1 SHA1) { |
|
||||||
copy(sha1[:], h.Hash.Sum(nil)) |
|
||||||
return sha1 |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert" |
|
||||||
) |
|
||||||
|
|
||||||
func TestIsValidSHAPattern(t *testing.T) { |
|
||||||
assert.True(t, IsValidSHAPattern("fee1")) |
|
||||||
assert.True(t, IsValidSHAPattern("abc000")) |
|
||||||
assert.True(t, IsValidSHAPattern("9023902390239023902390239023902390239023")) |
|
||||||
assert.False(t, IsValidSHAPattern("90239023902390239023902390239023902390239023")) |
|
||||||
assert.False(t, IsValidSHAPattern("abc")) |
|
||||||
assert.False(t, IsValidSHAPattern("123g")) |
|
||||||
assert.False(t, IsValidSHAPattern("some random text")) |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue