github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/misc/vscodeextensions/vscodeextensions.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 vscodeextensions extracts vscode extensions. 16 package vscodeextensions 17 18 import ( 19 "context" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "path/filepath" 24 "strings" 25 26 "github.com/google/osv-scalibr/extractor" 27 "github.com/google/osv-scalibr/extractor/filesystem" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/plugin" 30 "github.com/google/osv-scalibr/purl" 31 ) 32 33 // Name is the name for the vscode extensions extractor 34 const Name = "vscode/extensions" 35 36 const extensionsSubPath = "/.vscode/extensions/extensions.json" 37 38 type extension struct { 39 Identifier struct { 40 ID string `json:"id"` 41 } `json:"identifier"` 42 Version string `json:"version"` 43 Location struct { 44 Path string `json:"path"` 45 } `json:"location"` 46 Metadata Metadata `json:"metadata"` 47 } 48 49 func (e *extension) validate() error { 50 if e.Identifier.ID == "" { 51 return errors.New("extension 'Identifier.ID' cannot be empty") 52 } 53 if e.Version == "" { 54 return errors.New("extension 'Version' cannot be empty") 55 } 56 return nil 57 } 58 59 // Extractor extracts vscode extensions 60 type Extractor struct{} 61 62 // New returns an vscode extractor. 63 func New() filesystem.Extractor { 64 return &Extractor{} 65 } 66 67 // Name of the extractor. 68 func (e Extractor) Name() string { return Name } 69 70 // Version of the extractor. 71 func (e Extractor) Version() int { return 0 } 72 73 // Requirements of the extractor. 74 func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } 75 76 // FileRequired returns true if the file contains vscode extensions information 77 func (e Extractor) FileRequired(api filesystem.FileAPI) bool { 78 path := api.Path() 79 path = filepath.ToSlash(path) 80 return strings.HasSuffix(path, extensionsSubPath) 81 } 82 83 // Extract extracts vscode extensions 84 func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 85 var exts []*extension 86 if err := json.NewDecoder(input.Reader).Decode(&exts); err != nil { 87 return inventory.Inventory{}, fmt.Errorf("could not extract: %w", err) 88 } 89 90 pkgs := make([]*extractor.Package, 0, len(exts)) 91 for _, ext := range exts { 92 if err := ext.validate(); err != nil { 93 return inventory.Inventory{}, fmt.Errorf("bad format: %w", err) 94 } 95 pkgs = append(pkgs, &extractor.Package{ 96 Name: ext.Identifier.ID, 97 Version: ext.Version, 98 PURLType: purl.TypeGeneric, 99 Locations: []string{ext.Location.Path, input.Path}, 100 Metadata: &ext.Metadata, 101 }) 102 } 103 104 return inventory.Inventory{Packages: pkgs}, nil 105 }