This repository was archived by the owner on Aug 2, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathadvisories.go
More file actions
143 lines (123 loc) · 3.39 KB
/
advisories.go
File metadata and controls
143 lines (123 loc) · 3.39 KB
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
package security
import (
"archive/zip"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// AdvisoryArchiveURL represents the advisories database URL
const AdvisoryArchiveURL = "https://codeload.github.com/FriendsOfPHP/security-advisories/zip/master"
// AdvisoryDB stores all known security advisories
type AdvisoryDB struct {
Advisories []Advisory
cacheDir string
noHTTPCalls bool
}
// Advisory represents a single security advisory
type Advisory struct {
Title string `yaml:"title"`
Link string `yaml:"link"`
CVE string `yaml:"cve"`
Branches map[string]*Branch `yaml:"branches"`
Reference string `yaml:"reference"`
}
// Branch represents a Git branch
type Branch struct {
Versions []string `yaml:"versions"`
Time Time `yaml:"time"`
}
// Cache stores the Github response to save bandwith
type Cache struct {
Key string
Date string
Body []byte
}
// NewDB fetches the advisory DB from Github
func NewDB(noHTTPCalls bool, advisoryArchiveURL, cacheDir string) (*AdvisoryDB, error) {
db := &AdvisoryDB{noHTTPCalls: noHTTPCalls, cacheDir: cacheDir}
if err := db.load(advisoryArchiveURL); err != nil {
return nil, fmt.Errorf("unable to fetch advisories: %s", err)
}
return db, nil
}
// load loads fetches the database from Github and reads/loads current advisories
// from the repository. Cache handling is delegated to http.Transport and
// **must** be handled appropriately.
func (db *AdvisoryDB) load(advisoryArchiveURL string) error {
if len(db.Advisories) > 0 {
return nil
}
db.Advisories = []Advisory{}
var cache *Cache
cachePath := filepath.Join(db.cacheDir, "php_sec_db.json")
if cacheContent, err := os.ReadFile(cachePath); err == nil {
// ignore errors
json.Unmarshal(cacheContent, &cache)
}
if db.noHTTPCalls && cache == nil {
return errors.New("--local can only be used when a local HTTP cache is available")
}
if !db.noHTTPCalls {
req, err := http.NewRequest("GET", advisoryArchiveURL, nil)
if err != nil {
return err
}
if cache != nil {
req.Header.Add("If-None-Match", cache.Key)
req.Header.Add("If-Modified-Since", cache.Date)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var body []byte
if resp.StatusCode != http.StatusNotModified {
body, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
key := resp.Header.Get("ETag")
date := resp.Header.Get("Date")
if key != "" || date != "" {
cache = &Cache{Key: key, Date: date, Body: body}
}
cacheContent, err := json.Marshal(cache)
if err == nil {
os.WriteFile(cachePath, cacheContent, 0644)
}
}
}
zipReader, err := zip.NewReader(bytes.NewReader(cache.Body), int64(len(cache.Body)))
if err != nil {
return err
}
// Read all the files from the zip archive
for _, zipFile := range zipReader.File {
if !strings.HasSuffix(zipFile.Name, ".yaml") {
continue
}
f, err := zipFile.Open()
if err != nil {
return err
}
defer f.Close()
contents, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("unable to read %s: %s", zipFile.Name, err)
}
var pa Advisory
if err := yaml.Unmarshal(contents, &pa); err != nil {
return fmt.Errorf("%s is not a valid YAML file: %s", zipFile.Name, err)
}
db.Advisories = append(db.Advisories, pa)
}
return nil
}