blob: eeddee831e08fa18378efa8534451ee749d0a225 [file] [log] [blame]
// 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 frontend
import (
"fmt"
"net/http"
"path"
"golang.org/x/mod/semver"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/vulndb/osv"
)
// A Vuln contains information to display about a vulnerability.
type Vuln struct {
// The vulndb ID.
ID string
// A description of the vulnerability, or the problem in obtaining it.
Details string
// The version is which the vulnerability has been fixed.
FixedVersion string
}
type vulnEntriesFunc func(string) ([]*osv.Entry, error)
// Vulns obtains vulnerability information for the given package.
// If packagePath is empty, it returns all entries for the module at version.
// The getVulnEntries function should retrieve all entries for the given module path.
// It is passed to facilitate testing.
// If there is an error, Vulns returns a single Vuln that describes the error.
func Vulns(modulePath, version, packagePath string, getVulnEntries vulnEntriesFunc) []Vuln {
vs, err := vulns(modulePath, version, packagePath, getVulnEntries)
if err != nil {
return []Vuln{{Details: fmt.Sprintf("could not get vulnerability data: %v", err)}}
}
return vs
}
func vulns(modulePath, version, packagePath string, getVulnEntries vulnEntriesFunc) (_ []Vuln, err error) {
defer derrors.Wrap(&err, "vulns(%q, %q, %q)", modulePath, version, packagePath)
// Get all the vulns for this module.
entries, err := getVulnEntries(modulePath)
if err != nil {
return nil, err
}
// Each entry describes a single vuln. Select the ones that apply to this
// package at this version.
var vulns []Vuln
for _, e := range entries {
if vuln, ok := entryVuln(e, packagePath, version); ok {
vulns = append(vulns, vuln)
}
}
return vulns, nil
}
func entryVuln(e *osv.Entry, packagePath, version string) (Vuln, bool) {
for _, a := range e.Affected {
if (packagePath == "" || a.Package.Name == packagePath) && a.Ranges.AffectsSemver(version) {
// Choose the latest fixed version, if any.
var fixed string
for _, r := range a.Ranges {
if r.Type == osv.TypeGit {
continue
}
for _, re := range r.Events {
if re.Fixed != "" && (fixed == "" || semver.Compare(re.Fixed, fixed) > 0) {
fixed = re.Fixed
}
}
}
if fixed != "" {
fixed = "v" + fixed
}
return Vuln{
ID: e.ID,
Details: e.Details,
// TODO(golang/go#48223): handle stdlib versions
FixedVersion: fixed,
}, true
}
}
return Vuln{}, false
}
const vulndbURL = "https://go.googlesource.com/vulndb/+/refs/heads/master/reports/"
func (s *Server) serveVuln(w http.ResponseWriter, r *http.Request, _ internal.DataSource) error {
if r.URL.Path == "/" {
http.Redirect(w, r, "/golang.org/x/vulndb", http.StatusFound)
return nil
}
if r.URL.Path == "/list" {
http.Redirect(w, r, vulndbURL, http.StatusFound)
return nil
}
// Otherwise, the path should be "/ID".
http.Redirect(w, r, path.Join(vulndbURL, r.URL.Path+".yaml"), http.StatusFound)
return nil
}