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

     1  package packaging
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"context"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  
    11  	"golang.org/x/xerrors"
    12  
    13  	dio "github.com/aquasecurity/go-dep-parser/pkg/io"
    14  	"github.com/aquasecurity/go-dep-parser/pkg/python/packaging"
    15  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    16  	"github.com/devseccon/trivy/pkg/fanal/analyzer/language"
    17  	"github.com/devseccon/trivy/pkg/fanal/types"
    18  )
    19  
    20  func init() {
    21  	analyzer.RegisterAnalyzer(&packagingAnalyzer{})
    22  }
    23  
    24  const version = 1
    25  
    26  var (
    27  	requiredFiles = []string{
    28  		// .egg format
    29  		// https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats
    30  		".egg", // zip format
    31  		"EGG-INFO/PKG-INFO",
    32  
    33  		// .egg-info format: .egg-info can be a file or directory
    34  		// https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats
    35  		".egg-info",
    36  		".egg-info/PKG-INFO",
    37  
    38  		// wheel
    39  		".dist-info/METADATA",
    40  	}
    41  )
    42  
    43  type packagingAnalyzer struct{}
    44  
    45  // Analyze analyzes egg and wheel files.
    46  func (a packagingAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
    47  	r := input.Content
    48  
    49  	// .egg file is zip format and PKG-INFO needs to be extracted from the zip file.
    50  	if strings.HasSuffix(input.FilePath, ".egg") {
    51  		pkginfoInZip, err := a.analyzeEggZip(input.Content, input.Info.Size())
    52  		if err != nil {
    53  			return nil, xerrors.Errorf("egg analysis error: %w", err)
    54  		}
    55  
    56  		// Egg archive may not contain required files, then we will get nil. Skip this archives
    57  		if pkginfoInZip == nil {
    58  			return nil, nil
    59  		}
    60  
    61  		r = pkginfoInZip
    62  	}
    63  
    64  	p := packaging.NewParser()
    65  	return language.AnalyzePackage(types.PythonPkg, input.FilePath, r, p, input.Options.FileChecksum)
    66  }
    67  
    68  func (a packagingAnalyzer) analyzeEggZip(r io.ReaderAt, size int64) (dio.ReadSeekerAt, error) {
    69  	zr, err := zip.NewReader(r, size)
    70  	if err != nil {
    71  		return nil, xerrors.Errorf("zip reader error: %w", err)
    72  	}
    73  
    74  	for _, file := range zr.File {
    75  		if !a.Required(file.Name, nil) {
    76  			continue
    77  		}
    78  
    79  		return a.open(file)
    80  	}
    81  
    82  	return nil, nil
    83  }
    84  
    85  func (a packagingAnalyzer) open(file *zip.File) (dio.ReadSeekerAt, error) {
    86  	f, err := file.Open()
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	defer f.Close()
    91  
    92  	b, err := io.ReadAll(f)
    93  	if err != nil {
    94  		return nil, xerrors.Errorf("file %s open error: %w", file.Name, err)
    95  	}
    96  
    97  	return bytes.NewReader(b), nil
    98  }
    99  
   100  func (a packagingAnalyzer) Required(filePath string, _ os.FileInfo) bool {
   101  	for _, r := range requiredFiles {
   102  		if strings.HasSuffix(filePath, r) {
   103  			return true
   104  		}
   105  	}
   106  	return false
   107  }
   108  
   109  func (a packagingAnalyzer) Type() analyzer.Type {
   110  	return analyzer.TypePythonPkg
   111  }
   112  
   113  func (a packagingAnalyzer) Version() int {
   114  	return version
   115  }