forked from golang/vuln
/
cache.go
154 lines (137 loc) · 3.6 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package govulncheck
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"github.com/julieqiu/vuln/internal"
"github.com/julieqiu/vuln/internal/client"
"github.com/julieqiu/vuln/osv"
)
// The cache uses a single JSON index file for each vulnerability database
// which contains the map from packages to the time the last
// vulnerability for that package was added/modified and the time that
// the index was retrieved from the vulnerability database. The JSON
// format is as follows:
//
// $GOMODCACHE/cache/download/vulndb/{db hostname}/indexes/index.json
// {
// Retrieved time.Time
// Index client.DBIndex
// }
//
// Each package also has a JSON file which contains the array of vulnerability
// entries for the package. The JSON format is as follows:
//
// $GOMODCACHE/cache/download/vulndb/{db hostname}/{import path}/vulns.json
// []*osv.Entry
// FSCache is a thread-safe file-system cache implementing osv.Cache
//
// TODO: use something like cmd/go/internal/lockedfile for thread safety?
type FSCache struct {
mu sync.Mutex
rootDir string
}
// Assert that *FSCache implements client.Cache.
var _ client.Cache = (*FSCache)(nil)
var (
initDefaultCache sync.Once
defaultCache *FSCache
defaultCacheErr error
)
func DefaultCache() (*FSCache, error) {
initDefaultCache.Do(func() {
mod, err := internal.GoEnv("GOMODCACHE")
if err != nil {
defaultCacheErr = err
return
}
defaultCache = &FSCache{
rootDir: filepath.Join(mod, "/cache/download/vulndb"),
}
})
return defaultCache, defaultCacheErr
}
type cachedIndex struct {
Retrieved time.Time
Index client.DBIndex
}
func (c *FSCache) ReadIndex(dbName string) (client.DBIndex, time.Time, error) {
c.mu.Lock()
defer c.mu.Unlock()
b, err := os.ReadFile(filepath.Join(c.rootDir, dbName, "index.json"))
if err != nil {
if os.IsNotExist(err) {
return nil, time.Time{}, nil
}
return nil, time.Time{}, err
}
var index cachedIndex
if err := json.Unmarshal(b, &index); err != nil {
return nil, time.Time{}, err
}
return index.Index, index.Retrieved, nil
}
func (c *FSCache) WriteIndex(dbName string, index client.DBIndex, retrieved time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
path := filepath.Join(c.rootDir, dbName)
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
j, err := json.Marshal(cachedIndex{
Index: index,
Retrieved: retrieved,
})
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(path, "index.json"), j, 0666); err != nil {
return err
}
return nil
}
func (c *FSCache) ReadEntries(dbName string, p string) ([]*osv.Entry, error) {
c.mu.Lock()
defer c.mu.Unlock()
ep, err := client.EscapeModulePath(p)
if err != nil {
return nil, err
}
b, err := os.ReadFile(filepath.Join(c.rootDir, dbName, ep, "vulns.json"))
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
var entries []*osv.Entry
if err := json.Unmarshal(b, &entries); err != nil {
return nil, err
}
return entries, nil
}
func (c *FSCache) WriteEntries(dbName string, p string, entries []*osv.Entry) error {
c.mu.Lock()
defer c.mu.Unlock()
ep, err := client.EscapeModulePath(p)
if err != nil {
return err
}
path := filepath.Join(c.rootDir, dbName, ep)
if err := os.MkdirAll(path, 0777); err != nil {
return err
}
j, err := json.Marshal(entries)
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(path, "vulns.json"), j, 0666); err != nil {
return err
}
return nil
}