mirror of https://github.com/go-gitea/gitea.git
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