| // Copyright 2022 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 cvelistrepo supports working with the repo |
| // containing the list of CVEs. |
| package cvelistrepo |
| |
| import ( |
| "fmt" |
| "path" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "github.com/go-git/go-git/v5" |
| "github.com/go-git/go-git/v5/plumbing" |
| "github.com/go-git/go-git/v5/plumbing/filemode" |
| "github.com/go-git/go-git/v5/plumbing/object" |
| "golang.org/x/vulndb/internal/derrors" |
| "golang.org/x/vulndb/internal/gitrepo" |
| "golang.org/x/vulndb/internal/idstr" |
| ) |
| |
| // URLs of the CVE project list repos. |
| const ( |
| URLv4 = "https://github.com/CVEProject/cvelist" |
| URLv5 = "https://github.com/CVEProject/cvelistV5" |
| ) |
| |
| // A File is a file in the cvelist repo that contains a CVE. |
| type File struct { |
| DirPath string |
| Filename string |
| TreeHash plumbing.Hash |
| BlobHash plumbing.Hash |
| Year int |
| Number int |
| } |
| |
| // Files returns all the CVE files in the given repo commit, sorted by |
| // name. |
| func Files(repo *git.Repository, commit *object.Commit) (_ []File, err error) { |
| defer derrors.Wrap(&err, "CVEFiles(%s)", commit.Hash) |
| |
| root, err := repo.TreeObject(commit.TreeHash) |
| if err != nil { |
| return nil, fmt.Errorf("TreeObject: %v", err) |
| } |
| files, err := walkFiles(repo, root, "", nil) |
| if err != nil { |
| return nil, err |
| } |
| sort.Slice(files, func(i, j int) bool { |
| // Compare the year and the number, as ints. Using the ID directly |
| // would put CVE-2014-100009 before CVE-2014-10001. |
| if files[i].Year != files[j].Year { |
| return files[i].Year < files[j].Year |
| } |
| return files[i].Number < files[j].Number |
| }) |
| return files, nil |
| } |
| |
| // walkFiles collects CVE files from a repo tree. |
| func walkFiles(repo *git.Repository, tree *object.Tree, dirpath string, files []File) ([]File, error) { |
| for _, e := range tree.Entries { |
| if e.Mode == filemode.Dir { |
| dir, err := repo.TreeObject(e.Hash) |
| if err != nil { |
| return nil, err |
| } |
| files, err = walkFiles(repo, dir, path.Join(dirpath, e.Name), files) |
| if err != nil { |
| return nil, err |
| } |
| } else if isCVEFilename(e.Name) { |
| // e.Name is CVE-YEAR-NUMBER.json |
| year, err := strconv.Atoi(e.Name[4:8]) |
| if err != nil { |
| return nil, err |
| } |
| number, err := strconv.Atoi(e.Name[9 : len(e.Name)-5]) |
| if err != nil { |
| return nil, err |
| } |
| files = append(files, File{ |
| DirPath: dirpath, |
| Filename: e.Name, |
| TreeHash: tree.Hash, |
| BlobHash: e.Hash, |
| Year: year, |
| Number: number, |
| }) |
| } |
| } |
| return files, nil |
| } |
| |
| // isCVEFilename reports whether name is the basename of a CVE file. |
| func isCVEFilename(name string) bool { |
| return strings.HasPrefix(name, "CVE-") && path.Ext(name) == ".json" |
| } |
| |
| func (f *File) ID() string { |
| return idstr.FindCVE(f.Filename) |
| } |
| |
| func (f *File) Name() string { |
| return f.Filename |
| } |
| |
| func (f *File) ReadAll(repo *git.Repository) ([]byte, error) { |
| return gitrepo.ReadAll(repo, f.BlobHash) |
| } |