Skip to content

Commit 577296f

Browse files
authored
Move files and crypto utilities into gofrog (#66)
1 parent f35fee9 commit 577296f

File tree

14 files changed

+832
-21
lines changed

14 files changed

+832
-21
lines changed

.github/workflows/analysis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
uses: actions/checkout@v4
1515

1616
- name: Install Go
17-
uses: actions/setup-go@v3
17+
uses: actions/setup-go@v5
1818
with:
19-
go-version: 1.20.x
19+
go-version: 1.22.x
2020

2121
- name: Static Code Analysis
2222
uses: golangci/golangci-lint-action@v3
@@ -32,9 +32,9 @@ jobs:
3232
uses: actions/checkout@v4
3333

3434
- name: Install Go
35-
uses: actions/setup-go@v3
35+
uses: actions/setup-go@v5
3636
with:
37-
go-version: 1.20.x
37+
go-version: 1.22.x
3838

3939
- name: Run Gosec Security Scanner
4040
uses: securego/gosec@master

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Setup Go
2929
uses: actions/setup-go@v5
3030
with:
31-
go-version: 1.20.x
31+
go-version: 1.22.x
3232
cache: false
3333

3434
- name: Tests

crypto/checksum.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package crypto
2+
3+
import (
4+
"bufio"
5+
"regexp"
6+
7+
// #nosec G501 -- md5 is supported by Artifactory.
8+
"crypto/md5"
9+
// #nosec G505 -- sha1 is supported by Artifactory.
10+
"crypto/sha1"
11+
"fmt"
12+
"hash"
13+
"io"
14+
"os"
15+
16+
ioutils "github.com/jfrog/gofrog/io"
17+
"github.com/minio/sha256-simd"
18+
)
19+
20+
type Algorithm int
21+
22+
const (
23+
MD5 Algorithm = iota
24+
SHA1
25+
SHA256
26+
)
27+
28+
var algorithmFunc = map[Algorithm]func() hash.Hash{
29+
// Go native crypto algorithms:
30+
MD5: md5.New,
31+
SHA1: sha1.New,
32+
// sha256-simd algorithm:
33+
SHA256: sha256.New,
34+
}
35+
36+
type Checksum struct {
37+
Sha1 string `json:"sha1,omitempty"`
38+
Md5 string `json:"md5,omitempty"`
39+
Sha256 string `json:"sha256,omitempty"`
40+
}
41+
42+
func (c *Checksum) IsEmpty() bool {
43+
return c.Md5 == "" && c.Sha1 == "" && c.Sha256 == ""
44+
}
45+
46+
// If the 'other' checksum matches the current one, return true.
47+
// 'other' checksum may contain regex values for sha1, sha256 and md5.
48+
func (c *Checksum) IsEqual(other Checksum) (bool, error) {
49+
match, err := regexp.MatchString(other.Md5, c.Md5)
50+
if !match || err != nil {
51+
return false, err
52+
}
53+
match, err = regexp.MatchString(other.Sha1, c.Sha1)
54+
if !match || err != nil {
55+
return false, err
56+
}
57+
match, err = regexp.MatchString(other.Sha256, c.Sha256)
58+
if !match || err != nil {
59+
return false, err
60+
}
61+
62+
return true, nil
63+
}
64+
65+
func GetFileChecksums(filePath string, checksumType ...Algorithm) (checksums map[Algorithm]string, err error) {
66+
file, err := os.Open(filePath)
67+
if err != nil {
68+
return
69+
}
70+
defer ioutils.Close(file, &err)
71+
return CalcChecksums(file, checksumType...)
72+
}
73+
74+
// CalcChecksums calculates all hashes at once using AsyncMultiWriter. The file is therefore read only once.
75+
func CalcChecksums(reader io.Reader, checksumType ...Algorithm) (map[Algorithm]string, error) {
76+
hashes, err := calcChecksums(reader, checksumType...)
77+
if err != nil {
78+
return nil, err
79+
}
80+
results := sumResults(hashes)
81+
return results, nil
82+
}
83+
84+
// CalcChecksumsBytes calculates hashes like `CalcChecksums`, returns result as bytes
85+
func CalcChecksumsBytes(reader io.Reader, checksumType ...Algorithm) (map[Algorithm][]byte, error) {
86+
hashes, err := calcChecksums(reader, checksumType...)
87+
if err != nil {
88+
return nil, err
89+
}
90+
results := sumResultsBytes(hashes)
91+
return results, nil
92+
}
93+
94+
func calcChecksums(reader io.Reader, checksumType ...Algorithm) (map[Algorithm]hash.Hash, error) {
95+
hashes := getChecksumByAlgorithm(checksumType...)
96+
var multiWriter io.Writer
97+
pageSize := os.Getpagesize()
98+
sizedReader := bufio.NewReaderSize(reader, pageSize)
99+
var hashWriter []io.Writer
100+
for _, v := range hashes {
101+
hashWriter = append(hashWriter, v)
102+
}
103+
multiWriter = ioutils.AsyncMultiWriter(pageSize, hashWriter...)
104+
_, err := io.Copy(multiWriter, sizedReader)
105+
if err != nil {
106+
return nil, err
107+
}
108+
return hashes, nil
109+
}
110+
111+
func sumResults(hashes map[Algorithm]hash.Hash) map[Algorithm]string {
112+
results := map[Algorithm]string{}
113+
for k, v := range hashes {
114+
results[k] = fmt.Sprintf("%x", v.Sum(nil))
115+
}
116+
return results
117+
}
118+
119+
func sumResultsBytes(hashes map[Algorithm]hash.Hash) map[Algorithm][]byte {
120+
results := map[Algorithm][]byte{}
121+
for k, v := range hashes {
122+
results[k] = v.Sum(nil)
123+
}
124+
return results
125+
}
126+
127+
func getChecksumByAlgorithm(checksumType ...Algorithm) map[Algorithm]hash.Hash {
128+
hashes := map[Algorithm]hash.Hash{}
129+
if len(checksumType) == 0 {
130+
for k, v := range algorithmFunc {
131+
hashes[k] = v()
132+
}
133+
return hashes
134+
}
135+
136+
for _, v := range checksumType {
137+
hashes[v] = algorithmFunc[v]()
138+
}
139+
return hashes
140+
}
141+
142+
func CalcChecksumDetails(filePath string) (checksum Checksum, err error) {
143+
file, err := os.Open(filePath)
144+
if err != nil {
145+
return
146+
}
147+
defer ioutils.Close(file, &err)
148+
149+
checksums, err := CalcChecksums(file)
150+
if err != nil {
151+
return Checksum{}, err
152+
}
153+
checksum = Checksum{Md5: checksums[MD5], Sha1: checksums[SHA1], Sha256: checksums[SHA256]}
154+
return
155+
}
156+
157+
type FileDetails struct {
158+
Checksum Checksum
159+
Size int64
160+
}
161+
162+
func GetFileDetails(filePath string, includeChecksums bool) (details *FileDetails, err error) {
163+
details = new(FileDetails)
164+
if includeChecksums {
165+
details.Checksum, err = CalcChecksumDetails(filePath)
166+
if err != nil {
167+
return
168+
}
169+
} else {
170+
details.Checksum = Checksum{}
171+
}
172+
173+
fileInfo, err := os.Stat(filePath)
174+
if err != nil {
175+
return
176+
}
177+
details.Size = fileInfo.Size()
178+
return
179+
}

crypto/checksum_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package crypto
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
const (
11+
fileContent = "Why did the robot bring a ladder to the bar? It heard the drinks were on the house."
12+
expectedMd5 = "70bd6370a86813f2504020281e4a2e2e"
13+
expectedSha1 = "8c3578ac814c9f02803001a5d3e5d78a7fd0f9cc"
14+
expectedSha256 = "093d901b28a59f7d95921f3f4fb97a03fe7a1cf8670507ffb1d6f9a01b3e890a"
15+
)
16+
17+
func TestGetFileChecksums(t *testing.T) {
18+
// Create a temporary file
19+
tempFile, err := os.CreateTemp("", "TestGetFileChecksums")
20+
assert.NoError(t, err)
21+
defer func() {
22+
assert.NoError(t, tempFile.Close())
23+
assert.NoError(t, os.Remove(tempFile.Name()))
24+
}()
25+
26+
// Write something to the file
27+
_, err = tempFile.Write([]byte(fileContent))
28+
assert.NoError(t, err)
29+
30+
// Calculate only sha1 and match
31+
checksums, err := GetFileChecksums(tempFile.Name(), SHA1)
32+
assert.NoError(t, err)
33+
assert.Len(t, checksums, 1)
34+
assert.Equal(t, expectedSha1, checksums[SHA1])
35+
36+
// Calculate md5, sha1 and sha256 checksums and match
37+
checksums, err = GetFileChecksums(tempFile.Name())
38+
assert.NoError(t, err)
39+
assert.Equal(t, expectedMd5, checksums[MD5])
40+
assert.Equal(t, expectedSha1, checksums[SHA1])
41+
assert.Equal(t, expectedSha256, checksums[SHA256])
42+
}

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module github.com/jfrog/gofrog
22

3-
go 1.20
3+
go 1.22
44

55
require (
66
github.com/jfrog/archiver/v3 v3.6.0
7+
github.com/minio/sha256-simd v1.0.1
78
github.com/pkg/errors v0.9.1
89
github.com/schollz/progressbar/v3 v3.14.2
9-
github.com/stretchr/testify v1.8.4
10+
github.com/stretchr/testify v1.9.0
1011
)
1112

1213
require (
@@ -15,6 +16,7 @@ require (
1516
github.com/dsnet/compress v0.0.1 // indirect
1617
github.com/golang/snappy v0.0.4 // indirect
1718
github.com/klauspost/compress v1.17.4 // indirect
19+
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
1820
github.com/klauspost/pgzip v1.2.6 // indirect
1921
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
2022
github.com/nwaples/rardecode v1.1.3 // indirect

go.sum

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
1515
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
1616
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
1717
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
18+
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
19+
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
1820
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
1921
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
2022
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
23+
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
24+
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
2125
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
2226
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
2327
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
@@ -34,15 +38,16 @@ github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyM
3438
github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4=
3539
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3640
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
37-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
38-
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
41+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
42+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3943
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
4044
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
4145
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
4246
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
4347
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
4448
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
4549
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
50+
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4651
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4752
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
4853
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

0 commit comments

Comments
 (0)