github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/php/composerlock/composerlock.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package composerlock extracts composer.lock files.
    16  package composerlock
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/google/osv-scalibr/extractor"
    26  	"github.com/google/osv-scalibr/extractor/filesystem"
    27  	"github.com/google/osv-scalibr/extractor/filesystem/osv"
    28  	"github.com/google/osv-scalibr/inventory"
    29  	"github.com/google/osv-scalibr/plugin"
    30  	"github.com/google/osv-scalibr/purl"
    31  )
    32  
    33  const (
    34  	// Name is the unique name of this extractor.
    35  	Name = "php/composerlock"
    36  )
    37  
    38  type composerPackage struct {
    39  	Name    string `json:"name"`
    40  	Version string `json:"version"`
    41  	Dist    struct {
    42  		Reference string `json:"reference"`
    43  	} `json:"dist"`
    44  }
    45  
    46  type composerLock struct {
    47  	Packages    []composerPackage `json:"packages"`
    48  	PackagesDev []composerPackage `json:"packages-dev"`
    49  }
    50  
    51  // Extractor extracts composer.lock files.
    52  type Extractor struct{}
    53  
    54  // New returns a new instance of the extractor.
    55  func New() filesystem.Extractor { return &Extractor{} }
    56  
    57  // Name of the extractor.
    58  func (e Extractor) Name() string { return Name }
    59  
    60  // Version of the extractor.
    61  func (e Extractor) Version() int { return 0 }
    62  
    63  // Requirements of the extractor.
    64  func (e Extractor) Requirements() *plugin.Capabilities {
    65  	return &plugin.Capabilities{}
    66  }
    67  
    68  // FileRequired returns true if the specified file matches composer.lock files.
    69  func (e Extractor) FileRequired(api filesystem.FileAPI) bool {
    70  	return filepath.Base(api.Path()) == "composer.lock"
    71  }
    72  
    73  func buildPackage(input *filesystem.ScanInput, pkg composerPackage, groups []string) *extractor.Package {
    74  	commit := pkg.Dist.Reference
    75  
    76  	// a dot means the reference is likely a tag, rather than a commit
    77  	if strings.Contains(commit, ".") {
    78  		commit = ""
    79  	}
    80  
    81  	return &extractor.Package{
    82  		Name:      pkg.Name,
    83  		Version:   pkg.Version,
    84  		PURLType:  purl.TypeComposer,
    85  		Locations: []string{input.Path},
    86  		SourceCode: &extractor.SourceCodeIdentifier{
    87  			Commit: commit,
    88  		},
    89  		Metadata: osv.DepGroupMetadata{
    90  			DepGroupVals: groups,
    91  		},
    92  	}
    93  }
    94  
    95  // Extract extracts packages from a composer.lock file passed through the scan input.
    96  func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
    97  	var parsedLockfile *composerLock
    98  
    99  	err := json.NewDecoder(input.Reader).Decode(&parsedLockfile)
   100  
   101  	if err != nil {
   102  		return inventory.Inventory{}, fmt.Errorf("could not extract: %w", err)
   103  	}
   104  
   105  	packages := make(
   106  		[]*extractor.Package,
   107  		0,
   108  		// len cannot return negative numbers, but the types can't reflect that
   109  		uint64(len(parsedLockfile.Packages))+uint64(len(parsedLockfile.PackagesDev)),
   110  	)
   111  
   112  	for _, pkg := range parsedLockfile.Packages {
   113  		packages = append(packages, buildPackage(input, pkg, []string{}))
   114  	}
   115  
   116  	for _, pkg := range parsedLockfile.PackagesDev {
   117  		packages = append(packages, buildPackage(input, pkg, []string{"dev"}))
   118  	}
   119  
   120  	return inventory.Inventory{Packages: packages}, nil
   121  }
   122  
   123  var _ filesystem.Extractor = Extractor{}