Projects STRLCPY syft Commits 42fa9e49
🤬
  • feat: update syft license concept to complex struct (#1743)

    this PR makes the following changes to update the underlying license model to have more expressive capabilities
    it also provides some guarantee's surrounding the license values themselves
    
    - Licenses are updated from string -> pkg.LicenseSet which contain pkg.License with the following fields:
    - original `Value` read by syft
    - If it's possible to construct licenses will always have a valid SPDX expression for downstream consumption
    - the above is run against a generated list of SPDX license ID to try and find the correct ID
    - SPDX concluded vs declared is added to the new struct
    - URL source for license is added to the new struct
    - Location source is added to the new struct to show where the expression was pulled from
  • Loading...
  • Christopher Angelo Phillips committed with GitHub 12 months ago
    42fa9e49
    1 parent 8046f095
Showing first 75 files as there are too many
  • ■ ■ ■ ■ ■
    go.mod
    skipped 55 lines
    56 56   github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574
    57 57   github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
    58 58   github.com/docker/docker v23.0.6+incompatible
     59 + github.com/github/go-spdx/v2 v2.1.2
    59 60   github.com/go-git/go-billy/v5 v5.4.1
    60 61   github.com/go-git/go-git/v5 v5.6.1
    61 62   github.com/google/go-containerregistry v0.15.1
    skipped 116 lines
  • ■ ■ ■ ■ ■ ■
    go.sum
    skipped 206 lines
    207 207  github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
    208 208  github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
    209 209  github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
     210 +github.com/github/go-spdx/v2 v2.1.2 h1:p+Tv0yMgcuO0/vnMe9Qh4tmUgYhI6AsLVlakZ/Sx+DM=
     211 +github.com/github/go-spdx/v2 v2.1.2/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
    210 212  github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
    211 213  github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
    212 214  github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
    skipped 990 lines
  • ■ ■ ■ ■
    internal/constants.go
    skipped 5 lines
    6 6   
    7 7   // JSONSchemaVersion is the current schema version output by the JSON encoder
    8 8   // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
    9  - JSONSchemaVersion = "7.1.6"
     9 + JSONSchemaVersion = "8.0.0"
    10 10  )
    11 11   
  • ■ ■ ■ ■ ■ ■
    internal/licenses/parser.go
    skipped 3 lines
    4 4   "io"
    5 5   
    6 6   "github.com/google/licensecheck"
    7  - "golang.org/x/exp/slices"
     7 + 
     8 + "github.com/anchore/syft/syft/license"
     9 + "github.com/anchore/syft/syft/pkg"
     10 + "github.com/anchore/syft/syft/source"
    8 11  )
    9 12   
    10 13  const (
    skipped 2 lines
    13 16  )
    14 17   
    15 18  // Parse scans the contents of a license file to attempt to determine the type of license it is
    16  -func Parse(reader io.Reader) (licenses []string, err error) {
     19 +func Parse(reader io.Reader, l source.Location) (licenses []pkg.License, err error) {
     20 + licenses = make([]pkg.License, 0)
    17 21   contents, err := io.ReadAll(reader)
    18 22   if err != nil {
    19 23   return nil, err
    20 24   }
    21 25   cov := licensecheck.Scan(contents)
    22  - 
    23  - if cov.Percent < float64(coverageThreshold) {
    24  - licenses = append(licenses, unknownLicenseType)
     26 + if cov.Percent < coverageThreshold {
     27 + // unknown or no licenses here?
     28 + return licenses, nil
    25 29   }
     30 + 
    26 31   for _, m := range cov.Match {
    27  - if slices.Contains(licenses, m.ID) {
    28  - continue
    29  - }
    30  - licenses = append(licenses, m.ID)
     32 + lic := pkg.NewLicenseFromLocations(m.ID, l)
     33 + lic.Type = license.Concluded
     34 + 
     35 + licenses = append(licenses, lic)
    31 36   }
    32  - return
     37 + 
     38 + return licenses, nil
    33 39  }
    34 40   
  • ■ ■ ■ ■ ■
    internal/stringset.go
    skipped 14 lines
    15 15  }
    16 16   
    17 17  // Add a string to the set.
    18  -func (s StringSet) Add(i string) {
    19  - s[i] = struct{}{}
     18 +func (s StringSet) Add(i ...string) {
     19 + for _, str := range i {
     20 + s[str] = struct{}{}
     21 + }
    20 22  }
    21 23   
    22 24  // Remove a string from the set.
    skipped 19 lines
    42 44   return ret
    43 45  }
    44 46   
     47 +func (s StringSet) Equals(o StringSet) bool {
     48 + if len(s) != len(o) {
     49 + return false
     50 + }
     51 + for k := range s {
     52 + if !o.Contains(k) {
     53 + return false
     54 + }
     55 + }
     56 + return true
     57 +}
     58 + 
     59 +func (s StringSet) Empty() bool {
     60 + return len(s) < 1
     61 +}
     62 + 
  • ■ ■ ■ ■ ■
    schema/json/generate.go
    skipped 77 lines
    78 78   }
    79 79   documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftjsonModel.Document{}))
    80 80   metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&artifactMetadataContainer{}))
    81  - 
    82 81   // TODO: inject source definitions
    83 82   
    84 83   // inject the definitions of all metadatas into the schema definitions
    skipped 89 lines
  • ■ ■ ■ ■ ■ ■
    schema/json/schema-8.0.0.json
     1 +{
     2 + "$schema": "https://json-schema.org/draft/2020-12/schema",
     3 + "$id": "https://github.com/anchore/syft/syft/formats/syftjson/model/document",
     4 + "$ref": "#/$defs/Document",
     5 + "$defs": {
     6 + "AlpmFileRecord": {
     7 + "properties": {
     8 + "path": {
     9 + "type": "string"
     10 + },
     11 + "type": {
     12 + "type": "string"
     13 + },
     14 + "uid": {
     15 + "type": "string"
     16 + },
     17 + "gid": {
     18 + "type": "string"
     19 + },
     20 + "time": {
     21 + "type": "string",
     22 + "format": "date-time"
     23 + },
     24 + "size": {
     25 + "type": "string"
     26 + },
     27 + "link": {
     28 + "type": "string"
     29 + },
     30 + "digest": {
     31 + "items": {
     32 + "$ref": "#/$defs/Digest"
     33 + },
     34 + "type": "array"
     35 + }
     36 + },
     37 + "type": "object"
     38 + },
     39 + "AlpmMetadata": {
     40 + "properties": {
     41 + "basepackage": {
     42 + "type": "string"
     43 + },
     44 + "package": {
     45 + "type": "string"
     46 + },
     47 + "version": {
     48 + "type": "string"
     49 + },
     50 + "description": {
     51 + "type": "string"
     52 + },
     53 + "architecture": {
     54 + "type": "string"
     55 + },
     56 + "size": {
     57 + "type": "integer"
     58 + },
     59 + "packager": {
     60 + "type": "string"
     61 + },
     62 + "url": {
     63 + "type": "string"
     64 + },
     65 + "validation": {
     66 + "type": "string"
     67 + },
     68 + "reason": {
     69 + "type": "integer"
     70 + },
     71 + "files": {
     72 + "items": {
     73 + "$ref": "#/$defs/AlpmFileRecord"
     74 + },
     75 + "type": "array"
     76 + },
     77 + "backup": {
     78 + "items": {
     79 + "$ref": "#/$defs/AlpmFileRecord"
     80 + },
     81 + "type": "array"
     82 + }
     83 + },
     84 + "type": "object",
     85 + "required": [
     86 + "basepackage",
     87 + "package",
     88 + "version",
     89 + "description",
     90 + "architecture",
     91 + "size",
     92 + "packager",
     93 + "url",
     94 + "validation",
     95 + "reason",
     96 + "files",
     97 + "backup"
     98 + ]
     99 + },
     100 + "ApkFileRecord": {
     101 + "properties": {
     102 + "path": {
     103 + "type": "string"
     104 + },
     105 + "ownerUid": {
     106 + "type": "string"
     107 + },
     108 + "ownerGid": {
     109 + "type": "string"
     110 + },
     111 + "permissions": {
     112 + "type": "string"
     113 + },
     114 + "digest": {
     115 + "$ref": "#/$defs/Digest"
     116 + }
     117 + },
     118 + "type": "object",
     119 + "required": [
     120 + "path"
     121 + ]
     122 + },
     123 + "ApkMetadata": {
     124 + "properties": {
     125 + "package": {
     126 + "type": "string"
     127 + },
     128 + "originPackage": {
     129 + "type": "string"
     130 + },
     131 + "maintainer": {
     132 + "type": "string"
     133 + },
     134 + "version": {
     135 + "type": "string"
     136 + },
     137 + "architecture": {
     138 + "type": "string"
     139 + },
     140 + "url": {
     141 + "type": "string"
     142 + },
     143 + "description": {
     144 + "type": "string"
     145 + },
     146 + "size": {
     147 + "type": "integer"
     148 + },
     149 + "installedSize": {
     150 + "type": "integer"
     151 + },
     152 + "pullDependencies": {
     153 + "items": {
     154 + "type": "string"
     155 + },
     156 + "type": "array"
     157 + },
     158 + "provides": {
     159 + "items": {
     160 + "type": "string"
     161 + },
     162 + "type": "array"
     163 + },
     164 + "pullChecksum": {
     165 + "type": "string"
     166 + },
     167 + "gitCommitOfApkPort": {
     168 + "type": "string"
     169 + },
     170 + "files": {
     171 + "items": {
     172 + "$ref": "#/$defs/ApkFileRecord"
     173 + },
     174 + "type": "array"
     175 + }
     176 + },
     177 + "type": "object",
     178 + "required": [
     179 + "package",
     180 + "originPackage",
     181 + "maintainer",
     182 + "version",
     183 + "architecture",
     184 + "url",
     185 + "description",
     186 + "size",
     187 + "installedSize",
     188 + "pullDependencies",
     189 + "provides",
     190 + "pullChecksum",
     191 + "gitCommitOfApkPort",
     192 + "files"
     193 + ]
     194 + },
     195 + "BinaryMetadata": {
     196 + "properties": {
     197 + "matches": {
     198 + "items": {
     199 + "$ref": "#/$defs/ClassifierMatch"
     200 + },
     201 + "type": "array"
     202 + }
     203 + },
     204 + "type": "object",
     205 + "required": [
     206 + "matches"
     207 + ]
     208 + },
     209 + "CargoPackageMetadata": {
     210 + "properties": {
     211 + "name": {
     212 + "type": "string"
     213 + },
     214 + "version": {
     215 + "type": "string"
     216 + },
     217 + "source": {
     218 + "type": "string"
     219 + },
     220 + "checksum": {
     221 + "type": "string"
     222 + },
     223 + "dependencies": {
     224 + "items": {
     225 + "type": "string"
     226 + },
     227 + "type": "array"
     228 + }
     229 + },
     230 + "type": "object",
     231 + "required": [
     232 + "name",
     233 + "version",
     234 + "source",
     235 + "checksum",
     236 + "dependencies"
     237 + ]
     238 + },
     239 + "ClassifierMatch": {
     240 + "properties": {
     241 + "classifier": {
     242 + "type": "string"
     243 + },
     244 + "location": {
     245 + "$ref": "#/$defs/Location"
     246 + }
     247 + },
     248 + "type": "object",
     249 + "required": [
     250 + "classifier",
     251 + "location"
     252 + ]
     253 + },
     254 + "CocoapodsMetadata": {
     255 + "properties": {
     256 + "checksum": {
     257 + "type": "string"
     258 + }
     259 + },
     260 + "type": "object",
     261 + "required": [
     262 + "checksum"
     263 + ]
     264 + },
     265 + "ConanLockMetadata": {
     266 + "properties": {
     267 + "ref": {
     268 + "type": "string"
     269 + },
     270 + "package_id": {
     271 + "type": "string"
     272 + },
     273 + "prev": {
     274 + "type": "string"
     275 + },
     276 + "requires": {
     277 + "type": "string"
     278 + },
     279 + "build_requires": {
     280 + "type": "string"
     281 + },
     282 + "py_requires": {
     283 + "type": "string"
     284 + },
     285 + "options": {
     286 + "patternProperties": {
     287 + ".*": {
     288 + "type": "string"
     289 + }
     290 + },
     291 + "type": "object"
     292 + },
     293 + "path": {
     294 + "type": "string"
     295 + },
     296 + "context": {
     297 + "type": "string"
     298 + }
     299 + },
     300 + "type": "object",
     301 + "required": [
     302 + "ref"
     303 + ]
     304 + },
     305 + "ConanMetadata": {
     306 + "properties": {
     307 + "ref": {
     308 + "type": "string"
     309 + }
     310 + },
     311 + "type": "object",
     312 + "required": [
     313 + "ref"
     314 + ]
     315 + },
     316 + "Coordinates": {
     317 + "properties": {
     318 + "path": {
     319 + "type": "string"
     320 + },
     321 + "layerID": {
     322 + "type": "string"
     323 + }
     324 + },
     325 + "type": "object",
     326 + "required": [
     327 + "path"
     328 + ]
     329 + },
     330 + "DartPubMetadata": {
     331 + "properties": {
     332 + "name": {
     333 + "type": "string"
     334 + },
     335 + "version": {
     336 + "type": "string"
     337 + },
     338 + "hosted_url": {
     339 + "type": "string"
     340 + },
     341 + "vcs_url": {
     342 + "type": "string"
     343 + }
     344 + },
     345 + "type": "object",
     346 + "required": [
     347 + "name",
     348 + "version"
     349 + ]
     350 + },
     351 + "Descriptor": {
     352 + "properties": {
     353 + "name": {
     354 + "type": "string"
     355 + },
     356 + "version": {
     357 + "type": "string"
     358 + },
     359 + "configuration": true
     360 + },
     361 + "type": "object",
     362 + "required": [
     363 + "name",
     364 + "version"
     365 + ]
     366 + },
     367 + "Digest": {
     368 + "properties": {
     369 + "algorithm": {
     370 + "type": "string"
     371 + },
     372 + "value": {
     373 + "type": "string"
     374 + }
     375 + },
     376 + "type": "object",
     377 + "required": [
     378 + "algorithm",
     379 + "value"
     380 + ]
     381 + },
     382 + "Document": {
     383 + "properties": {
     384 + "artifacts": {
     385 + "items": {
     386 + "$ref": "#/$defs/Package"
     387 + },
     388 + "type": "array"
     389 + },
     390 + "artifactRelationships": {
     391 + "items": {
     392 + "$ref": "#/$defs/Relationship"
     393 + },
     394 + "type": "array"
     395 + },
     396 + "files": {
     397 + "items": {
     398 + "$ref": "#/$defs/File"
     399 + },
     400 + "type": "array"
     401 + },
     402 + "secrets": {
     403 + "items": {
     404 + "$ref": "#/$defs/Secrets"
     405 + },
     406 + "type": "array"
     407 + },
     408 + "source": {
     409 + "$ref": "#/$defs/Source"
     410 + },
     411 + "distro": {
     412 + "$ref": "#/$defs/LinuxRelease"
     413 + },
     414 + "descriptor": {
     415 + "$ref": "#/$defs/Descriptor"
     416 + },
     417 + "schema": {
     418 + "$ref": "#/$defs/Schema"
     419 + }
     420 + },
     421 + "type": "object",
     422 + "required": [
     423 + "artifacts",
     424 + "artifactRelationships",
     425 + "source",
     426 + "distro",
     427 + "descriptor",
     428 + "schema"
     429 + ]
     430 + },
     431 + "DotnetDepsMetadata": {
     432 + "properties": {
     433 + "name": {
     434 + "type": "string"
     435 + },
     436 + "version": {
     437 + "type": "string"
     438 + },
     439 + "path": {
     440 + "type": "string"
     441 + },
     442 + "sha512": {
     443 + "type": "string"
     444 + },
     445 + "hashPath": {
     446 + "type": "string"
     447 + }
     448 + },
     449 + "type": "object",
     450 + "required": [
     451 + "name",
     452 + "version",
     453 + "path",
     454 + "sha512",
     455 + "hashPath"
     456 + ]
     457 + },
     458 + "DpkgFileRecord": {
     459 + "properties": {
     460 + "path": {
     461 + "type": "string"
     462 + },
     463 + "digest": {
     464 + "$ref": "#/$defs/Digest"
     465 + },
     466 + "isConfigFile": {
     467 + "type": "boolean"
     468 + }
     469 + },
     470 + "type": "object",
     471 + "required": [
     472 + "path",
     473 + "isConfigFile"
     474 + ]
     475 + },
     476 + "DpkgMetadata": {
     477 + "properties": {
     478 + "package": {
     479 + "type": "string"
     480 + },
     481 + "source": {
     482 + "type": "string"
     483 + },
     484 + "version": {
     485 + "type": "string"
     486 + },
     487 + "sourceVersion": {
     488 + "type": "string"
     489 + },
     490 + "architecture": {
     491 + "type": "string"
     492 + },
     493 + "maintainer": {
     494 + "type": "string"
     495 + },
     496 + "installedSize": {
     497 + "type": "integer"
     498 + },
     499 + "files": {
     500 + "items": {
     501 + "$ref": "#/$defs/DpkgFileRecord"
     502 + },
     503 + "type": "array"
     504 + }
     505 + },
     506 + "type": "object",
     507 + "required": [
     508 + "package",
     509 + "source",
     510 + "version",
     511 + "sourceVersion",
     512 + "architecture",
     513 + "maintainer",
     514 + "installedSize",
     515 + "files"
     516 + ]
     517 + },
     518 + "File": {
     519 + "properties": {
     520 + "id": {
     521 + "type": "string"
     522 + },
     523 + "location": {
     524 + "$ref": "#/$defs/Coordinates"
     525 + },
     526 + "metadata": {
     527 + "$ref": "#/$defs/FileMetadataEntry"
     528 + },
     529 + "contents": {
     530 + "type": "string"
     531 + },
     532 + "digests": {
     533 + "items": {
     534 + "$ref": "#/$defs/Digest"
     535 + },
     536 + "type": "array"
     537 + }
     538 + },
     539 + "type": "object",
     540 + "required": [
     541 + "id",
     542 + "location"
     543 + ]
     544 + },
     545 + "FileMetadataEntry": {
     546 + "properties": {
     547 + "mode": {
     548 + "type": "integer"
     549 + },
     550 + "type": {
     551 + "type": "string"
     552 + },
     553 + "linkDestination": {
     554 + "type": "string"
     555 + },
     556 + "userID": {
     557 + "type": "integer"
     558 + },
     559 + "groupID": {
     560 + "type": "integer"
     561 + },
     562 + "mimeType": {
     563 + "type": "string"
     564 + },
     565 + "size": {
     566 + "type": "integer"
     567 + }
     568 + },
     569 + "type": "object",
     570 + "required": [
     571 + "mode",
     572 + "type",
     573 + "userID",
     574 + "groupID",
     575 + "mimeType",
     576 + "size"
     577 + ]
     578 + },
     579 + "GemMetadata": {
     580 + "properties": {
     581 + "name": {
     582 + "type": "string"
     583 + },
     584 + "version": {
     585 + "type": "string"
     586 + },
     587 + "files": {
     588 + "items": {
     589 + "type": "string"
     590 + },
     591 + "type": "array"
     592 + },
     593 + "authors": {
     594 + "items": {
     595 + "type": "string"
     596 + },
     597 + "type": "array"
     598 + },
     599 + "homepage": {
     600 + "type": "string"
     601 + }
     602 + },
     603 + "type": "object",
     604 + "required": [
     605 + "name",
     606 + "version"
     607 + ]
     608 + },
     609 + "GolangBinMetadata": {
     610 + "properties": {
     611 + "goBuildSettings": {
     612 + "patternProperties": {
     613 + ".*": {
     614 + "type": "string"
     615 + }
     616 + },
     617 + "type": "object"
     618 + },
     619 + "goCompiledVersion": {
     620 + "type": "string"
     621 + },
     622 + "architecture": {
     623 + "type": "string"
     624 + },
     625 + "h1Digest": {
     626 + "type": "string"
     627 + },
     628 + "mainModule": {
     629 + "type": "string"
     630 + }
     631 + },
     632 + "type": "object",
     633 + "required": [
     634 + "goCompiledVersion",
     635 + "architecture"
     636 + ]
     637 + },
     638 + "GolangModMetadata": {
     639 + "properties": {
     640 + "h1Digest": {
     641 + "type": "string"
     642 + }
     643 + },
     644 + "type": "object"
     645 + },
     646 + "HackageMetadata": {
     647 + "properties": {
     648 + "name": {
     649 + "type": "string"
     650 + },
     651 + "version": {
     652 + "type": "string"
     653 + },
     654 + "pkgHash": {
     655 + "type": "string"
     656 + },
     657 + "snapshotURL": {
     658 + "type": "string"
     659 + }
     660 + },
     661 + "type": "object",
     662 + "required": [
     663 + "name",
     664 + "version"
     665 + ]
     666 + },
     667 + "IDLikes": {
     668 + "items": {
     669 + "type": "string"
     670 + },
     671 + "type": "array"
     672 + },
     673 + "JavaManifest": {
     674 + "properties": {
     675 + "main": {
     676 + "patternProperties": {
     677 + ".*": {
     678 + "type": "string"
     679 + }
     680 + },
     681 + "type": "object"
     682 + },
     683 + "namedSections": {
     684 + "patternProperties": {
     685 + ".*": {
     686 + "patternProperties": {
     687 + ".*": {
     688 + "type": "string"
     689 + }
     690 + },
     691 + "type": "object"
     692 + }
     693 + },
     694 + "type": "object"
     695 + }
     696 + },
     697 + "type": "object"
     698 + },
     699 + "JavaMetadata": {
     700 + "properties": {
     701 + "virtualPath": {
     702 + "type": "string"
     703 + },
     704 + "manifest": {
     705 + "$ref": "#/$defs/JavaManifest"
     706 + },
     707 + "pomProperties": {
     708 + "$ref": "#/$defs/PomProperties"
     709 + },
     710 + "pomProject": {
     711 + "$ref": "#/$defs/PomProject"
     712 + },
     713 + "digest": {
     714 + "items": {
     715 + "$ref": "#/$defs/Digest"
     716 + },
     717 + "type": "array"
     718 + }
     719 + },
     720 + "type": "object",
     721 + "required": [
     722 + "virtualPath"
     723 + ]
     724 + },
     725 + "KbPackageMetadata": {
     726 + "properties": {
     727 + "product_id": {
     728 + "type": "string"
     729 + },
     730 + "kb": {
     731 + "type": "string"
     732 + }
     733 + },
     734 + "type": "object",
     735 + "required": [
     736 + "product_id",
     737 + "kb"
     738 + ]
     739 + },
     740 + "License": {
     741 + "properties": {
     742 + "value": {
     743 + "type": "string"
     744 + },
     745 + "spdxExpression": {
     746 + "type": "string"
     747 + },
     748 + "type": {
     749 + "type": "string"
     750 + },
     751 + "url": {
     752 + "items": {
     753 + "type": "string"
     754 + },
     755 + "type": "array"
     756 + },
     757 + "locations": {
     758 + "items": {
     759 + "$ref": "#/$defs/Location"
     760 + },
     761 + "type": "array"
     762 + }
     763 + },
     764 + "type": "object",
     765 + "required": [
     766 + "value",
     767 + "spdxExpression",
     768 + "type",
     769 + "url",
     770 + "locations"
     771 + ]
     772 + },
     773 + "LinuxKernelMetadata": {
     774 + "properties": {
     775 + "name": {
     776 + "type": "string"
     777 + },
     778 + "architecture": {
     779 + "type": "string"
     780 + },
     781 + "version": {
     782 + "type": "string"
     783 + },
     784 + "extendedVersion": {
     785 + "type": "string"
     786 + },
     787 + "buildTime": {
     788 + "type": "string"
     789 + },
     790 + "author": {
     791 + "type": "string"
     792 + },
     793 + "format": {
     794 + "type": "string"
     795 + },
     796 + "rwRootFS": {
     797 + "type": "boolean"
     798 + },
     799 + "swapDevice": {
     800 + "type": "integer"
     801 + },
     802 + "rootDevice": {
     803 + "type": "integer"
     804 + },
     805 + "videoMode": {
     806 + "type": "string"
     807 + }
     808 + },
     809 + "type": "object",
     810 + "required": [
     811 + "name",
     812 + "architecture",
     813 + "version"
     814 + ]
     815 + },
     816 + "LinuxKernelModuleMetadata": {
     817 + "properties": {
     818 + "name": {
     819 + "type": "string"
     820 + },
     821 + "version": {
     822 + "type": "string"
     823 + },
     824 + "sourceVersion": {
     825 + "type": "string"
     826 + },
     827 + "path": {
     828 + "type": "string"
     829 + },
     830 + "description": {
     831 + "type": "string"
     832 + },
     833 + "author": {
     834 + "type": "string"
     835 + },
     836 + "license": {
     837 + "type": "string"
     838 + },
     839 + "kernelVersion": {
     840 + "type": "string"
     841 + },
     842 + "versionMagic": {
     843 + "type": "string"
     844 + },
     845 + "parameters": {
     846 + "patternProperties": {
     847 + ".*": {
     848 + "$ref": "#/$defs/LinuxKernelModuleParameter"
     849 + }
     850 + },
     851 + "type": "object"
     852 + }
     853 + },
     854 + "type": "object"
     855 + },
     856 + "LinuxKernelModuleParameter": {
     857 + "properties": {
     858 + "type": {
     859 + "type": "string"
     860 + },
     861 + "description": {
     862 + "type": "string"
     863 + }
     864 + },
     865 + "type": "object"
     866 + },
     867 + "LinuxRelease": {
     868 + "properties": {
     869 + "prettyName": {
     870 + "type": "string"
     871 + },
     872 + "name": {
     873 + "type": "string"
     874 + },
     875 + "id": {
     876 + "type": "string"
     877 + },
     878 + "idLike": {
     879 + "$ref": "#/$defs/IDLikes"
     880 + },
     881 + "version": {
     882 + "type": "string"
     883 + },
     884 + "versionID": {
     885 + "type": "string"
     886 + },
     887 + "versionCodename": {
     888 + "type": "string"
     889 + },
     890 + "buildID": {
     891 + "type": "string"
     892 + },
     893 + "imageID": {
     894 + "type": "string"
     895 + },
     896 + "imageVersion": {
     897 + "type": "string"
     898 + },
     899 + "variant": {
     900 + "type": "string"
     901 + },
     902 + "variantID": {
     903 + "type": "string"
     904 + },
     905 + "homeURL": {
     906 + "type": "string"
     907 + },
     908 + "supportURL": {
     909 + "type": "string"
     910 + },
     911 + "bugReportURL": {
     912 + "type": "string"
     913 + },
     914 + "privacyPolicyURL": {
     915 + "type": "string"
     916 + },
     917 + "cpeName": {
     918 + "type": "string"
     919 + },
     920 + "supportEnd": {
     921 + "type": "string"
     922 + }
     923 + },
     924 + "type": "object"
     925 + },
     926 + "Location": {
     927 + "properties": {
     928 + "path": {
     929 + "type": "string"
     930 + },
     931 + "layerID": {
     932 + "type": "string"
     933 + },
     934 + "annotations": {
     935 + "patternProperties": {
     936 + ".*": {
     937 + "type": "string"
     938 + }
     939 + },
     940 + "type": "object"
     941 + }
     942 + },
     943 + "type": "object",
     944 + "required": [
     945 + "path"
     946 + ]
     947 + },
     948 + "MixLockMetadata": {
     949 + "properties": {
     950 + "name": {
     951 + "type": "string"
     952 + },
     953 + "version": {
     954 + "type": "string"
     955 + },
     956 + "pkgHash": {
     957 + "type": "string"
     958 + },
     959 + "pkgHashExt": {
     960 + "type": "string"
     961 + }
     962 + },
     963 + "type": "object",
     964 + "required": [
     965 + "name",
     966 + "version",
     967 + "pkgHash",
     968 + "pkgHashExt"
     969 + ]
     970 + },
     971 + "NixStoreMetadata": {
     972 + "properties": {
     973 + "outputHash": {
     974 + "type": "string"
     975 + },
     976 + "output": {
     977 + "type": "string"
     978 + },
     979 + "files": {
     980 + "items": {
     981 + "type": "string"
     982 + },
     983 + "type": "array"
     984 + }
     985 + },
     986 + "type": "object",
     987 + "required": [
     988 + "outputHash",
     989 + "files"
     990 + ]
     991 + },
     992 + "NpmPackageJSONMetadata": {
     993 + "properties": {
     994 + "name": {
     995 + "type": "string"
     996 + },
     997 + "version": {
     998 + "type": "string"
     999 + },
     1000 + "author": {
     1001 + "type": "string"
     1002 + },
     1003 + "homepage": {
     1004 + "type": "string"
     1005 + },
     1006 + "description": {
     1007 + "type": "string"
     1008 + },
     1009 + "url": {
     1010 + "type": "string"
     1011 + },
     1012 + "private": {
     1013 + "type": "boolean"
     1014 + }
     1015 + },
     1016 + "type": "object",
     1017 + "required": [
     1018 + "name",
     1019 + "version",
     1020 + "author",
     1021 + "homepage",
     1022 + "description",
     1023 + "url",
     1024 + "private"
     1025 + ]
     1026 + },
     1027 + "NpmPackageLockJSONMetadata": {
     1028 + "properties": {
     1029 + "resolved": {
     1030 + "type": "string"
     1031 + },
     1032 + "integrity": {
     1033 + "type": "string"
     1034 + }
     1035 + },
     1036 + "type": "object",
     1037 + "required": [
     1038 + "resolved",
     1039 + "integrity"
     1040 + ]
     1041 + },
     1042 + "Package": {
     1043 + "properties": {
     1044 + "id": {
     1045 + "type": "string"
     1046 + },
     1047 + "name": {
     1048 + "type": "string"
     1049 + },
     1050 + "version": {
     1051 + "type": "string"
     1052 + },
     1053 + "type": {
     1054 + "type": "string"
     1055 + },
     1056 + "foundBy": {
     1057 + "type": "string"
     1058 + },
     1059 + "locations": {
     1060 + "items": {
     1061 + "$ref": "#/$defs/Location"
     1062 + },
     1063 + "type": "array"
     1064 + },
     1065 + "licenses": {
     1066 + "$ref": "#/$defs/licenses"
     1067 + },
     1068 + "language": {
     1069 + "type": "string"
     1070 + },
     1071 + "cpes": {
     1072 + "items": {
     1073 + "type": "string"
     1074 + },
     1075 + "type": "array"
     1076 + },
     1077 + "purl": {
     1078 + "type": "string"
     1079 + },
     1080 + "metadataType": {
     1081 + "type": "string"
     1082 + },
     1083 + "metadata": {
     1084 + "anyOf": [
     1085 + {
     1086 + "type": "null"
     1087 + },
     1088 + {
     1089 + "$ref": "#/$defs/AlpmMetadata"
     1090 + },
     1091 + {
     1092 + "$ref": "#/$defs/ApkMetadata"
     1093 + },
     1094 + {
     1095 + "$ref": "#/$defs/BinaryMetadata"
     1096 + },
     1097 + {
     1098 + "$ref": "#/$defs/CargoPackageMetadata"
     1099 + },
     1100 + {
     1101 + "$ref": "#/$defs/CocoapodsMetadata"
     1102 + },
     1103 + {
     1104 + "$ref": "#/$defs/ConanLockMetadata"
     1105 + },
     1106 + {
     1107 + "$ref": "#/$defs/ConanMetadata"
     1108 + },
     1109 + {
     1110 + "$ref": "#/$defs/DartPubMetadata"
     1111 + },
     1112 + {
     1113 + "$ref": "#/$defs/DotnetDepsMetadata"
     1114 + },
     1115 + {
     1116 + "$ref": "#/$defs/DpkgMetadata"
     1117 + },
     1118 + {
     1119 + "$ref": "#/$defs/GemMetadata"
     1120 + },
     1121 + {
     1122 + "$ref": "#/$defs/GolangBinMetadata"
     1123 + },
     1124 + {
     1125 + "$ref": "#/$defs/GolangModMetadata"
     1126 + },
     1127 + {
     1128 + "$ref": "#/$defs/HackageMetadata"
     1129 + },
     1130 + {
     1131 + "$ref": "#/$defs/JavaMetadata"
     1132 + },
     1133 + {
     1134 + "$ref": "#/$defs/KbPackageMetadata"
     1135 + },
     1136 + {
     1137 + "$ref": "#/$defs/LinuxKernelMetadata"
     1138 + },
     1139 + {
     1140 + "$ref": "#/$defs/LinuxKernelModuleMetadata"
     1141 + },
     1142 + {
     1143 + "$ref": "#/$defs/MixLockMetadata"
     1144 + },
     1145 + {
     1146 + "$ref": "#/$defs/NixStoreMetadata"
     1147 + },
     1148 + {
     1149 + "$ref": "#/$defs/NpmPackageJSONMetadata"
     1150 + },
     1151 + {
     1152 + "$ref": "#/$defs/NpmPackageLockJSONMetadata"
     1153 + },
     1154 + {
     1155 + "$ref": "#/$defs/PhpComposerJSONMetadata"
     1156 + },
     1157 + {
     1158 + "$ref": "#/$defs/PortageMetadata"
     1159 + },
     1160 + {
     1161 + "$ref": "#/$defs/PythonPackageMetadata"
     1162 + },
     1163 + {
     1164 + "$ref": "#/$defs/PythonPipfileLockMetadata"
     1165 + },
     1166 + {
     1167 + "$ref": "#/$defs/PythonRequirementsMetadata"
     1168 + },
     1169 + {
     1170 + "$ref": "#/$defs/RDescriptionFileMetadata"
     1171 + },
     1172 + {
     1173 + "$ref": "#/$defs/RebarLockMetadata"
     1174 + },
     1175 + {
     1176 + "$ref": "#/$defs/RpmMetadata"
     1177 + }
     1178 + ]
     1179 + }
     1180 + },
     1181 + "type": "object",
     1182 + "required": [
     1183 + "id",
     1184 + "name",
     1185 + "version",
     1186 + "type",
     1187 + "foundBy",
     1188 + "locations",
     1189 + "licenses",
     1190 + "language",
     1191 + "cpes",
     1192 + "purl"
     1193 + ]
     1194 + },
     1195 + "PhpComposerAuthors": {
     1196 + "properties": {
     1197 + "name": {
     1198 + "type": "string"
     1199 + },
     1200 + "email": {
     1201 + "type": "string"
     1202 + },
     1203 + "homepage": {
     1204 + "type": "string"
     1205 + }
     1206 + },
     1207 + "type": "object",
     1208 + "required": [
     1209 + "name"
     1210 + ]
     1211 + },
     1212 + "PhpComposerExternalReference": {
     1213 + "properties": {
     1214 + "type": {
     1215 + "type": "string"
     1216 + },
     1217 + "url": {
     1218 + "type": "string"
     1219 + },
     1220 + "reference": {
     1221 + "type": "string"
     1222 + },
     1223 + "shasum": {
     1224 + "type": "string"
     1225 + }
     1226 + },
     1227 + "type": "object",
     1228 + "required": [
     1229 + "type",
     1230 + "url",
     1231 + "reference"
     1232 + ]
     1233 + },
     1234 + "PhpComposerJSONMetadata": {
     1235 + "properties": {
     1236 + "name": {
     1237 + "type": "string"
     1238 + },
     1239 + "version": {
     1240 + "type": "string"
     1241 + },
     1242 + "source": {
     1243 + "$ref": "#/$defs/PhpComposerExternalReference"
     1244 + },
     1245 + "dist": {
     1246 + "$ref": "#/$defs/PhpComposerExternalReference"
     1247 + },
     1248 + "require": {
     1249 + "patternProperties": {
     1250 + ".*": {
     1251 + "type": "string"
     1252 + }
     1253 + },
     1254 + "type": "object"
     1255 + },
     1256 + "provide": {
     1257 + "patternProperties": {
     1258 + ".*": {
     1259 + "type": "string"
     1260 + }
     1261 + },
     1262 + "type": "object"
     1263 + },
     1264 + "require-dev": {
     1265 + "patternProperties": {
     1266 + ".*": {
     1267 + "type": "string"
     1268 + }
     1269 + },
     1270 + "type": "object"
     1271 + },
     1272 + "suggest": {
     1273 + "patternProperties": {
     1274 + ".*": {
     1275 + "type": "string"
     1276 + }
     1277 + },
     1278 + "type": "object"
     1279 + },
     1280 + "license": {
     1281 + "items": {
     1282 + "type": "string"
     1283 + },
     1284 + "type": "array"
     1285 + },
     1286 + "type": {
     1287 + "type": "string"
     1288 + },
     1289 + "notification-url": {
     1290 + "type": "string"
     1291 + },
     1292 + "bin": {
     1293 + "items": {
     1294 + "type": "string"
     1295 + },
     1296 + "type": "array"
     1297 + },
     1298 + "authors": {
     1299 + "items": {
     1300 + "$ref": "#/$defs/PhpComposerAuthors"
     1301 + },
     1302 + "type": "array"
     1303 + },
     1304 + "description": {
     1305 + "type": "string"
     1306 + },
     1307 + "homepage": {
     1308 + "type": "string"
     1309 + },
     1310 + "keywords": {
     1311 + "items": {
     1312 + "type": "string"
     1313 + },
     1314 + "type": "array"
     1315 + },
     1316 + "time": {
     1317 + "type": "string"
     1318 + }
     1319 + },
     1320 + "type": "object",
     1321 + "required": [
     1322 + "name",
     1323 + "version",
     1324 + "source",
     1325 + "dist"
     1326 + ]
     1327 + },
     1328 + "PomParent": {
     1329 + "properties": {
     1330 + "groupId": {
     1331 + "type": "string"
     1332 + },
     1333 + "artifactId": {
     1334 + "type": "string"
     1335 + },
     1336 + "version": {
     1337 + "type": "string"
     1338 + }
     1339 + },
     1340 + "type": "object",
     1341 + "required": [
     1342 + "groupId",
     1343 + "artifactId",
     1344 + "version"
     1345 + ]
     1346 + },
     1347 + "PomProject": {
     1348 + "properties": {
     1349 + "path": {
     1350 + "type": "string"
     1351 + },
     1352 + "parent": {
     1353 + "$ref": "#/$defs/PomParent"
     1354 + },
     1355 + "groupId": {
     1356 + "type": "string"
     1357 + },
     1358 + "artifactId": {
     1359 + "type": "string"
     1360 + },
     1361 + "version": {
     1362 + "type": "string"
     1363 + },
     1364 + "name": {
     1365 + "type": "string"
     1366 + },
     1367 + "description": {
     1368 + "type": "string"
     1369 + },
     1370 + "url": {
     1371 + "type": "string"
     1372 + }
     1373 + },
     1374 + "type": "object",
     1375 + "required": [
     1376 + "path",
     1377 + "groupId",
     1378 + "artifactId",
     1379 + "version",
     1380 + "name"
     1381 + ]
     1382 + },
     1383 + "PomProperties": {
     1384 + "properties": {
     1385 + "path": {
     1386 + "type": "string"
     1387 + },
     1388 + "name": {
     1389 + "type": "string"
     1390 + },
     1391 + "groupId": {
     1392 + "type": "string"
     1393 + },
     1394 + "artifactId": {
     1395 + "type": "string"
     1396 + },
     1397 + "version": {
     1398 + "type": "string"
     1399 + },
     1400 + "extraFields": {
     1401 + "patternProperties": {
     1402 + ".*": {
     1403 + "type": "string"
     1404 + }
     1405 + },
     1406 + "type": "object"
     1407 + }
     1408 + },
     1409 + "type": "object",
     1410 + "required": [
     1411 + "path",
     1412 + "name",
     1413 + "groupId",
     1414 + "artifactId",
     1415 + "version"
     1416 + ]
     1417 + },
     1418 + "PortageFileRecord": {
     1419 + "properties": {
     1420 + "path": {
     1421 + "type": "string"
     1422 + },
     1423 + "digest": {
     1424 + "$ref": "#/$defs/Digest"
     1425 + }
     1426 + },
     1427 + "type": "object",
     1428 + "required": [
     1429 + "path"
     1430 + ]
     1431 + },
     1432 + "PortageMetadata": {
     1433 + "properties": {
     1434 + "installedSize": {
     1435 + "type": "integer"
     1436 + },
     1437 + "files": {
     1438 + "items": {
     1439 + "$ref": "#/$defs/PortageFileRecord"
     1440 + },
     1441 + "type": "array"
     1442 + }
     1443 + },
     1444 + "type": "object",
     1445 + "required": [
     1446 + "installedSize",
     1447 + "files"
     1448 + ]
     1449 + },
     1450 + "PythonDirectURLOriginInfo": {
     1451 + "properties": {
     1452 + "url": {
     1453 + "type": "string"
     1454 + },
     1455 + "commitId": {
     1456 + "type": "string"
     1457 + },
     1458 + "vcs": {
     1459 + "type": "string"
     1460 + }
     1461 + },
     1462 + "type": "object",
     1463 + "required": [
     1464 + "url"
     1465 + ]
     1466 + },
     1467 + "PythonFileDigest": {
     1468 + "properties": {
     1469 + "algorithm": {
     1470 + "type": "string"
     1471 + },
     1472 + "value": {
     1473 + "type": "string"
     1474 + }
     1475 + },
     1476 + "type": "object",
     1477 + "required": [
     1478 + "algorithm",
     1479 + "value"
     1480 + ]
     1481 + },
     1482 + "PythonFileRecord": {
     1483 + "properties": {
     1484 + "path": {
     1485 + "type": "string"
     1486 + },
     1487 + "digest": {
     1488 + "$ref": "#/$defs/PythonFileDigest"
     1489 + },
     1490 + "size": {
     1491 + "type": "string"
     1492 + }
     1493 + },
     1494 + "type": "object",
     1495 + "required": [
     1496 + "path"
     1497 + ]
     1498 + },
     1499 + "PythonPackageMetadata": {
     1500 + "properties": {
     1501 + "name": {
     1502 + "type": "string"
     1503 + },
     1504 + "version": {
     1505 + "type": "string"
     1506 + },
     1507 + "author": {
     1508 + "type": "string"
     1509 + },
     1510 + "authorEmail": {
     1511 + "type": "string"
     1512 + },
     1513 + "platform": {
     1514 + "type": "string"
     1515 + },
     1516 + "files": {
     1517 + "items": {
     1518 + "$ref": "#/$defs/PythonFileRecord"
     1519 + },
     1520 + "type": "array"
     1521 + },
     1522 + "sitePackagesRootPath": {
     1523 + "type": "string"
     1524 + },
     1525 + "topLevelPackages": {
     1526 + "items": {
     1527 + "type": "string"
     1528 + },
     1529 + "type": "array"
     1530 + },
     1531 + "directUrlOrigin": {
     1532 + "$ref": "#/$defs/PythonDirectURLOriginInfo"
     1533 + }
     1534 + },
     1535 + "type": "object",
     1536 + "required": [
     1537 + "name",
     1538 + "version",
     1539 + "author",
     1540 + "authorEmail",
     1541 + "platform",
     1542 + "sitePackagesRootPath"
     1543 + ]
     1544 + },
     1545 + "PythonPipfileLockMetadata": {
     1546 + "properties": {
     1547 + "hashes": {
     1548 + "items": {
     1549 + "type": "string"
     1550 + },
     1551 + "type": "array"
     1552 + },
     1553 + "index": {
     1554 + "type": "string"
     1555 + }
     1556 + },
     1557 + "type": "object",
     1558 + "required": [
     1559 + "hashes",
     1560 + "index"
     1561 + ]
     1562 + },
     1563 + "PythonRequirementsMetadata": {
     1564 + "properties": {
     1565 + "name": {
     1566 + "type": "string"
     1567 + },
     1568 + "extras": {
     1569 + "items": {
     1570 + "type": "string"
     1571 + },
     1572 + "type": "array"
     1573 + },
     1574 + "versionConstraint": {
     1575 + "type": "string"
     1576 + },
     1577 + "url": {
     1578 + "type": "string"
     1579 + },
     1580 + "markers": {
     1581 + "patternProperties": {
     1582 + ".*": {
     1583 + "type": "string"
     1584 + }
     1585 + },
     1586 + "type": "object"
     1587 + }
     1588 + },
     1589 + "type": "object",
     1590 + "required": [
     1591 + "name",
     1592 + "extras",
     1593 + "versionConstraint",
     1594 + "url",
     1595 + "markers"
     1596 + ]
     1597 + },
     1598 + "RDescriptionFileMetadata": {
     1599 + "properties": {
     1600 + "title": {
     1601 + "type": "string"
     1602 + },
     1603 + "description": {
     1604 + "type": "string"
     1605 + },
     1606 + "author": {
     1607 + "type": "string"
     1608 + },
     1609 + "maintainer": {
     1610 + "type": "string"
     1611 + },
     1612 + "url": {
     1613 + "items": {
     1614 + "type": "string"
     1615 + },
     1616 + "type": "array"
     1617 + },
     1618 + "repository": {
     1619 + "type": "string"
     1620 + },
     1621 + "built": {
     1622 + "type": "string"
     1623 + },
     1624 + "needsCompilation": {
     1625 + "type": "boolean"
     1626 + },
     1627 + "imports": {
     1628 + "items": {
     1629 + "type": "string"
     1630 + },
     1631 + "type": "array"
     1632 + },
     1633 + "depends": {
     1634 + "items": {
     1635 + "type": "string"
     1636 + },
     1637 + "type": "array"
     1638 + },
     1639 + "suggests": {
     1640 + "items": {
     1641 + "type": "string"
     1642 + },
     1643 + "type": "array"
     1644 + }
     1645 + },
     1646 + "type": "object"
     1647 + },
     1648 + "RebarLockMetadata": {
     1649 + "properties": {
     1650 + "name": {
     1651 + "type": "string"
     1652 + },
     1653 + "version": {
     1654 + "type": "string"
     1655 + },
     1656 + "pkgHash": {
     1657 + "type": "string"
     1658 + },
     1659 + "pkgHashExt": {
     1660 + "type": "string"
     1661 + }
     1662 + },
     1663 + "type": "object",
     1664 + "required": [
     1665 + "name",
     1666 + "version",
     1667 + "pkgHash",
     1668 + "pkgHashExt"
     1669 + ]
     1670 + },
     1671 + "Relationship": {
     1672 + "properties": {
     1673 + "parent": {
     1674 + "type": "string"
     1675 + },
     1676 + "child": {
     1677 + "type": "string"
     1678 + },
     1679 + "type": {
     1680 + "type": "string"
     1681 + },
     1682 + "metadata": true
     1683 + },
     1684 + "type": "object",
     1685 + "required": [
     1686 + "parent",
     1687 + "child",
     1688 + "type"
     1689 + ]
     1690 + },
     1691 + "RpmMetadata": {
     1692 + "properties": {
     1693 + "name": {
     1694 + "type": "string"
     1695 + },
     1696 + "version": {
     1697 + "type": "string"
     1698 + },
     1699 + "epoch": {
     1700 + "oneOf": [
     1701 + {
     1702 + "type": "integer"
     1703 + },
     1704 + {
     1705 + "type": "null"
     1706 + }
     1707 + ]
     1708 + },
     1709 + "architecture": {
     1710 + "type": "string"
     1711 + },
     1712 + "release": {
     1713 + "type": "string"
     1714 + },
     1715 + "sourceRpm": {
     1716 + "type": "string"
     1717 + },
     1718 + "size": {
     1719 + "type": "integer"
     1720 + },
     1721 + "vendor": {
     1722 + "type": "string"
     1723 + },
     1724 + "modularityLabel": {
     1725 + "type": "string"
     1726 + },
     1727 + "files": {
     1728 + "items": {
     1729 + "$ref": "#/$defs/RpmdbFileRecord"
     1730 + },
     1731 + "type": "array"
     1732 + }
     1733 + },
     1734 + "type": "object",
     1735 + "required": [
     1736 + "name",
     1737 + "version",
     1738 + "epoch",
     1739 + "architecture",
     1740 + "release",
     1741 + "sourceRpm",
     1742 + "size",
     1743 + "vendor",
     1744 + "modularityLabel",
     1745 + "files"
     1746 + ]
     1747 + },
     1748 + "RpmdbFileRecord": {
     1749 + "properties": {
     1750 + "path": {
     1751 + "type": "string"
     1752 + },
     1753 + "mode": {
     1754 + "type": "integer"
     1755 + },
     1756 + "size": {
     1757 + "type": "integer"
     1758 + },
     1759 + "digest": {
     1760 + "$ref": "#/$defs/Digest"
     1761 + },
     1762 + "userName": {
     1763 + "type": "string"
     1764 + },
     1765 + "groupName": {
     1766 + "type": "string"
     1767 + },
     1768 + "flags": {
     1769 + "type": "string"
     1770 + }
     1771 + },
     1772 + "type": "object",
     1773 + "required": [
     1774 + "path",
     1775 + "mode",
     1776 + "size",
     1777 + "digest",
     1778 + "userName",
     1779 + "groupName",
     1780 + "flags"
     1781 + ]
     1782 + },
     1783 + "Schema": {
     1784 + "properties": {
     1785 + "version": {
     1786 + "type": "string"
     1787 + },
     1788 + "url": {
     1789 + "type": "string"
     1790 + }
     1791 + },
     1792 + "type": "object",
     1793 + "required": [
     1794 + "version",
     1795 + "url"
     1796 + ]
     1797 + },
     1798 + "SearchResult": {
     1799 + "properties": {
     1800 + "classification": {
     1801 + "type": "string"
     1802 + },
     1803 + "lineNumber": {
     1804 + "type": "integer"
     1805 + },
     1806 + "lineOffset": {
     1807 + "type": "integer"
     1808 + },
     1809 + "seekPosition": {
     1810 + "type": "integer"
     1811 + },
     1812 + "length": {
     1813 + "type": "integer"
     1814 + },
     1815 + "value": {
     1816 + "type": "string"
     1817 + }
     1818 + },
     1819 + "type": "object",
     1820 + "required": [
     1821 + "classification",
     1822 + "lineNumber",
     1823 + "lineOffset",
     1824 + "seekPosition",
     1825 + "length"
     1826 + ]
     1827 + },
     1828 + "Secrets": {
     1829 + "properties": {
     1830 + "location": {
     1831 + "$ref": "#/$defs/Coordinates"
     1832 + },
     1833 + "secrets": {
     1834 + "items": {
     1835 + "$ref": "#/$defs/SearchResult"
     1836 + },
     1837 + "type": "array"
     1838 + }
     1839 + },
     1840 + "type": "object",
     1841 + "required": [
     1842 + "location",
     1843 + "secrets"
     1844 + ]
     1845 + },
     1846 + "Source": {
     1847 + "properties": {
     1848 + "id": {
     1849 + "type": "string"
     1850 + },
     1851 + "type": {
     1852 + "type": "string"
     1853 + },
     1854 + "target": true
     1855 + },
     1856 + "type": "object",
     1857 + "required": [
     1858 + "id",
     1859 + "type",
     1860 + "target"
     1861 + ]
     1862 + },
     1863 + "licenses": {
     1864 + "items": {
     1865 + "$ref": "#/$defs/License"
     1866 + },
     1867 + "type": "array"
     1868 + }
     1869 + }
     1870 +}
     1871 + 
  • ■ ■ ■ ■ ■ ■
    syft/file/license.go
     1 +package file
     2 + 
     3 +import (
     4 + "github.com/anchore/syft/internal/log"
     5 + "github.com/anchore/syft/syft/license"
     6 +)
     7 + 
     8 +type License struct {
     9 + Value string
     10 + SPDXExpression string
     11 + Type license.Type
     12 + LicenseEvidence *LicenseEvidence // evidence from license classifier
     13 +}
     14 + 
     15 +type LicenseEvidence struct {
     16 + Confidence int
     17 + Offset int
     18 + Extent int
     19 +}
     20 + 
     21 +func NewLicense(value string) License {
     22 + spdxExpression, err := license.ParseExpression(value)
     23 + if err != nil {
     24 + log.Trace("unable to parse license expression: %s, %w", value, err)
     25 + }
     26 + 
     27 + return License{
     28 + Value: value,
     29 + SPDXExpression: spdxExpression,
     30 + Type: license.Concluded,
     31 + }
     32 +}
     33 + 
  • ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/component.go
    skipped 77 lines
    78 78   Name: c.Name,
    79 79   Version: c.Version,
    80 80   Locations: decodeLocations(values),
    81  - Licenses: decodeLicenses(c),
     81 + Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
    82 82   CPEs: decodeCPEs(c),
    83 83   PURL: c.PackageURL,
    84 84   }
    skipped 50 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/component_test.go
    skipped 35 lines
    36 36   OriginPackage: "libc-dev",
    37 37   Maintainer: "Natanael Copa <[email protected]>",
    38 38   Version: "0.7.2-r0",
    39  - License: "BSD",
    40 39   Architecture: "x86_64",
    41 40   URL: "http://alpinelinux.org",
    42 41   Description: "Meta package to pull in correct libc",
    skipped 97 lines
    140 139   Version: "0.9.2",
    141 140   SourceRpm: "dive-0.9.2-1.src.rpm",
    142 141   Size: 12406784,
    143  - License: "MIT",
    144 142   Vendor: "",
    145 143   Files: []pkg.RpmdbFileRecord{},
    146 144   },
    skipped 203 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/decoder_test.go
    skipped 321 lines
    322 322   },
    323 323   },
    324 324   })
    325  - 
    326  - assert.Len(t, pkg.Licenses, 0)
     325 + assert.Equal(t, pkg.Licenses.Empty(), true)
    327 326  }
    328 327   
    329 328  func Test_missingComponentsDecode(t *testing.T) {
    skipped 11 lines
  • ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/external_references_test.go
    skipped 49 lines
    50 50   Language: pkg.Rust,
    51 51   Type: pkg.RustPkg,
    52 52   MetadataType: pkg.RustCargoPackageMetadataType,
    53  - Licenses: nil,
     53 + Licenses: pkg.NewLicenseSet(),
    54 54   Metadata: pkg.CargoPackageMetadata{
    55 55   Name: "ansi_term",
    56 56   Version: "0.12.1",
    skipped 79 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/licenses.go
    1 1  package cyclonedxhelpers
    2 2   
    3 3  import (
     4 + "fmt"
     5 + "strings"
     6 + 
    4 7   "github.com/CycloneDX/cyclonedx-go"
    5 8   
    6 9   "github.com/anchore/syft/internal/spdxlicense"
    7 10   "github.com/anchore/syft/syft/pkg"
    8 11  )
    9 12   
     13 +// This should be a function that just surfaces licenses already validated in the package struct
    10 14  func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
    11  - lc := cyclonedx.Licenses{}
    12  - for _, licenseName := range p.Licenses {
    13  - if value, exists := spdxlicense.ID(licenseName); exists {
    14  - lc = append(lc, cyclonedx.LicenseChoice{
     15 + spdxc, otherc, ex := separateLicenses(p)
     16 + if len(otherc) > 0 {
     17 + // found non spdx related licenses
     18 + // build individual license choices for each
     19 + // complex expressions are not combined and set as NAME fields
     20 + for _, e := range ex {
     21 + otherc = append(otherc, cyclonedx.LicenseChoice{
     22 + License: &cyclonedx.License{
     23 + Name: e,
     24 + },
     25 + })
     26 + }
     27 + otherc = append(otherc, spdxc...)
     28 + return &otherc
     29 + }
     30 + 
     31 + if len(spdxc) > 0 {
     32 + for _, l := range ex {
     33 + spdxc = append(spdxc, cyclonedx.LicenseChoice{
     34 + License: &cyclonedx.License{
     35 + Name: l,
     36 + },
     37 + })
     38 + }
     39 + return &spdxc
     40 + }
     41 + 
     42 + if len(ex) > 0 {
     43 + // only expressions found
     44 + var expressions cyclonedx.Licenses
     45 + expressions = append(expressions, cyclonedx.LicenseChoice{
     46 + Expression: mergeSPDX(ex),
     47 + })
     48 + return &expressions
     49 + }
     50 + 
     51 + return nil
     52 +}
     53 + 
     54 +func decodeLicenses(c *cyclonedx.Component) []pkg.License {
     55 + licenses := make([]pkg.License, 0)
     56 + if c == nil || c.Licenses == nil {
     57 + return licenses
     58 + }
     59 + 
     60 + for _, l := range *c.Licenses {
     61 + if l.License == nil {
     62 + continue
     63 + }
     64 + // these fields are mutually exclusive in the spec
     65 + switch {
     66 + case l.License.ID != "":
     67 + licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.ID, l.License.URL))
     68 + case l.License.Name != "":
     69 + licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.Name, l.License.URL))
     70 + case l.Expression != "":
     71 + licenses = append(licenses, pkg.NewLicenseFromURLs(l.Expression, l.License.URL))
     72 + default:
     73 + }
     74 + }
     75 + 
     76 + return licenses
     77 +}
     78 + 
     79 +// nolint:funlen
     80 +func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expressions []string) {
     81 + ex := make([]string, 0)
     82 + spdxc := cyclonedx.Licenses{}
     83 + otherc := cyclonedx.Licenses{}
     84 + /*
     85 + pkg.License can be a couple of things: see above declarations
     86 + - Complex SPDX expression
     87 + - Some other Valid license ID
     88 + - Some non-standard non spdx license
     89 + 
     90 + To determine if an expression is a singular ID we first run it against the SPDX license list.
     91 + 
     92 + The weird case we run into is if there is a package with a license that is not a valid SPDX expression
     93 + and a license that is a valid complex expression. In this case we will surface the valid complex expression
     94 + as a license choice and the invalid expression as a license string.
     95 + 
     96 + */
     97 + seen := make(map[string]bool)
     98 + for _, l := range p.Licenses.ToSlice() {
     99 + // singular expression case
     100 + // only ID field here since we guarantee that the license is valid
     101 + if value, exists := spdxlicense.ID(l.SPDXExpression); exists {
     102 + if !l.URL.Empty() {
     103 + processLicenseURLs(l, value, &spdxc)
     104 + continue
     105 + }
     106 + 
     107 + if _, exists := seen[value]; exists {
     108 + continue
     109 + }
     110 + // try making set of license choices to avoid duplicates
     111 + // only update if the license has more information
     112 + spdxc = append(spdxc, cyclonedx.LicenseChoice{
    15 113   License: &cyclonedx.License{
    16 114   ID: value,
    17 115   },
    18 116   })
     117 + seen[value] = true
     118 + // we have added the license to the SPDX license list check next license
    19 119   continue
    20 120   }
    21 121   
    22  - // not found so append the licenseName as is
    23  - lc = append(lc, cyclonedx.LicenseChoice{
     122 + if l.SPDXExpression != "" {
     123 + // COMPLEX EXPRESSION CASE
     124 + ex = append(ex, l.SPDXExpression)
     125 + continue
     126 + }
     127 + 
     128 + // license string that are not valid spdx expressions or ids
     129 + // we only use license Name here since we cannot guarantee that the license is a valid SPDX expression
     130 + if !l.URL.Empty() {
     131 + processLicenseURLs(l, "", &otherc)
     132 + continue
     133 + }
     134 + otherc = append(otherc, cyclonedx.LicenseChoice{
    24 135   License: &cyclonedx.License{
    25  - Name: licenseName,
     136 + Name: l.Value,
    26 137   },
    27 138   })
    28 139   }
    29  - if len(lc) > 0 {
    30  - return &lc
     140 + return spdxc, otherc, ex
     141 +}
     142 + 
     143 +func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
     144 + for _, url := range l.URL.ToSlice() {
     145 + if spdxID == "" {
     146 + *populate = append(*populate, cyclonedx.LicenseChoice{
     147 + License: &cyclonedx.License{
     148 + URL: url,
     149 + Name: l.Value,
     150 + },
     151 + })
     152 + } else {
     153 + *populate = append(*populate, cyclonedx.LicenseChoice{
     154 + License: &cyclonedx.License{
     155 + ID: spdxID,
     156 + URL: url,
     157 + },
     158 + })
     159 + }
    31 160   }
    32  - return nil
    33 161  }
    34 162   
    35  -func decodeLicenses(c *cyclonedx.Component) (out []string) {
    36  - if c.Licenses != nil {
    37  - for _, l := range *c.Licenses {
    38  - if l.License != nil {
    39  - var lic string
    40  - switch {
    41  - case l.License.ID != "":
    42  - lic = l.License.ID
    43  - case l.License.Name != "":
    44  - lic = l.License.Name
    45  - default:
    46  - continue
    47  - }
    48  - out = append(out, lic)
    49  - }
     163 +func mergeSPDX(ex []string) string {
     164 + var candidate []string
     165 + for _, e := range ex {
     166 + // if the expression does not have balanced parens add them
     167 + if !strings.HasPrefix(e, "(") && !strings.HasSuffix(e, ")") {
     168 + e = "(" + e + ")"
     169 + candidate = append(candidate, e)
     170 + }
     171 + }
     172 + 
     173 + if len(candidate) == 1 {
     174 + return reduceOuter(strings.Join(candidate, " AND "))
     175 + }
     176 + 
     177 + return strings.Join(candidate, " AND ")
     178 +}
     179 + 
     180 +func reduceOuter(expression string) string {
     181 + var (
     182 + sb strings.Builder
     183 + openCount int
     184 + )
     185 + 
     186 + for _, c := range expression {
     187 + if string(c) == "(" && openCount > 0 {
     188 + fmt.Fprintf(&sb, "%c", c)
     189 + }
     190 + if string(c) == "(" {
     191 + openCount++
     192 + continue
     193 + }
     194 + if string(c) == ")" && openCount > 1 {
     195 + fmt.Fprintf(&sb, "%c", c)
     196 + }
     197 + if string(c) == ")" {
     198 + openCount--
     199 + continue
    50 200   }
     201 + fmt.Fprintf(&sb, "%c", c)
    51 202   }
    52  - return
     203 + 
     204 + return sb.String()
    53 205  }
    54 206   
  • ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/licenses_test.go
    skipped 5 lines
    6 6   "github.com/CycloneDX/cyclonedx-go"
    7 7   "github.com/stretchr/testify/assert"
    8 8   
     9 + "github.com/anchore/syft/internal"
     10 + "github.com/anchore/syft/syft/license"
    9 11   "github.com/anchore/syft/syft/pkg"
    10 12  )
    11 13   
    skipped 11 lines
    23 25   {
    24 26   name: "no SPDX licenses",
    25 27   input: pkg.Package{
    26  - Licenses: []string{
    27  - "made-up",
     28 + Licenses: pkg.NewLicenseSet(
     29 + pkg.NewLicense("RandomLicense"),
     30 + ),
     31 + },
     32 + expected: &cyclonedx.Licenses{
     33 + {
     34 + License: &cyclonedx.License{
     35 + Name: "RandomLicense",
     36 + },
    28 37   },
    29 38   },
     39 + },
     40 + {
     41 + name: "single SPDX ID and Non SPDX ID",
     42 + input: pkg.Package{
     43 + Licenses: pkg.NewLicenseSet(
     44 + pkg.NewLicense("mit"),
     45 + pkg.NewLicense("FOOBAR"),
     46 + ),
     47 + },
    30 48   expected: &cyclonedx.Licenses{
    31  - {License: &cyclonedx.License{Name: "made-up"}},
     49 + {
     50 + License: &cyclonedx.License{
     51 + Name: "FOOBAR",
     52 + },
     53 + },
     54 + {
     55 + License: &cyclonedx.License{
     56 + ID: "MIT",
     57 + },
     58 + },
    32 59   },
    33 60   },
    34 61   {
    35  - name: "with SPDX license",
     62 + name: "with complex SPDX license expression",
    36 63   input: pkg.Package{
    37  - Licenses: []string{
    38  - "MIT",
     64 + Licenses: pkg.NewLicenseSet(
     65 + pkg.NewLicense("MIT AND GPL-3.0-only"),
     66 + ),
     67 + },
     68 + expected: &cyclonedx.Licenses{
     69 + {
     70 + Expression: "MIT AND GPL-3.0-only",
    39 71   },
    40 72   },
     73 + },
     74 + {
     75 + name: "with multiple complex SPDX license expression",
     76 + input: pkg.Package{
     77 + Licenses: pkg.NewLicenseSet(
     78 + pkg.NewLicense("MIT AND GPL-3.0-only"),
     79 + pkg.NewLicense("MIT AND GPL-3.0-only WITH Classpath-exception-2.0"),
     80 + ),
     81 + },
    41 82   expected: &cyclonedx.Licenses{
    42  - {License: &cyclonedx.License{ID: "MIT"}},
     83 + {
     84 + Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
     85 + },
    43 86   },
    44 87   },
    45 88   {
    46  - name: "with SPDX license expression",
     89 + name: "with multiple URLs and expressions",
    47 90   input: pkg.Package{
    48  - Licenses: []string{
    49  - "MIT",
    50  - "GPL-3.0",
    51  - },
     91 + Licenses: pkg.NewLicenseSet(
     92 + pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
     93 + pkg.NewLicense("MIT AND GPL-3.0-only"),
     94 + pkg.NewLicenseFromURLs("FakeLicense", "htts://someurl.com"),
     95 + ),
    52 96   },
    53 97   expected: &cyclonedx.Licenses{
    54  - {License: &cyclonedx.License{ID: "MIT"}},
    55  - {License: &cyclonedx.License{ID: "GPL-3.0-only"}},
     98 + {
     99 + License: &cyclonedx.License{
     100 + Name: "FakeLicense",
     101 + URL: "htts://someurl.com",
     102 + },
     103 + },
     104 + {
     105 + License: &cyclonedx.License{
     106 + Name: "MIT AND GPL-3.0-only",
     107 + },
     108 + },
     109 + {
     110 + License: &cyclonedx.License{
     111 + ID: "MIT",
     112 + URL: "https://opensource.org/licenses/MIT",
     113 + },
     114 + },
     115 + {
     116 + License: &cyclonedx.License{
     117 + ID: "MIT",
     118 + URL: "https://spdx.org/licenses/MIT.html",
     119 + },
     120 + },
    56 121   },
    57 122   },
    58 123   {
    59  - name: "cap insensitive",
     124 + name: "with multiple values licenses are deduplicated",
    60 125   input: pkg.Package{
    61  - Licenses: []string{
    62  - "gpl-3.0",
    63  - },
     126 + Licenses: pkg.NewLicenseSet(
     127 + pkg.NewLicense("Apache-2"),
     128 + pkg.NewLicense("Apache-2.0"),
     129 + ),
    64 130   },
    65 131   expected: &cyclonedx.Licenses{
    66  - {License: &cyclonedx.License{ID: "GPL-3.0-only"}},
     132 + {
     133 + License: &cyclonedx.License{
     134 + ID: "Apache-2.0",
     135 + },
     136 + },
    67 137   },
    68 138   },
    69 139   {
    70  - name: "debian to spdx conversion",
     140 + name: "with multiple URLs and single with no URL",
    71 141   input: pkg.Package{
    72  - Licenses: []string{
    73  - "GPL-2",
    74  - },
     142 + Licenses: pkg.NewLicenseSet(
     143 + pkg.NewLicense("MIT"),
     144 + pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
     145 + pkg.NewLicense("MIT AND GPL-3.0-only"),
     146 + ),
    75 147   },
    76 148   expected: &cyclonedx.Licenses{
    77  - {License: &cyclonedx.License{ID: "GPL-2.0-only"}},
     149 + {
     150 + License: &cyclonedx.License{
     151 + ID: "MIT",
     152 + URL: "https://opensource.org/licenses/MIT",
     153 + },
     154 + },
     155 + {
     156 + License: &cyclonedx.License{
     157 + ID: "MIT",
     158 + URL: "https://spdx.org/licenses/MIT.html",
     159 + },
     160 + },
     161 + {
     162 + License: &cyclonedx.License{
     163 + Name: "MIT AND GPL-3.0-only",
     164 + },
     165 + },
    78 166   },
    79 167   },
     168 + // TODO: do we drop the non SPDX ID license and do a single expression
     169 + // OR do we keep the non SPDX ID license and do multiple licenses where the complex
     170 + // expressions are set as the NAME field?
     171 + //{
     172 + // name: "with multiple complex SPDX license expression and a non spdx id",
     173 + // input: pkg.Package{
     174 + // Licenses: []pkg.License{
     175 + // {
     176 + // SPDXExpression: "MIT AND GPL-3.0-only",
     177 + // },
     178 + // {
     179 + // SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
     180 + // },
     181 + // {
     182 + // Value: "FOOBAR",
     183 + // },
     184 + // },
     185 + // },
     186 + // expected: &cyclonedx.Licenses{
     187 + // {
     188 + // Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
     189 + // },
     190 + // },
     191 + //},
    80 192   }
    81 193   for _, test := range tests {
    82 194   t.Run(test.name, func(t *testing.T) {
    skipped 2 lines
    85 197   }
    86 198  }
    87 199   
     200 +func TestDecodeLicenses(t *testing.T) {
     201 + tests := []struct {
     202 + name string
     203 + input *cyclonedx.Component
     204 + expected []pkg.License
     205 + }{
     206 + {
     207 + name: "no licenses",
     208 + input: &cyclonedx.Component{},
     209 + expected: []pkg.License{},
     210 + },
     211 + {
     212 + name: "no SPDX license ID or expression",
     213 + input: &cyclonedx.Component{
     214 + Licenses: &cyclonedx.Licenses{
     215 + {
     216 + License: &cyclonedx.License{
     217 + Name: "RandomLicense",
     218 + },
     219 + },
     220 + },
     221 + },
     222 + expected: []pkg.License{
     223 + {
     224 + Value: "RandomLicense",
     225 + // CycloneDX specification doesn't give a field for determining the license type
     226 + Type: license.Declared,
     227 + URL: internal.NewStringSet(),
     228 + },
     229 + },
     230 + },
     231 + {
     232 + name: "with SPDX license ID",
     233 + input: &cyclonedx.Component{
     234 + Licenses: &cyclonedx.Licenses{
     235 + {
     236 + License: &cyclonedx.License{
     237 + ID: "MIT",
     238 + },
     239 + },
     240 + },
     241 + },
     242 + expected: []pkg.License{
     243 + {
     244 + Value: "MIT",
     245 + SPDXExpression: "MIT",
     246 + Type: license.Declared,
     247 + URL: internal.NewStringSet(),
     248 + },
     249 + },
     250 + },
     251 + {
     252 + name: "with complex SPDX license expression",
     253 + input: &cyclonedx.Component{
     254 + Licenses: &cyclonedx.Licenses{
     255 + {
     256 + License: &cyclonedx.License{},
     257 + Expression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
     258 + },
     259 + },
     260 + },
     261 + expected: []pkg.License{
     262 + {
     263 + Value: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
     264 + SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
     265 + Type: license.Declared,
     266 + URL: internal.NewStringSet(),
     267 + },
     268 + },
     269 + },
     270 + }
     271 + for _, test := range tests {
     272 + t.Run(test.name, func(t *testing.T) {
     273 + assert.Equal(t, test.expected, decodeLicenses(test.input))
     274 + })
     275 + }
     276 +}
     277 + 
  • ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/license.go
    skipped 3 lines
    4 4   "strings"
    5 5   
    6 6   "github.com/anchore/syft/internal/spdxlicense"
     7 + "github.com/anchore/syft/syft/license"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9  )
    9 10   
    10  -func License(p pkg.Package) string {
     11 +func License(p pkg.Package) (concluded, declared string) {
    11 12   // source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
    12 13   // The options to populate this field are limited to:
    13 14   // A valid SPDX License Expression as defined in Appendix IV;
    skipped 3 lines
    17 18   // (ii) the SPDX file creator has made no attempt to determine this field; or
    18 19   // (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
    19 20   
    20  - if len(p.Licenses) == 0 {
    21  - return NONE
     21 + if p.Licenses.Empty() {
     22 + return NOASSERTION, NOASSERTION
    22 23   }
    23 24   
    24  - // take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
    25  - parsedLicenses := parseLicenses(p.Licenses)
     25 + // take all licenses and assume an AND expression;
     26 + // for information about license expressions see:
     27 + // https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
     28 + pc, pd := parseLicenses(p.Licenses.ToSlice())
     29 + 
     30 + for i, v := range pc {
     31 + if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
     32 + pc[i] = SanitizeElementID(v)
     33 + }
     34 + }
    26 35   
    27  - for i, v := range parsedLicenses {
     36 + for i, v := range pd {
    28 37   if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
    29  - parsedLicenses[i] = SanitizeElementID(v)
     38 + pd[i] = SanitizeElementID(v)
    30 39   }
    31 40   }
    32 41   
    33  - if len(parsedLicenses) == 0 {
     42 + return joinLicenses(pc), joinLicenses(pd)
     43 +}
     44 + 
     45 +func joinLicenses(licenses []string) string {
     46 + if len(licenses) == 0 {
    34 47   return NOASSERTION
    35 48   }
    36 49   
    37  - return strings.Join(parsedLicenses, " AND ")
     50 + var newLicenses []string
     51 + 
     52 + for _, v := range licenses {
     53 + // check if license does not start or end with parens
     54 + if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
     55 + // if license contains AND, OR, or WITH, then wrap in parens
     56 + if strings.Contains(v, " AND ") ||
     57 + strings.Contains(v, " OR ") ||
     58 + strings.Contains(v, " WITH ") {
     59 + newLicenses = append(newLicenses, "("+v+")")
     60 + continue
     61 + }
     62 + }
     63 + newLicenses = append(newLicenses, v)
     64 + }
     65 + 
     66 + return strings.Join(newLicenses, " AND ")
    38 67  }
    39 68   
    40  -func parseLicenses(raw []string) (parsedLicenses []string) {
     69 +func parseLicenses(raw []pkg.License) (concluded, declared []string) {
    41 70   for _, l := range raw {
    42  - if value, exists := spdxlicense.ID(l); exists {
    43  - parsedLicenses = append(parsedLicenses, value)
     71 + var candidate string
     72 + if l.SPDXExpression != "" {
     73 + candidate = l.SPDXExpression
    44 74   } else {
    45 75   // we did not find a valid SPDX license ID so treat as separate license
    46  - otherLicense := spdxlicense.LicenseRefPrefix + l
    47  - parsedLicenses = append(parsedLicenses, otherLicense)
     76 + candidate = spdxlicense.LicenseRefPrefix + l.Value
     77 + }
     78 + 
     79 + switch l.Type {
     80 + case license.Concluded:
     81 + concluded = append(concluded, candidate)
     82 + case license.Declared:
     83 + declared = append(declared, candidate)
    48 84   }
    49 85   }
    50  - return
     86 + return concluded, declared
    51 87  }
    52 88   
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/license_test.go
    skipped 8 lines
    9 9  )
    10 10   
    11 11  func Test_License(t *testing.T) {
     12 + type expected struct {
     13 + concluded string
     14 + declared string
     15 + }
    12 16   tests := []struct {
    13 17   name string
    14 18   input pkg.Package
    15  - expected string
     19 + expected expected
    16 20   }{
    17 21   {
    18  - name: "no licenses",
    19  - input: pkg.Package{},
    20  - expected: NONE,
     22 + name: "no licenses",
     23 + input: pkg.Package{},
     24 + expected: expected{
     25 + concluded: "NOASSERTION",
     26 + declared: "NOASSERTION",
     27 + },
    21 28   },
    22 29   {
    23 30   name: "no SPDX licenses",
    24 31   input: pkg.Package{
    25  - Licenses: []string{
    26  - "made-up",
    27  - },
     32 + Licenses: pkg.NewLicenseSet(pkg.NewLicense("made-up")),
     33 + },
     34 + expected: expected{
     35 + concluded: "NOASSERTION",
     36 + declared: "LicenseRef-made-up",
    28 37   },
    29  - expected: "LicenseRef-made-up",
    30 38   },
    31 39   {
    32 40   name: "with SPDX license",
    33 41   input: pkg.Package{
    34  - Licenses: []string{
    35  - "MIT",
    36  - },
     42 + Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
    37 43   },
    38  - expected: "MIT",
     44 + expected: struct {
     45 + concluded string
     46 + declared string
     47 + }{
     48 + concluded: "NOASSERTION",
     49 + declared: "MIT",
     50 + },
    39 51   },
    40 52   {
    41 53   name: "with SPDX license expression",
    42 54   input: pkg.Package{
    43  - Licenses: []string{
    44  - "MIT",
    45  - "GPL-3.0",
    46  - },
     55 + Licenses: pkg.NewLicenseSet(
     56 + pkg.NewLicense("MIT"),
     57 + pkg.NewLicense("GPL-3.0-only"),
     58 + ),
    47 59   },
    48  - expected: "MIT AND GPL-3.0-only",
     60 + expected: expected{
     61 + concluded: "NOASSERTION",
     62 + // because we sort licenses alphabetically GPL ends up at the start
     63 + declared: "GPL-3.0-only AND MIT",
     64 + },
    49 65   },
    50 66   {
    51  - name: "cap insensitive",
     67 + name: "includes valid LicenseRef-",
    52 68   input: pkg.Package{
    53  - Licenses: []string{
    54  - "gpl-3.0",
    55  - },
     69 + Licenses: pkg.NewLicenseSet(
     70 + pkg.NewLicense("one thing first"),
     71 + pkg.NewLicense("two things/#$^second"),
     72 + pkg.NewLicense("MIT"),
     73 + ),
     74 + },
     75 + expected: expected{
     76 + concluded: "NOASSERTION",
     77 + // because we separate licenses between valid SPDX and non valid, valid ID always end at the front
     78 + declared: "MIT AND LicenseRef-one-thing-first AND LicenseRef-two-things----second",
    56 79   },
    57  - expected: "GPL-3.0-only",
    58 80   },
    59 81   {
    60  - name: "debian to spdx conversion",
     82 + name: "join parentheses correctly",
    61 83   input: pkg.Package{
    62  - Licenses: []string{
    63  - "GPL-2",
    64  - },
     84 + Licenses: pkg.NewLicenseSet(
     85 + pkg.NewLicense("one thing first"),
     86 + pkg.NewLicense("MIT AND GPL-3.0-only"),
     87 + pkg.NewLicense("MIT OR APACHE-2.0"),
     88 + ),
    65 89   },
    66  - expected: "GPL-2.0-only",
    67  - },
    68  - {
    69  - name: "includes valid LicenseRef-",
    70  - input: pkg.Package{
    71  - Licenses: []string{
    72  - "one thing first",
    73  - "two things/#$^second",
    74  - "MIT",
    75  - },
     90 + expected: expected{
     91 + concluded: "NOASSERTION",
     92 + // because we separate licenses between valid SPDX and non valid, valid ID always end at the front
     93 + declared: "(MIT AND GPL-3.0-only) AND (MIT OR APACHE-2.0) AND LicenseRef-one-thing-first",
    76 94   },
    77  - expected: "LicenseRef-one-thing-first AND LicenseRef-two-things----second AND MIT",
    78 95   },
    79 96   }
    80 97   for _, test := range tests {
    81 98   t.Run(test.name, func(t *testing.T) {
    82  - assert.Equal(t, test.expected, License(test.input))
     99 + c, d := License(test.input)
     100 + assert.Equal(t, test.expected.concluded, c)
     101 + assert.Equal(t, test.expected.declared, d)
     102 + })
     103 + }
     104 +}
     105 + 
     106 +func Test_joinLicenses(t *testing.T) {
     107 + tests := []struct {
     108 + name string
     109 + args []string
     110 + want string
     111 + }{
     112 + {
     113 + name: "multiple licenses",
     114 + args: []string{"MIT", "GPL-3.0-only"},
     115 + want: "MIT AND GPL-3.0-only",
     116 + },
     117 + {
     118 + name: "multiple licenses with complex expressions",
     119 + args: []string{"MIT AND Apache", "GPL-3.0-only"},
     120 + want: "(MIT AND Apache) AND GPL-3.0-only",
     121 + },
     122 + }
     123 + for _, tt := range tests {
     124 + t.Run(tt.name, func(t *testing.T) {
     125 + assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
    83 126   })
    84 127   }
    85 128  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_format_model.go
    skipped 169 lines
    170 170   // If the Concluded License is not the same as the Declared License, a written explanation should be provided
    171 171   // in the Comments on License field (section 7.16). With respect to NOASSERTION, a written explanation in
    172 172   // the Comments on License field (section 7.16) is preferred.
    173  - license := License(p)
     173 + // extract these correctly to the spdx license format
     174 + concluded, declared := License(p)
    174 175   
    175 176   // two ways to get filesAnalyzed == true:
    176 177   // 1. syft has generated a sha1 digest for the package itself - usually in the java cataloger
    skipped 97 lines
    274 275   // Cardinality: mandatory, one
    275 276   // Purpose: Contain the license the SPDX file creator has concluded as governing the
    276 277   // package or alternative values, if the governing license cannot be determined.
    277  - PackageLicenseConcluded: license,
     278 + PackageLicenseConcluded: concluded,
    278 279   
    279 280   // 7.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
    280 281   // Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
    skipped 5 lines
    286 287   // Purpose: List the licenses that have been declared by the authors of the package.
    287 288   // Any license information that does not originate from the package authors, e.g. license
    288 289   // information from a third party repository, should not be included in this field.
    289  - PackageLicenseDeclared: license,
     290 + PackageLicenseDeclared: declared,
    290 291   
    291 292   // 7.16: Comments on License
    292 293   // Cardinality: optional, one
    skipped 241 lines
    534 535   return ty
    535 536  }
    536 537   
     538 +// other licenses are for licenses from the pkg.Package that do not have an SPDXExpression
     539 +// field. The spdxexpression field is only filled given a validated Value field.
    537 540  func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
    538 541   licenses := map[string]bool{}
    539 542   for _, p := range catalog.Sorted() {
    540  - for _, license := range parseLicenses(p.Licenses) {
     543 + declaredLicenses, concludedLicenses := parseLicenses(p.Licenses.ToSlice())
     544 + for _, license := range declaredLicenses {
     545 + if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
     546 + licenses[license] = true
     547 + }
     548 + }
     549 + for _, license := range concludedLicenses {
    541 550   if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
    542 551   licenses[license] = true
    543 552   }
    skipped 5 lines
    549 558   sorted := maps.Keys(licenses)
    550 559   slices.Sort(sorted)
    551 560   for _, license := range sorted {
    552  - // separate the actual ID from the prefix
     561 + // separate the found value from the prefix
     562 + // this only contains licenses that are not found on the SPDX License List
    553 563   name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
    554 564   result = append(result, &spdx.OtherLicense{
    555 565   LicenseIdentifier: SanitizeElementID(license),
    556  - LicenseName: name,
    557  - ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
     566 + ExtractedText: name,
    558 567   })
    559 568   }
    560 569   return result
    skipped 54 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_format_model_test.go
    skipped 447 lines
    448 448   {
    449 449   name: "no licenseRef",
    450 450   pkg: pkg.Package{
    451  - Licenses: []string{
    452  - "MIT",
    453  - },
     451 + Licenses: pkg.NewLicenseSet(),
    454 452   },
    455 453   expected: nil,
    456 454   },
    457 455   {
    458 456   name: "single licenseRef",
    459 457   pkg: pkg.Package{
    460  - Licenses: []string{
    461  - "un known",
    462  - },
     458 + Licenses: pkg.NewLicenseSet(
     459 + pkg.NewLicense("foobar"),
     460 + ),
    463 461   },
    464 462   expected: []*spdx.OtherLicense{
    465 463   {
    466  - LicenseIdentifier: "LicenseRef-un-known",
    467  - LicenseName: "un known",
    468  - ExtractedText: NONE,
     464 + LicenseIdentifier: "LicenseRef-foobar",
     465 + ExtractedText: "foobar",
    469 466   },
    470 467   },
    471 468   },
    472 469   {
    473 470   name: "multiple licenseRef",
    474 471   pkg: pkg.Package{
    475  - Licenses: []string{
    476  - "un known",
    477  - "not known %s",
    478  - "MIT",
    479  - },
     472 + Licenses: pkg.NewLicenseSet(
     473 + pkg.NewLicense("internal made up license name"),
     474 + pkg.NewLicense("new apple license 2.0"),
     475 + ),
    480 476   },
    481 477   expected: []*spdx.OtherLicense{
    482 478   {
    483  - LicenseIdentifier: "LicenseRef-not-known--s",
    484  - LicenseName: "not known %s",
    485  - ExtractedText: NONE,
     479 + LicenseIdentifier: "LicenseRef-internal-made-up-license-name",
     480 + ExtractedText: "internal made up license name",
    486 481   },
    487 482   {
    488  - LicenseIdentifier: "LicenseRef-un-known",
    489  - LicenseName: "un known",
    490  - ExtractedText: NONE,
     483 + LicenseIdentifier: "LicenseRef-new-apple-license-2.0",
     484 + ExtractedText: "new apple license 2.0",
    491 485   },
    492 486   },
    493 487   },
    skipped 52 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_syft_model.go
    skipped 13 lines
    14 14   "github.com/anchore/syft/syft/cpe"
    15 15   "github.com/anchore/syft/syft/file"
    16 16   "github.com/anchore/syft/syft/formats/common/util"
     17 + "github.com/anchore/syft/syft/license"
    17 18   "github.com/anchore/syft/syft/linux"
    18 19   "github.com/anchore/syft/syft/pkg"
    19 20   "github.com/anchore/syft/syft/sbom"
    skipped 259 lines
    279 280   Type: info.typ,
    280 281   Name: p.PackageName,
    281 282   Version: p.PackageVersion,
    282  - Licenses: parseLicense(p.PackageLicenseDeclared),
     283 + Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
    283 284   CPEs: extractCPEs(p),
    284 285   PURL: info.purl.String(),
    285 286   Language: info.lang,
    skipped 6 lines
    292 293   return &sP
    293 294  }
    294 295   
     296 +func parseSPDXLicenses(p *spdx.Package) []pkg.License {
     297 + licenses := make([]pkg.License, 0)
     298 + 
     299 + // concluded
     300 + if p.PackageLicenseConcluded != NOASSERTION && p.PackageLicenseConcluded != NONE && p.PackageLicenseConcluded != "" {
     301 + l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseConcluded))
     302 + l.Type = license.Concluded
     303 + licenses = append(licenses, l)
     304 + }
     305 + 
     306 + // declared
     307 + if p.PackageLicenseDeclared != NOASSERTION && p.PackageLicenseDeclared != NONE && p.PackageLicenseDeclared != "" {
     308 + l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseDeclared))
     309 + l.Type = license.Declared
     310 + licenses = append(licenses, l)
     311 + }
     312 + 
     313 + return licenses
     314 +}
     315 + 
     316 +func cleanSPDXID(id string) string {
     317 + if strings.HasPrefix(id, "LicenseRef-") {
     318 + return strings.TrimPrefix(id, "LicenseRef-")
     319 + }
     320 + return id
     321 +}
     322 + 
    295 323  //nolint:funlen
    296 324  func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface{}) {
    297 325   arch := info.qualifierValue(pkg.PURLQualifierArch)
    skipped 19 lines
    317 345   OriginPackage: upstreamName,
    318 346   Maintainer: supplier,
    319 347   Version: p.PackageVersion,
    320  - License: p.PackageLicenseDeclared,
    321 348   Architecture: arch,
    322 349   URL: p.PackageHomePage,
    323 350   Description: p.PackageDescription,
    skipped 6 lines
    330 357   } else {
    331 358   epoch = &converted
    332 359   }
    333  - license := p.PackageLicenseDeclared
    334  - if license == "" {
    335  - license = p.PackageLicenseConcluded
    336  - }
    337 360   return pkg.RpmMetadataType, pkg.RpmMetadata{
    338 361   Name: p.PackageName,
    339 362   Version: p.PackageVersion,
    340 363   Epoch: epoch,
    341 364   Arch: arch,
    342 365   SourceRpm: upstreamValue,
    343  - License: license,
    344 366   Vendor: originator,
    345 367   }
    346 368   case pkg.DebPkg:
    skipped 54 lines
    401 423   return cpes
    402 424  }
    403 425   
    404  -func parseLicense(l string) []string {
    405  - if l == NOASSERTION || l == NONE {
    406  - return nil
    407  - }
    408  - return strings.Split(l, " AND ")
    409  -}
    410  - 
  • ■ ■ ■ ■ ■ ■
    syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
    skipped 1 lines
    2 2   "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
    3 3   "bomFormat": "CycloneDX",
    4 4   "specVersion": "1.4",
    5  - "serialNumber": "urn:uuid:redacted",
     5 + "serialNumber": "urn:uuid:1b71a5b4-4bc5-4548-a51a-212e631976cd",
    6 6   "version": 1,
    7 7   "metadata": {
    8  - "timestamp": "timestamp:redacted",
     8 + "timestamp": "2023-05-08T14:40:32-04:00",
    9 9   "tools": [
    10 10   {
    11 11   "vendor": "anchore",
    skipped 2 lines
    14 14   }
    15 15   ],
    16 16   "component": {
    17  - "bom-ref": "redacted",
     17 + "bom-ref": "163686ac6e30c752",
    18 18   "type": "file",
    19 19   "name": "/some/path"
    20 20   }
    21 21   },
    22 22   "components": [
    23 23   {
    24  - "bom-ref": "redacted",
     24 + "bom-ref": "8c7e1242588c971a",
    25 25   "type": "library",
    26 26   "name": "package-1",
    27 27   "version": "1.0.1",
    skipped 30 lines
    58 58   ]
    59 59   },
    60 60   {
    61  - "bom-ref": "redacted",
     61 + "bom-ref": "pkg:deb/debian/[email protected]?package-id=db4abfe497c180d3",
    62 62   "type": "library",
    63 63   "name": "package-2",
    64 64   "version": "2.0.1",
    skipped 57 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/cyclonedxjson/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
    skipped 1 lines
    2 2   "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
    3 3   "bomFormat": "CycloneDX",
    4 4   "specVersion": "1.4",
    5  - "serialNumber": "urn:uuid:redacted",
     5 + "serialNumber": "urn:uuid:1695d6ae-0ddf-4e77-9c9d-74df1bdd8d5b",
    6 6   "version": 1,
    7 7   "metadata": {
    8  - "timestamp": "timestamp:redacted",
     8 + "timestamp": "2023-05-08T14:40:32-04:00",
    9 9   "tools": [
    10 10   {
    11 11   "vendor": "anchore",
    skipped 2 lines
    14 14   }
    15 15   ],
    16 16   "component": {
    17  - "bom-ref": "redacted",
     17 + "bom-ref": "38160ebc2a6876e8",
    18 18   "type": "container",
    19 19   "name": "user-image-input",
    20  - "version": "sha256:redacted"
     20 + "version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
    21 21   }
    22 22   },
    23 23   "components": [
    24 24   {
    25  - "bom-ref": "redacted",
     25 + "bom-ref": "ec2e0c93617507ef",
    26 26   "type": "library",
    27 27   "name": "package-1",
    28 28   "version": "1.0.1",
    skipped 25 lines
    54 54   },
    55 55   {
    56 56   "name": "syft:location:0:layerID",
    57  - "value": "sha256:redacted"
     57 + "value": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
    58 58   },
    59 59   {
    60 60   "name": "syft:location:0:path",
    skipped 2 lines
    63 63   ]
    64 64   },
    65 65   {
    66  - "bom-ref": "redacted",
     66 + "bom-ref": "pkg:deb/debian/[email protected]?package-id=958443e2d9304af4",
    67 67   "type": "library",
    68 68   "name": "package-2",
    69 69   "version": "2.0.1",
    skipped 14 lines
    84 84   },
    85 85   {
    86 86   "name": "syft:location:0:layerID",
    87  - "value": "sha256:redacted"
     87 + "value": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
    88 88   },
    89 89   {
    90 90   "name": "syft:location:0:path",
    skipped 40 lines
  • syft/formats/cyclonedxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden
    Binary file.
  • ■ ■ ■ ■ ■
    syft/formats/cyclonedxxml/decoder_test.go
    skipped 6 lines
    7 7   "testing"
    8 8   
    9 9   "github.com/stretchr/testify/assert"
     10 + "github.com/stretchr/testify/require"
    10 11  )
    11 12   
    12 13  func Test_decodeXML(t *testing.T) {
    skipped 21 lines
    34 35   for _, test := range tests {
    35 36   t.Run(test.file, func(t *testing.T) {
    36 37   reader, err := os.Open("test-fixtures/" + test.file)
    37  - assert.NoError(t, err)
     38 + require.NoError(t, err)
    38 39   
    39 40   if test.err {
    40 41   err = Format().Validate(reader)
    skipped 3 lines
    44 45   
    45 46   bom, err := Format().Decode(reader)
    46 47   
    47  - assert.NoError(t, err)
     48 + require.NoError(t, err)
    48 49   
    49 50   split := strings.SplitN(test.distro, ":", 2)
    50 51   name := split[0]
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden
    1 1  <?xml version="1.0" encoding="UTF-8"?>
    2  -<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2939b822-b9cb-489d-8a8b-4431b755031d" version="1">
     2 +<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:60f4e726-e884-4ae3-9b0e-18a918fbb02e" version="1">
    3 3   <metadata>
    4  - <timestamp>2022-11-07T09:11:06-05:00</timestamp>
     4 + <timestamp>2023-05-08T14:40:52-04:00</timestamp>
    5 5   <tools>
    6 6   <tool>
    7 7   <vendor>anchore</vendor>
    skipped 6 lines
    14 14   </component>
    15 15   </metadata>
    16 16   <components>
    17  - <component bom-ref="1b1d0be59ac59d2c" type="library">
     17 + <component bom-ref="8c7e1242588c971a" type="library">
    18 18   <name>package-1</name>
    19 19   <version>1.0.1</version>
    20 20   <licenses>
    skipped 41 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/cyclonedxxml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden
    1 1  <?xml version="1.0" encoding="UTF-8"?>
    2  -<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2896b5ce-2016-49e8-a422-239d662846c7" version="1">
     2 +<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:c8894728-c156-4fc5-8f5d-3e397eede5a7" version="1">
    3 3   <metadata>
    4  - <timestamp>2022-11-07T09:11:06-05:00</timestamp>
     4 + <timestamp>2023-05-08T14:40:52-04:00</timestamp>
    5 5   <tools>
    6 6   <tool>
    7 7   <vendor>anchore</vendor>
    skipped 1 lines
    9 9   <version>v0.42.0-bogus</version>
    10 10   </tool>
    11 11   </tools>
    12  - <component bom-ref="522dc6b135a55bb4" type="container">
     12 + <component bom-ref="38160ebc2a6876e8" type="container">
    13 13   <name>user-image-input</name>
    14 14   <version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
    15 15   </component>
    16 16   </metadata>
    17 17   <components>
    18  - <component bom-ref="66ba429119b8bec6" type="library">
     18 + <component bom-ref="ec2e0c93617507ef" type="library">
    19 19   <name>package-1</name>
    20 20   <version>1.0.1</version>
    21 21   <licenses>
    skipped 8 lines
    30 30   <property name="syft:package:language">python</property>
    31 31   <property name="syft:package:metadataType">PythonPackageMetadata</property>
    32 32   <property name="syft:package:type">python</property>
    33  - <property name="syft:location:0:layerID">sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59</property>
     33 + <property name="syft:location:0:layerID">sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777</property>
    34 34   <property name="syft:location:0:path">/somefile-1.txt</property>
    35 35   </properties>
    36 36   </component>
    skipped 6 lines
    43 43   <property name="syft:package:foundBy">the-cataloger-2</property>
    44 44   <property name="syft:package:metadataType">DpkgMetadata</property>
    45 45   <property name="syft:package:type">deb</property>
    46  - <property name="syft:location:0:layerID">sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec</property>
     46 + <property name="syft:location:0:layerID">sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2</property>
    47 47   <property name="syft:location:0:path">/somefile-2.txt</property>
    48 48   <property name="syft:metadata:installedSize">0</property>
    49 49   </properties>
    skipped 15 lines
  • syft/formats/cyclonedxxml/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden
    Binary file.
  • ■ ■ ■ ■ ■
    syft/formats/internal/testutils/utils.go
    skipped 161 lines
    162 162   FoundBy: "the-cataloger-1",
    163 163   Language: pkg.Python,
    164 164   MetadataType: pkg.PythonPackageMetadataType,
    165  - Licenses: []string{"MIT"},
     165 + Licenses: pkg.NewLicenseSet(
     166 + pkg.NewLicense("MIT"),
     167 + ),
    166 168   Metadata: pkg.PythonPackageMetadata{
    167 169   Name: "package-1",
    168 170   Version: "1.0.1",
    skipped 99 lines
    268 270   ),
    269 271   Language: pkg.Python,
    270 272   MetadataType: pkg.PythonPackageMetadataType,
    271  - Licenses: []string{"MIT"},
     273 + Licenses: pkg.NewLicenseSet(
     274 + pkg.NewLicense("MIT"),
     275 + ),
    272 276   Metadata: pkg.PythonPackageMetadata{
    273 277   Name: "package-1",
    274 278   Version: "1.0.1",
    skipped 44 lines
    319 323   ),
    320 324   Language: pkg.Python,
    321 325   MetadataType: pkg.PythonPackageMetadataType,
    322  - Licenses: []string{"MIT"},
     326 + Licenses: pkg.NewLicenseSet(
     327 + pkg.NewLicense("MIT"),
     328 + ),
    323 329   Metadata: pkg.PythonPackageMetadata{
    324 330   Name: "package-1",
    325 331   Version: "1.0.1",
    skipped 65 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden
    skipped 2 lines
    3 3   "dataLicense": "CC0-1.0",
    4 4   "SPDXID": "SPDXRef-DOCUMENT",
    5 5   "name": "/some/path",
    6  - "documentNamespace": "https://anchore.com/syft/dir/some/path-4029b5ec-6d70-4c0c-aedf-b61c8f5ea93c",
     6 + "documentNamespace": "https://anchore.com/syft/dir/some/path-5ea40e59-d91a-4682-a016-da45ddd540e4",
    7 7   "creationInfo": {
    8 8   "licenseListVersion": "3.20",
    9 9   "creators": [
    10 10   "Organization: Anchore, Inc",
    11 11   "Tool: syft-v0.42.0-bogus"
    12 12   ],
    13  - "created": "2023-05-02T18:24:17Z"
     13 + "created": "2023-05-09T17:11:26Z"
    14 14   },
    15 15   "packages": [
    16 16   {
    17 17   "name": "package-1",
    18  - "SPDXID": "SPDXRef-Package-python-package-1-1b1d0be59ac59d2c",
     18 + "SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a",
    19 19   "versionInfo": "1.0.1",
    20 20   "downloadLocation": "NOASSERTION",
    21 21   "sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
    22  - "licenseConcluded": "MIT",
     22 + "licenseConcluded": "NOASSERTION",
    23 23   "licenseDeclared": "MIT",
    24 24   "copyrightText": "NOASSERTION",
    25 25   "externalRefs": [
    skipped 15 lines
    41 41   "versionInfo": "2.0.1",
    42 42   "downloadLocation": "NOASSERTION",
    43 43   "sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
    44  - "licenseConcluded": "NONE",
    45  - "licenseDeclared": "NONE",
     44 + "licenseConcluded": "NOASSERTION",
     45 + "licenseDeclared": "NOASSERTION",
    46 46   "copyrightText": "NOASSERTION",
    47 47   "externalRefs": [
    48 48   {
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden
    skipped 2 lines
    3 3   "dataLicense": "CC0-1.0",
    4 4   "SPDXID": "SPDXRef-DOCUMENT",
    5 5   "name": "user-image-input",
    6  - "documentNamespace": "https://anchore.com/syft/image/user-image-input-6b0c6ff8-0f5f-4d95-8c1b-eb966d400804",
     6 + "documentNamespace": "https://anchore.com/syft/image/user-image-input-2cc737fb-af51-4e4b-9395-cceabcc305eb",
    7 7   "creationInfo": {
    8 8   "licenseListVersion": "3.20",
    9 9   "creators": [
    10 10   "Organization: Anchore, Inc",
    11 11   "Tool: syft-v0.42.0-bogus"
    12 12   ],
    13  - "created": "2023-05-02T18:24:18Z"
     13 + "created": "2023-05-09T17:11:26Z"
    14 14   },
    15 15   "packages": [
    16 16   {
    17 17   "name": "package-1",
    18  - "SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     18 + "SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    19 19   "versionInfo": "1.0.1",
    20 20   "downloadLocation": "NOASSERTION",
    21 21   "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
    22  - "licenseConcluded": "MIT",
     22 + "licenseConcluded": "NOASSERTION",
    23 23   "licenseDeclared": "MIT",
    24 24   "copyrightText": "NOASSERTION",
    25 25   "externalRefs": [
    skipped 15 lines
    41 41   "versionInfo": "2.0.1",
    42 42   "downloadLocation": "NOASSERTION",
    43 43   "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
    44  - "licenseConcluded": "NONE",
    45  - "licenseDeclared": "NONE",
     44 + "licenseConcluded": "NOASSERTION",
     45 + "licenseDeclared": "NOASSERTION",
    46 46   "copyrightText": "NOASSERTION",
    47 47   "externalRefs": [
    48 48   {
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden
    skipped 2 lines
    3 3   "dataLicense": "CC0-1.0",
    4 4   "SPDXID": "SPDXRef-DOCUMENT",
    5 5   "name": "user-image-input",
    6  - "documentNamespace": "https://anchore.com/syft/image/user-image-input-ec2f9b25-22ca-46b8-b7f4-484994fe126c",
     6 + "documentNamespace": "https://anchore.com/syft/image/user-image-input-1de3ac0e-5829-4294-9198-8d8fcdb5dd51",
    7 7   "creationInfo": {
    8 8   "licenseListVersion": "3.20",
    9 9   "creators": [
    10 10   "Organization: Anchore, Inc",
    11 11   "Tool: syft-v0.42.0-bogus"
    12 12   ],
    13  - "created": "2023-05-02T18:24:18Z"
     13 + "created": "2023-05-09T17:11:26Z"
    14 14   },
    15 15   "packages": [
    16 16   {
    17 17   "name": "package-1",
    18  - "SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     18 + "SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    19 19   "versionInfo": "1.0.1",
    20 20   "downloadLocation": "NOASSERTION",
    21 21   "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
    22  - "licenseConcluded": "MIT",
     22 + "licenseConcluded": "NOASSERTION",
    23 23   "licenseDeclared": "MIT",
    24 24   "copyrightText": "NOASSERTION",
    25 25   "externalRefs": [
    skipped 15 lines
    41 41   "versionInfo": "2.0.1",
    42 42   "downloadLocation": "NOASSERTION",
    43 43   "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
    44  - "licenseConcluded": "NONE",
    45  - "licenseDeclared": "NONE",
     44 + "licenseConcluded": "NOASSERTION",
     45 + "licenseDeclared": "NOASSERTION",
    46 46   "copyrightText": "NOASSERTION",
    47 47   "externalRefs": [
    48 48   {
    skipped 103 lines
    152 152   ],
    153 153   "relationships": [
    154 154   {
    155  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     155 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    156 156   "relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
    157 157   "relationshipType": "CONTAINS"
    158 158   },
    159 159   {
    160  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     160 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    161 161   "relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
    162 162   "relationshipType": "CONTAINS"
    163 163   },
    164 164   {
    165  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     165 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    166 166   "relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
    167 167   "relationshipType": "CONTAINS"
    168 168   },
    169 169   {
    170  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     170 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    171 171   "relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
    172 172   "relationshipType": "CONTAINS"
    173 173   },
    174 174   {
    175  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     175 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    176 176   "relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
    177 177   "relationshipType": "CONTAINS"
    178 178   },
    179 179   {
    180  - "spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
     180 + "spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
    181 181   "relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
    182 182   "relationshipType": "CONTAINS"
    183 183   },
    skipped 8 lines
  • syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden
    Binary file.
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden
    skipped 1 lines
    2 2  DataLicense: CC0-1.0
    3 3  SPDXID: SPDXRef-DOCUMENT
    4 4  DocumentName: foobar/baz
    5  -DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-9c1f31fb-7c72-40a6-8c81-3a08590000a2
     5 +DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-1813dede-1ac5-4c44-a640-4c56e213d575
    6 6  LicenseListVersion: 3.20
    7 7  Creator: Organization: Anchore, Inc
    8 8  Creator: Tool: syft-v0.42.0-bogus
    9  -Created: 2023-05-02T18:24:33Z
     9 +Created: 2023-05-09T17:11:49Z
    10 10   
    11 11  ##### Package: @at-sign
    12 12   
    skipped 2 lines
    15 15  PackageDownloadLocation: NOASSERTION
    16 16  FilesAnalyzed: false
    17 17  PackageSourceInfo: acquired package info from the following paths:
    18  -PackageLicenseConcluded: NONE
    19  -PackageLicenseDeclared: NONE
     18 +PackageLicenseConcluded: NOASSERTION
     19 +PackageLicenseDeclared: NOASSERTION
    20 20  PackageCopyrightText: NOASSERTION
    21 21   
    22 22  ##### Package: some/slashes
    skipped 3 lines
    26 26  PackageDownloadLocation: NOASSERTION
    27 27  FilesAnalyzed: false
    28 28  PackageSourceInfo: acquired package info from the following paths:
    29  -PackageLicenseConcluded: NONE
    30  -PackageLicenseDeclared: NONE
     29 +PackageLicenseConcluded: NOASSERTION
     30 +PackageLicenseDeclared: NOASSERTION
    31 31  PackageCopyrightText: NOASSERTION
    32 32   
    33 33  ##### Package: under_scores
    skipped 3 lines
    37 37  PackageDownloadLocation: NOASSERTION
    38 38  FilesAnalyzed: false
    39 39  PackageSourceInfo: acquired package info from the following paths:
    40  -PackageLicenseConcluded: NONE
    41  -PackageLicenseDeclared: NONE
     40 +PackageLicenseConcluded: NOASSERTION
     41 +PackageLicenseDeclared: NOASSERTION
    42 42  PackageCopyrightText: NOASSERTION
    43 43   
    44 44  ##### Relationships
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden
    skipped 1 lines
    2 2  DataLicense: CC0-1.0
    3 3  SPDXID: SPDXRef-DOCUMENT
    4 4  DocumentName: user-image-input
    5  -DocumentNamespace: https://anchore.com/syft/image/user-image-input-5be37b11-b99a-47ff-8725-3984e323d129
     5 +DocumentNamespace: https://anchore.com/syft/image/user-image-input-96ea886a-3297-4847-b211-6da405ff1f8f
    6 6  LicenseListVersion: 3.20
    7 7  Creator: Organization: Anchore, Inc
    8 8  Creator: Tool: syft-v0.42.0-bogus
    9  -Created: 2023-05-02T18:24:33Z
     9 +Created: 2023-05-09T17:11:49Z
    10 10   
    11 11  ##### Unpackaged files
    12 12   
    skipped 41 lines
    54 54  PackageDownloadLocation: NOASSERTION
    55 55  FilesAnalyzed: false
    56 56  PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
    57  -PackageLicenseConcluded: NONE
    58  -PackageLicenseDeclared: NONE
     57 +PackageLicenseConcluded: NOASSERTION
     58 +PackageLicenseDeclared: NOASSERTION
    59 59  PackageCopyrightText: NOASSERTION
    60 60  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
    61 61  ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/[email protected]
    skipped 1 lines
    63 63  ##### Package: package-1
    64 64   
    65 65  PackageName: package-1
    66  -SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
     66 +SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
    67 67  PackageVersion: 1.0.1
    68 68  PackageDownloadLocation: NOASSERTION
    69 69  FilesAnalyzed: false
    70 70  PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
    71  -PackageLicenseConcluded: MIT
     71 +PackageLicenseConcluded: NOASSERTION
    72 72  PackageLicenseDeclared: MIT
    73 73  PackageCopyrightText: NOASSERTION
    74 74  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
    skipped 1 lines
    76 76   
    77 77  ##### Relationships
    78 78   
    79  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
    80  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
    81  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
    82  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
    83  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
    84  -Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
     79 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
     80 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
     81 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
     82 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
     83 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
     84 +Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
    85 85  Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
    86 86   
    87 87   
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden
    skipped 1 lines
    2 2  DataLicense: CC0-1.0
    3 3  SPDXID: SPDXRef-DOCUMENT
    4 4  DocumentName: /some/path
    5  -DocumentNamespace: https://anchore.com/syft/dir/some/path-0f346656-6d10-4dec-b549-a256468cbd35
     5 +DocumentNamespace: https://anchore.com/syft/dir/some/path-f7bdb1ee-7fef-48e7-a386-6ee3836d4a28
    6 6  LicenseListVersion: 3.20
    7 7  Creator: Organization: Anchore, Inc
    8 8  Creator: Tool: syft-v0.42.0-bogus
    9  -Created: 2023-05-02T18:24:33Z
     9 +Created: 2023-05-09T17:11:49Z
    10 10   
    11 11  ##### Package: package-2
    12 12   
    skipped 3 lines
    16 16  PackageDownloadLocation: NOASSERTION
    17 17  FilesAnalyzed: false
    18 18  PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1
    19  -PackageLicenseConcluded: NONE
    20  -PackageLicenseDeclared: NONE
     19 +PackageLicenseConcluded: NOASSERTION
     20 +PackageLicenseDeclared: NOASSERTION
    21 21  PackageCopyrightText: NOASSERTION
    22 22  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
    23 23  ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/[email protected]
    skipped 1 lines
    25 25  ##### Package: package-1
    26 26   
    27 27  PackageName: package-1
    28  -SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
     28 +SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a
    29 29  PackageVersion: 1.0.1
    30 30  PackageDownloadLocation: NOASSERTION
    31 31  FilesAnalyzed: false
    32 32  PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1
    33  -PackageLicenseConcluded: MIT
     33 +PackageLicenseConcluded: NOASSERTION
    34 34  PackageLicenseDeclared: MIT
    35 35  PackageCopyrightText: NOASSERTION
    36 36  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden
    skipped 1 lines
    2 2  DataLicense: CC0-1.0
    3 3  SPDXID: SPDXRef-DOCUMENT
    4 4  DocumentName: user-image-input
    5  -DocumentNamespace: https://anchore.com/syft/image/user-image-input-4ce1e7c7-642f-4428-bb44-1b48b8edf74d
     5 +DocumentNamespace: https://anchore.com/syft/image/user-image-input-44d44a85-2207-4b51-bd73-d0c7b080f6d3
    6 6  LicenseListVersion: 3.20
    7 7  Creator: Organization: Anchore, Inc
    8 8  Creator: Tool: syft-v0.42.0-bogus
    9  -Created: 2023-05-02T18:24:33Z
     9 +Created: 2023-05-09T17:11:49Z
    10 10   
    11 11  ##### Package: package-2
    12 12   
    skipped 3 lines
    16 16  PackageDownloadLocation: NOASSERTION
    17 17  FilesAnalyzed: false
    18 18  PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
    19  -PackageLicenseConcluded: NONE
    20  -PackageLicenseDeclared: NONE
     19 +PackageLicenseConcluded: NOASSERTION
     20 +PackageLicenseDeclared: NOASSERTION
    21 21  PackageCopyrightText: NOASSERTION
    22 22  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
    23 23  ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/[email protected]
    skipped 1 lines
    25 25  ##### Package: package-1
    26 26   
    27 27  PackageName: package-1
    28  -SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
     28 +SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
    29 29  PackageVersion: 1.0.1
    30 30  PackageDownloadLocation: NOASSERTION
    31 31  FilesAnalyzed: false
    32 32  PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
    33  -PackageLicenseConcluded: MIT
     33 +PackageLicenseConcluded: NOASSERTION
    34 34  PackageLicenseDeclared: MIT
    35 35  PackageCopyrightText: NOASSERTION
    36 36  ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
    skipped 7 lines
  • syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden
    Binary file.
  • ■ ■ ■ ■
    syft/formats/syftjson/encoder_test.go
    skipped 60 lines
    61 61   FoundBy: "the-cataloger-1",
    62 62   Language: pkg.Python,
    63 63   MetadataType: pkg.PythonPackageMetadataType,
    64  - Licenses: []string{"MIT"},
     64 + Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
    65 65   Metadata: pkg.PythonPackageMetadata{
    66 66   Name: "package-1",
    67 67   Version: "1.0.1",
    skipped 148 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/model/package.go
    skipped 6 lines
    7 7   "reflect"
    8 8   
    9 9   "github.com/anchore/syft/internal/log"
     10 + "github.com/anchore/syft/syft/license"
    10 11   "github.com/anchore/syft/syft/pkg"
    11 12   "github.com/anchore/syft/syft/source"
    12 13  )
    skipped 14 lines
    27 28   Type pkg.Type `json:"type"`
    28 29   FoundBy string `json:"foundBy"`
    29 30   Locations []source.Location `json:"locations"`
    30  - Licenses []string `json:"licenses"`
     31 + Licenses licenses `json:"licenses"`
    31 32   Language pkg.Language `json:"language"`
    32 33   CPEs []string `json:"cpes"`
    33 34   PURL string `json:"purl"`
     35 +}
     36 + 
     37 +type licenses []License
     38 + 
     39 +type License struct {
     40 + Value string `json:"value"`
     41 + SPDXExpression string `json:"spdxExpression"`
     42 + Type license.Type `json:"type"`
     43 + URL []string `json:"url"`
     44 + Location []source.Location `json:"locations"`
     45 +}
     46 + 
     47 +func newModelLicensesFromValues(licenses []string) (ml []License) {
     48 + for _, v := range licenses {
     49 + expression, err := license.ParseExpression(v)
     50 + if err != nil {
     51 + log.Trace("could not find valid spdx expression for %s: %w", v, err)
     52 + }
     53 + ml = append(ml, License{
     54 + Value: v,
     55 + SPDXExpression: expression,
     56 + Type: license.Declared,
     57 + })
     58 + }
     59 + return ml
     60 +}
     61 + 
     62 +func (f *licenses) UnmarshalJSON(b []byte) error {
     63 + var licenses []License
     64 + if err := json.Unmarshal(b, &licenses); err != nil {
     65 + var simpleLicense []string
     66 + if err := json.Unmarshal(b, &simpleLicense); err != nil {
     67 + return fmt.Errorf("unable to unmarshal license: %w", err)
     68 + }
     69 + licenses = newModelLicensesFromValues(simpleLicense)
     70 + }
     71 + *f = licenses
     72 + return nil
    34 73  }
    35 74   
    36 75  // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
    skipped 69 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/model/package_test.go
    skipped 7 lines
    8 8   "github.com/stretchr/testify/assert"
    9 9   "github.com/stretchr/testify/require"
    10 10   
     11 + "github.com/anchore/syft/syft/license"
    11 12   "github.com/anchore/syft/syft/pkg"
    12 13  )
    13 14   
    skipped 16 lines
    30 31   "path": "/Users/hal/go/bin/syft"
    31 32   }
    32 33   ],
    33  - "licenses": [],
     34 + "licenses": [
     35 + {
     36 + "value": "MIT",
     37 + "spdxExpression": "MIT",
     38 + "type": "declared",
     39 + "url": []
     40 + }
     41 + ],
    34 42   "language": "go",
    35 43   "cpes": [],
    36 44   "purl": "pkg:golang/gopkg.in/square/[email protected]",
    skipped 24 lines
    61 69   "path": "/Users/hal/go/bin/syft"
    62 70   }
    63 71   ],
    64  - "licenses": [],
     72 + "licenses": [
     73 + {
     74 + "value": "MIT",
     75 + "spdxExpression": "MIT",
     76 + "type": "declared",
     77 + "url": ["https://www.github.com"]
     78 + },
     79 + {
     80 + "value": "MIT",
     81 + "spdxExpression": "MIT",
     82 + "type": "declared",
     83 + "locations": [{"path": "/Users/hal/go/bin/syft"}]
     84 + }
     85 + ],
    65 86   "language": "go",
    66 87   "cpes": [],
    67 88   "purl": "pkg:golang/gopkg.in/square/[email protected]"
    skipped 3 lines
    71 92   assert.Empty(t, p.Metadata)
    72 93   },
    73 94   },
     95 + {
     96 + name: "can handle package with []string licenses",
     97 + packageData: []byte(`{
     98 + "id": "8b594519bc23da50",
     99 + "name": "gopkg.in/square/go-jose.v2",
     100 + "version": "v2.6.0",
     101 + "type": "go-module",
     102 + "foundBy": "go-mod-cataloger",
     103 + "locations": [
     104 + {
     105 + "path": "/Users/hal/go/bin/syft"
     106 + }
     107 + ],
     108 + "licenses": ["MIT", "Apache-2.0"],
     109 + "language": "go",
     110 + "cpes": [],
     111 + "purl": "pkg:golang/gopkg.in/square/[email protected]"
     112 + }`),
     113 + assert: func(p *Package) {
     114 + assert.Equal(t, licenses{
     115 + {
     116 + Value: "MIT",
     117 + SPDXExpression: "MIT",
     118 + Type: license.Declared,
     119 + },
     120 + {
     121 + Value: "Apache-2.0",
     122 + SPDXExpression: "Apache-2.0",
     123 + Type: license.Declared,
     124 + },
     125 + }, p.Licenses)
     126 + },
     127 + },
     128 + {
     129 + name: "can handle package with []pkg.License licenses",
     130 + packageData: []byte(`{
     131 + "id": "8b594519bc23da50",
     132 + "name": "gopkg.in/square/go-jose.v2",
     133 + "version": "v2.6.0",
     134 + "type": "go-module",
     135 + "foundBy": "go-mod-cataloger",
     136 + "locations": [
     137 + {
     138 + "path": "/Users/hal/go/bin/syft"
     139 + }
     140 + ],
     141 + "licenses": [
     142 + {
     143 + "value": "MIT",
     144 + "spdxExpression": "MIT",
     145 + "type": "declared"
     146 + },
     147 + {
     148 + "value": "Apache-2.0",
     149 + "spdxExpression": "Apache-2.0",
     150 + "type": "declared"
     151 + }
     152 + ],
     153 + "language": "go",
     154 + "cpes": [],
     155 + "purl": "pkg:golang/gopkg.in/square/[email protected]"
     156 + }`),
     157 + assert: func(p *Package) {
     158 + assert.Equal(t, licenses{
     159 + {
     160 + Value: "MIT",
     161 + SPDXExpression: "MIT",
     162 + Type: license.Declared,
     163 + },
     164 + {
     165 + Value: "Apache-2.0",
     166 + SPDXExpression: "Apache-2.0",
     167 + Type: license.Declared,
     168 + },
     169 + }, p.Licenses)
     170 + },
     171 + },
    74 172   }
    75 173   
    76 174   for _, test := range tests {
    skipped 74 lines
    151 249   "path": "/var/lib/rpm/Packages",
    152 250   "layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
    153 251   }
    154  - ],
    155  - "licenses": [
    156  - "GPLv2+"
    157 252   ],
    158 253   "language": "",
    159 254   "cpes": [
    skipped 121 lines
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/test-fixtures/snapshot/TestDirectoryEncoder.golden
    1 1  {
    2 2   "artifacts": [
    3 3   {
    4  - "id": "1b1d0be59ac59d2c",
     4 + "id": "9265397e5e15168a",
    5 5   "name": "package-1",
    6 6   "version": "1.0.1",
    7 7   "type": "python",
    skipped 4 lines
    12 12   }
    13 13   ],
    14 14   "licenses": [
    15  - "MIT"
     15 + {
     16 + "value": "MIT",
     17 + "spdxExpression": "MIT",
     18 + "type": "declared",
     19 + "url": [],
     20 + "locations": []
     21 + }
    16 22   ],
    17 23   "language": "python",
    18 24   "cpes": [
    skipped 4 lines
    23 29   "metadata": {
    24 30   "name": "package-1",
    25 31   "version": "1.0.1",
    26  - "license": "",
    27 32   "author": "",
    28 33   "authorEmail": "",
    29 34   "platform": "",
    skipped 57 lines
    87 92   "configuration": {
    88 93   "config-key": "config-value"
    89 94   }
     95 + },
     96 + "schema": {
     97 + "version": "8.0.0",
     98 + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
    90 99   }
    91 100  }
    92 101   
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden
    1 1  {
    2 2   "artifacts": [
    3 3   {
    4  - "id": "304a5a8e5958a49d",
     4 + "id": "271e49ba46e0b601",
    5 5   "name": "package-1",
    6 6   "version": "1.0.1",
    7 7   "type": "python",
    skipped 4 lines
    12 12   }
    13 13   ],
    14 14   "licenses": [
    15  - "MIT"
     15 + {
     16 + "value": "MIT",
     17 + "spdxExpression": "MIT",
     18 + "type": "declared",
     19 + "url": [],
     20 + "locations": []
     21 + }
    16 22   ],
    17 23   "language": "python",
    18 24   "cpes": [
    skipped 4 lines
    23 29   "metadata": {
    24 30   "name": "package-1",
    25 31   "version": "1.0.1",
    26  - "license": "",
    27 32   "author": "",
    28 33   "authorEmail": "",
    29 34   "platform": "",
    skipped 157 lines
    187 192   "configuration": {
    188 193   "config-key": "config-value"
    189 194   }
     195 + },
     196 + "schema": {
     197 + "version": "8.0.0",
     198 + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
    190 199   }
    191 200  }
    192 201   
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/test-fixtures/snapshot/TestImageEncoder.golden
    1 1  {
    2 2   "artifacts": [
    3 3   {
    4  - "id": "66ba429119b8bec6",
     4 + "id": "125840abc1c66dd7",
    5 5   "name": "package-1",
    6 6   "version": "1.0.1",
    7 7   "type": "python",
    skipped 1 lines
    9 9   "locations": [
    10 10   {
    11 11   "path": "/somefile-1.txt",
    12  - "layerID": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a"
     12 + "layerID": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
    13 13   }
    14 14   ],
    15 15   "licenses": [
    16  - "MIT"
     16 + {
     17 + "value": "MIT",
     18 + "spdxExpression": "MIT",
     19 + "type": "declared",
     20 + "url": [],
     21 + "locations": []
     22 + }
    17 23   ],
    18 24   "language": "python",
    19 25   "cpes": [
    skipped 4 lines
    24 30   "metadata": {
    25 31   "name": "package-1",
    26 32   "version": "1.0.1",
    27  - "license": "",
    28 33   "author": "",
    29 34   "authorEmail": "",
    30 35   "platform": "",
    skipped 9 lines
    40 45   "locations": [
    41 46   {
    42 47   "path": "/somefile-2.txt",
    43  - "layerID": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac"
     48 + "layerID": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
    44 49   }
    45 50   ],
    46 51   "licenses": [],
    skipped 17 lines
    64 69   ],
    65 70   "artifactRelationships": [],
    66 71   "source": {
    67  - "id": "0af8fa79f5497297e4e32f3e03de14ac20ad695159df0ac8373e6543614b9a50",
     72 + "id": "c8ac88bbaf3d1c036f6a1d601c3d52bafbf05571c97d68322e7cb3a7ecaa304f",
    68 73   "type": "image",
    69 74   "target": {
    70 75   "userInput": "user-image-input",
    71  - "imageID": "sha256:0cb4395791986bda17562bd6f76811bb6f163f686e198397197ef8241bed58df",
     76 + "imageID": "sha256:a3c61dc134d2f31b415c50324e75842d7f91622f39a89468e51938330b3fd3af",
    72 77   "manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
    73 78   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    74 79   "tags": [
    skipped 3 lines
    78 83   "layers": [
    79 84   {
    80 85   "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
    81  - "digest": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a",
     86 + "digest": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777",
    82 87   "size": 22
    83 88   },
    84 89   {
    85 90   "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
    86  - "digest": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac",
     91 + "digest": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2",
    87 92   "size": 16
    88 93   }
    89 94   ],
    90  - "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzEsImRpZ2VzdCI6InNoYTI1NjowY2I0Mzk1NzkxOTg2YmRhMTc1NjJiZDZmNzY4MTFiYjZmMTYzZjY4NmUxOTgzOTcxOTdlZjgyNDFiZWQ1OGRmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo3ZTEzOTMxMGJkNmNlMDk1NmQ2NWE3MGQyNmE2ZDMxYjI0MGE0ZjQ3MDk0YTgzMTYzOGYwNWQzODFiNmM0MjRhIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmNjODMzYmYzMWE0ODBjMDY0ZDY1Y2E2N2VlMzdmNzdmMGQwYzhhYjk4ZWVkZGU3YjI4NmFkMWVmNmY1YmRjYWMifV19",
    91  - "config": "eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMzAxMDI2MzhaIiwiaGlzdG9yeSI6W3siY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMjg3OTQyNzEzWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMy0wNC0xOFQxNDowOTo0Mi4zMDEwMjYzOFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMi50eHQgL3NvbWVmaWxlLTIudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwib3MiOiJsaW51eCIsInJvb3RmcyI6eyJ0eXBlIjoibGF5ZXJzIiwiZGlmZl9pZHMiOlsic2hhMjU2OjdlMTM5MzEwYmQ2Y2UwOTU2ZDY1YTcwZDI2YTZkMzFiMjQwYTRmNDcwOTRhODMxNjM4ZjA1ZDM4MWI2YzQyNGEiLCJzaGEyNTY6Y2M4MzNiZjMxYTQ4MGMwNjRkNjVjYTY3ZWUzN2Y3N2YwZDBjOGFiOThlZWRkZTdiMjg2YWQxZWY2ZjViZGNhYyJdfX0=",
     95 + "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjphM2M2MWRjMTM0ZDJmMzFiNDE1YzUwMzI0ZTc1ODQyZDdmOTE2MjJmMzlhODk0NjhlNTE5MzgzMzBiM2ZkM2FmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjphYjYyMDE2ZjliZWM3Mjg2YWY2NTYwNDA4MTU2NGNhZGVlYjM2NGE0OGZhY2EyMzQ2YzNmNWE1YTFmNWVmNzc3In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmYxODAzODQ1YjY3NDdkOTRkNmU0ZWNjZTIzMzE0NTdlNWYxYzRmYjk3ZGU1MjE2ZjM5MmE3NmY0NTgyZjYzYjIifV19",
     96 + "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIzLTA0LTIxVDE5OjEwOjM3LjYwNzYxMzU1NVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6YWI2MjAxNmY5YmVjNzI4NmFmNjU2MDQwODE1NjRjYWRlZWIzNjRhNDhmYWNhMjM0NmMzZjVhNWExZjVlZjc3NyIsInNoYTI1NjpmMTgwMzg0NWI2NzQ3ZDk0ZDZlNGVjY2UyMzMxNDU3ZTVmMWM0ZmI5N2RlNTIxNmYzOTJhNzZmNDU4MmY2M2IyIl19fQ==",
    92 97   "repoDigests": [],
    93 98   "architecture": "",
    94 99   "os": ""
    skipped 15 lines
    110 115   "configuration": {
    111 116   "config-key": "config-value"
    112 117   }
     118 + },
     119 + "schema": {
     120 + "version": "8.0.0",
     121 + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
    113 122   }
    114 123  }
    115 124   
  • syft/formats/syftjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden
    Binary file.
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/to_format_model.go
    skipped 183 lines
    184 184   return artifacts
    185 185  }
    186 186   
     187 +func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
     188 + for _, l := range pkgLicenses {
     189 + // guarantee collection
     190 + locations := make([]source.Location, 0)
     191 + if v := l.Location.ToSlice(); v != nil {
     192 + locations = v
     193 + }
     194 + modelLicenses = append(modelLicenses, model.License{
     195 + Value: l.Value,
     196 + SPDXExpression: l.SPDXExpression,
     197 + Type: l.Type,
     198 + URL: l.URL.ToSlice(),
     199 + Location: locations,
     200 + })
     201 + }
     202 + return
     203 +}
     204 + 
    187 205  // toPackageModel crates a new Package from the given pkg.Package.
    188 206  func toPackageModel(p pkg.Package) model.Package {
    189 207   var cpes = make([]string, len(p.CPEs))
    skipped 1 lines
    191 209   cpes[i] = cpe.String(c)
    192 210   }
    193 211   
    194  - var licenses = make([]string, 0)
    195  - if p.Licenses != nil {
    196  - licenses = p.Licenses
     212 + // we want to make sure all catalogers are
     213 + // initializing the array; this is a good choke point for this check
     214 + var licenses = make([]model.License, 0)
     215 + if !p.Licenses.Empty() {
     216 + licenses = toLicenseModel(p.Licenses.ToSlice())
    197 217   }
    198 218   
    199 219   return model.Package{
    skipped 75 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_syft_model.go
    skipped 8 lines
    9 9   "github.com/google/go-cmp/cmp"
    10 10   
    11 11   stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     12 + "github.com/anchore/syft/internal"
    12 13   "github.com/anchore/syft/internal/log"
    13 14   "github.com/anchore/syft/syft/artifact"
    14 15   "github.com/anchore/syft/syft/cpe"
    skipped 84 lines
    99 100   }
    100 101   
    101 102   return ret
     103 +}
     104 + 
     105 +func toSyftLicenses(m []model.License) (p []pkg.License) {
     106 + for _, l := range m {
     107 + p = append(p, pkg.License{
     108 + Value: l.Value,
     109 + SPDXExpression: l.SPDXExpression,
     110 + Type: l.Type,
     111 + URL: internal.NewStringSet(l.URL...),
     112 + Location: source.NewLocationSet(l.Location...),
     113 + })
     114 + }
     115 + return
    102 116  }
    103 117   
    104 118  func toSyftFileType(ty string) stereoscopeFile.Type {
    skipped 199 lines
    304 318   Version: p.Version,
    305 319   FoundBy: p.FoundBy,
    306 320   Locations: source.NewLocationSet(p.Locations...),
    307  - Licenses: p.Licenses,
     321 + Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
    308 322   Language: p.Language,
    309 323   Type: p.Type,
    310 324   CPEs: cpes,
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    syft/license/license.go
     1 +// package license provides common methods for working with SPDX license data
     2 +package license
     3 + 
     4 +import (
     5 + "fmt"
     6 + 
     7 + "github.com/github/go-spdx/v2/spdxexp"
     8 + 
     9 + "github.com/anchore/syft/internal/spdxlicense"
     10 +)
     11 + 
     12 +type Type string
     13 + 
     14 +const (
     15 + Declared Type = "declared"
     16 + Concluded Type = "concluded"
     17 +)
     18 + 
     19 +func ParseExpression(expression string) (string, error) {
     20 + licenseID, exists := spdxlicense.ID(expression)
     21 + if exists {
     22 + return licenseID, nil
     23 + }
     24 + 
     25 + // If it doesn't exist initially in the SPDX list it might be a more complex expression
     26 + // ignored variable is any invalid expressions
     27 + // TODO: contribute to spdxexp to expose deprecated license IDs
     28 + // https://github.com/anchore/syft/issues/1814
     29 + valid, _ := spdxexp.ValidateLicenses([]string{expression})
     30 + if !valid {
     31 + return "", fmt.Errorf("failed to validate spdx expression: %s", expression)
     32 + }
     33 + 
     34 + return expression, nil
     35 +}
     36 + 
  • ■ ■ ■ ■ ■ ■
    syft/license/license_test.go
     1 +package license
     2 + 
     3 +import "testing"
     4 + 
     5 +func TestParseExpression(t *testing.T) {
     6 + tests := []struct {
     7 + name string
     8 + expression string
     9 + want string
     10 + wantErr bool
     11 + }{
     12 + {
     13 + name: "valid single ID expression returns SPDX ID",
     14 + expression: "mit",
     15 + want: "MIT",
     16 + wantErr: false,
     17 + },
     18 + {
     19 + name: "Valid SPDX expression returns SPDX expression",
     20 + expression: "MIT OR Apache-2.0",
     21 + want: "MIT OR Apache-2.0",
     22 + },
     23 + {
     24 + name: "Invalid SPDX expression returns error",
     25 + expression: "MIT OR Apache-2.0 OR invalid",
     26 + want: "",
     27 + wantErr: true,
     28 + },
     29 + }
     30 + for _, tt := range tests {
     31 + t.Run(tt.name, func(t *testing.T) {
     32 + got, err := ParseExpression(tt.expression)
     33 + if (err != nil) != tt.wantErr {
     34 + t.Errorf("ParseExpression() error = %v, wantErr %v", err, tt.wantErr)
     35 + return
     36 + }
     37 + if got != tt.want {
     38 + t.Errorf("ParseExpression() got = %v, want %v", got, tt.want)
     39 + }
     40 + })
     41 + }
     42 +}
     43 + 
  • ■ ■ ■ ■ ■ ■
    syft/pkg/alpm_metadata.go
    skipped 19 lines
    20 20   Description string `mapstructure:"desc" json:"description" cyclonedx:"description"`
    21 21   Architecture string `mapstructure:"arch" json:"architecture" cyclonedx:"architecture"`
    22 22   Size int `mapstructure:"size" json:"size" cyclonedx:"size"`
    23  - Packager string `mapstructure:"packager" json:"packager" cyclonedx:"packager"`
    24  - License string `mapstructure:"license" json:"license" cyclonedx:"license"`
    25  - URL string `mapstructure:"url" json:"url" cyclonedx:"url"`
    26  - Validation string `mapstructure:"validation" json:"validation" cyclonedx:"validation"`
    27  - Reason int `mapstructure:"reason" json:"reason" cyclonedx:"reason"`
    28  - Files []AlpmFileRecord `mapstructure:"files" json:"files" cyclonedx:"files"`
    29  - Backup []AlpmFileRecord `mapstructure:"backup" json:"backup" cyclonedx:"backup"`
     23 + Packager string `mapstructure:"packager" json:"packager"`
     24 + URL string `mapstructure:"url" json:"url"`
     25 + Validation string `mapstructure:"validation" json:"validation"`
     26 + Reason int `mapstructure:"reason" json:"reason"`
     27 + Files []AlpmFileRecord `mapstructure:"files" json:"files"`
     28 + Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
    30 29  }
    31 30   
    32 31  type AlpmFileRecord struct {
    skipped 22 lines
  • ■ ■ ■ ■ ■
    syft/pkg/apk_metadata.go
    skipped 26 lines
    27 27   OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
    28 28   Maintainer string `mapstructure:"m" json:"maintainer"`
    29 29   Version string `mapstructure:"V" json:"version"`
    30  - License string `mapstructure:"L" json:"license"`
    31 30   Architecture string `mapstructure:"A" json:"architecture"`
    32 31   URL string `mapstructure:"U" json:"url"`
    33 32   Description string `mapstructure:"T" json:"description"`
    skipped 84 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/apk_metadata_test.go
    skipped 46 lines
    47 47   OriginPackage: "pax-utils",
    48 48   Maintainer: "Natanael Copa <[email protected]>",
    49 49   Version: "1.3.4-r0",
    50  - License: "GPL-2.0-only",
    51 50   Architecture: "x86_64",
    52 51   URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
    53 52   Description: "Scan ELF binaries for stuff",
    skipped 32 lines
    86 85   OriginPackage: "pax-utils",
    87 86   Maintainer: "Natanael Copa <[email protected]>",
    88 87   Version: "1.3.4-r0",
    89  - License: "GPL-2.0-only",
    90 88   Architecture: "x86_64",
    91 89   URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
    92 90   Description: "Scan ELF binaries for stuff",
    skipped 74 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/catalog_test.go
    skipped 16 lines
    17 17   byPath map[string]*strset.Set
    18 18  }
    19 19   
     20 +func TestCatalogMergePackageLicenses(t *testing.T) {
     21 + tests := []struct {
     22 + name string
     23 + pkgs []Package
     24 + expectedPkgs []Package
     25 + }{
     26 + {
     27 + name: "merges licenses of packages with equal ID",
     28 + pkgs: []Package{
     29 + {
     30 + id: "equal",
     31 + Licenses: NewLicenseSet(
     32 + NewLicensesFromValues("foo", "baq", "quz")...,
     33 + ),
     34 + },
     35 + {
     36 + id: "equal",
     37 + Licenses: NewLicenseSet(
     38 + NewLicensesFromValues("bar", "baz", "foo", "qux")...,
     39 + ),
     40 + },
     41 + },
     42 + expectedPkgs: []Package{
     43 + {
     44 + id: "equal",
     45 + Licenses: NewLicenseSet(
     46 + NewLicensesFromValues("foo", "baq", "quz", "qux", "bar", "baz")...,
     47 + ),
     48 + },
     49 + },
     50 + },
     51 + }
     52 + 
     53 + for _, test := range tests {
     54 + t.Run(test.name, func(t *testing.T) {
     55 + collection := NewCollection(test.pkgs...)
     56 + for i, p := range collection.Sorted() {
     57 + assert.Equal(t, test.expectedPkgs[i].Licenses, p.Licenses)
     58 + }
     59 + })
     60 + }
     61 +}
     62 + 
    20 63  func TestCatalogDeleteRemovesPackages(t *testing.T) {
    21 64   tests := []struct {
    22 65   name string
    skipped 400 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/cataloger_test.go
    skipped 12 lines
    13 13  )
    14 14   
    15 15  func TestAlpmCataloger(t *testing.T) {
    16  - 
     16 + dbLocation := source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
    17 17   expectedPkgs := []pkg.Package{
    18 18   {
    19  - Name: "gmp",
    20  - Version: "6.2.1-2",
    21  - Type: pkg.AlpmPkg,
    22  - FoundBy: "alpmdb-cataloger",
    23  - Licenses: []string{"LGPL3", "GPL"},
    24  - Locations: source.NewLocationSet(source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")),
     19 + Name: "gmp",
     20 + Version: "6.2.1-2",
     21 + Type: pkg.AlpmPkg,
     22 + FoundBy: "alpmdb-cataloger",
     23 + Licenses: pkg.NewLicenseSet(
     24 + pkg.NewLicenseFromLocations("LGPL3", dbLocation),
     25 + pkg.NewLicenseFromLocations("GPL", dbLocation),
     26 + ),
     27 + Locations: source.NewLocationSet(dbLocation),
    25 28   CPEs: nil,
    26 29   PURL: "",
    27 30   MetadataType: "AlpmMetadata",
    skipped 5 lines
    33 36   Architecture: "x86_64",
    34 37   Size: 1044438,
    35 38   Packager: "Antonio Rojas <[email protected]>",
    36  - License: "LGPL3\nGPL",
    37 39   URL: "https://gmplib.org/",
    38 40   Validation: "pgp",
    39 41   Reason: 1,
    skipped 171 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/package.go
    1 1  package alpm
    2 2   
    3 3  import (
     4 + "strings"
     5 + 
    4 6   "github.com/anchore/packageurl-go"
    5  - "github.com/anchore/syft/internal"
    6 7   "github.com/anchore/syft/syft/linux"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/source"
    9 10  )
    10 11   
    11  -func newPackage(m pkg.AlpmMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
     12 +func newPackage(m *parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
     13 + licenseCandidates := strings.Split(m.Licenses, "\n")
     14 + 
    12 15   p := pkg.Package{
    13 16   Name: m.Package,
    14 17   Version: m.Version,
    15  - Locations: source.NewLocationSet(locations...),
     18 + Locations: source.NewLocationSet(dbLocation),
     19 + Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
    16 20   Type: pkg.AlpmPkg,
    17  - Licenses: internal.SplitAny(m.License, " \n"),
    18 21   PURL: packageURL(m, release),
    19 22   MetadataType: pkg.AlpmMetadataType,
    20  - Metadata: m,
     23 + Metadata: m.AlpmMetadata,
    21 24   }
    22 25   p.SetID()
     26 + 
    23 27   return p
    24 28  }
    25 29   
    26  -func packageURL(m pkg.AlpmMetadata, distro *linux.Release) string {
     30 +func packageURL(m *parsedData, distro *linux.Release) string {
    27 31   if distro == nil || distro.ID != "arch" {
    28 32   // note: there is no namespace variation (like with debian ID_LIKE for ubuntu ID, for example)
    29 33   return ""
    skipped 23 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/package_test.go
    skipped 12 lines
    13 13  func Test_PackageURL(t *testing.T) {
    14 14   tests := []struct {
    15 15   name string
    16  - metadata pkg.AlpmMetadata
     16 + metadata *parsedData
    17 17   distro linux.Release
    18 18   expected string
    19 19   }{
    20 20   {
    21 21   name: "bad distro id",
    22  - metadata: pkg.AlpmMetadata{
    23  - Package: "p",
    24  - Version: "v",
    25  - Architecture: "a",
     22 + metadata: &parsedData{
     23 + Licenses: "",
     24 + AlpmMetadata: pkg.AlpmMetadata{
     25 + Package: "p",
     26 + Version: "v",
     27 + Architecture: "a",
     28 + },
    26 29   },
    27 30   distro: linux.Release{
    28 31   ID: "something-else",
    skipped 3 lines
    32 35   },
    33 36   {
    34 37   name: "gocase",
    35  - metadata: pkg.AlpmMetadata{
    36  - Package: "p",
    37  - Version: "v",
    38  - Architecture: "a",
     38 + metadata: &parsedData{
     39 + Licenses: "",
     40 + AlpmMetadata: pkg.AlpmMetadata{
     41 + Package: "p",
     42 + Version: "v",
     43 + Architecture: "a",
     44 + },
    39 45   },
    40 46   distro: linux.Release{
    41 47   ID: "arch",
    skipped 3 lines
    45 51   },
    46 52   {
    47 53   name: "missing architecture",
    48  - metadata: pkg.AlpmMetadata{
    49  - Package: "p",
    50  - Version: "v",
     54 + metadata: &parsedData{
     55 + Licenses: "",
     56 + AlpmMetadata: pkg.AlpmMetadata{
     57 + Package: "p",
     58 + Version: "v",
     59 + },
    51 60   },
    52 61   distro: linux.Release{
    53 62   ID: "arch",
    skipped 1 lines
    55 64   expected: "pkg:alpm/arch/p@v?distro=arch",
    56 65   },
    57 66   {
    58  - metadata: pkg.AlpmMetadata{
    59  - Package: "python",
    60  - Version: "3.10.0",
    61  - Architecture: "any",
     67 + metadata: &parsedData{
     68 + Licenses: "",
     69 + AlpmMetadata: pkg.AlpmMetadata{
     70 + Package: "python",
     71 + Version: "3.10.0",
     72 + Architecture: "any",
     73 + },
    62 74   },
    63 75   distro: linux.Release{
    64 76   ID: "arch",
    skipped 2 lines
    67 79   expected: "pkg:alpm/arch/[email protected]?arch=any&distro=arch-rolling",
    68 80   },
    69 81   {
    70  - metadata: pkg.AlpmMetadata{
    71  - Package: "g plus plus",
    72  - Version: "v84",
    73  - Architecture: "x86_64",
     82 + metadata: &parsedData{
     83 + Licenses: "",
     84 + AlpmMetadata: pkg.AlpmMetadata{
     85 + Package: "g plus plus",
     86 + Version: "v84",
     87 + Architecture: "x86_64",
     88 + },
    74 89   },
    75 90   distro: linux.Release{
    76 91   ID: "arch",
    skipped 3 lines
    80 95   },
    81 96   {
    82 97   name: "add source information as qualifier",
    83  - metadata: pkg.AlpmMetadata{
    84  - Package: "p",
    85  - Version: "v",
    86  - Architecture: "a",
    87  - BasePackage: "origin",
     98 + metadata: &parsedData{
     99 + Licenses: "",
     100 + AlpmMetadata: pkg.AlpmMetadata{
     101 + Package: "p",
     102 + Version: "v",
     103 + Architecture: "a",
     104 + BasePackage: "origin",
     105 + },
    88 106   },
    89 107   distro: linux.Release{
    90 108   ID: "arch",
    skipped 43 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/parse_alpm_db.go
    skipped 30 lines
    31 31   }
    32 32  )
    33 33   
     34 +type parsedData struct {
     35 + Licenses string `mapstructure:"license"`
     36 + pkg.AlpmMetadata `mapstructure:",squash"`
     37 +}
     38 + 
    34 39  func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    35  - metadata, err := parseAlpmDBEntry(reader)
     40 + data, err := parseAlpmDBEntry(reader)
    36 41   if err != nil {
    37 42   return nil, nil, err
    38 43   }
    skipped 9 lines
    48 53   return nil, nil, err
    49 54   }
    50 55   
    51  - // The replace the files found the the pacman database with the files from the mtree These contain more metadata and
     56 + // replace the files found the pacman database with the files from the mtree These contain more metadata and
    52 57   // thus more useful.
    53  - metadata.Files = pkgFiles
     58 + // TODO: probably want to use MTREE and PKGINFO here
     59 + data.Files = pkgFiles
    54 60   
    55 61   // We only really do this to get any backup database entries from the files database
    56 62   files := filepath.Join(base, "files")
    skipped 5 lines
    62 68   if err != nil {
    63 69   return nil, nil, err
    64 70   } else if filesMetadata != nil {
    65  - metadata.Backup = filesMetadata.Backup
     71 + data.Backup = filesMetadata.Backup
    66 72   }
    67 73   
    68  - if metadata.Package == "" {
     74 + if data.Package == "" {
    69 75   return nil, nil, nil
    70 76   }
    71 77   
    72 78   return []pkg.Package{
    73 79   newPackage(
    74  - *metadata,
     80 + data,
    75 81   env.LinuxRelease,
    76 82   reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    77 83   ),
    78 84   }, nil, nil
    79 85  }
    80 86   
    81  -func parseAlpmDBEntry(reader io.Reader) (*pkg.AlpmMetadata, error) {
     87 +func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
    82 88   scanner := newScanner(reader)
    83 89   metadata, err := parseDatabase(scanner)
    84 90   if err != nil {
    skipped 43 lines
    128 134   return dbContentReader, nil
    129 135  }
    130 136   
    131  -func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
    132  - var entry pkg.AlpmMetadata
     137 +func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
    133 138   var err error
    134 139   pkgFields := make(map[string]interface{})
    135 140   for b.Scan() {
    skipped 45 lines
    181 186   pkgFields[key] = value
    182 187   }
    183 188   }
     189 + 
     190 + return parsePkgFiles(pkgFields)
     191 +}
     192 + 
     193 +func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
     194 + var entry parsedData
    184 195   if err := mapstructure.Decode(pkgFields, &entry); err != nil {
    185 196   return nil, fmt.Errorf("unable to parse ALPM metadata: %w", err)
    186  - }
    187  - if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
    188  - return nil, nil
    189 197   }
    190 198   
    191 199   if entry.Backup == nil {
    192 200   entry.Backup = make([]pkg.AlpmFileRecord, 0)
     201 + }
     202 + 
     203 + if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
     204 + return nil, nil
    193 205   }
    194 206   return &entry, nil
    195 207  }
    skipped 53 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/package.go
    skipped 8 lines
    9 9   "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12  -func newPackage(d pkg.ApkMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
     12 +func newPackage(d parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
     13 + licenseStrings := strings.Split(d.License, " ")
     14 + 
    13 15   p := pkg.Package{
    14 16   Name: d.Package,
    15 17   Version: d.Version,
    16  - Locations: source.NewLocationSet(locations...),
    17  - Licenses: strings.Split(d.License, " "),
    18  - PURL: packageURL(d, release),
     18 + Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
     19 + Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
     20 + PURL: packageURL(d.ApkMetadata, release),
    19 21   Type: pkg.ApkPkg,
    20 22   MetadataType: pkg.ApkMetadataType,
    21  - Metadata: d,
     23 + Metadata: d.ApkMetadata,
    22 24   }
    23 25   
    24 26   p.SetID()
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/package_test.go
    skipped 14 lines
    15 15  func Test_PackageURL(t *testing.T) {
    16 16   tests := []struct {
    17 17   name string
    18  - metadata pkg.ApkMetadata
     18 + metadata parsedData
    19 19   distro linux.Release
    20 20   expected string
    21 21   }{
    22 22   {
    23 23   name: "non-alpine distro",
    24  - metadata: pkg.ApkMetadata{
    25  - Package: "p",
    26  - Version: "v",
    27  - Architecture: "a",
     24 + metadata: parsedData{
     25 + License: "",
     26 + ApkMetadata: pkg.ApkMetadata{
     27 + Package: "p",
     28 + Version: "v",
     29 + Architecture: "a",
     30 + },
    28 31   },
    29 32   distro: linux.Release{
    30 33   ID: "something else",
    skipped 3 lines
    34 37   },
    35 38   {
    36 39   name: "gocase",
    37  - metadata: pkg.ApkMetadata{
    38  - Package: "p",
    39  - Version: "v",
    40  - Architecture: "a",
     40 + metadata: parsedData{
     41 + License: "",
     42 + ApkMetadata: pkg.ApkMetadata{
     43 + Package: "p",
     44 + Version: "v",
     45 + Architecture: "a",
     46 + },
    41 47   },
    42 48   distro: linux.Release{
    43 49   ID: "alpine",
    skipped 3 lines
    47 53   },
    48 54   {
    49 55   name: "missing architecture",
    50  - metadata: pkg.ApkMetadata{
    51  - Package: "p",
    52  - Version: "v",
     56 + metadata: parsedData{
     57 + License: "",
     58 + ApkMetadata: pkg.ApkMetadata{
     59 + Package: "p",
     60 + Version: "v",
     61 + },
    53 62   },
    54 63   distro: linux.Release{
    55 64   ID: "alpine",
    skipped 3 lines
    59 68   },
    60 69   // verify #351
    61 70   {
    62  - metadata: pkg.ApkMetadata{
    63  - Package: "g++",
    64  - Version: "v84",
    65  - Architecture: "am86",
     71 + metadata: parsedData{
     72 + License: "",
     73 + ApkMetadata: pkg.ApkMetadata{
     74 + Package: "g++",
     75 + Version: "v84",
     76 + Architecture: "am86",
     77 + },
    66 78   },
    67 79   distro: linux.Release{
    68 80   ID: "alpine",
    skipped 2 lines
    71 83   expected: "pkg:apk/alpine/g++@v84?arch=am86&distro=alpine-3.4.6",
    72 84   },
    73 85   {
    74  - metadata: pkg.ApkMetadata{
    75  - Package: "g plus plus",
    76  - Version: "v84",
    77  - Architecture: "am86",
     86 + metadata: parsedData{
     87 + License: "",
     88 + ApkMetadata: pkg.ApkMetadata{
     89 + Package: "g plus plus",
     90 + Version: "v84",
     91 + Architecture: "am86",
     92 + },
    78 93   },
    79 94   distro: linux.Release{
    80 95   ID: "alpine",
    skipped 3 lines
    84 99   },
    85 100   {
    86 101   name: "add source information as qualifier",
    87  - metadata: pkg.ApkMetadata{
    88  - Package: "p",
    89  - Version: "v",
    90  - Architecture: "a",
    91  - OriginPackage: "origin",
     102 + metadata: parsedData{
     103 + License: "",
     104 + ApkMetadata: pkg.ApkMetadata{
     105 + Package: "p",
     106 + Version: "v",
     107 + Architecture: "a",
     108 + OriginPackage: "origin",
     109 + },
    92 110   },
    93 111   distro: linux.Release{
    94 112   ID: "alpine",
    skipped 3 lines
    98 116   },
    99 117   {
    100 118   name: "wolfi distro",
    101  - metadata: pkg.ApkMetadata{
    102  - Package: "p",
    103  - Version: "v",
    104  - Architecture: "a",
     119 + metadata: parsedData{
     120 + License: "",
     121 + ApkMetadata: pkg.ApkMetadata{
     122 + Package: "p",
     123 + Version: "v",
     124 + Architecture: "a",
     125 + },
    105 126   },
    106 127   distro: linux.Release{
    107 128   ID: "wolfi",
    skipped 5 lines
    113 134   
    114 135   for _, test := range tests {
    115 136   t.Run(test.name, func(t *testing.T) {
    116  - actual := packageURL(test.metadata, &test.distro)
     137 + actual := packageURL(test.metadata.ApkMetadata, &test.distro)
    117 138   if actual != test.expected {
    118 139   dmp := diffmatchpatch.New()
    119 140   diffs := dmp.DiffMain(test.expected, actual, true)
    skipped 71 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/parse_apk_db.go
    skipped 25 lines
    26 26   repoRegex = regexp.MustCompile(`(?m)^https://.*\.alpinelinux\.org/alpine/v([^/]+)/([a-zA-Z0-9_]+)$`)
    27 27  )
    28 28   
     29 +type parsedData struct {
     30 + License string `mapstructure:"L" json:"license"`
     31 + pkg.ApkMetadata
     32 +}
     33 + 
    29 34  // parseApkDB parses packages from a given APK installed DB file. For more
    30 35  // information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
    31 36  //
    skipped 1 lines
    33 38  func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    34 39   scanner := bufio.NewScanner(reader)
    35 40   
    36  - var apks []pkg.ApkMetadata
    37  - var currentEntry pkg.ApkMetadata
     41 + var apks []parsedData
     42 + var currentEntry parsedData
    38 43   entryParsingInProgress := false
    39 44   fileParsingCtx := newApkFileParsingContext()
    40 45   
    41 46   // creating a dedicated append-like function here instead of using `append(...)`
    42 47   // below since there is nontrivial logic to be performed for each finalized apk
    43 48   // entry.
    44  - appendApk := func(p pkg.ApkMetadata) {
     49 + appendApk := func(p parsedData) {
    45 50   if files := fileParsingCtx.files; len(files) >= 1 {
    46 51   // attached accumulated files to current package
    47 52   p.Files = files
    skipped 20 lines
    68 73   entryParsingInProgress = false
    69 74   
    70 75   // zero-out currentEntry for use by any future entry
    71  - currentEntry = pkg.ApkMetadata{}
     76 + currentEntry = parsedData{}
    72 77   
    73 78   continue
    74 79   }
    skipped 48 lines
    123 128   
    124 129   pkgs := make([]pkg.Package, 0, len(apks))
    125 130   for _, apk := range apks {
    126  - pkgs = append(pkgs, newPackage(apk, r, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)))
     131 + pkgs = append(pkgs, newPackage(apk, r, reader.Location))
    127 132   }
    128 133   
    129 134   return pkgs, discoverPackageDependencies(pkgs), nil
    skipped 71 lines
    201 206  }
    202 207   
    203 208  //nolint:funlen
    204  -func (f apkField) apply(p *pkg.ApkMetadata, ctx *apkFileParsingContext) {
     209 +func (f apkField) apply(p *parsedData, ctx *apkFileParsingContext) {
    205 210   switch f.name {
    206 211   // APKINDEX field parsing
    207 212   
    skipped 139 lines
    347 352   return nil
    348 353  }
    349 354   
    350  -func nilFieldsToEmptySlice(p *pkg.ApkMetadata) {
     355 +func nilFieldsToEmptySlice(p *parsedData) {
    351 356   if p.Dependencies == nil {
    352 357   p.Dependencies = []string{}
    353 358   }
    skipped 84 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/parse_apk_db_test.go
    skipped 79 lines
    80 80  func TestSinglePackageDetails(t *testing.T) {
    81 81   tests := []struct {
    82 82   fixture string
    83  - expected pkg.ApkMetadata
     83 + expected pkg.Package
    84 84   }{
    85 85   {
    86 86   fixture: "test-fixtures/single",
    87  - expected: pkg.ApkMetadata{
    88  - Package: "musl-utils",
    89  - OriginPackage: "musl",
    90  - Version: "1.1.24-r2",
    91  - Description: "the musl c library (libc) implementation",
    92  - Maintainer: "Timo Teräs <[email protected]>",
    93  - License: "MIT BSD GPL2+",
    94  - Architecture: "x86_64",
    95  - URL: "https://musl.libc.org/",
    96  - Size: 37944,
    97  - InstalledSize: 151552,
    98  - Dependencies: []string{"scanelf", "so:libc.musl-x86_64.so.1"},
    99  - Provides: []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"},
    100  - Checksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
    101  - GitCommit: "4024cc3b29ad4c65544ad068b8f59172b5494306",
    102  - Files: []pkg.ApkFileRecord{
    103  - {
    104  - Path: "/sbin",
    105  - },
    106  - {
    107  - Path: "/sbin/ldconfig",
    108  - OwnerUID: "0",
    109  - OwnerGID: "0",
    110  - Permissions: "755",
    111  - Digest: &file.Digest{
    112  - Algorithm: "'Q1'+base64(sha1)",
    113  - Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
     87 + expected: pkg.Package{
     88 + Name: "musl-utils",
     89 + Version: "1.1.24-r2",
     90 + Licenses: pkg.NewLicenseSet(
     91 + pkg.NewLicense("MIT"),
     92 + pkg.NewLicense("BSD"),
     93 + pkg.NewLicense("GPL2+"),
     94 + ),
     95 + Type: pkg.ApkPkg,
     96 + MetadataType: pkg.ApkMetadataType,
     97 + Metadata: pkg.ApkMetadata{
     98 + Package: "musl-utils",
     99 + OriginPackage: "musl",
     100 + Version: "1.1.24-r2",
     101 + Description: "the musl c library (libc) implementation",
     102 + Maintainer: "Timo Teräs <[email protected]>",
     103 + Architecture: "x86_64",
     104 + URL: "https://musl.libc.org/",
     105 + Size: 37944,
     106 + InstalledSize: 151552,
     107 + Dependencies: []string{"scanelf", "so:libc.musl-x86_64.so.1"},
     108 + Provides: []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"},
     109 + Checksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
     110 + GitCommit: "4024cc3b29ad4c65544ad068b8f59172b5494306",
     111 + Files: []pkg.ApkFileRecord{
     112 + {
     113 + Path: "/sbin",
    114 114   },
    115  - },
    116  - {
    117  - Path: "/usr",
    118  - },
    119  - {
    120  - Path: "/usr/bin",
    121  - },
    122  - {
    123  - Path: "/usr/bin/iconv",
    124  - OwnerUID: "0",
    125  - OwnerGID: "0",
    126  - Permissions: "755",
    127  - Digest: &file.Digest{
    128  - Algorithm: "'Q1'+base64(sha1)",
    129  - Value: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
     115 + {
     116 + Path: "/sbin/ldconfig",
     117 + OwnerUID: "0",
     118 + OwnerGID: "0",
     119 + Permissions: "755",
     120 + Digest: &file.Digest{
     121 + Algorithm: "'Q1'+base64(sha1)",
     122 + Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
     123 + },
     124 + },
     125 + {
     126 + Path: "/usr",
    130 127   },
    131  - },
    132  - {
    133  - Path: "/usr/bin/ldd",
    134  - OwnerUID: "0",
    135  - OwnerGID: "0",
    136  - Permissions: "755",
    137  - Digest: &file.Digest{
    138  - Algorithm: "'Q1'+base64(sha1)",
    139  - Value: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
     128 + {
     129 + Path: "/usr/bin",
    140 130   },
    141  - },
    142  - {
    143  - Path: "/usr/bin/getconf",
    144  - OwnerUID: "0",
    145  - OwnerGID: "0",
    146  - Permissions: "755",
    147  - Digest: &file.Digest{
    148  - Algorithm: "'Q1'+base64(sha1)",
    149  - Value: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
     131 + {
     132 + Path: "/usr/bin/iconv",
     133 + OwnerUID: "0",
     134 + OwnerGID: "0",
     135 + Permissions: "755",
     136 + Digest: &file.Digest{
     137 + Algorithm: "'Q1'+base64(sha1)",
     138 + Value: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
     139 + },
    150 140   },
    151  - },
    152  - {
    153  - Path: "/usr/bin/getent",
    154  - OwnerUID: "0",
    155  - OwnerGID: "0",
    156  - Permissions: "755",
    157  - Digest: &file.Digest{
    158  - Algorithm: "'Q1'+base64(sha1)",
    159  - Value: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
     141 + {
     142 + Path: "/usr/bin/ldd",
     143 + OwnerUID: "0",
     144 + OwnerGID: "0",
     145 + Permissions: "755",
     146 + Digest: &file.Digest{
     147 + Algorithm: "'Q1'+base64(sha1)",
     148 + Value: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
     149 + },
     150 + },
     151 + {
     152 + Path: "/usr/bin/getconf",
     153 + OwnerUID: "0",
     154 + OwnerGID: "0",
     155 + Permissions: "755",
     156 + Digest: &file.Digest{
     157 + Algorithm: "'Q1'+base64(sha1)",
     158 + Value: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
     159 + },
     160 + },
     161 + {
     162 + Path: "/usr/bin/getent",
     163 + OwnerUID: "0",
     164 + OwnerGID: "0",
     165 + Permissions: "755",
     166 + Digest: &file.Digest{
     167 + Algorithm: "'Q1'+base64(sha1)",
     168 + Value: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
     169 + },
    160 170   },
    161 171   },
    162 172   },
    skipped 1 lines
    164 174   },
    165 175   {
    166 176   fixture: "test-fixtures/empty-deps-and-provides",
    167  - expected: pkg.ApkMetadata{
    168  - Package: "alpine-baselayout-data",
    169  - OriginPackage: "alpine-baselayout",
    170  - Version: "3.4.0-r0",
    171  - Description: "Alpine base dir structure and init scripts",
    172  - Maintainer: "Natanael Copa <[email protected]>",
    173  - License: "GPL-2.0-only",
    174  - Architecture: "x86_64",
    175  - URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
    176  - Size: 11664,
    177  - InstalledSize: 77824,
    178  - Dependencies: []string{},
    179  - Provides: []string{},
    180  - Checksum: "Q15ffjKT28lB7iSXjzpI/eDdYRCwM=",
    181  - GitCommit: "bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24",
    182  - Files: []pkg.ApkFileRecord{
    183  - {Path: "/etc"},
    184  - {Path: "/etc/fstab"},
    185  - {Path: "/etc/group"},
    186  - {Path: "/etc/hostname"},
    187  - {Path: "/etc/hosts"},
    188  - {Path: "/etc/inittab"},
    189  - {Path: "/etc/modules"},
    190  - {Path: "/etc/mtab", OwnerUID: "0", OwnerGID: "0", Permissions: "0777"},
    191  - {Path: "/etc/nsswitch.conf"},
    192  - {Path: "/etc/passwd"},
    193  - {Path: "/etc/profile"},
    194  - {Path: "/etc/protocols"},
    195  - {Path: "/etc/services"},
    196  - {Path: "/etc/shadow", OwnerUID: "0", OwnerGID: "148", Permissions: "0640"},
    197  - {Path: "/etc/shells"},
    198  - {Path: "/etc/sysctl.conf"},
     177 + expected: pkg.Package{
     178 + Name: "alpine-baselayout-data",
     179 + Version: "3.4.0-r0",
     180 + Licenses: pkg.NewLicenseSet(
     181 + pkg.NewLicense("GPL-2.0-only"),
     182 + ),
     183 + Type: pkg.ApkPkg,
     184 + MetadataType: pkg.ApkMetadataType,
     185 + Metadata: pkg.ApkMetadata{
     186 + Package: "alpine-baselayout-data",
     187 + OriginPackage: "alpine-baselayout",
     188 + Version: "3.4.0-r0",
     189 + Description: "Alpine base dir structure and init scripts",
     190 + Maintainer: "Natanael Copa <[email protected]>",
     191 + Architecture: "x86_64",
     192 + URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
     193 + Size: 11664,
     194 + InstalledSize: 77824,
     195 + Dependencies: []string{},
     196 + Provides: []string{},
     197 + Checksum: "Q15ffjKT28lB7iSXjzpI/eDdYRCwM=",
     198 + GitCommit: "bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24",
     199 + Files: []pkg.ApkFileRecord{
     200 + {Path: "/etc"},
     201 + {Path: "/etc/fstab"},
     202 + {Path: "/etc/group"},
     203 + {Path: "/etc/hostname"},
     204 + {Path: "/etc/hosts"},
     205 + {Path: "/etc/inittab"},
     206 + {Path: "/etc/modules"},
     207 + {Path: "/etc/mtab", OwnerUID: "0", OwnerGID: "0", Permissions: "0777"},
     208 + {Path: "/etc/nsswitch.conf"},
     209 + {Path: "/etc/passwd"},
     210 + {Path: "/etc/profile"},
     211 + {Path: "/etc/protocols"},
     212 + {Path: "/etc/services"},
     213 + {Path: "/etc/shadow", OwnerUID: "0", OwnerGID: "148", Permissions: "0640"},
     214 + {Path: "/etc/shells"},
     215 + {Path: "/etc/sysctl.conf"},
     216 + },
    199 217   },
    200 218   },
    201 219   },
    202 220   {
    203 221   fixture: "test-fixtures/base",
    204  - expected: pkg.ApkMetadata{
    205  - Package: "alpine-baselayout",
    206  - OriginPackage: "alpine-baselayout",
    207  - Version: "3.2.0-r6",
    208  - Description: "Alpine base dir structure and init scripts",
    209  - Maintainer: "Natanael Copa <[email protected]>",
    210  - License: "GPL-2.0-only",
    211  - Architecture: "x86_64",
    212  - URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
    213  - Size: 19917,
    214  - InstalledSize: 409600,
    215  - Dependencies: []string{"/bin/sh", "so:libc.musl-x86_64.so.1"},
    216  - Provides: []string{"cmd:mkmntdirs"},
    217  - Checksum: "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=",
    218  - GitCommit: "e1c51734fa96fa4bac92e9f14a474324c67916fc",
    219  - Files: []pkg.ApkFileRecord{
    220  - {
    221  - Path: "/dev",
    222  - },
    223  - {
    224  - Path: "/dev/pts",
    225  - },
    226  - {
    227  - Path: "/dev/shm",
    228  - },
    229  - {
    230  - Path: "/etc",
    231  - },
    232  - {
    233  - Path: "/etc/fstab",
    234  - Digest: &file.Digest{
    235  - Algorithm: "'Q1'+base64(sha1)",
    236  - Value: "Q11Q7hNe8QpDS531guqCdrXBzoA/o=",
     222 + expected: pkg.Package{
     223 + Name: "alpine-baselayout",
     224 + Version: "3.2.0-r6",
     225 + Licenses: pkg.NewLicenseSet(
     226 + pkg.NewLicense("GPL-2.0-only"),
     227 + ),
     228 + Type: pkg.ApkPkg,
     229 + PURL: "",
     230 + MetadataType: pkg.ApkMetadataType,
     231 + Metadata: pkg.ApkMetadata{
     232 + Package: "alpine-baselayout",
     233 + OriginPackage: "alpine-baselayout",
     234 + Version: "3.2.0-r6",
     235 + Description: "Alpine base dir structure and init scripts",
     236 + Maintainer: "Natanael Copa <[email protected]>",
     237 + Architecture: "x86_64",
     238 + URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
     239 + Size: 19917,
     240 + InstalledSize: 409600,
     241 + Dependencies: []string{"/bin/sh", "so:libc.musl-x86_64.so.1"},
     242 + Provides: []string{"cmd:mkmntdirs"},
     243 + Checksum: "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=",
     244 + GitCommit: "e1c51734fa96fa4bac92e9f14a474324c67916fc",
     245 + Files: []pkg.ApkFileRecord{
     246 + {
     247 + Path: "/dev",
    237 248   },
    238  - },
    239  - {
    240  - Path: "/etc/group",
    241  - Digest: &file.Digest{
    242  - Algorithm: "'Q1'+base64(sha1)",
    243  - Value: "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=",
     249 + {
     250 + Path: "/dev/pts",
    244 251   },
    245  - },
    246  - {
    247  - Path: "/etc/hostname",
    248  - Digest: &file.Digest{
    249  - Algorithm: "'Q1'+base64(sha1)",
    250  - Value: "Q16nVwYVXP/tChvUPdukVD2ifXOmc=",
     252 + {
     253 + Path: "/dev/shm",
    251 254   },
    252  - },
    253  - {
    254  - Path: "/etc/hosts",
    255  - Digest: &file.Digest{
    256  - Algorithm: "'Q1'+base64(sha1)",
    257  - Value: "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=",
     255 + {
     256 + Path: "/etc",
    258 257   },
    259  - },
    260  - {
    261  - Path: "/etc/inittab",
    262  - Digest: &file.Digest{
    263  - Algorithm: "'Q1'+base64(sha1)",
    264  - Value: "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=",
     258 + {
     259 + Path: "/etc/fstab",
     260 + Digest: &file.Digest{
     261 + Algorithm: "'Q1'+base64(sha1)",
     262 + Value: "Q11Q7hNe8QpDS531guqCdrXBzoA/o=",
     263 + },
    265 264   },
    266  - },
    267  - {
    268  - Path: "/etc/modules",
    269  - Digest: &file.Digest{
    270  - Algorithm: "'Q1'+base64(sha1)",
    271  - Value: "Q1toogjUipHGcMgECgPJX64SwUT1M=",
     265 + {
     266 + Path: "/etc/group",
     267 + Digest: &file.Digest{
     268 + Algorithm: "'Q1'+base64(sha1)",
     269 + Value: "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=",
     270 + },
    272 271   },
    273  - },
    274  - {
    275  - Path: "/etc/motd",
    276  - Digest: &file.Digest{
    277  - Algorithm: "'Q1'+base64(sha1)",
    278  - Value: "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=",
     272 + {
     273 + Path: "/etc/hostname",
     274 + Digest: &file.Digest{
     275 + Algorithm: "'Q1'+base64(sha1)",
     276 + Value: "Q16nVwYVXP/tChvUPdukVD2ifXOmc=",
     277 + },
    279 278   },
    280  - },
    281  - {
    282  - Path: "/etc/mtab",
    283  - OwnerUID: "0",
    284  - OwnerGID: "0",
    285  - Permissions: "777",
    286  - Digest: &file.Digest{
    287  - Algorithm: "'Q1'+base64(sha1)",
    288  - Value: "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=",
     279 + {
     280 + Path: "/etc/hosts",
     281 + Digest: &file.Digest{
     282 + Algorithm: "'Q1'+base64(sha1)",
     283 + Value: "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=",
     284 + },
    289 285   },
    290  - },
    291  - {
    292  - Path: "/etc/passwd",
    293  - Digest: &file.Digest{
    294  - Algorithm: "'Q1'+base64(sha1)",
    295  - Value: "Q1TchuuLUfur0izvfZQZxgN/LJhB8=",
     286 + {
     287 + Path: "/etc/inittab",
     288 + Digest: &file.Digest{
     289 + Algorithm: "'Q1'+base64(sha1)",
     290 + Value: "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=",
     291 + },
    296 292   },
    297  - },
    298  - {
    299  - Path: "/etc/profile",
    300  - Digest: &file.Digest{
    301  - Algorithm: "'Q1'+base64(sha1)",
    302  - Value: "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=",
     293 + {
     294 + Path: "/etc/modules",
     295 + Digest: &file.Digest{
     296 + Algorithm: "'Q1'+base64(sha1)",
     297 + Value: "Q1toogjUipHGcMgECgPJX64SwUT1M=",
     298 + },
    303 299   },
    304  - },
    305  - {
    306  - Path: "/etc/protocols",
    307  - Digest: &file.Digest{
    308  - Algorithm: "'Q1'+base64(sha1)",
    309  - Value: "Q13FqXUnvuOpMDrH/6rehxuYAEE34=",
     300 + {
     301 + Path: "/etc/motd",
     302 + Digest: &file.Digest{
     303 + Algorithm: "'Q1'+base64(sha1)",
     304 + Value: "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=",
     305 + },
    310 306   },
    311  - },
    312  - {
    313  - Path: "/etc/services",
    314  - Digest: &file.Digest{
    315  - Algorithm: "'Q1'+base64(sha1)",
    316  - Value: "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=",
     307 + {
     308 + Path: "/etc/mtab",
     309 + OwnerUID: "0",
     310 + OwnerGID: "0",
     311 + Permissions: "777",
     312 + Digest: &file.Digest{
     313 + Algorithm: "'Q1'+base64(sha1)",
     314 + Value: "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=",
     315 + },
    317 316   },
    318  - },
    319  - {
    320  - Path: "/etc/shadow",
    321  - OwnerUID: "0",
    322  - OwnerGID: "42",
    323  - Permissions: "640",
    324  - Digest: &file.Digest{
    325  - Algorithm: "'Q1'+base64(sha1)",
    326  - Value: "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=",
     317 + {
     318 + Path: "/etc/passwd",
     319 + Digest: &file.Digest{
     320 + Algorithm: "'Q1'+base64(sha1)",
     321 + Value: "Q1TchuuLUfur0izvfZQZxgN/LJhB8=",
     322 + },
    327 323   },
    328  - },
    329  - {
    330  - Path: "/etc/shells",
    331  - Digest: &file.Digest{
    332  - Algorithm: "'Q1'+base64(sha1)",
    333  - Value: "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=",
     324 + {
     325 + Path: "/etc/profile",
     326 + Digest: &file.Digest{
     327 + Algorithm: "'Q1'+base64(sha1)",
     328 + Value: "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=",
     329 + },
    334 330   },
    335  - },
    336  - {
    337  - Path: "/etc/sysctl.conf",
    338  - Digest: &file.Digest{
    339  - Algorithm: "'Q1'+base64(sha1)",
    340  - Value: "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=",
     331 + {
     332 + Path: "/etc/protocols",
     333 + Digest: &file.Digest{
     334 + Algorithm: "'Q1'+base64(sha1)",
     335 + Value: "Q13FqXUnvuOpMDrH/6rehxuYAEE34=",
     336 + },
    341 337   },
    342  - },
    343  - {
    344  - Path: "/etc/apk",
    345  - },
    346  - {
    347  - Path: "/etc/conf.d",
    348  - },
    349  - {
    350  - Path: "/etc/crontabs",
    351  - },
    352  - {
    353  - Path: "/etc/crontabs/root",
    354  - OwnerUID: "0",
    355  - OwnerGID: "0",
    356  - Permissions: "600",
    357  - Digest: &file.Digest{
    358  - Algorithm: "'Q1'+base64(sha1)",
    359  - Value: "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=",
     338 + {
     339 + Path: "/etc/services",
     340 + Digest: &file.Digest{
     341 + Algorithm: "'Q1'+base64(sha1)",
     342 + Value: "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=",
     343 + },
    360 344   },
    361  - },
    362  - {
    363  - Path: "/etc/init.d",
    364  - },
    365  - {
    366  - Path: "/etc/modprobe.d",
    367  - },
    368  - {
    369  - Path: "/etc/modprobe.d/aliases.conf",
    370  - Digest: &file.Digest{
    371  - Algorithm: "'Q1'+base64(sha1)",
    372  - Value: "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=",
     345 + {
     346 + Path: "/etc/shadow",
     347 + OwnerUID: "0",
     348 + OwnerGID: "42",
     349 + Permissions: "640",
     350 + Digest: &file.Digest{
     351 + Algorithm: "'Q1'+base64(sha1)",
     352 + Value: "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=",
     353 + },
    373 354   },
    374  - },
    375  - {
    376  - Path: "/etc/modprobe.d/blacklist.conf",
    377  - Digest: &file.Digest{
    378  - Algorithm: "'Q1'+base64(sha1)",
    379  - Value: "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=",
     355 + {
     356 + Path: "/etc/shells",
     357 + Digest: &file.Digest{
     358 + Algorithm: "'Q1'+base64(sha1)",
     359 + Value: "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=",
     360 + },
    380 361   },
    381  - },
    382  - {
    383  - Path: "/etc/modprobe.d/i386.conf",
    384  - Digest: &file.Digest{
    385  - Algorithm: "'Q1'+base64(sha1)",
    386  - Value: "Q1pnay/njn6ol9cCssL7KiZZ8etlc=",
     362 + {
     363 + Path: "/etc/sysctl.conf",
     364 + Digest: &file.Digest{
     365 + Algorithm: "'Q1'+base64(sha1)",
     366 + Value: "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=",
     367 + },
    387 368   },
    388  - },
    389  - {
    390  - Path: "/etc/modprobe.d/kms.conf",
    391  - Digest: &file.Digest{
    392  - Algorithm: "'Q1'+base64(sha1)",
    393  - Value: "Q1ynbLn3GYDpvajba/ldp1niayeog=",
     369 + {
     370 + Path: "/etc/apk",
    394 371   },
    395  - },
    396  - {
    397  - Path: "/etc/modules-load.d",
    398  - },
    399  - {
    400  - Path: "/etc/network",
    401  - },
    402  - {
    403  - Path: "/etc/network/if-down.d",
    404  - },
    405  - {
    406  - Path: "/etc/network/if-post-down.d",
    407  - },
    408  - {
    409  - Path: "/etc/network/if-pre-up.d",
    410  - },
    411  - {
    412  - Path: "/etc/network/if-up.d",
    413  - },
    414  - {
    415  - Path: "/etc/opt",
    416  - },
    417  - {
    418  - Path: "/etc/periodic",
    419  - },
    420  - {
    421  - Path: "/etc/periodic/15min",
    422  - },
    423  - {
    424  - Path: "/etc/periodic/daily",
    425  - },
    426  - {
    427  - Path: "/etc/periodic/hourly",
    428  - },
    429  - {
    430  - Path: "/etc/periodic/monthly",
    431  - },
    432  - {
    433  - Path: "/etc/periodic/weekly",
    434  - },
    435  - {
    436  - Path: "/etc/profile.d",
    437  - },
    438  - {
    439  - Path: "/etc/profile.d/color_prompt",
    440  - Digest: &file.Digest{
    441  - Algorithm: "'Q1'+base64(sha1)",
    442  - Value: "Q10wL23GuSCVfumMRgakabUI6EsSk=",
     372 + {
     373 + Path: "/etc/conf.d",
    443 374   },
    444  - },
    445  - {
    446  - Path: "/etc/profile.d/locale",
    447  - Digest: &file.Digest{
    448  - Algorithm: "'Q1'+base64(sha1)",
    449  - Value: "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=",
     375 + {
     376 + Path: "/etc/crontabs",
    450 377   },
    451  - },
    452  - {
    453  - Path: "/etc/sysctl.d",
    454  - },
    455  - {
    456  - Path: "/home",
    457  - },
    458  - {
    459  - Path: "/lib",
    460  - },
    461  - {
    462  - Path: "/lib/firmware",
    463  - },
    464  - {
    465  - Path: "/lib/mdev",
    466  - },
    467  - {
    468  - Path: "/lib/modules-load.d",
    469  - },
    470  - {
    471  - Path: "/lib/sysctl.d",
    472  - },
    473  - {
    474  - Path: "/lib/sysctl.d/00-alpine.conf",
    475  - Digest: &file.Digest{
    476  - Algorithm: "'Q1'+base64(sha1)",
    477  - Value: "Q1HpElzW1xEgmKfERtTy7oommnq6c=",
     378 + {
     379 + Path: "/etc/crontabs/root",
     380 + OwnerUID: "0",
     381 + OwnerGID: "0",
     382 + Permissions: "600",
     383 + Digest: &file.Digest{
     384 + Algorithm: "'Q1'+base64(sha1)",
     385 + Value: "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=",
     386 + },
    478 387   },
    479  - },
    480  - {
    481  - Path: "/media",
    482  - },
    483  - {
    484  - Path: "/media/cdrom",
    485  - },
    486  - {
    487  - Path: "/media/floppy",
    488  - },
    489  - {
    490  - Path: "/media/usb",
    491  - },
    492  - {
    493  - Path: "/mnt",
    494  - },
    495  - {
    496  - Path: "/opt",
    497  - },
    498  - {
    499  - Path: "/proc",
    500  - },
    501  - {
    502  - Path: "/root",
    503  - OwnerUID: "0",
    504  - OwnerGID: "0",
    505  - Permissions: "700",
    506  - },
    507  - {
    508  - Path: "/run",
    509  - },
    510  - {
    511  - Path: "/sbin",
    512  - },
    513  - {
    514  - Path: "/sbin/mkmntdirs",
    515  - OwnerUID: "0",
    516  - OwnerGID: "0",
    517  - Permissions: "755",
    518  - Digest: &file.Digest{
    519  - Algorithm: "'Q1'+base64(sha1)",
    520  - Value: "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=",
     388 + {
     389 + Path: "/etc/init.d",
    521 390   },
    522  - },
    523  - {
    524  - Path: "/srv",
    525  - },
    526  - {
    527  - Path: "/sys",
    528  - },
    529  - {
    530  - Path: "/tmp",
    531  - OwnerUID: "0",
    532  - OwnerGID: "0",
    533  - Permissions: "1777",
    534  - },
    535  - {
    536  - Path: "/usr",
    537  - },
    538  - {
    539  - Path: "/usr/lib",
    540  - },
    541  - {
    542  - Path: "/usr/lib/modules-load.d",
    543  - },
    544  - {
    545  - Path: "/usr/local",
    546  - },
    547  - {
    548  - Path: "/usr/local/bin",
    549  - },
    550  - {
    551  - Path: "/usr/local/lib",
    552  - },
    553  - {
    554  - Path: "/usr/local/share",
    555  - },
    556  - {
    557  - Path: "/usr/sbin",
    558  - },
    559  - {
    560  - Path: "/usr/share",
    561  - },
    562  - {
    563  - Path: "/usr/share/man",
    564  - },
    565  - {
    566  - Path: "/usr/share/misc",
    567  - },
    568  - {
    569  - Path: "/var",
    570  - },
    571  - {
    572  - Path: "/var/run",
    573  - OwnerUID: "0",
    574  - OwnerGID: "0",
    575  - Permissions: "777",
    576  - Digest: &file.Digest{
    577  - Algorithm: "'Q1'+base64(sha1)",
    578  - Value: "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=",
     391 + {
     392 + Path: "/etc/modprobe.d",
     393 + },
     394 + {
     395 + Path: "/etc/modprobe.d/aliases.conf",
     396 + Digest: &file.Digest{
     397 + Algorithm: "'Q1'+base64(sha1)",
     398 + Value: "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=",
     399 + },
    579 400   },
    580  - },
    581  - {
    582  - Path: "/var/cache",
    583  - },
    584  - {
    585  - Path: "/var/cache/misc",
    586  - },
    587  - {
    588  - Path: "/var/empty",
    589  - OwnerUID: "0",
    590  - OwnerGID: "0",
    591  - Permissions: "555",
    592  - },
    593  - {
    594  - Path: "/var/lib",
    595  - },
    596  - {
    597  - Path: "/var/lib/misc",
    598  - },
    599  - {
    600  - Path: "/var/local",
    601  - },
    602  - {
    603  - Path: "/var/lock",
    604  - },
    605  - {
    606  - Path: "/var/lock/subsys",
    607  - },
    608  - {
    609  - Path: "/var/log",
    610  - },
    611  - {
    612  - Path: "/var/mail",
    613  - },
    614  - {
    615  - Path: "/var/opt",
    616  - },
    617  - {
    618  - Path: "/var/spool",
    619  - },
    620  - {
    621  - Path: "/var/spool/mail",
    622  - OwnerUID: "0",
    623  - OwnerGID: "0",
    624  - Permissions: "777",
    625  - Digest: &file.Digest{
    626  - Algorithm: "'Q1'+base64(sha1)",
    627  - Value: "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=",
     401 + {
     402 + Path: "/etc/modprobe.d/blacklist.conf",
     403 + Digest: &file.Digest{
     404 + Algorithm: "'Q1'+base64(sha1)",
     405 + Value: "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=",
     406 + },
     407 + },
     408 + {
     409 + Path: "/etc/modprobe.d/i386.conf",
     410 + Digest: &file.Digest{
     411 + Algorithm: "'Q1'+base64(sha1)",
     412 + Value: "Q1pnay/njn6ol9cCssL7KiZZ8etlc=",
     413 + },
     414 + },
     415 + {
     416 + Path: "/etc/modprobe.d/kms.conf",
     417 + Digest: &file.Digest{
     418 + Algorithm: "'Q1'+base64(sha1)",
     419 + Value: "Q1ynbLn3GYDpvajba/ldp1niayeog=",
     420 + },
     421 + },
     422 + {
     423 + Path: "/etc/modules-load.d",
     424 + },
     425 + {
     426 + Path: "/etc/network",
     427 + },
     428 + {
     429 + Path: "/etc/network/if-down.d",
     430 + },
     431 + {
     432 + Path: "/etc/network/if-post-down.d",
     433 + },
     434 + {
     435 + Path: "/etc/network/if-pre-up.d",
     436 + },
     437 + {
     438 + Path: "/etc/network/if-up.d",
    628 439   },
    629  - },
    630  - {
    631  - Path: "/var/spool/cron",
    632  - },
    633  - {
    634  - Path: "/var/spool/cron/crontabs",
    635  - OwnerUID: "0",
    636  - OwnerGID: "0",
    637  - Permissions: "777",
    638  - Digest: &file.Digest{
    639  - Algorithm: "'Q1'+base64(sha1)",
    640  - Value: "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=",
     440 + {
     441 + Path: "/etc/opt",
    641 442   },
    642  - },
    643  - {
    644  - Path: "/var/tmp",
    645  - OwnerUID: "0",
    646  - OwnerGID: "0",
    647  - Permissions: "1777",
     443 + {
     444 + Path: "/etc/periodic",
     445 + },
     446 + {
     447 + Path: "/etc/periodic/15min",
     448 + },
     449 + {
     450 + Path: "/etc/periodic/daily",
     451 + },
     452 + {
     453 + Path: "/etc/periodic/hourly",
     454 + },
     455 + {
     456 + Path: "/etc/periodic/monthly",
     457 + },
     458 + {
     459 + Path: "/etc/periodic/weekly",
     460 + },
     461 + {
     462 + Path: "/etc/profile.d",
     463 + },
     464 + {
     465 + Path: "/etc/profile.d/color_prompt",
     466 + Digest: &file.Digest{
     467 + Algorithm: "'Q1'+base64(sha1)",
     468 + Value: "Q10wL23GuSCVfumMRgakabUI6EsSk=",
     469 + },
     470 + },
     471 + {
     472 + Path: "/etc/profile.d/locale",
     473 + Digest: &file.Digest{
     474 + Algorithm: "'Q1'+base64(sha1)",
     475 + Value: "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=",
     476 + },
     477 + },
     478 + {
     479 + Path: "/etc/sysctl.d",
     480 + },
     481 + {
     482 + Path: "/home",
     483 + },
     484 + {
     485 + Path: "/lib",
     486 + },
     487 + {
     488 + Path: "/lib/firmware",
     489 + },
     490 + {
     491 + Path: "/lib/mdev",
     492 + },
     493 + {
     494 + Path: "/lib/modules-load.d",
     495 + },
     496 + {
     497 + Path: "/lib/sysctl.d",
     498 + },
     499 + {
     500 + Path: "/lib/sysctl.d/00-alpine.conf",
     501 + Digest: &file.Digest{
     502 + Algorithm: "'Q1'+base64(sha1)",
     503 + Value: "Q1HpElzW1xEgmKfERtTy7oommnq6c=",
     504 + },
     505 + },
     506 + {
     507 + Path: "/media",
     508 + },
     509 + {
     510 + Path: "/media/cdrom",
     511 + },
     512 + {
     513 + Path: "/media/floppy",
     514 + },
     515 + {
     516 + Path: "/media/usb",
     517 + },
     518 + {
     519 + Path: "/mnt",
     520 + },
     521 + {
     522 + Path: "/opt",
     523 + },
     524 + {
     525 + Path: "/proc",
     526 + },
     527 + {
     528 + Path: "/root",
     529 + OwnerUID: "0",
     530 + OwnerGID: "0",
     531 + Permissions: "700",
     532 + },
     533 + {
     534 + Path: "/run",
     535 + },
     536 + {
     537 + Path: "/sbin",
     538 + },
     539 + {
     540 + Path: "/sbin/mkmntdirs",
     541 + OwnerUID: "0",
     542 + OwnerGID: "0",
     543 + Permissions: "755",
     544 + Digest: &file.Digest{
     545 + Algorithm: "'Q1'+base64(sha1)",
     546 + Value: "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=",
     547 + },
     548 + },
     549 + {
     550 + Path: "/srv",
     551 + },
     552 + {
     553 + Path: "/sys",
     554 + },
     555 + {
     556 + Path: "/tmp",
     557 + OwnerUID: "0",
     558 + OwnerGID: "0",
     559 + Permissions: "1777",
     560 + },
     561 + {
     562 + Path: "/usr",
     563 + },
     564 + {
     565 + Path: "/usr/lib",
     566 + },
     567 + {
     568 + Path: "/usr/lib/modules-load.d",
     569 + },
     570 + {
     571 + Path: "/usr/local",
     572 + },
     573 + {
     574 + Path: "/usr/local/bin",
     575 + },
     576 + {
     577 + Path: "/usr/local/lib",
     578 + },
     579 + {
     580 + Path: "/usr/local/share",
     581 + },
     582 + {
     583 + Path: "/usr/sbin",
     584 + },
     585 + {
     586 + Path: "/usr/share",
     587 + },
     588 + {
     589 + Path: "/usr/share/man",
     590 + },
     591 + {
     592 + Path: "/usr/share/misc",
     593 + },
     594 + {
     595 + Path: "/var",
     596 + },
     597 + {
     598 + Path: "/var/run",
     599 + OwnerUID: "0",
     600 + OwnerGID: "0",
     601 + Permissions: "777",
     602 + Digest: &file.Digest{
     603 + Algorithm: "'Q1'+base64(sha1)",
     604 + Value: "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=",
     605 + },
     606 + },
     607 + {
     608 + Path: "/var/cache",
     609 + },
     610 + {
     611 + Path: "/var/cache/misc",
     612 + },
     613 + {
     614 + Path: "/var/empty",
     615 + OwnerUID: "0",
     616 + OwnerGID: "0",
     617 + Permissions: "555",
     618 + },
     619 + {
     620 + Path: "/var/lib",
     621 + },
     622 + {
     623 + Path: "/var/lib/misc",
     624 + },
     625 + {
     626 + Path: "/var/local",
     627 + },
     628 + {
     629 + Path: "/var/lock",
     630 + },
     631 + {
     632 + Path: "/var/lock/subsys",
     633 + },
     634 + {
     635 + Path: "/var/log",
     636 + },
     637 + {
     638 + Path: "/var/mail",
     639 + },
     640 + {
     641 + Path: "/var/opt",
     642 + },
     643 + {
     644 + Path: "/var/spool",
     645 + },
     646 + {
     647 + Path: "/var/spool/mail",
     648 + OwnerUID: "0",
     649 + OwnerGID: "0",
     650 + Permissions: "777",
     651 + Digest: &file.Digest{
     652 + Algorithm: "'Q1'+base64(sha1)",
     653 + Value: "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=",
     654 + },
     655 + },
     656 + {
     657 + Path: "/var/spool/cron",
     658 + },
     659 + {
     660 + Path: "/var/spool/cron/crontabs",
     661 + OwnerUID: "0",
     662 + OwnerGID: "0",
     663 + Permissions: "777",
     664 + Digest: &file.Digest{
     665 + Algorithm: "'Q1'+base64(sha1)",
     666 + Value: "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=",
     667 + },
     668 + },
     669 + {
     670 + Path: "/var/tmp",
     671 + OwnerUID: "0",
     672 + OwnerGID: "0",
     673 + Permissions: "1777",
     674 + },
    648 675   },
    649 676   },
    650 677   },
    skipped 2 lines
    653 680   
    654 681   for _, test := range tests {
    655 682   t.Run(test.fixture, func(t *testing.T) {
    656  - lrc := newLocationReadCloser(t, test.fixture)
    657  - 
    658  - pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc)
    659  - require.NoError(t, err)
    660  - require.Len(t, pkgs, 1)
    661  - metadata := pkgs[0].Metadata.(pkg.ApkMetadata)
    662  - 
    663  - if diff := cmp.Diff(test.expected, metadata); diff != "" {
    664  - t.Errorf("Entry mismatch (-want +got):\n%s", diff)
     683 + fixtureLocation := source.NewLocation(test.fixture)
     684 + test.expected.Locations = source.NewLocationSet(fixtureLocation)
     685 + licenses := test.expected.Licenses.ToSlice()
     686 + for i := range licenses {
     687 + licenses[i].Location.Add(fixtureLocation)
    665 688   }
     689 + test.expected.Licenses = pkg.NewLicenseSet(licenses...)
     690 + pkgtest.TestFileParser(t, test.fixture, parseApkDB, []pkg.Package{test.expected}, nil)
    666 691   })
    667 692   }
    668 693  }
    669 694   
    670 695  func TestMultiplePackages(t *testing.T) {
    671 696   fixture := "test-fixtures/multiple"
    672  - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture))
     697 + location := source.NewLocation(fixture)
     698 + fixtureLocationSet := source.NewLocationSet(location)
    673 699   expectedPkgs := []pkg.Package{
    674 700   {
    675  - Name: "libc-utils",
    676  - Version: "0.7.2-r0",
    677  - Licenses: []string{"BSD"},
     701 + Name: "libc-utils",
     702 + Version: "0.7.2-r0",
     703 + Licenses: pkg.NewLicenseSet(
     704 + pkg.NewLicenseFromLocations("BSD", location),
     705 + ),
    678 706   Type: pkg.ApkPkg,
    679 707   PURL: "pkg:apk/alpine/[email protected]?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
    680 708   Locations: fixtureLocationSet,
    skipped 3 lines
    684 712   OriginPackage: "libc-dev",
    685 713   Maintainer: "Natanael Copa <[email protected]>",
    686 714   Version: "0.7.2-r0",
    687  - License: "BSD",
    688 715   Architecture: "x86_64",
    689 716   URL: "http://alpinelinux.org",
    690 717   Description: "Meta package to pull in correct libc",
    skipped 7 lines
    698 725   },
    699 726   },
    700 727   {
    701  - Name: "musl-utils",
    702  - Version: "1.1.24-r2",
    703  - Licenses: []string{"MIT", "BSD", "GPL2+"},
    704  - Type: pkg.ApkPkg,
    705  - PURL: "pkg:apk/alpine/[email protected]?arch=x86_64&upstream=musl&distro=alpine-3.12",
    706  - Locations: fixtureLocationSet,
     728 + Name: "musl-utils",
     729 + Version: "1.1.24-r2",
     730 + Type: pkg.ApkPkg,
     731 + PURL: "pkg:apk/alpine/[email protected]?arch=x86_64&upstream=musl&distro=alpine-3.12",
     732 + Locations: fixtureLocationSet,
     733 + Licenses: pkg.NewLicenseSet(
     734 + pkg.NewLicenseFromLocations("MIT", location),
     735 + pkg.NewLicenseFromLocations("BSD", location),
     736 + pkg.NewLicenseFromLocations("GPL2+", location),
     737 + ),
    707 738   MetadataType: pkg.ApkMetadataType,
    708 739   Metadata: pkg.ApkMetadata{
    709 740   Package: "musl-utils",
    skipped 1 lines
    711 742   Version: "1.1.24-r2",
    712 743   Description: "the musl c library (libc) implementation",
    713 744   Maintainer: "Timo Teräs <[email protected]>",
    714  - License: "MIT BSD GPL2+",
    715 745   Architecture: "x86_64",
    716 746   URL: "https://musl.libc.org/",
    717 747   Size: 37944,
    skipped 276 lines
    994 1024   t.Run(test.name, func(t *testing.T) {
    995 1025   pkgs, wantRelationships := test.genFn()
    996 1026   gotRelationships := discoverPackageDependencies(pkgs)
    997  - d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, source.LocationSet{}))
     1027 + d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, source.LocationSet{}, pkg.LicenseSet{}))
    998 1028   if d != "" {
    999 1029   t.Fail()
    1000 1030   t.Log(d)
    skipped 237 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/cataloger_test.go
    skipped 9 lines
    10 10  )
    11 11   
    12 12  func TestDpkgCataloger(t *testing.T) {
     13 + licenseLocation := source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
    13 14   expected := []pkg.Package{
    14 15   {
    15  - Name: "libpam-runtime",
    16  - Version: "1.1.8-3.6",
    17  - FoundBy: "dpkgdb-cataloger",
    18  - Licenses: []string{"GPL-1", "GPL-2", "LGPL-2.1"},
     16 + Name: "libpam-runtime",
     17 + Version: "1.1.8-3.6",
     18 + FoundBy: "dpkgdb-cataloger",
     19 + Licenses: pkg.NewLicenseSet(
     20 + pkg.NewLicenseFromLocations("GPL-1", licenseLocation),
     21 + pkg.NewLicenseFromLocations("GPL-2", licenseLocation),
     22 + pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation),
     23 + ),
    19 24   Locations: source.NewLocationSet(
    20 25   source.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
    21 26   source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
    skipped 89 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/package.go
    skipped 21 lines
    22 22  )
    23 23   
    24 24  func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver source.FileResolver, release *linux.Release) pkg.Package {
     25 + // TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
     26 + licenses := make([]pkg.License, 0)
    25 27   p := pkg.Package{
    26 28   Name: d.Package,
    27 29   Version: d.Version,
     30 + Licenses: pkg.NewLicenseSet(licenses...),
    28 31   Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    29 32   PURL: packageURL(d, release),
    30 33   Type: pkg.DebPkg,
    skipped 62 lines
    93 96   if copyrightReader != nil && copyrightLocation != nil {
    94 97   defer internal.CloseAndLogError(copyrightReader, copyrightLocation.VirtualPath)
    95 98   // attach the licenses
    96  - p.Licenses = parseLicensesFromCopyright(copyrightReader)
    97  - 
     99 + licenseStrs := parseLicensesFromCopyright(copyrightReader)
     100 + for _, licenseStr := range licenseStrs {
     101 + p.Licenses.Add(pkg.NewLicenseFromLocations(licenseStr, copyrightLocation.WithoutAnnotations()))
     102 + }
    98 103   // keep a record of the file where this was discovered
    99 104   p.Locations.Add(*copyrightLocation)
    100 105   }
    skipped 168 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/parse_dpkg_db_test.go
    skipped 306 lines
    307 307   Name: "apt",
    308 308   Type: "deb",
    309 309   PURL: "pkg:deb/debian/apt?distro=debian-10",
     310 + Licenses: pkg.NewLicenseSet(),
    310 311   Locations: source.NewLocationSet(source.NewLocation("place")),
    311 312   MetadataType: "DpkgMetadata",
    312 313   Metadata: pkg.DpkgMetadata{
    skipped 88 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/golang/licenses.go
    skipped 21 lines
    22 22   "github.com/anchore/syft/internal/licenses"
    23 23   "github.com/anchore/syft/internal/log"
    24 24   "github.com/anchore/syft/syft/event"
     25 + "github.com/anchore/syft/syft/pkg"
    25 26   "github.com/anchore/syft/syft/source"
    26 27  )
    27 28   
    skipped 46 lines
    74 75   return r
    75 76  }
    76 77   
    77  -func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []string, err error) {
     78 +func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) {
    78 79   licenses, err = findLicenses(resolver,
    79 80   fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
    80 81   )
    skipped 12 lines
    93 94   return requireCollection(licenses), err
    94 95  }
    95 96   
    96  -func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]string, error) {
     97 +func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]pkg.License, error) {
    97 98   if !c.opts.searchLocalModCacheLicenses {
    98 99   return nil, nil
    99 100   }
    skipped 3 lines
    103 104   return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
    104 105  }
    105 106   
    106  -func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]string, error) {
     107 +func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
    107 108   if !c.opts.searchRemoteLicenses {
    108 109   return nil, nil
    109 110   }
    skipped 38 lines
    148 149   return fmt.Sprintf("%s/*", moduleDir(moduleName, moduleVersion))
    149 150  }
    150 151   
    151  -func requireCollection(licenses []string) []string {
     152 +func requireCollection(licenses []pkg.License) []pkg.License {
    152 153   if licenses == nil {
    153  - return []string{}
     154 + return make([]pkg.License, 0)
    154 155   }
    155 156   return licenses
    156 157  }
    157 158   
    158  -func findLicenses(resolver source.FileResolver, globMatch string) (out []string, err error) {
     159 +func findLicenses(resolver source.FileResolver, globMatch string) (out []pkg.License, err error) {
     160 + out = make([]pkg.License, 0)
    159 161   if resolver == nil {
    160 162   return
    161 163   }
    skipped 10 lines
    172 174   if err != nil {
    173 175   return nil, err
    174 176   }
    175  - parsed, err := licenses.Parse(contents)
     177 + parsed, err := licenses.Parse(contents, l)
    176 178   if err != nil {
    177 179   return nil, err
    178 180   }
    skipped 109 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/golang/licenses_test.go
    skipped 12 lines
    13 13   
    14 14   "github.com/stretchr/testify/require"
    15 15   
     16 + "github.com/anchore/syft/internal"
     17 + "github.com/anchore/syft/syft/license"
     18 + "github.com/anchore/syft/syft/pkg"
    16 19   "github.com/anchore/syft/syft/source"
    17 20  )
    18 21   
    19 22  func Test_LocalLicenseSearch(t *testing.T) {
     23 + loc1 := source.NewLocation("github.com/someorg/[email protected]/LICENSE")
     24 + loc2 := source.NewLocation("github.com/!cap!o!r!g/[email protected]/LICENSE.txt")
     25 + 
    20 26   tests := []struct {
    21 27   name string
    22 28   version string
    23  - expected string
     29 + expected pkg.License
    24 30   }{
    25 31   {
    26  - name: "github.com/someorg/somename",
    27  - version: "v0.3.2",
    28  - expected: "Apache-2.0",
     32 + name: "github.com/someorg/somename",
     33 + version: "v0.3.2",
     34 + expected: pkg.License{
     35 + Value: "Apache-2.0",
     36 + SPDXExpression: "Apache-2.0",
     37 + Type: license.Concluded,
     38 + Location: source.NewLocationSet(loc1),
     39 + URL: internal.NewStringSet(),
     40 + },
    29 41   },
    30 42   {
    31  - name: "github.com/CapORG/CapProject",
    32  - version: "v4.111.5",
    33  - expected: "MIT",
     43 + name: "github.com/CapORG/CapProject",
     44 + version: "v4.111.5",
     45 + expected: pkg.License{
     46 + Value: "MIT",
     47 + SPDXExpression: "MIT",
     48 + Type: license.Concluded,
     49 + Location: source.NewLocationSet(loc2),
     50 + URL: internal.NewStringSet(),
     51 + },
    34 52   },
    35 53   }
    36 54   
    skipped 2 lines
    39 57   
    40 58   for _, test := range tests {
    41 59   t.Run(test.name, func(t *testing.T) {
    42  - l := newGoLicenses(GoCatalogerOpts{
    43  - searchLocalModCacheLicenses: true,
    44  - localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
    45  - })
     60 + l := newGoLicenses(
     61 + GoCatalogerOpts{
     62 + searchLocalModCacheLicenses: true,
     63 + localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
     64 + },
     65 + )
    46 66   licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version)
    47 67   require.NoError(t, err)
    48 68   
    skipped 5 lines
    54 74  }
    55 75   
    56 76  func Test_RemoteProxyLicenseSearch(t *testing.T) {
     77 + loc1 := source.NewLocation("github.com/someorg/[email protected]/LICENSE")
     78 + loc2 := source.NewLocation("github.com/!cap!o!r!g/[email protected]/LICENSE.txt")
     79 + 
    57 80   server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    58 81   buf := &bytes.Buffer{}
    59 82   uri := strings.TrimPrefix(strings.TrimSuffix(r.RequestURI, ".zip"), "/")
    skipped 34 lines
    94 117   tests := []struct {
    95 118   name string
    96 119   version string
    97  - expected string
     120 + expected pkg.License
    98 121   }{
    99 122   {
    100  - name: "github.com/someorg/somename",
    101  - version: "v0.3.2",
    102  - expected: "Apache-2.0",
     123 + name: "github.com/someorg/somename",
     124 + version: "v0.3.2",
     125 + expected: pkg.License{
     126 + Value: "Apache-2.0",
     127 + SPDXExpression: "Apache-2.0",
     128 + Type: license.Concluded,
     129 + Location: source.NewLocationSet(loc1),
     130 + URL: internal.NewStringSet(),
     131 + },
    103 132   },
    104 133   {
    105  - name: "github.com/CapORG/CapProject",
    106  - version: "v4.111.5",
    107  - expected: "MIT",
     134 + name: "github.com/CapORG/CapProject",
     135 + version: "v4.111.5",
     136 + expected: pkg.License{
     137 + Value: "MIT",
     138 + SPDXExpression: "MIT",
     139 + Type: license.Concluded,
     140 + Location: source.NewLocationSet(loc2),
     141 + URL: internal.NewStringSet(),
     142 + },
    108 143   },
    109 144   }
    110 145   
    skipped 87 lines
  • ■ ■ ■ ■
    syft/pkg/cataloger/golang/package.go
    skipped 23 lines
    24 24   p := pkg.Package{
    25 25   Name: dep.Path,
    26 26   Version: dep.Version,
    27  - Licenses: licenses,
     27 + Licenses: pkg.NewLicenseSet(licenses...),
    28 28   PURL: packageURL(dep.Path, dep.Version),
    29 29   Language: pkg.Go,
    30 30   Type: pkg.GoModulePkg,
    skipped 47 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/golang/parse_go_binary_test.go
    skipped 496 lines
    497 497   t.Run(test.name, func(t *testing.T) {
    498 498   for i := range test.expected {
    499 499   p := &test.expected[i]
    500  - if p.Licenses == nil {
    501  - p.Licenses = []string{}
    502  - }
    503 500   p.SetID()
    504 501   }
    505 502   location := source.NewLocationFromCoordinates(
    skipped 13 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/golang/parse_go_mod.go
    skipped 49 lines
    50 50   packages[m.Mod.Path] = pkg.Package{
    51 51   Name: m.Mod.Path,
    52 52   Version: m.Mod.Version,
    53  - Licenses: licenses,
     53 + Licenses: pkg.NewLicenseSet(licenses...),
    54 54   Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    55 55   PURL: packageURL(m.Mod.Path, m.Mod.Version),
    56 56   Language: pkg.Go,
    skipped 15 lines
    72 72   packages[m.New.Path] = pkg.Package{
    73 73   Name: m.New.Path,
    74 74   Version: m.New.Version,
    75  - Licenses: licenses,
     75 + Licenses: pkg.NewLicenseSet(licenses...),
    76 76   Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    77 77   PURL: packageURL(m.New.Path, m.New.Version),
    78 78   Language: pkg.Go,
    skipped 65 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/golang/parse_go_mod_test.go
    skipped 87 lines
    88 88   
    89 89   for _, test := range tests {
    90 90   t.Run(test.fixture, func(t *testing.T) {
    91  - for i := range test.expected {
    92  - p := &test.expected[i]
    93  - if p.Licenses == nil {
    94  - p.Licenses = []string{}
    95  - }
    96  - }
    97 91   c := goModCataloger{}
    98 92   pkgtest.NewCatalogTester().
    99 93   FromFile(t, test.fixture).
    skipped 54 lines
    154 148   
    155 149   for _, test := range tests {
    156 150   t.Run(test.fixture, func(t *testing.T) {
    157  - for i := range test.expected {
    158  - p := &test.expected[i]
    159  - if p.Licenses == nil {
    160  - p.Licenses = []string{}
    161  - }
    162  - }
    163 151   pkgtest.NewCatalogTester().
    164 152   FromDirectory(t, test.fixture).
    165 153   Expects(test.expected, nil).
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go
    skipped 20 lines
    21 21  )
    22 22   
    23 23  type locationComparer func(x, y source.Location) bool
     24 +type licenseComparer func(x, y pkg.License) bool
    24 25   
    25 26  type CatalogTester struct {
    26 27   expectedPkgs []pkg.Package
    skipped 9 lines
    36 37   wantErr require.ErrorAssertionFunc
    37 38   compareOptions []cmp.Option
    38 39   locationComparer locationComparer
     40 + licenseComparer licenseComparer
    39 41  }
    40 42   
    41 43  func NewCatalogTester() *CatalogTester {
    42 44   return &CatalogTester{
    43 45   wantErr: require.NoError,
    44 46   locationComparer: DefaultLocationComparer,
     47 + licenseComparer: DefaultLicenseComparer,
    45 48   ignoreUnfulfilledPathResponses: map[string][]string{
    46 49   "FilesByPath": {
    47 50   // most catalogers search for a linux release, which will not be fulfilled in testing
    skipped 9 lines
    57 60   
    58 61  func DefaultLocationComparer(x, y source.Location) bool {
    59 62   return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
     63 +}
     64 + 
     65 +func DefaultLicenseComparer(x, y pkg.License) bool {
     66 + return cmp.Equal(x, y, cmp.Comparer(DefaultLocationComparer), cmp.Comparer(
     67 + func(x, y source.LocationSet) bool {
     68 + xs := x.ToSlice()
     69 + ys := y.ToSlice()
     70 + if len(xs) != len(ys) {
     71 + return false
     72 + }
     73 + for i, xe := range xs {
     74 + ye := ys[i]
     75 + if !DefaultLocationComparer(xe, ye) {
     76 + return false
     77 + }
     78 + }
     79 + return true
     80 + },
     81 + ))
    60 82  }
    61 83   
    62 84  func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
    skipped 76 lines
    139 161   p.locationComparer = func(x, y source.Location) bool {
    140 162   return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.VirtualPath, y.VirtualPath)
    141 163   }
     164 + 
     165 + // we need to update the license comparer to use the ignored location layer
     166 + p.licenseComparer = func(x, y pkg.License) bool {
     167 + return cmp.Equal(x, y, cmp.Comparer(p.locationComparer), cmp.Comparer(
     168 + func(x, y source.LocationSet) bool {
     169 + xs := x.ToSlice()
     170 + ys := y.ToSlice()
     171 + if len(xs) != len(ys) {
     172 + return false
     173 + }
     174 + for i, xe := range xs {
     175 + ye := ys[i]
     176 + if !p.locationComparer(xe, ye) {
     177 + return false
     178 + }
     179 + }
     180 + 
     181 + return true
     182 + }))
     183 + }
    142 184   return p
    143 185  }
    144 186   
    skipped 64 lines
    209 251   }
    210 252  }
    211 253   
     254 +// nolint:funlen
    212 255  func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
    213 256   t.Helper()
    214 257   
    skipped 18 lines
    233 276   return true
    234 277   },
    235 278   ),
     279 + cmp.Comparer(
     280 + func(x, y pkg.LicenseSet) bool {
     281 + xs := x.ToSlice()
     282 + ys := y.ToSlice()
     283 + 
     284 + if len(xs) != len(ys) {
     285 + return false
     286 + }
     287 + for i, xe := range xs {
     288 + ye := ys[i]
     289 + if !p.licenseComparer(xe, ye) {
     290 + return false
     291 + }
     292 + }
     293 + 
     294 + return true
     295 + },
     296 + ),
     297 + cmp.Comparer(
     298 + p.locationComparer,
     299 + ),
     300 + cmp.Comparer(
     301 + p.licenseComparer,
     302 + ),
    236 303   )
    237 304   
    238 305   {
    skipped 55 lines
    294 361   
    295 362   return true
    296 363   },
     364 + ),
     365 + cmp.Comparer(
     366 + func(x, y pkg.LicenseSet) bool {
     367 + xs := x.ToSlice()
     368 + ys := y.ToSlice()
     369 + 
     370 + if len(xs) != len(ys) {
     371 + return false
     372 + }
     373 + for i, xe := range xs {
     374 + ye := ys[i]
     375 + if !DefaultLicenseComparer(xe, ye) {
     376 + return false
     377 + }
     378 + }
     379 + 
     380 + return true
     381 + },
     382 + ),
     383 + cmp.Comparer(
     384 + DefaultLocationComparer,
     385 + ),
     386 + cmp.Comparer(
     387 + DefaultLicenseComparer,
    297 388   ),
    298 389   }
    299 390   
    skipped 30 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/java/archive_parser.go
    skipped 184 lines
    185 185   log.Warnf("failed to create digest for file=%q: %+v", j.archivePath, err)
    186 186   }
    187 187   
     188 + // we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
     189 + licenses := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
    188 190   return &pkg.Package{
    189 191   Name: selectName(manifest, j.fileInfo),
    190 192   Version: selectVersion(manifest, j.fileInfo),
    191  - Licenses: selectLicense(manifest),
    192 193   Language: pkg.Java,
     194 + Licenses: pkg.NewLicenseSet(licenses...),
    193 195   Locations: source.NewLocationSet(
    194 196   j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    195 197   ),
    skipped 270 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/java/archive_parser_test.go
    skipped 95 lines
    96 96   },
    97 97   expected: map[string]pkg.Package{
    98 98   "example-jenkins-plugin": {
    99  - Name: "example-jenkins-plugin",
    100  - Version: "1.0-SNAPSHOT",
    101  - PURL: "pkg:maven/io.jenkins.plugins/[email protected]",
    102  - Licenses: []string{"MIT License"},
     99 + Name: "example-jenkins-plugin",
     100 + Version: "1.0-SNAPSHOT",
     101 + PURL: "pkg:maven/io.jenkins.plugins/[email protected]",
     102 + Licenses: pkg.NewLicenseSet(
     103 + pkg.NewLicenseFromLocations("MIT License", source.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")),
     104 + ),
    103 105   Language: pkg.Java,
    104 106   Type: pkg.JenkinsPluginPkg,
    105 107   MetadataType: pkg.JavaMetadataType,
    skipped 44 lines
    150 152   Name: "example-java-app-gradle",
    151 153   Version: "0.1.0",
    152 154   PURL: "pkg:maven/example-java-app-gradle/[email protected]",
    153  - Licenses: []string{},
    154 155   Language: pkg.Java,
    155 156   Type: pkg.JavaPkg,
    156 157   MetadataType: pkg.JavaMetadataType,
    skipped 48 lines
    205 206   Name: "example-java-app-maven",
    206 207   Version: "0.1.0",
    207 208   PURL: "pkg:maven/org.anchore/[email protected]",
    208  - Licenses: []string{},
    209 209   Language: pkg.Java,
    210 210   Type: pkg.JavaPkg,
    211 211   MetadataType: pkg.JavaMetadataType,
    skipped 790 lines
  • ■ ■ ■ ■
    syft/pkg/cataloger/java/parse_java_manifest.go
    skipped 156 lines
    157 157   return ""
    158 158  }
    159 159   
    160  -func selectLicense(manifest *pkg.JavaManifest) []string {
     160 +func selectLicenses(manifest *pkg.JavaManifest) []string {
    161 161   result := []string{}
    162 162   if manifest == nil {
    163 163   return result
    skipped 30 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/javascript/cataloger_test.go
    skipped 11 lines
    12 12   locationSet := source.NewLocationSet(source.NewLocation("package-lock.json"))
    13 13   expectedPkgs := []pkg.Package{
    14 14   {
    15  - Name: "@actions/core",
    16  - Version: "1.6.0",
    17  - FoundBy: "javascript-lock-cataloger",
    18  - PURL: "pkg:npm/%40actions/[email protected]",
    19  - Locations: locationSet,
    20  - Language: pkg.JavaScript,
    21  - Type: pkg.NpmPkg,
    22  - Licenses: []string{"MIT"},
     15 + Name: "@actions/core",
     16 + Version: "1.6.0",
     17 + FoundBy: "javascript-lock-cataloger",
     18 + PURL: "pkg:npm/%40actions/[email protected]",
     19 + Locations: locationSet,
     20 + Language: pkg.JavaScript,
     21 + Type: pkg.NpmPkg,
     22 + Licenses: pkg.NewLicenseSet(
     23 + pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
     24 + ),
    23 25   MetadataType: pkg.NpmPackageLockJSONMetadataType,
    24 26   Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", Integrity: "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw=="},
    25 27   },
    skipped 9 lines
    35 37   Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", Integrity: "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="},
    36 38   },
    37 39   {
    38  - Name: "cowsay",
    39  - Version: "1.4.0",
    40  - FoundBy: "javascript-lock-cataloger",
    41  - PURL: "pkg:npm/[email protected]",
    42  - Locations: locationSet,
    43  - Language: pkg.JavaScript,
    44  - Type: pkg.NpmPkg,
    45  - Licenses: []string{"MIT"},
     40 + Name: "cowsay",
     41 + Version: "1.4.0",
     42 + FoundBy: "javascript-lock-cataloger",
     43 + PURL: "pkg:npm/[email protected]",
     44 + Locations: locationSet,
     45 + Language: pkg.JavaScript,
     46 + Type: pkg.NpmPkg,
     47 + Licenses: pkg.NewLicenseSet(
     48 + pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
     49 + ),
    46 50   MetadataType: pkg.NpmPackageLockJSONMetadataType,
    47 51   Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/cowsay/-/cowsay-1.4.0.tgz", Integrity: "sha512-rdg5k5PsHFVJheO/pmE3aDg2rUDDTfPJau6yYkZYlHFktUz+UxbE+IgnUAEyyCyv4noL5ltxXD0gZzmHPCy/9g=="},
    48 52   },
    skipped 149 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/javascript/package.go
    skipped 11 lines
    12 12   "github.com/anchore/syft/syft/source"
    13 13  )
    14 14   
    15  -func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Package {
    16  - licenses, err := u.licensesFromJSON()
     15 +func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Package {
     16 + licenseCandidates, err := u.licensesFromJSON()
    17 17   if err != nil {
    18 18   log.Warnf("unable to extract licenses from javascript package.json: %+v", err)
    19 19   }
    20 20   
     21 + license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
    21 22   p := pkg.Package{
    22 23   Name: u.Name,
    23 24   Version: u.Version,
    24  - Licenses: licenses,
    25 25   PURL: packageURL(u.Name, u.Version),
    26  - Locations: source.NewLocationSet(locations...),
     26 + Locations: source.NewLocationSet(indexLocation),
    27 27   Language: pkg.JavaScript,
     28 + Licenses: pkg.NewLicenseSet(license...),
    28 29   Type: pkg.NpmPkg,
    29 30   MetadataType: pkg.NpmPackageJSONMetadataType,
    30 31   Metadata: pkg.NpmPackageJSONMetadata{
    31 32   Name: u.Name,
    32 33   Version: u.Version,
     34 + Description: u.Description,
    33 35   Author: u.Author.AuthorString(),
    34 36   Homepage: u.Homepage,
    35 37   URL: u.Repository.URL,
    36  - Licenses: licenses,
    37 38   Private: u.Private,
    38  - Description: u.Description,
    39 39   },
    40 40   }
    41 41   
    skipped 35 lines
    77 77  }
    78 78   
    79 79  func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package {
    80  - var licenses []string
    81  - 
    82  - if u.License != nil {
    83  - licenses = u.License
    84  - }
    85  - 
    86 80   return finalizeLockPkg(
    87 81   resolver,
    88 82   location,
    skipped 1 lines
    90 84   Name: name,
    91 85   Version: u.Version,
    92 86   Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
     87 + Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...),
    93 88   PURL: packageURL(name, u.Version),
    94 89   Language: pkg.JavaScript,
    95 90   Type: pkg.NpmPkg,
    96  - Licenses: licenses,
    97 91   MetadataType: pkg.NpmPackageLockJSONMetadataType,
    98 92   Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: u.Resolved, Integrity: u.Integrity},
    99 93   },
    skipped 31 lines
    131 125  }
    132 126   
    133 127  func finalizeLockPkg(resolver source.FileResolver, location source.Location, p pkg.Package) pkg.Package {
    134  - p.Licenses = append(p.Licenses, addLicenses(p.Name, resolver, location)...)
     128 + licenseCandidate := addLicenses(p.Name, resolver, location)
     129 + p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
    135 130   p.SetID()
    136 131   return p
    137 132  }
    skipped 2 lines
    140 135   if resolver == nil {
    141 136   return allLicenses
    142 137   }
     138 + 
    143 139   dir := path.Dir(location.RealPath)
    144 140   pkgPath := []string{dir, "node_modules"}
    145 141   pkgPath = append(pkgPath, strings.Split(name, "/")...)
    146 142   pkgPath = append(pkgPath, "package.json")
    147 143   pkgFile := path.Join(pkgPath...)
    148 144   locations, err := resolver.FilesByPath(pkgFile)
    149  - 
    150 145   if err != nil {
    151 146   log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
    152 147   return allLicenses
    skipped 58 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/javascript/parse_package_json.go
    skipped 139 lines
    140 140   return nil
    141 141  }
    142 142   
    143  -type license struct {
     143 +type npmPackageLicense struct {
    144 144   Type string `json:"type"`
    145 145   URL string `json:"url"`
    146 146  }
    skipped 7 lines
    154 154   }
    155 155   
    156 156   // then try as object (this format is deprecated)
    157  - var licenseObject license
     157 + var licenseObject npmPackageLicense
    158 158   err = json.Unmarshal(b, &licenseObject)
    159 159   if err == nil {
    160 160   return licenseObject.Type, nil
    skipped 17 lines
    178 178   
    179 179   // The "licenses" field is deprecated. It should be inspected as a last resort.
    180 180   if multiLicense != nil && err == nil {
    181  - mapLicenses := func(licenses []license) []string {
     181 + mapLicenses := func(licenses []npmPackageLicense) []string {
    182 182   mappedLicenses := make([]string, len(licenses))
    183 183   for i, l := range licenses {
    184 184   mappedLicenses[i] = l.Type
    skipped 7 lines
    192 192   return nil, err
    193 193  }
    194 194   
    195  -func licensesFromJSON(b []byte) ([]license, error) {
    196  - var licenseObject []license
     195 +func licensesFromJSON(b []byte) ([]npmPackageLicense, error) {
     196 + var licenseObject []npmPackageLicense
    197 197   err := json.Unmarshal(b, &licenseObject)
    198 198   if err == nil {
    199 199   return licenseObject, nil
    skipped 21 lines
Please wait...
Page is in error, reload to recover