| // Copyright 2023 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 client |
| |
| import ( |
| "compress/gzip" |
| "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "io/fs" |
| "net/http" |
| "os" |
| "path/filepath" |
| |
| "golang.org/x/vuln/internal/derrors" |
| "golang.org/x/vuln/internal/osv" |
| ) |
| |
| type source interface { |
| // get returns the raw, uncompressed bytes at the |
| // requested endpoint, which should be bare with no file extensions |
| // (e.g., "index/modules" instead of "index/modules.json.gz"). |
| // It errors if the endpoint cannot be reached or does not exist |
| // in the expected form. |
| get(ctx context.Context, endpoint string) ([]byte, error) |
| } |
| |
| func newHTTPSource(url string, opts *Options) *httpSource { |
| c := http.DefaultClient |
| if opts != nil && opts.HTTPClient != nil { |
| c = opts.HTTPClient |
| } |
| return &httpSource{url: url, c: c} |
| } |
| |
| // httpSource reads a vulnerability database from an http(s) source. |
| type httpSource struct { |
| url string |
| c *http.Client |
| } |
| |
| func (hs *httpSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { |
| derrors.Wrap(&err, "get(%s)", endpoint) |
| |
| method := http.MethodGet |
| reqURL := fmt.Sprintf("%s/%s", hs.url, endpoint+".json.gz") |
| req, err := http.NewRequestWithContext(ctx, method, reqURL, nil) |
| if err != nil { |
| return nil, err |
| } |
| resp, err := hs.c.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| defer resp.Body.Close() |
| if resp.StatusCode != http.StatusOK { |
| return nil, fmt.Errorf("HTTP %s %s returned unexpected status: %s", method, reqURL, resp.Status) |
| } |
| |
| // Uncompress the result. |
| r, err := gzip.NewReader(resp.Body) |
| if err != nil { |
| return nil, err |
| } |
| defer r.Close() |
| |
| return io.ReadAll(r) |
| } |
| |
| func newLocalSource(dir string) *localSource { |
| return &localSource{fs: os.DirFS(dir)} |
| } |
| |
| // localSource reads a vulnerability database from a local file system. |
| type localSource struct { |
| fs fs.FS |
| } |
| |
| func (ls *localSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { |
| derrors.Wrap(&err, "get(%s)", endpoint) |
| |
| return fs.ReadFile(ls.fs, endpoint+".json") |
| } |
| |
| func newHybridSource(dir string) (*hybridSource, error) { |
| index, err := indexFromDir(dir) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &hybridSource{ |
| index: &inMemorySource{data: index}, |
| osv: &localSource{fs: os.DirFS(dir)}, |
| }, nil |
| } |
| |
| // hybridSource reads OSV entries from a local file system, but reads |
| // indexes from an in-memory map. |
| type hybridSource struct { |
| index *inMemorySource |
| osv *localSource |
| } |
| |
| func (hs *hybridSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { |
| derrors.Wrap(&err, "get(%s)", endpoint) |
| |
| dir, file := filepath.Split(endpoint) |
| |
| if filepath.Dir(dir) == indexDir { |
| return hs.index.get(ctx, endpoint) |
| } |
| |
| return hs.osv.get(ctx, file) |
| } |
| |
| // newInMemorySource creates a new in-memory source from OSV entries. |
| // Adapted from x/vulndb/internal/database.go. |
| func newInMemorySource(entries []*osv.Entry) (*inMemorySource, error) { |
| data, err := indexFromEntries(entries) |
| if err != nil { |
| return nil, err |
| } |
| |
| for _, entry := range entries { |
| b, err := json.Marshal(entry) |
| if err != nil { |
| return nil, err |
| } |
| data[entryEndpoint(entry.ID)] = b |
| } |
| |
| return &inMemorySource{data: data}, nil |
| } |
| |
| // inMemorySource reads databases from an in-memory map. |
| // Currently intended for use only in unit tests. |
| type inMemorySource struct { |
| data map[string][]byte |
| } |
| |
| func (db *inMemorySource) get(ctx context.Context, endpoint string) ([]byte, error) { |
| b, ok := db.data[endpoint] |
| if !ok { |
| return nil, fmt.Errorf("no data found at endpoint %q", endpoint) |
| } |
| return b, nil |
| } |