| // 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 main |
| |
| import ( |
| "flag" |
| "fmt" |
| "html/template" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "golang.org/x/vulndb/report" |
| "gopkg.in/yaml.v2" |
| ) |
| |
| var indexTemplate = template.Must(template.New("index").Parse(`<html> |
| <body> |
| <h1>Go Vulnerability Database</h1> |
| <ul> |
| {{range .Vulns}}<li><a href="{{.}}.html">{{.}}</a></li>{{end}} |
| </ul> |
| <h2>Packages</h2> |
| <ul> |
| {{range .Packages}}<li><a href="{{.}}.html">{{.}}</a></li>{{end}} |
| </ul> |
| </body> |
| </html>`)) |
| var packageIndexTemplate = template.Must(template.New("package-index").Parse(`<html> |
| <body> |
| <h1>{{.Name}} Vulnerabilities</h1> |
| <ul> |
| {{range .Vulns}}<li><a href="{{.}}.html">{{.}}</a></li>{{end}} |
| </ul> |
| </body> |
| </html>`)) |
| var vulnTemplate = template.Must(template.New("vuln").Parse(`<html> |
| <body> |
| <h1>{{.Name}}</h1> |
| {{if .Vuln.Severity}}<p><b>Severity: </b>{{.Vuln.Severity}}</p>{{end}} |
| {{if .Vuln.OS}}<p><b>Affected Operating Systems: </b>{{.Vuln.OS}}</p>{{end}} |
| {{if .Vuln.Arch}}<p><b>Affected Architectures: </b>{{.Vuln.Arch}}</p>{{end}} |
| <p>{{.Vuln.Description}}</p> |
| {{if .Vuln.Credit}}<p><b>Credit: </b>{{.Vuln.Credit}}</p>{{end}} |
| {{if .Vuln.CVE}}<p><b>CVE: </b>{{.Vuln.CVE}}</p>{{end}} |
| |
| <h2>Affected Packages</h2> |
| <table> |
| <tr> |
| <th>Package</th> |
| <th>Introduced</th> |
| <th>Fixed</th> |
| <th>Symbols</th> |
| </tr> |
| <tr> |
| <td><code>{{.Vuln.Package}}</code></td> |
| {{if not .Vuln.Versions}}<td colspan="2" style="text-align: center">All available versions are vulnerable</td>{{else}} |
| {{range .Vuln.Versions}} |
| <td style="text-align: center">{{.Introduced}}</td> |
| <td style="text-align: center">{{.Fixed}}</td> |
| {{end}} |
| {{end}} |
| <td> |
| <ul> |
| {{range .Vuln.Symbols}}<li><code>{{.}}</code></li>{{end}} |
| </ul> |
| </td> |
| </tr> |
| {{range .Vuln.AdditionalPackages}} |
| <tr> |
| <td><code>{{.Package}}</code></td> |
| {{if not .Versions}}<td colspan="2" style="text-align: center">All available versions are vulnerable</td>{{else}} |
| {{range .Versions}} |
| <td style="text-align: center">{{.Introduced}}</td> |
| <td style="text-align: center">{{.Fixed}}</td> |
| {{end}} |
| {{end}} |
| <td> |
| <ul> |
| {{range .Symbols}}<li><code>{{.}}</code></li>{{end}} |
| </ul> |
| </td> |
| </tr> |
| {{end}} |
| </table> |
| |
| <h2>Context</h2> |
| {{if .Vuln.Links.Commit}}<p><b>Commit: </b><a href="{{.Vuln.Links.Commit}}">{{.Vuln.Links.Commit}}</a></p>{{end}} |
| {{if .Vuln.Links.PR}}<p><b>PR: </b><a href="{{.Vuln.Links.PR}}">{{.Vuln.Links.PR}}</a></p>{{end}} |
| {{if .Vuln.Links.Context}}<p><b>Additional links:</b><ul>{{range .Vuln.Links.Context}}<li><a href="{{.}}">{{.}}</a></li>{{end}}</ul></p>{{end}} |
| </body> |
| </html>`)) |
| |
| func generateWebsite(vulns map[string]report.Report, htmlDir string) error { |
| index := map[string][]string{} |
| var vulnNames []string |
| for name, vuln := range vulns { |
| index[vuln.Package] = append(index[vuln.Package], name) |
| for _, additional := range vuln.AdditionalPackages { |
| index[additional.Package] = append(index[additional.Package], name) |
| } |
| vulnNames = append(vulnNames, name) |
| |
| filename := filepath.Join(htmlDir, name+".html") |
| file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) |
| if err != nil { |
| return err |
| } |
| defer file.Close() |
| err = vulnTemplate.Execute(file, struct { |
| Name string |
| Vuln report.Report |
| }{ |
| Name: name, |
| Vuln: vuln, |
| }) |
| if err != nil { |
| return err |
| } |
| } |
| |
| for p, vulns := range index { |
| filename := filepath.Join(htmlDir, p+".html") |
| if err := os.MkdirAll(strings.TrimSuffix(filename, filepath.Base(filename)), 0755); err != nil { |
| return err |
| } |
| file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) |
| if err != nil { |
| return err |
| } |
| defer file.Close() |
| err = packageIndexTemplate.Execute(file, struct { |
| Name string |
| Vulns []string |
| }{ |
| Name: p, |
| Vulns: vulns, |
| }) |
| if err != nil { |
| return err |
| } |
| } |
| |
| var packageNames []string |
| for name := range index { |
| packageNames = append(packageNames, name) |
| } |
| |
| sort.Strings(packageNames) |
| sort.Strings(vulnNames) |
| file, err := os.OpenFile(filepath.Join(htmlDir, "index.html"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) |
| if err != nil { |
| return err |
| } |
| defer file.Close() |
| err = indexTemplate.Execute(file, struct { |
| Vulns []string |
| Packages []string |
| }{ |
| Vulns: vulnNames, |
| Packages: packageNames, |
| }) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func fail(why string) { |
| fmt.Fprintln(os.Stderr, why) |
| os.Exit(1) |
| } |
| |
| func main() { |
| tomlDir := flag.String("reports", "Directory containing toml reports", "") |
| htmlDir := flag.String("out", "Directory to write website to", "") |
| flag.Parse() |
| |
| htmlVulns := map[string]report.Report{} |
| tomlFiles, err := ioutil.ReadDir(*tomlDir) |
| if err != nil { |
| fail(fmt.Sprintf("can't read %q: %s", *tomlDir, err)) |
| } |
| for _, f := range tomlFiles { |
| if !strings.HasSuffix(f.Name(), ".toml") { |
| continue |
| } |
| content, err := ioutil.ReadFile(f.Name()) |
| if err != nil { |
| fail(fmt.Sprintf("can't read %q: %s", f.Name(), err)) |
| } |
| var vuln report.Report |
| err = yaml.Unmarshal(content, &vuln) |
| if err != nil { |
| fail(fmt.Sprintf("unable to unmarshal %q: %s", f.Name(), err)) |
| } |
| if err = vuln.Lint(); err != nil { |
| fail(fmt.Sprintf("invalid vulnerability %q: %s", f.Name(), err)) |
| } |
| name := strings.TrimSuffix(filepath.Base(f.Name()), filepath.Ext(f.Name())) |
| htmlVulns[name] = vuln |
| } |
| err = generateWebsite(htmlVulns, *htmlDir) |
| if err != nil { |
| fail(fmt.Sprintf("failed to generate website: %s", err)) |
| } |
| } |