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{}