151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
// 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
|
|
}
|