github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/javascript/parse_yarn_lock.go (about) 1 package javascript 2 3 import ( 4 "bufio" 5 "strings" 6 7 "github.com/anchore/syft/syft/artifact" 8 "github.com/anchore/syft/syft/file" 9 "github.com/anchore/syft/syft/pkg" 10 "github.com/anchore/syft/syft/pkg/cataloger/generic" 11 "github.com/anchore/syft/syft/pkg/cataloger/javascript/key" 12 yarnparse "github.com/anchore/syft/syft/pkg/cataloger/javascript/parser/yarn" 13 ) 14 15 // integrity check 16 var _ generic.Parser = parseYarnLock 17 18 type yarnLockPackage struct { 19 Name string 20 Version string 21 Integrity string 22 Resolved string 23 Dependencies map[string]string 24 } 25 26 func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 27 yarnMap := parseYarnLockFile(resolver, reader) 28 pkgs, _ := finalizeYarnLockWithoutPackageJSON(resolver, yarnMap, reader.Location) 29 return pkgs, nil, nil 30 } 31 32 func newYarnLockPackage(resolver file.Resolver, location file.Location, p *yarnLockPackage) pkg.Package { 33 if p == nil { 34 return pkg.Package{} 35 } 36 37 return finalizeLockPkg( 38 resolver, 39 location, 40 pkg.Package{ 41 Name: p.Name, 42 Version: p.Version, 43 Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 44 PURL: packageURL(p.Name, p.Version), 45 MetadataType: pkg.NpmPackageLockJSONMetadataType, 46 Language: pkg.JavaScript, 47 Type: pkg.NpmPkg, 48 Metadata: pkg.NpmPackageLockJSONMetadata{ 49 Resolved: p.Resolved, 50 Integrity: p.Integrity, 51 }, 52 }, 53 ) 54 } 55 56 // parseYarnLockFile takes a yarn.lock file and returns a map of packages 57 func parseYarnLockFile(_ file.Resolver, file file.LocationReadCloser) map[string]*yarnLockPackage { 58 /* 59 name@version[, name@version]: 60 version "xxx" 61 resolved "xxx" 62 integrity "xxx" 63 dependencies: 64 name "xxx" 65 name "xxx" 66 */ 67 lineNumber := 1 68 lockMap := map[string]*yarnLockPackage{} 69 70 scanner := bufio.NewScanner(file.ReadCloser) 71 scanner.Split(yarnparse.ScanBlocks) 72 for scanner.Scan() { 73 block := scanner.Bytes() 74 pkg, refVersions, newLine, err := parseYarnPkgBlock(block, lineNumber) 75 lineNumber = newLine + 2 76 if err != nil { 77 return nil 78 } else if pkg.Name == "" { 79 continue 80 } 81 82 for _, refVersion := range refVersions { 83 lockMap[refVersion] = &pkg 84 } 85 } 86 87 if err := scanner.Err(); err != nil { 88 return nil 89 } 90 91 return lockMap 92 } 93 94 // rootNameFromPath returns a "fake" root name of a package based on it 95 // directory name. This is used when there is no package.json file 96 // to create a root package. 97 func rootNameFromPath(location file.Location) string { 98 splits := strings.Split(location.RealPath, "/") 99 if len(splits) < 2 { 100 return "" 101 } 102 return splits[len(splits)-2] 103 } 104 105 // finalizeYarnLockWithPackageJSON takes a yarn.lock file and a package.json file and returns a map of packages 106 // nolint:funlen 107 func finalizeYarnLockWithPackageJSON(resolver file.Resolver, pkgjson *packageJSON, yarnlock map[string]*yarnLockPackage, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) { 108 if pkgjson == nil { 109 return nil, nil 110 } 111 112 var pkgs []pkg.Package 113 var relationships []artifact.Relationship 114 var root pkg.Package 115 seenPkgMap := make(map[string]bool) 116 117 p := yarnLockPackage{ 118 Name: pkgjson.Name, 119 Version: pkgjson.Version, 120 } 121 root = newYarnLockPackage(resolver, indexLocation, &p) 122 123 for name, version := range pkgjson.Dependencies { 124 depPkg := yarnlock[key.NpmPackageKey(name, version)] 125 dep := newYarnLockPackage(resolver, indexLocation, depPkg) 126 rel := artifact.Relationship{ 127 From: dep, 128 To: root, 129 Type: artifact.DependencyOfRelationship, 130 } 131 relationships = append(relationships, rel) 132 } 133 for name, version := range pkgjson.DevDependencies { 134 depPkg := yarnlock[key.NpmPackageKey(name, version)] 135 dep := newYarnLockPackage(resolver, indexLocation, depPkg) 136 rel := artifact.Relationship{ 137 From: dep, 138 To: root, 139 Type: artifact.DependencyOfRelationship, 140 } 141 relationships = append(relationships, rel) 142 } 143 pkgs = append(pkgs, root) 144 145 // create packages 146 for _, lockPkg := range yarnlock { 147 if seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] { 148 continue 149 } 150 151 pkg := newYarnLockPackage(resolver, indexLocation, lockPkg) 152 pkgs = append(pkgs, pkg) 153 seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] = true 154 } 155 156 // create relationships 157 for _, lockPkg := range yarnlock { 158 pkg := newYarnLockPackage(resolver, indexLocation, lockPkg) 159 160 for name, version := range lockPkg.Dependencies { 161 dep := yarnlock[key.NpmPackageKey(name, version)] 162 depPkg := newYarnLockPackage( 163 resolver, 164 indexLocation, 165 dep, 166 ) 167 168 rel := artifact.Relationship{ 169 From: depPkg, 170 To: pkg, 171 Type: artifact.DependencyOfRelationship, 172 } 173 relationships = append(relationships, rel) 174 } 175 } 176 177 pkg.Sort(pkgs) 178 pkg.SortRelationships(relationships) 179 return pkgs, relationships 180 } 181 182 // finalizeYarnLockWithoutPackageJSON takes a yarn.lock file and returns a map of packages 183 func finalizeYarnLockWithoutPackageJSON(resolver file.Resolver, yarnlock map[string]*yarnLockPackage, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) { 184 var pkgs []pkg.Package 185 var relationships []artifact.Relationship 186 seenPkgMap := make(map[string]bool) 187 188 name := rootNameFromPath(indexLocation) 189 if name != "" { 190 p := yarnLockPackage{ 191 Name: name, 192 Version: "0.0.0", 193 } 194 root := newYarnLockPackage(resolver, indexLocation, &p) 195 pkgs = append(pkgs, root) 196 } 197 198 // create packages 199 for _, lockPkg := range yarnlock { 200 if seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] { 201 continue 202 } 203 204 pkg := newYarnLockPackage(resolver, indexLocation, lockPkg) 205 pkgs = append(pkgs, pkg) 206 seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] = true 207 } 208 209 // create relationships 210 for _, lockPkg := range yarnlock { 211 pkg := newYarnLockPackage(resolver, indexLocation, lockPkg) 212 213 for name, version := range lockPkg.Dependencies { 214 dep := yarnlock[key.NpmPackageKey(name, version)] 215 depPkg := newYarnLockPackage( 216 resolver, 217 indexLocation, 218 dep, 219 ) 220 221 rel := artifact.Relationship{ 222 From: depPkg, 223 To: pkg, 224 Type: artifact.DependencyOfRelationship, 225 } 226 relationships = append(relationships, rel) 227 } 228 } 229 230 pkg.Sort(pkgs) 231 pkg.SortRelationships(relationships) 232 return pkgs, relationships 233 } 234 235 /* 236 parseYarnPkgBlock parses a yarn package block like this and return a yarnLockPackage struct 237 and refVersions which are "tslib@^2.1.0" and "tslib@^2.3.0" in this example 238 239 "tslib@^2.1.0", "tslib@^2.3.0": 240 241 "integrity" "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" 242 "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz" 243 "version" "2.4.1" 244 */ 245 func parseYarnPkgBlock(block []byte, lineNum int) (pkg yarnLockPackage, refVersions []string, newLine int, err error) { 246 pkgRef, lineNumber, err := yarnparse.ParseBlock(block, lineNum) 247 for _, pattern := range pkgRef.Patterns { 248 nv := strings.Split(pattern, ":") 249 if len(nv) != 2 { 250 continue 251 } 252 refVersions = append(refVersions, key.NpmPackageKey(nv[0], nv[1])) 253 } 254 255 return yarnLockPackage{ 256 Name: pkgRef.Name, 257 Version: pkgRef.Version, 258 Integrity: pkgRef.Integrity, 259 Resolved: pkgRef.Resolved, 260 Dependencies: pkgRef.Dependencies, 261 }, refVersions, lineNumber, err 262 }