src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

source.go (3659B)


      1 // Copyright 2023 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package client
      6 
      7 import (
      8 	"compress/gzip"
      9 	"context"
     10 	"encoding/json"
     11 	"fmt"
     12 	"io"
     13 	"io/fs"
     14 	"net/http"
     15 	"os"
     16 	"path/filepath"
     17 
     18 	"golang.org/x/vuln/internal/derrors"
     19 	"golang.org/x/vuln/internal/osv"
     20 )
     21 
     22 type source interface {
     23 	// get returns the raw, uncompressed bytes at the
     24 	// requested endpoint, which should be bare with no file extensions
     25 	// (e.g., "index/modules" instead of "index/modules.json.gz").
     26 	// It errors if the endpoint cannot be reached or does not exist
     27 	// in the expected form.
     28 	get(ctx context.Context, endpoint string) ([]byte, error)
     29 }
     30 
     31 func newHTTPSource(url string, opts *Options) *httpSource {
     32 	c := http.DefaultClient
     33 	if opts != nil && opts.HTTPClient != nil {
     34 		c = opts.HTTPClient
     35 	}
     36 	return &httpSource{url: url, c: c}
     37 }
     38 
     39 // httpSource reads a vulnerability database from an http(s) source.
     40 type httpSource struct {
     41 	url string
     42 	c   *http.Client
     43 }
     44 
     45 func (hs *httpSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
     46 	derrors.Wrap(&err, "get(%s)", endpoint)
     47 
     48 	method := http.MethodGet
     49 	reqURL := fmt.Sprintf("%s/%s", hs.url, endpoint+".json.gz")
     50 	req, err := http.NewRequestWithContext(ctx, method, reqURL, nil)
     51 	if err != nil {
     52 		return nil, err
     53 	}
     54 	resp, err := hs.c.Do(req)
     55 	if err != nil {
     56 		return nil, err
     57 	}
     58 	defer resp.Body.Close()
     59 	if resp.StatusCode != http.StatusOK {
     60 		return nil, fmt.Errorf("HTTP %s %s returned unexpected status: %s", method, reqURL, resp.Status)
     61 	}
     62 
     63 	// Uncompress the result.
     64 	r, err := gzip.NewReader(resp.Body)
     65 	if err != nil {
     66 		return nil, err
     67 	}
     68 	defer r.Close()
     69 
     70 	return io.ReadAll(r)
     71 }
     72 
     73 func newLocalSource(dir string) *localSource {
     74 	return &localSource{fs: os.DirFS(dir)}
     75 }
     76 
     77 // localSource reads a vulnerability database from a local file system.
     78 type localSource struct {
     79 	fs fs.FS
     80 }
     81 
     82 func (ls *localSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
     83 	derrors.Wrap(&err, "get(%s)", endpoint)
     84 
     85 	return fs.ReadFile(ls.fs, endpoint+".json")
     86 }
     87 
     88 func newHybridSource(dir string) (*hybridSource, error) {
     89 	index, err := indexFromDir(dir)
     90 	if err != nil {
     91 		return nil, err
     92 	}
     93 
     94 	return &hybridSource{
     95 		index: &inMemorySource{data: index},
     96 		osv:   &localSource{fs: os.DirFS(dir)},
     97 	}, nil
     98 }
     99 
    100 // hybridSource reads OSV entries from a local file system, but reads
    101 // indexes from an in-memory map.
    102 type hybridSource struct {
    103 	index *inMemorySource
    104 	osv   *localSource
    105 }
    106 
    107 func (hs *hybridSource) get(ctx context.Context, endpoint string) (_ []byte, err error) {
    108 	derrors.Wrap(&err, "get(%s)", endpoint)
    109 
    110 	dir, file := filepath.Split(endpoint)
    111 
    112 	if filepath.Dir(dir) == indexDir {
    113 		return hs.index.get(ctx, endpoint)
    114 	}
    115 
    116 	return hs.osv.get(ctx, file)
    117 }
    118 
    119 // newInMemorySource creates a new in-memory source from OSV entries.
    120 // Adapted from x/vulndb/internal/database.go.
    121 func newInMemorySource(entries []*osv.Entry) (*inMemorySource, error) {
    122 	data, err := indexFromEntries(entries)
    123 	if err != nil {
    124 		return nil, err
    125 	}
    126 
    127 	for _, entry := range entries {
    128 		b, err := json.Marshal(entry)
    129 		if err != nil {
    130 			return nil, err
    131 		}
    132 		data[entryEndpoint(entry.ID)] = b
    133 	}
    134 
    135 	return &inMemorySource{data: data}, nil
    136 }
    137 
    138 // inMemorySource reads databases from an in-memory map.
    139 // Currently intended for use only in unit tests.
    140 type inMemorySource struct {
    141 	data map[string][]byte
    142 }
    143 
    144 func (db *inMemorySource) get(ctx context.Context, endpoint string) ([]byte, error) {
    145 	b, ok := db.data[endpoint]
    146 	if !ok {
    147 		return nil, fmt.Errorf("no data found at endpoint %q", endpoint)
    148 	}
    149 	return b, nil
    150 }