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