github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/analyzer/language/dart/pub/pubspec.go (about)

     1  package pub
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/fs"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"sort"
    11  
    12  	"github.com/samber/lo"
    13  	"golang.org/x/exp/maps"
    14  	"golang.org/x/xerrors"
    15  	"gopkg.in/yaml.v3"
    16  
    17  	"github.com/aquasecurity/go-dep-parser/pkg/dart/pub"
    18  	godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
    19  	"github.com/aquasecurity/go-dep-parser/pkg/utils"
    20  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    21  	"github.com/devseccon/trivy/pkg/fanal/analyzer/language"
    22  	"github.com/devseccon/trivy/pkg/fanal/types"
    23  	"github.com/devseccon/trivy/pkg/log"
    24  	"github.com/devseccon/trivy/pkg/utils/fsutils"
    25  )
    26  
    27  func init() {
    28  	analyzer.RegisterPostAnalyzer(analyzer.TypePubSpecLock, newPubSpecLockAnalyzer)
    29  }
    30  
    31  const (
    32  	version             = 2
    33  	pubSpecYamlFileName = "pubspec.yaml"
    34  )
    35  
    36  // pubSpecLockAnalyzer analyzes `pubspec.lock`
    37  type pubSpecLockAnalyzer struct {
    38  	parser godeptypes.Parser
    39  }
    40  
    41  func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) {
    42  	return pubSpecLockAnalyzer{
    43  		parser: pub.NewParser(),
    44  	}, nil
    45  }
    46  
    47  func (a pubSpecLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) {
    48  	var apps []types.Application
    49  
    50  	// get all DependsOn from cache dir
    51  	// lib ID -> DependsOn names
    52  	allDependsOn, err := findDependsOn()
    53  	if err != nil {
    54  		log.Logger.Warnf("Unable to parse cache dir: %s", err)
    55  	}
    56  
    57  	required := func(path string, d fs.DirEntry) bool {
    58  		return filepath.Base(path) == types.PubSpecLock
    59  	}
    60  
    61  	err = fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error {
    62  		app, err := language.Parse(types.Pub, path, r, a.parser)
    63  		if err != nil {
    64  			return xerrors.Errorf("unable to parse %q: %w", path, err)
    65  		}
    66  
    67  		if app == nil {
    68  			return nil
    69  		}
    70  
    71  		if allDependsOn != nil {
    72  			// Required to search for library versions for DependsOn.
    73  			libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, string) {
    74  				return lib.Name, lib.ID
    75  			})
    76  
    77  			for i, lib := range app.Libraries {
    78  				var dependsOn []string
    79  				for _, depName := range allDependsOn[lib.ID] {
    80  					if depID, ok := libs[depName]; ok {
    81  						dependsOn = append(dependsOn, depID)
    82  					}
    83  				}
    84  				app.Libraries[i].DependsOn = dependsOn
    85  			}
    86  		}
    87  
    88  		sort.Sort(app.Libraries)
    89  		apps = append(apps, *app)
    90  		return nil
    91  	})
    92  	if err != nil {
    93  		return nil, xerrors.Errorf("walk error: %w", err)
    94  	}
    95  
    96  	return &analyzer.AnalysisResult{
    97  		Applications: apps,
    98  	}, nil
    99  }
   100  
   101  func findDependsOn() (map[string][]string, error) {
   102  	dir := cacheDir()
   103  	if !fsutils.DirExists(dir) {
   104  		log.Logger.Debugf("Cache dir (%s) not found. Need 'dart pub get' to fill dependency relationships", dir)
   105  		return nil, nil
   106  	}
   107  
   108  	required := func(path string, d fs.DirEntry) bool {
   109  		return filepath.Base(path) == pubSpecYamlFileName
   110  	}
   111  
   112  	deps := make(map[string][]string)
   113  	if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, d fs.DirEntry, r io.Reader) error {
   114  		id, dependsOn, err := parsePubSpecYaml(r)
   115  		if err != nil {
   116  			log.Logger.Debugf("Unable to parse %q: %s", path, err)
   117  			return nil
   118  		}
   119  		if id != "" {
   120  			deps[id] = dependsOn
   121  		}
   122  		return nil
   123  
   124  	}); err != nil {
   125  		return nil, xerrors.Errorf("walk error: %w", err)
   126  	}
   127  	return deps, nil
   128  }
   129  
   130  // https://dart.dev/tools/pub/glossary#system-cache
   131  func cacheDir() string {
   132  	if dir := os.Getenv("PUB_CACHE"); dir != "" {
   133  		return dir
   134  	}
   135  
   136  	// `%LOCALAPPDATA%\Pub\Cache` for Windows
   137  	if runtime.GOOS == "windows" {
   138  		return filepath.Join(os.Getenv("LOCALAPPDATA"), "Pub", "Cache")
   139  	}
   140  
   141  	// `~/.pub-cache` for Linux or Mac
   142  	return filepath.Join(os.Getenv("HOME"), ".pub_cache")
   143  }
   144  
   145  type pubSpecYaml struct {
   146  	Name         string                 `yaml:"name"`
   147  	Version      string                 `yaml:"version,omitempty"`
   148  	Dependencies map[string]interface{} `yaml:"dependencies,omitempty"`
   149  }
   150  
   151  func parsePubSpecYaml(r io.Reader) (string, []string, error) {
   152  	var spec pubSpecYaml
   153  	if err := yaml.NewDecoder(r).Decode(&spec); err != nil {
   154  		return "", nil, xerrors.Errorf("unable to decode: %w", err)
   155  	}
   156  
   157  	// Version is a required field only for packages from pub.dev:
   158  	// https://dart.dev/tools/pub/pubspec#version
   159  	// We can skip packages without version,
   160  	// because we compare packages by ID (name+version)
   161  	if spec.Version == "" || len(spec.Dependencies) == 0 {
   162  		return "", nil, nil
   163  	}
   164  
   165  	// pubspec.yaml uses version ranges
   166  	// save only dependencies names
   167  	dependsOn := maps.Keys(spec.Dependencies)
   168  
   169  	return utils.PackageID(spec.Name, spec.Version), dependsOn, nil
   170  }
   171  
   172  func (a pubSpecLockAnalyzer) Required(filePath string, _ os.FileInfo) bool {
   173  	return filepath.Base(filePath) == types.PubSpecLock
   174  }
   175  
   176  func (a pubSpecLockAnalyzer) Type() analyzer.Type {
   177  	return analyzer.TypePubSpecLock
   178  }
   179  
   180  func (a pubSpecLockAnalyzer) Version() int {
   181  	return version
   182  }