mirror of
https://github.com/anchore/syft.git
synced 2026-04-05 22:30:35 +02:00
Merge the .NET deps.json and PE binary catalogers (#3563)
* add combined deps.json + pe binary cataloger Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * deprecate pe and deps standalone catalogers Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * parse resource names + add tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix integration and CLI tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add some helpful code comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * allow for dropping Dep packages that are missing DLLs Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * migrate json schema changes to 24 Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep application configuration Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * correct config help Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * [wip] detect claims of dlls within deps.json Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * [wip] fix tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add assembly repack detection Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * .net package count is lower due to dll claim requirement Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
4a9437808e
commit
ad9928cb2a
6
.github/scripts/find_cache_paths.py
vendored
6
.github/scripts/find_cache_paths.py
vendored
@ -86,8 +86,10 @@ def main(file_path: str | None):
|
||||
show("The following paths are missing or have no content, but have corresponding .fingerprint files:")
|
||||
for path in sorted(missing_content):
|
||||
show(f"- {path}")
|
||||
show("Please ensure these paths exist and have content if they are directories.")
|
||||
exit(1)
|
||||
# when adding new cache directories there is a time where it is not possible to have this directory without
|
||||
# running the tests first... but this step is a prerequisite for running the tests. We should not block on this.
|
||||
# show("Please ensure these paths exist and have content if they are directories.")
|
||||
# exit(1)
|
||||
|
||||
sha256_hash = calculate_sha256(fingerprint_contents)
|
||||
|
||||
|
||||
@ -168,7 +168,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
return pkgcataloging.Config{
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Dotnet: dotnet.DefaultCatalogerConfig().
|
||||
WithCertificateValidation(cfg.Dotnet.EnableCertificateValidation),
|
||||
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
||||
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
||||
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
|
||||
Golang: golang.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
|
||||
@ -6,7 +6,11 @@ import (
|
||||
)
|
||||
|
||||
type dotnetConfig struct {
|
||||
EnableCertificateValidation bool `json:"enable-certificate-validation" yaml:"enable-certificate-validation" mapstructure:"enable-certificate-validation"`
|
||||
DepPackagesMustHaveDLL bool `mapstructure:"dep-packages-must-have-dll" json:"dep-packages-must-have-dll" yaml:"dep-packages-must-have-dll"`
|
||||
|
||||
DepPackagesMustClaimDLL bool `mapstructure:"dep-packages-must-claim-dll" json:"dep-packages-must-claim-dll" yaml:"dep-packages-must-claim-dll"`
|
||||
|
||||
RelaxDLLClaimsWhenBundlingDetected bool `mapstructure:"relax-dll-claims-when-bundling-detected" json:"relax-dll-claims-when-bundling-detected" yaml:"relax-dll-claims-when-bundling-detected"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
@ -14,12 +18,16 @@ var _ interface {
|
||||
} = (*dotnetConfig)(nil)
|
||||
|
||||
func (o *dotnetConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.EnableCertificateValidation, `enable certificate validation -- this requires an active internet connection to download certificates and CRLs`)
|
||||
descriptions.Add(&o.DepPackagesMustHaveDLL, `only keep dep.json packages which an executable on disk can be found for`)
|
||||
descriptions.Add(&o.DepPackagesMustClaimDLL, `only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk)`)
|
||||
descriptions.Add(&o.RelaxDLLClaimsWhenBundlingDetected, `show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)`)
|
||||
}
|
||||
|
||||
func defaultDotnetConfig() dotnetConfig {
|
||||
def := dotnet.DefaultCatalogerConfig()
|
||||
return dotnetConfig{
|
||||
EnableCertificateValidation: def.EnableCertificateValidation,
|
||||
DepPackagesMustHaveDLL: def.DepPackagesMustHaveDLL,
|
||||
DepPackagesMustClaimDLL: def.DepPackagesMustClaimDLL,
|
||||
RelaxDLLClaimsWhenBundlingDetected: def.RelaxDLLClaimsWhenBundlingDetected,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,11 +78,27 @@ var imageOnlyTestCases = []testCase{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find dot net executable",
|
||||
name: "find .NET packages (deps.json + .dlls)",
|
||||
pkgType: pkg.DotnetPkg,
|
||||
pkgLanguage: pkg.Dotnet,
|
||||
pkgInfo: map[string]string{
|
||||
// executable
|
||||
"DocuSign.eSign": "6.8.0.0",
|
||||
// deps.json
|
||||
"AWSSDK.Core": "3.7.10.6",
|
||||
"Microsoft.Extensions.DependencyInjection": "6.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
|
||||
"Microsoft.Extensions.Logging": "6.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "6.0.0",
|
||||
"Microsoft.Extensions.Options": "6.0.0",
|
||||
"Microsoft.Extensions.Primitives": "6.0.0",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"Serilog": "2.10.0",
|
||||
"Serilog.Sinks.Console": "4.0.1",
|
||||
//"System.Diagnostics.DiagnosticSource": "6.0.0", // no dll claims in deps.json targets section
|
||||
//"System.Runtime.CompilerServices.Unsafe": "6.0.0", // no dll claims in deps.json targets section
|
||||
"TestCommon": "1.0.0",
|
||||
"TestLibrary": "1.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -241,10 +257,11 @@ var dirOnlyTestCases = []testCase{
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find dotnet packages",
|
||||
name: "find dotnet packages (.deps.json)",
|
||||
pkgType: pkg.DotnetPkg,
|
||||
pkgLanguage: pkg.Dotnet,
|
||||
pkgInfo: map[string]string{
|
||||
// all from deps.json
|
||||
"AWSSDK.Core": "3.7.10.6",
|
||||
"Microsoft.Extensions.DependencyInjection": "6.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
|
||||
@ -255,10 +272,10 @@ var dirOnlyTestCases = []testCase{
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"Serilog": "2.10.0",
|
||||
"Serilog.Sinks.Console": "4.0.1",
|
||||
"System.Diagnostics.DiagnosticSource": "6.0.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
|
||||
"TestCommon": "1.0.0",
|
||||
"TestLibrary": "1.0.0",
|
||||
//"System.Diagnostics.DiagnosticSource": "6.0.0", // no dll claims in deps.json targets section
|
||||
//"System.Runtime.CompilerServices.Unsafe": "6.0.0", // no dll claims in deps.json targets section
|
||||
"TestCommon": "1.0.0",
|
||||
"TestLibrary": "1.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -52,8 +52,16 @@ func TestAllPackageCatalogersReachableInTasks(t *testing.T) {
|
||||
|
||||
assert.Equal(t, len(taskTagsByName), constructorCount, "mismatch in number of cataloger constructors and task names")
|
||||
|
||||
exceptions := strset.New(
|
||||
// not reachable since they are deprecated
|
||||
"dotnet-portable-executable-cataloger",
|
||||
"dotnet-deps-cataloger",
|
||||
// not reachable by design
|
||||
"sbom-cataloger",
|
||||
)
|
||||
|
||||
for taskName, tags := range taskTagsByName {
|
||||
if taskName == "sbom-cataloger" {
|
||||
if exceptions.Has(taskName) {
|
||||
continue // this is a special case
|
||||
}
|
||||
if !strset.New(tags...).HasAny(pkgcataloging.ImageTag, pkgcataloging.DirectoryTag) {
|
||||
|
||||
3
go.mod
3
go.mod
@ -65,7 +65,6 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c
|
||||
github.com/saferwall/pe v1.5.6
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/sanity-io/litter v1.5.8
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
@ -141,7 +140,6 @@ require (
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
@ -209,7 +207,6 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
|
||||
7
go.sum
7
go.sum
@ -279,8 +279,6 @@ github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj6
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY=
|
||||
@ -725,8 +723,6 @@ github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8
|
||||
github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc=
|
||||
github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
@ -743,8 +739,6 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d h1:RQqyEogx5J6wPdoxqL132b100j8KjcVHO1c0KLRoIhc=
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d/go.mod h1:PegD7EVqlN88z7TpCqH92hHP+GBpfomGCCnw1PFtNOA=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
@ -1109,7 +1103,6 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.23"
|
||||
JSONSchemaVersion = "16.0.24"
|
||||
)
|
||||
|
||||
@ -76,7 +76,6 @@ func DefaultPackageTaskFactories() Factories {
|
||||
// language-specific package declared catalogers ///////////////////////////////////////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"),
|
||||
newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
|
||||
newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"),
|
||||
newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"),
|
||||
@ -117,11 +116,13 @@ func DefaultPackageTaskFactories() Factories {
|
||||
newSimplePackageTaskFactory(ocaml.NewOpamPackageManagerCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "ocaml", "opam"),
|
||||
|
||||
// language-specific package for both image and directory scans (but not necessarily declared) ////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return dotnet.NewDotnetPortableExecutableCataloger(cfg.PackagesConfig.Dotnet)
|
||||
}, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"),
|
||||
return dotnet.NewDotnetDepsBinaryCataloger(cfg.PackagesConfig.Dotnet)
|
||||
},
|
||||
pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#",
|
||||
),
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
@ -160,5 +161,10 @@ func DefaultPackageTaskFactories() Factories {
|
||||
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"),
|
||||
|
||||
// deprecated catalogers ////////////////////////////////////////
|
||||
// these are catalogers that should not be selectable other than specific inclusion via name or "deprecated" tag (to remain backwards compatible)
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0
|
||||
}
|
||||
}
|
||||
|
||||
2897
schema/json/schema-16.0.24.json
Normal file
2897
schema/json/schema-16.0.24.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.23/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.24/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -519,6 +519,14 @@
|
||||
},
|
||||
"hashPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"executables": {
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"$ref": "#/$defs/DotnetPortableExecutableEntry"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
|
||||
@ -21,4 +21,7 @@ const (
|
||||
|
||||
// LanguageTag should be used to identify catalogers that cataloging language-specific packages.
|
||||
LanguageTag = "language"
|
||||
|
||||
// DeprecatedTag should be used to identify catalogers that are deprecated.
|
||||
DeprecatedTag = "deprecated"
|
||||
)
|
||||
|
||||
@ -126,13 +126,13 @@ func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGeneratio
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPackagesConfig allows for defining any specific behavior for syft-implemented catalogers.
|
||||
// WithPackagesConfig allows for defining any specific package cataloging behavior for syft-implemented catalogers.
|
||||
func (c *CreateSBOMConfig) WithPackagesConfig(cfg pkgcataloging.Config) *CreateSBOMConfig {
|
||||
c.Packages = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPackagesConfig allows for defining any specific behavior for syft-implemented catalogers.
|
||||
// WithLicenseConfig allows for defining any specific license cataloging behavior for syft-implemented catalogers.
|
||||
func (c *CreateSBOMConfig) WithLicenseConfig(cfg cataloging.LicenseConfig) *CreateSBOMConfig {
|
||||
c.Licenses = cfg
|
||||
return c
|
||||
@ -278,6 +278,10 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if deprecatedNames := deprecatedTasks(finalTaskGroups); len(deprecatedNames) > 0 {
|
||||
log.WithFields("catalogers", strings.Join(deprecatedNames, ", ")).Warn("deprecated catalogers are being used (please remove them from your configuration)")
|
||||
}
|
||||
|
||||
finalPkgTasks := finalTaskGroups[0]
|
||||
finalFileTasks := finalTaskGroups[1]
|
||||
|
||||
@ -309,6 +313,18 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t
|
||||
return finalPkgTasks, finalFileTasks, &selection, nil
|
||||
}
|
||||
|
||||
func deprecatedTasks(taskGroups [][]task.Task) []string {
|
||||
// we want to identify any deprecated catalogers that are being used but default selections will always additionally select `file`
|
||||
// catalogers. For this reason, we must explicitly remove `file` catalogers in the selection request. This means if we
|
||||
// deprecate a file cataloger we will need special processing.
|
||||
_, selection, err := task.SelectInGroups(taskGroups, cataloging.SelectionRequest{DefaultNamesOrTags: []string{pkgcataloging.DeprecatedTag}, RemoveNamesOrTags: []string{filecataloging.FileTag}})
|
||||
if err != nil {
|
||||
// ignore the error, as it is not critical
|
||||
return nil
|
||||
}
|
||||
return selection.Result.List()
|
||||
}
|
||||
|
||||
func logTaskNames(tasks []task.Task, kind string) {
|
||||
// log as tree output (like tree command)
|
||||
log.Debugf("selected %d %s tasks", len(tasks), kind)
|
||||
|
||||
@ -22,6 +22,12 @@ var knownNonMetadataTypeNames = strset.New(
|
||||
"LicenseSet",
|
||||
)
|
||||
|
||||
// these are names that would be removed due to common convention (e.g. used within another metadata type) but are
|
||||
// known to be metadata types themselves. Adding to this list will prevent the removal of the type from the schema.
|
||||
var knownMetadaTypeNames = strset.New(
|
||||
"DotnetPortableExecutableEntry",
|
||||
)
|
||||
|
||||
func DiscoverTypeNames() ([]string, error) {
|
||||
root, err := RepoRoot()
|
||||
if err != nil {
|
||||
@ -66,7 +72,8 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) {
|
||||
}
|
||||
|
||||
// any definition that is used within another struct should not be considered a top-level metadata definition
|
||||
names.Remove(usedNames.List()...)
|
||||
removeNames := strset.Difference(usedNames, knownMetadaTypeNames)
|
||||
names.Remove(removeNames.List()...)
|
||||
|
||||
// remove known exceptions, that is, types exported in the pkg Package that are not used
|
||||
// in a metadata type but are not metadata types themselves.
|
||||
|
||||
@ -23,6 +23,7 @@ func main() {
|
||||
panic(fmt.Errorf("unable to get all metadata type names: %w", err))
|
||||
}
|
||||
|
||||
// useful for debugging...
|
||||
// for _, typeName := range typeNames {
|
||||
// fmt.Printf(" - %s\n", typeName)
|
||||
//}
|
||||
|
||||
39
syft/pkg/cataloger/dotnet/binary_cataloger.go
Normal file
39
syft/pkg/cataloger/dotnet/binary_cataloger.go
Normal file
@ -0,0 +1,39 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// binary cataloger will search for .dll and .exe files and create packages based off of the version resources embedded
|
||||
// as a resource directory within the executable. If there is no evidence of a .NET runtime (a CLR header) then no
|
||||
// package will be created.
|
||||
// Deprecated: use depsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0).
|
||||
type binaryCataloger struct {
|
||||
}
|
||||
|
||||
func (c binaryCataloger) Name() string {
|
||||
return "dotnet-portable-executable-cataloger"
|
||||
}
|
||||
|
||||
func (c binaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var unknowns error
|
||||
peFiles, ldpeUnknownErr, err := findPEFiles(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ldpeUnknownErr != nil {
|
||||
unknowns = unknown.Join(unknowns, ldpeUnknownErr)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, pe := range peFiles {
|
||||
pkgs = append(pkgs, newDotnetBinaryPackage(pe.VersionResources, pe.Location))
|
||||
}
|
||||
|
||||
return pkgs, nil, unknowns
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
/*
|
||||
Package dotnet provides a concrete Cataloger implementation relating to packages within the C#/.NET language/runtime ecosystem.
|
||||
*/
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
@ -8,19 +5,27 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewDotnetDepsCataloger returns a new Dotnet cataloger object base on deps json files.
|
||||
// NewDotnetDepsBinaryCataloger returns a cataloger based on PE and deps.json file contents.
|
||||
func NewDotnetDepsBinaryCataloger(config CatalogerConfig) pkg.Cataloger {
|
||||
return &depsBinaryCataloger{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDotnetDepsCataloger returns a cataloger based on deps.json file contents.
|
||||
// Deprecated: use NewDotnetDepsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0).
|
||||
func NewDotnetDepsCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dotnet-deps-cataloger").
|
||||
WithParserByGlobs(parseDotnetDeps, "**/*.deps.json")
|
||||
return &depsCataloger{}
|
||||
}
|
||||
|
||||
// NewDotnetPortableExecutableCataloger returns a new Dotnet cataloger object base on portable executable files.
|
||||
func NewDotnetPortableExecutableCataloger(cfg CatalogerConfig) pkg.Cataloger {
|
||||
p := dotnetPortableExecutableParser{cfg: cfg}
|
||||
return generic.NewCataloger("dotnet-portable-executable-cataloger").
|
||||
WithParserByGlobs(p.parseDotnetPortableExecutable, "**/*.dll", "**/*.exe")
|
||||
// NewDotnetPortableExecutableCataloger returns a cataloger based on PE file contents.
|
||||
// Deprecated: use NewDotnetDepsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0).
|
||||
func NewDotnetPortableExecutableCataloger() pkg.Cataloger {
|
||||
return &binaryCataloger{}
|
||||
}
|
||||
|
||||
// NewDotnetPackagesLockCataloger returns a cataloger based on packages.lock.json files.
|
||||
func NewDotnetPackagesLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dotnet-packages-lock-cataloger").WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json")
|
||||
return generic.NewCataloger("dotnet-packages-lock-cataloger").
|
||||
WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,37 @@
|
||||
package dotnet
|
||||
|
||||
type CatalogerConfig struct {
|
||||
EnableCertificateValidation bool `json:"enable-certificate-validation" yaml:"enable-certificate-validation" mapstructure:"enable-certificate-validation"`
|
||||
// DepPackagesMustHaveDLL allows for deps.json packages to be included only if there is a DLL on disk for that package.
|
||||
DepPackagesMustHaveDLL bool `mapstructure:"dep-packages-must-have-dll" json:"dep-packages-must-have-dll" yaml:"dep-packages-must-have-dll"`
|
||||
|
||||
// DepPackagesMustClaimDLL allows for deps.json packages to be included only if there is a runtime/resource DLL claimed in the deps.json targets section.
|
||||
// This does not require such claimed DLLs to exist on disk. The behavior of this
|
||||
DepPackagesMustClaimDLL bool `mapstructure:"dep-packages-must-claim-dll" json:"dep-packages-must-claim-dll" yaml:"dep-packages-must-claim-dll"`
|
||||
|
||||
// RelaxDLLClaimsWhenBundlingDetected will look for indications of IL bundle tooling via deps.json package names
|
||||
// and, if found (and this config option is enabled), will relax the DepPackagesMustClaimDLL value to `false` only in those cases.
|
||||
RelaxDLLClaimsWhenBundlingDetected bool `mapstructure:"relax-dll-claims-when-bundling-detected" json:"relax-dll-claims-when-bundling-detected" yaml:"relax-dll-claims-when-bundling-detected"`
|
||||
}
|
||||
|
||||
func (c CatalogerConfig) WithCertificateValidation(enable bool) CatalogerConfig {
|
||||
c.EnableCertificateValidation = enable
|
||||
func (c CatalogerConfig) WithDepPackagesMustHaveDLL(requireDlls bool) CatalogerConfig {
|
||||
c.DepPackagesMustHaveDLL = requireDlls
|
||||
return c
|
||||
}
|
||||
|
||||
func (c CatalogerConfig) WithDepPackagesMustClaimDLL(requireDlls bool) CatalogerConfig {
|
||||
c.DepPackagesMustClaimDLL = requireDlls
|
||||
return c
|
||||
}
|
||||
|
||||
func (c CatalogerConfig) WithRelaxDLLClaimsWhenBundlingDetected(relax bool) CatalogerConfig {
|
||||
c.RelaxDLLClaimsWhenBundlingDetected = relax
|
||||
return c
|
||||
}
|
||||
|
||||
func DefaultCatalogerConfig() CatalogerConfig {
|
||||
return CatalogerConfig{}
|
||||
return CatalogerConfig{
|
||||
DepPackagesMustHaveDLL: false,
|
||||
DepPackagesMustClaimDLL: true,
|
||||
RelaxDLLClaimsWhenBundlingDetected: true,
|
||||
}
|
||||
}
|
||||
|
||||
396
syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
Normal file
396
syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
Normal file
@ -0,0 +1,396 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
depsJSONGlob = "**/*.deps.json"
|
||||
dllGlob = "**/*.dll"
|
||||
exeGlob = "**/*.exe"
|
||||
)
|
||||
|
||||
// depsBinaryCataloger will search for both deps.json evidence and PE file evidence to create packages. All packages
|
||||
// from both sources are raised up, but with one merge operation applied; If a deps.json package reference can be
|
||||
// correlated with a PE file, the PE file is attached to the package as supporting evidence.
|
||||
type depsBinaryCataloger struct {
|
||||
config CatalogerConfig
|
||||
}
|
||||
|
||||
func (c depsBinaryCataloger) Name() string {
|
||||
return "dotnet-deps-binary-cataloger"
|
||||
}
|
||||
|
||||
func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
depJSONDocs, unknowns, err := findDepsJSON(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
peFiles, ldpeUnknownErr, err := findPEFiles(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ldpeUnknownErr != nil {
|
||||
unknowns = unknown.Join(unknowns, ldpeUnknownErr)
|
||||
}
|
||||
|
||||
// partition the logical PE files by location and pair them with the logicalDepsJSON
|
||||
pairedDepsJSONs, remainingPeFiles, remainingDepsJSONs := partitionPEs(depJSONDocs, peFiles)
|
||||
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
depDocGroups := [][]logicalDepsJSON{pairedDepsJSONs}
|
||||
|
||||
if !c.config.DepPackagesMustHaveDLL {
|
||||
depDocGroups = append(depDocGroups, remainingDepsJSONs)
|
||||
}
|
||||
|
||||
for _, docs := range depDocGroups {
|
||||
for _, doc := range docs {
|
||||
ps, rs := packagesFromLogicalDepsJSON(doc, c.config)
|
||||
pkgs = append(pkgs, ps...)
|
||||
relationships = append(relationships, rs...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pe := range remainingPeFiles {
|
||||
pkgs = append(pkgs, newDotnetBinaryPackage(pe.VersionResources, pe.Location))
|
||||
}
|
||||
|
||||
return pkgs, relationships, unknowns
|
||||
}
|
||||
|
||||
// partitionPEs pairs PE files with the deps.json based on directory containment.
|
||||
func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) {
|
||||
// sort deps.json paths from longest to shortest. This is so we are processing the most specific match first.
|
||||
sort.Slice(depJsons, func(i, j int) bool {
|
||||
return len(depJsons[i].Location.RealPath) > len(depJsons[j].Location.RealPath)
|
||||
})
|
||||
|
||||
peFilesByPath := make(map[file.Coordinates][]logicalPE)
|
||||
var remainingPeFiles []logicalPE
|
||||
for _, pe := range peFiles {
|
||||
var found bool
|
||||
for i := range depJsons {
|
||||
dep := &depJsons[i]
|
||||
if isParentOf(dep.Location.RealPath, pe.Location.RealPath) && attachAssociatedExecutables(dep, pe) {
|
||||
peFilesByPath[dep.Location.Coordinates] = append(peFilesByPath[dep.Location.Coordinates], pe)
|
||||
found = true
|
||||
// note: we cannot break from the dep JSON search since the same binary could be associated with multiple packages
|
||||
// across multiple deps.json files.
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
remainingPeFiles = append(remainingPeFiles, pe)
|
||||
}
|
||||
}
|
||||
|
||||
var pairedDepsJSON []logicalDepsJSON
|
||||
var remainingDepsJSON []logicalDepsJSON
|
||||
|
||||
for _, dep := range depJsons {
|
||||
if _, ok := peFilesByPath[dep.Location.Coordinates]; !ok {
|
||||
remainingDepsJSON = append(remainingDepsJSON, dep)
|
||||
} else {
|
||||
pairedDepsJSON = append(pairedDepsJSON, dep)
|
||||
}
|
||||
}
|
||||
|
||||
return pairedDepsJSON, remainingPeFiles, remainingDepsJSON
|
||||
}
|
||||
|
||||
// attachAssociatedExecutables looks for PE files matching runtime or resource entries
|
||||
// and attaches them to the appropriate package.
|
||||
func attachAssociatedExecutables(dep *logicalDepsJSON, pe logicalPE) bool {
|
||||
appDir := path.Dir(dep.Location.RealPath)
|
||||
relativeDllPath := strings.TrimPrefix(strings.TrimPrefix(pe.Location.RealPath, appDir), "/")
|
||||
|
||||
var found bool
|
||||
for key, p := range dep.PackagesByNameVersion {
|
||||
if targetPath, ok := p.RuntimePathsByRelativeDLLPath[relativeDllPath]; ok {
|
||||
pe.TargetPath = targetPath
|
||||
p.Executables = append(p.Executables, pe)
|
||||
dep.PackagesByNameVersion[key] = p // update the map with the modified package
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
|
||||
if targetPath, ok := p.ResourcePathsByRelativeDLLPath[relativeDllPath]; ok {
|
||||
pe.TargetPath = targetPath
|
||||
p.Executables = append(p.Executables, pe)
|
||||
dep.PackagesByNameVersion[key] = p // update the map with the modified package
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
var libPrefixPattern = regexp.MustCompile(`^lib/net[^/]+/`)
|
||||
|
||||
// trimLibPrefix removes prefixes like "lib/net6.0/" from a path.
|
||||
func trimLibPrefix(s string) string {
|
||||
if match := libPrefixPattern.FindString(s); match != "" {
|
||||
parts := strings.Split(s, "/")
|
||||
if len(parts) > 2 {
|
||||
return strings.Join(parts[2:], "/")
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// isParentOf checks if parentFile's directory is a prefix of childFile's directory.
|
||||
func isParentOf(parentFile, childFile string) bool {
|
||||
parentDir := path.Dir(parentFile)
|
||||
childDir := path.Dir(childFile)
|
||||
return strings.HasPrefix(childDir, parentDir)
|
||||
}
|
||||
|
||||
// packagesFromDepsJSON creates packages from a list of logicalDepsJSON documents.
|
||||
func packagesFromDepsJSON(docs []logicalDepsJSON, config CatalogerConfig) ([]pkg.Package, []artifact.Relationship) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
for _, ldj := range docs {
|
||||
ps, rs := packagesFromLogicalDepsJSON(ldj, config)
|
||||
pkgs = append(pkgs, ps...)
|
||||
relationships = append(relationships, rs...)
|
||||
}
|
||||
return pkgs, relationships
|
||||
}
|
||||
|
||||
// packagesFromLogicalDepsJSON converts a logicalDepsJSON (using the new map type) into catalog packages.
|
||||
func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) ([]pkg.Package, []artifact.Relationship) {
|
||||
var rootPkg *pkg.Package
|
||||
if rootLpkg, hasRoot := doc.RootPackage(); !hasRoot {
|
||||
rootPkg = newDotnetDepsPackage(rootLpkg, doc.Location)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
pkgMap := make(map[string]pkg.Package)
|
||||
if rootPkg != nil {
|
||||
pkgs = append(pkgs, *rootPkg)
|
||||
pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg
|
||||
}
|
||||
|
||||
nameVersions := doc.PackageNameVersions.List()
|
||||
sort.Strings(nameVersions)
|
||||
|
||||
// process each non-root package
|
||||
skippedDepPkgs := make(map[string]logicalDepsJSONPackage)
|
||||
for _, nameVersion := range nameVersions {
|
||||
name, version := extractNameAndVersion(nameVersion)
|
||||
if rootPkg != nil && name == rootPkg.Name && version == rootPkg.Version {
|
||||
continue
|
||||
}
|
||||
lp := doc.PackagesByNameVersion[nameVersion]
|
||||
if config.DepPackagesMustHaveDLL && len(lp.Executables) == 0 {
|
||||
// could not find a paired DLL and the user required this...
|
||||
skippedDepPkgs[nameVersion] = lp
|
||||
continue
|
||||
}
|
||||
|
||||
claimsDLLs := len(lp.RuntimePathsByRelativeDLLPath) > 0 || len(lp.ResourcePathsByRelativeDLLPath) > 0
|
||||
|
||||
if config.DepPackagesMustClaimDLL && !claimsDLLs {
|
||||
if config.RelaxDLLClaimsWhenBundlingDetected && !doc.BundlingDetected || !config.RelaxDLLClaimsWhenBundlingDetected {
|
||||
// could not find a runtime or resource path and the user required this...
|
||||
// and there is no evidence of a bundler in the dependencies (e.g. ILRepack)
|
||||
skippedDepPkgs[nameVersion] = lp
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dotnetPkg := newDotnetDepsPackage(lp, doc.Location)
|
||||
if dotnetPkg != nil {
|
||||
pkgs = append(pkgs, *dotnetPkg)
|
||||
pkgMap[nameVersion] = *dotnetPkg
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs)
|
||||
}
|
||||
|
||||
// relationshipsFromLogicalDepsJSON creates relationships from a logicalDepsJSON document for only the given syft packages.
|
||||
// It is possible that the document describes more packages than that is provided as syft packages, in which cases
|
||||
// those relationships will not be created. If there are any skipped packages, we still want to logically represent
|
||||
// dependency relationships, jumping over the skipped packages.
|
||||
func relationshipsFromLogicalDepsJSON(doc logicalDepsJSON, pkgMap map[string]pkg.Package, skipped map[string]logicalDepsJSONPackage) []artifact.Relationship {
|
||||
var relationships []artifact.Relationship
|
||||
for _, lp := range doc.PackagesByNameVersion {
|
||||
if lp.Targets == nil {
|
||||
continue
|
||||
}
|
||||
for depName, depVersion := range lp.Targets.Dependencies {
|
||||
depNameVersion := createNameAndVersion(depName, depVersion)
|
||||
thisPkg, ok := pkgMap[lp.NameVersion]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var depPkgs []pkg.Package
|
||||
depPkg, ok := pkgMap[depNameVersion]
|
||||
if !ok {
|
||||
skippedDepPkg, ok := skipped[depNameVersion]
|
||||
if !ok {
|
||||
// this package wasn't explicitly skipped, so it could be a malformed deps.json file
|
||||
// ignore this case and do not create a relationships
|
||||
continue
|
||||
}
|
||||
// we have a skipped package, so we need to create a relationship but looking a the nearest
|
||||
// package with an associated PE file for even dependency listed on the skipped package.
|
||||
// Take note that the skipped depedency's dependency could also be skipped, so we need to
|
||||
// do this recursively.
|
||||
depPkgs = findNearestDependencyPackages(skippedDepPkg, pkgMap, skipped, strset.New())
|
||||
} else {
|
||||
depPkgs = append(depPkgs, depPkg)
|
||||
}
|
||||
|
||||
for _, d := range depPkgs {
|
||||
rel := artifact.Relationship{
|
||||
From: d,
|
||||
To: thisPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
relationships = append(relationships, rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relationship.Sort(relationships)
|
||||
return relationships
|
||||
}
|
||||
|
||||
func findNearestDependencyPackages(skippedDep logicalDepsJSONPackage, pkgMap map[string]pkg.Package, skipped map[string]logicalDepsJSONPackage, processed *strset.Set) []pkg.Package {
|
||||
var nearestPkgs []pkg.Package
|
||||
|
||||
// if we have already processed this package, skip it to avoid infinite recursion
|
||||
if processed.Has(skippedDep.NameVersion) {
|
||||
return nearestPkgs
|
||||
}
|
||||
|
||||
processed.Add(skippedDep.NameVersion)
|
||||
|
||||
for depName, depVersion := range skippedDep.Targets.Dependencies {
|
||||
depNameVersion := createNameAndVersion(depName, depVersion)
|
||||
depPkg, ok := pkgMap[depNameVersion]
|
||||
if !ok {
|
||||
skippedDepPkg, ok := skipped[depNameVersion]
|
||||
if !ok {
|
||||
// this package wasn't explicitly skipped, so it could be a malformed deps.json file
|
||||
// ignore this case and do not create a relationships
|
||||
continue
|
||||
}
|
||||
|
||||
nearestPkgs = append(nearestPkgs, findNearestDependencyPackages(skippedDepPkg, pkgMap, skipped, processed)...)
|
||||
} else {
|
||||
nearestPkgs = append(nearestPkgs, depPkg)
|
||||
}
|
||||
}
|
||||
return nearestPkgs
|
||||
}
|
||||
|
||||
// findDepsJSON locates and parses all deps.json files.
|
||||
func findDepsJSON(resolver file.Resolver) ([]logicalDepsJSON, error, error) {
|
||||
locs, err := resolver.FilesByGlob(depsJSONGlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to find deps.json files: %w", err)
|
||||
}
|
||||
|
||||
var depsJSONs []logicalDepsJSON
|
||||
var unknownErr error
|
||||
for _, loc := range locs {
|
||||
dj, err := readDepsJSON(resolver, loc)
|
||||
if err != nil {
|
||||
unknownErr = unknown.Append(unknownErr, loc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj))
|
||||
}
|
||||
|
||||
return depsJSONs, unknownErr, nil
|
||||
}
|
||||
|
||||
// readDepsJSON reads and parses a single deps.json file.
|
||||
func readDepsJSON(resolver file.Resolver, loc file.Location) (*depsJSON, error) {
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("unable to read deps.json file: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
dj, err := newDepsJSON(file.NewLocationReadCloser(loc, reader))
|
||||
if err != nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("unable to parse deps.json file: %w", err))
|
||||
}
|
||||
|
||||
if dj == nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("expected to find packages in deps.json but did not: %q", loc.RealPath))
|
||||
}
|
||||
|
||||
return dj, nil
|
||||
}
|
||||
|
||||
// findPEFiles locates and parses all PE files (dll/exe).
|
||||
func findPEFiles(resolver file.Resolver) ([]logicalPE, error, error) {
|
||||
peLocs, err := resolver.FilesByGlob(dllGlob, exeGlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to find PE files: %w", err)
|
||||
}
|
||||
|
||||
var peFiles []logicalPE
|
||||
var unknownErr error
|
||||
for _, loc := range peLocs {
|
||||
ldpe, err := readPEFile(resolver, loc)
|
||||
if err != nil {
|
||||
unknownErr = unknown.Append(unknownErr, loc, err)
|
||||
continue
|
||||
}
|
||||
if ldpe == nil {
|
||||
continue
|
||||
}
|
||||
peFiles = append(peFiles, *ldpe)
|
||||
}
|
||||
|
||||
return peFiles, unknownErr, nil
|
||||
}
|
||||
|
||||
// readPEFile reads and parses a single PE file.
|
||||
func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) {
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("unable to read PE file: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
ldpe, err := getLogicalDotnetPE(file.NewLocationReadCloser(loc, reader))
|
||||
if err != nil {
|
||||
return nil, unknown.New(loc, fmt.Errorf("unable to parse PE file: %w", err))
|
||||
}
|
||||
|
||||
if ldpe == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !ldpe.CLR.hasEvidenceOfCLR() {
|
||||
// this is not a .NET binary
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return ldpe, nil
|
||||
}
|
||||
31
syft/pkg/cataloger/dotnet/deps_cataloger.go
Normal file
31
syft/pkg/cataloger/dotnet/deps_cataloger.go
Normal file
@ -0,0 +1,31 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// depsCataloger will search for deps.json file contents.
|
||||
// Deprecated: use depsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0).
|
||||
type depsCataloger struct {
|
||||
}
|
||||
|
||||
func (c depsCataloger) Name() string {
|
||||
return "dotnet-deps-cataloger"
|
||||
}
|
||||
|
||||
func (c depsCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
depJSONDocs, unknowns, err := findDepsJSON(resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgs, rels := packagesFromDepsJSON(depJSONDocs, CatalogerConfig{
|
||||
DepPackagesMustHaveDLL: false,
|
||||
DepPackagesMustClaimDLL: false,
|
||||
})
|
||||
return pkgs, rels, unknowns
|
||||
}
|
||||
168
syft/pkg/cataloger/dotnet/deps_json.go
Normal file
168
syft/pkg/cataloger/dotnet/deps_json.go
Normal file
@ -0,0 +1,168 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
type depsJSON struct {
|
||||
Location file.Location
|
||||
RuntimeTarget runtimeTarget `json:"runtimeTarget"`
|
||||
Targets map[string]map[string]depsTarget `json:"targets"`
|
||||
Libraries map[string]depsLibrary `json:"libraries"`
|
||||
}
|
||||
|
||||
type runtimeTarget struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type depsTarget struct {
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
Runtime map[string]map[string]string `json:"runtime"`
|
||||
Resources map[string]map[string]string `json:"resources"`
|
||||
}
|
||||
|
||||
type depsLibrary struct {
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path"`
|
||||
Sha512 string `json:"sha512"`
|
||||
HashPath string `json:"hashPath"`
|
||||
}
|
||||
|
||||
// logicalDepsJSONPackage merges target and library information for a given package from all dep.json entries.
|
||||
// Note: this is not a real construct of the deps.json, just a useful reorganization of the data for downstream processing.
|
||||
type logicalDepsJSONPackage struct {
|
||||
NameVersion string
|
||||
Targets *depsTarget
|
||||
Library *depsLibrary
|
||||
|
||||
// RuntimePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file
|
||||
// to the target path as described in the deps.json target entry under "runtime".
|
||||
RuntimePathsByRelativeDLLPath map[string]string
|
||||
|
||||
// ResourcePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file
|
||||
// to the target path as described in the deps.json target entry under "resource".
|
||||
ResourcePathsByRelativeDLLPath map[string]string
|
||||
|
||||
// Executables is a list of all the executables that are part of this package. This is populated by the PE cataloger
|
||||
// and not something that is found in the deps.json file. This allows us to associate the PE files with this package
|
||||
// based on the relative path to the DLL.
|
||||
Executables []logicalPE
|
||||
}
|
||||
|
||||
type logicalDepsJSON struct {
|
||||
Location file.Location
|
||||
RuntimeTarget runtimeTarget
|
||||
PackagesByNameVersion map[string]logicalDepsJSONPackage
|
||||
PackageNameVersions *strset.Set
|
||||
BundlingDetected bool
|
||||
}
|
||||
|
||||
func (l logicalDepsJSON) RootPackage() (logicalDepsJSONPackage, bool) {
|
||||
rootName := getDepsJSONFilePrefix(l.Location.RealPath)
|
||||
if rootName == "" {
|
||||
return logicalDepsJSONPackage{}, false
|
||||
}
|
||||
|
||||
// iterate over the map to find the root package. If we don't find the root package, that's ok! We still want to
|
||||
// get all of the packages that are defined in this deps.json file.
|
||||
for _, p := range l.PackagesByNameVersion {
|
||||
name, _ := extractNameAndVersion(p.NameVersion)
|
||||
// there can be multiple projects defined in a deps.json and only by convention is the root project the same name as the deps.json file
|
||||
// however there are other configurations that can lead to differences (e.g. "tool_fsc" vs "fsc.deps.json").
|
||||
if p.Library != nil && p.Library.Type == "project" && name == rootName {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return logicalDepsJSONPackage{}, false
|
||||
}
|
||||
|
||||
func newDepsJSON(reader file.LocationReadCloser) (*depsJSON, error) {
|
||||
var doc depsJSON
|
||||
dec := json.NewDecoder(reader)
|
||||
if err := dec.Decode(&doc); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse deps.json file: %w", err)
|
||||
}
|
||||
doc.Location = reader.Location
|
||||
return &doc, nil
|
||||
}
|
||||
|
||||
var knownBundlers = strset.New(
|
||||
"ILRepack.Lib.MSBuild.Task", // The most official use of ILRepack https://github.com/gluck/il-repack
|
||||
"ILRepack.Lib", // library interface for ILRepack
|
||||
"ILRepack.Lib.MSBuild", // uses Cecil 0.10
|
||||
"ILRepack.Lib.NET", // uses ModuleDefinitions instead of filenames
|
||||
"ILRepack.NETStandard", // .NET Standard compatible version
|
||||
"ILRepack.FullAuto", // https://github.com/kekyo/ILRepack.FullAuto
|
||||
"ILMerge", // deprecated, but still used in some projects https://github.com/dotnet/ILMerge
|
||||
"JetBrains.Build.ILRepack", // generally from https://www.nuget.org/packages?q=ilrepack&sortBy=relevance
|
||||
|
||||
// other bundling/modification tools found in results
|
||||
"PostSharp.Community.Packer", // Embeds dependencies as resources
|
||||
"Brokenevent.ILStrip", // assembly cleaner (removes unused parts)
|
||||
"Brokenevent.ILStrip.CLI", // command-line/MSBuild variant
|
||||
"Costura.Fody", // referenced in MSBuildRazorCompiler.Lib
|
||||
"Fody", // IL weaving framework
|
||||
)
|
||||
|
||||
func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON {
|
||||
packageMap := make(map[string]*logicalDepsJSONPackage)
|
||||
nameVersions := strset.New()
|
||||
|
||||
for _, targets := range deps.Targets {
|
||||
for libName, target := range targets {
|
||||
_, exists := packageMap[libName]
|
||||
if !exists {
|
||||
var lib *depsLibrary
|
||||
l, ok := deps.Libraries[libName]
|
||||
if ok {
|
||||
lib = &l
|
||||
}
|
||||
runtimePaths := make(map[string]string)
|
||||
for path := range target.Runtime {
|
||||
runtimePaths[trimLibPrefix(path)] = path
|
||||
}
|
||||
resourcePaths := make(map[string]string)
|
||||
for path := range target.Resources {
|
||||
trimmedPath := trimLibPrefix(path)
|
||||
if _, exists := resourcePaths[trimmedPath]; exists {
|
||||
continue
|
||||
}
|
||||
resourcePaths[trimmedPath] = path
|
||||
}
|
||||
|
||||
p := &logicalDepsJSONPackage{
|
||||
NameVersion: libName,
|
||||
Library: lib,
|
||||
Targets: &target,
|
||||
RuntimePathsByRelativeDLLPath: runtimePaths,
|
||||
ResourcePathsByRelativeDLLPath: resourcePaths,
|
||||
}
|
||||
packageMap[libName] = p
|
||||
nameVersions.Add(libName)
|
||||
}
|
||||
}
|
||||
}
|
||||
packages := make(map[string]logicalDepsJSONPackage)
|
||||
var bundlingDetected bool
|
||||
for _, p := range packageMap {
|
||||
name := strings.Split(p.NameVersion, "/")[0]
|
||||
if !bundlingDetected && knownBundlers.Has(name) {
|
||||
bundlingDetected = true
|
||||
}
|
||||
packages[p.NameVersion] = *p
|
||||
}
|
||||
|
||||
return logicalDepsJSON{
|
||||
Location: deps.Location,
|
||||
RuntimeTarget: deps.RuntimeTarget,
|
||||
PackagesByNameVersion: packages,
|
||||
PackageNameVersions: nameVersions,
|
||||
BundlingDetected: bundlingDetected,
|
||||
}
|
||||
}
|
||||
@ -5,26 +5,36 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/go-version"
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package {
|
||||
name, version := extractNameAndVersion(nameVersion)
|
||||
var (
|
||||
// spaceRegex includes nbsp (#160) considered to be a space character
|
||||
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
|
||||
numberRegex = regexp.MustCompile(`\d`)
|
||||
versionPunctuationRegex = regexp.MustCompile(`[.,]+`)
|
||||
)
|
||||
|
||||
m := pkg.DotnetDepsEntry{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Path: lib.Path,
|
||||
Sha512: lib.Sha512,
|
||||
HashPath: lib.HashPath,
|
||||
// newDotnetDepsPackage creates a new Dotnet dependency package from a logicalDepsJSONPackage.
|
||||
// Note that the new logicalDepsJSONPackage now directly holds library and executable information.
|
||||
func newDotnetDepsPackage(lp logicalDepsJSONPackage, depsLocation file.Location) *pkg.Package {
|
||||
name, ver := extractNameAndVersion(lp.NameVersion)
|
||||
locs := file.NewLocationSet(depsLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
|
||||
for _, pe := range lp.Executables {
|
||||
locs.Add(pe.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
m := newDotnetDepsEntry(lp)
|
||||
|
||||
p := &pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(locations...),
|
||||
Version: ver,
|
||||
Locations: locs,
|
||||
PURL: packageURL(m),
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
@ -36,6 +46,66 @@ func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations .
|
||||
return p
|
||||
}
|
||||
|
||||
// newDotnetDepsEntry creates a Dotnet dependency entry using the new logicalDepsJSONPackage.
|
||||
func newDotnetDepsEntry(lp logicalDepsJSONPackage) pkg.DotnetDepsEntry {
|
||||
name, ver := extractNameAndVersion(lp.NameVersion)
|
||||
|
||||
// since this is a metadata type, we should not allocate this collection unless there are entries; otherwise
|
||||
// the JSON serialization will produce an empty object instead of omitting the field.
|
||||
var pes map[string]pkg.DotnetPortableExecutableEntry
|
||||
if len(lp.Executables) > 0 {
|
||||
pes = make(map[string]pkg.DotnetPortableExecutableEntry)
|
||||
for _, pe := range lp.Executables {
|
||||
pes[pe.TargetPath] = newDotnetPortableExecutableEntry(pe)
|
||||
}
|
||||
}
|
||||
|
||||
var path, sha, hashPath string
|
||||
lib := lp.Library
|
||||
if lib != nil {
|
||||
path = lib.Path
|
||||
sha = lib.Sha512
|
||||
hashPath = lib.HashPath
|
||||
}
|
||||
|
||||
return pkg.DotnetDepsEntry{
|
||||
Name: name,
|
||||
Version: ver,
|
||||
Path: path,
|
||||
Sha512: sha,
|
||||
HashPath: hashPath,
|
||||
Executables: pes,
|
||||
}
|
||||
}
|
||||
|
||||
// newDotnetPortableExecutableEntry creates a portable executable entry from a logicalPE.
|
||||
func newDotnetPortableExecutableEntry(pe logicalPE) pkg.DotnetPortableExecutableEntry {
|
||||
return newDotnetPortableExecutableEntryFromMap(pe.VersionResources)
|
||||
}
|
||||
|
||||
func newDotnetPortableExecutableEntryFromMap(vr map[string]string) pkg.DotnetPortableExecutableEntry {
|
||||
return pkg.DotnetPortableExecutableEntry{
|
||||
// for some reason, the assembly version is sometimes stored as "Assembly Version" and sometimes as "AssemblyVersion"
|
||||
AssemblyVersion: cleanVersionResourceField(vr["Assembly Version"], vr["AssemblyVersion"]),
|
||||
LegalCopyright: cleanVersionResourceField(vr["LegalCopyright"]),
|
||||
Comments: cleanVersionResourceField(vr["Comments"]),
|
||||
InternalName: cleanVersionResourceField(vr["InternalName"]),
|
||||
CompanyName: cleanVersionResourceField(vr["CompanyName"]),
|
||||
ProductName: cleanVersionResourceField(vr["ProductName"]),
|
||||
ProductVersion: cleanVersionResourceField(vr["ProductVersion"]),
|
||||
}
|
||||
}
|
||||
|
||||
func cleanVersionResourceField(values ...string) string {
|
||||
for _, value := range values {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getDepsJSONFilePrefix(p string) string {
|
||||
r := regexp.MustCompile(`([^\\\/]+)\.deps\.json$`)
|
||||
match := r.FindStringSubmatch(p)
|
||||
@ -48,7 +118,9 @@ func getDepsJSONFilePrefix(p string) string {
|
||||
func extractNameAndVersion(nameVersion string) (name, version string) {
|
||||
fields := strings.Split(nameVersion, "/")
|
||||
name = fields[0]
|
||||
version = fields[1]
|
||||
if len(fields) > 1 {
|
||||
version = fields[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -61,16 +133,8 @@ func packageURL(m pkg.DotnetDepsEntry) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
// This originally was packageurl.TypeDotnet, but this isn't a valid PURL type, according to:
|
||||
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
|
||||
// Some history:
|
||||
// https://github.com/anchore/packageurl-go/pull/8 added the type to Anchore's fork
|
||||
// due to this PR: https://github.com/anchore/syft/pull/951
|
||||
// There were questions about "dotnet" being the right purlType at the time, but it was
|
||||
// acknowledged that scanning a dotnet file does not necessarily mean the packages found
|
||||
// are nuget packages and so the alternate type was added. Since this is still an invalid
|
||||
// PURL type, however, we will use TypeNuget and revisit at such time there is a better
|
||||
// official PURL type available.
|
||||
// Although we use TypeNuget here due to historical reasons, note that it does not necessarily
|
||||
// mean the package is a NuGet package.
|
||||
packageurl.TypeNuget,
|
||||
"",
|
||||
m.Name,
|
||||
@ -79,3 +143,155 @@ func packageURL(m pkg.DotnetDepsEntry) string {
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func newDotnetBinaryPackage(versionResources map[string]string, f file.Location) pkg.Package {
|
||||
name := findNameFromVersionResources(versionResources)
|
||||
ver := findVersionFromVersionResources(versionResources)
|
||||
|
||||
metadata := newDotnetPortableExecutableEntryFromMap(versionResources)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: ver,
|
||||
Locations: file.NewLocationSet(f.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Type: pkg.DotnetPkg,
|
||||
Language: pkg.Dotnet,
|
||||
PURL: binaryPackageURL(name, ver),
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func binaryPackageURL(name, version string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeNuget,
|
||||
"",
|
||||
name,
|
||||
version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func findNameFromVersionResources(versionResources map[string]string) string {
|
||||
// PE files not authored by Microsoft tend to use ProductName as an identifier.
|
||||
nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"}
|
||||
|
||||
if isMicrosoftVersionResource(versionResources) {
|
||||
// For Microsoft files, prioritize FileDescription.
|
||||
nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"}
|
||||
}
|
||||
|
||||
for _, field := range nameFields {
|
||||
value := spaceNormalize(versionResources[field])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func isMicrosoftVersionResource(versionResources map[string]string) bool {
|
||||
return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") ||
|
||||
strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft")
|
||||
}
|
||||
|
||||
// spaceNormalize trims and normalizes whitespace in a string.
|
||||
func spaceNormalize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
// Ensure valid UTF-8.
|
||||
value = strings.ToValidUTF8(value, "")
|
||||
// Consolidate all whitespace.
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
// Remove non-printable characters.
|
||||
value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "")
|
||||
// Consolidate again and trim.
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
value = strings.TrimSpace(value)
|
||||
return value
|
||||
}
|
||||
|
||||
func findVersionFromVersionResources(versionResources map[string]string) string {
|
||||
productVersion := extractVersionFromResourcesValue(versionResources["ProductVersion"])
|
||||
fileVersion := extractVersionFromResourcesValue(versionResources["FileVersion"])
|
||||
|
||||
semanticVersionCompareResult := keepGreaterSemanticVersion(productVersion, fileVersion)
|
||||
if semanticVersionCompareResult != "" {
|
||||
return semanticVersionCompareResult
|
||||
}
|
||||
|
||||
productVersionDetail := punctuationCount(productVersion)
|
||||
fileVersionDetail := punctuationCount(fileVersion)
|
||||
|
||||
if containsNumber(productVersion) && productVersionDetail >= fileVersionDetail {
|
||||
return productVersion
|
||||
}
|
||||
if containsNumber(fileVersion) && fileVersionDetail > 0 {
|
||||
return fileVersion
|
||||
}
|
||||
if containsNumber(productVersion) {
|
||||
return productVersion
|
||||
}
|
||||
if containsNumber(fileVersion) {
|
||||
return fileVersion
|
||||
}
|
||||
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func extractVersionFromResourcesValue(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
out := ""
|
||||
for i, f := range strings.Fields(version) {
|
||||
if containsNumber(out) && !containsNumber(f) {
|
||||
return out
|
||||
}
|
||||
if i == 0 {
|
||||
out = f
|
||||
} else {
|
||||
out += " " + f
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func keepGreaterSemanticVersion(productVersion string, fileVersion string) string {
|
||||
semanticProductVersion, err := version.NewVersion(productVersion)
|
||||
if err != nil || semanticProductVersion == nil {
|
||||
log.Tracef("Unable to create semantic version from product version %s", productVersion)
|
||||
return ""
|
||||
}
|
||||
|
||||
semanticFileVersion, err := version.NewVersion(fileVersion)
|
||||
if err != nil || semanticFileVersion == nil {
|
||||
log.Tracef("Unable to create semantic version from file version %s", fileVersion)
|
||||
return productVersion
|
||||
}
|
||||
|
||||
if semanticProductVersion.Equal(semanticFileVersion) {
|
||||
return ""
|
||||
}
|
||||
if semanticFileVersion.GreaterThan(semanticProductVersion) {
|
||||
return fileVersion
|
||||
}
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func containsNumber(s string) bool {
|
||||
return numberRegex.MatchString(s)
|
||||
}
|
||||
|
||||
func punctuationCount(s string) int {
|
||||
return len(versionPunctuationRegex.FindAllString(s, -1))
|
||||
}
|
||||
|
||||
@ -4,6 +4,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_getDepsJSONFilePrefix(t *testing.T) {
|
||||
@ -39,3 +43,287 @@ func Test_getDepsJSONFilePrefix(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewDotnetBinaryPackage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
versionResources map[string]string
|
||||
expectedPackage pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "dotnet package with extra version info",
|
||||
versionResources: map[string]string{
|
||||
"InternalName": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
"FileVersion": "3.14.40721.0918 xxxfffdddjjjj",
|
||||
"FileDescription": "Active Directory Authentication Library",
|
||||
"ProductName": "Active Directory Authentication Library",
|
||||
"Comments": "",
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"LegalTrademarks": "",
|
||||
"LegalCopyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
"ProductVersion": "c61f043686a544863efc014114c42e844f905336",
|
||||
"Assembly Version": "3.14.2.11",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Active Directory Authentication Library",
|
||||
Version: "3.14.40721.0918",
|
||||
Metadata: pkg.DotnetPortableExecutableEntry{
|
||||
AssemblyVersion: "3.14.2.11",
|
||||
LegalCopyright: "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
InternalName: "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
CompanyName: "Microsoft Corporation",
|
||||
ProductName: "Active Directory Authentication Library",
|
||||
ProductVersion: "c61f043686a544863efc014114c42e844f905336",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// show we can do a best effort to make a package from bad data
|
||||
name: "dotnet package with malformed field and extended version",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"FileDescription": "äbFile\xa0\xa1Versi on",
|
||||
"FileVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
"InternalName": "äbFileVersion",
|
||||
"LegalCopyright": "© Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "TProductName",
|
||||
"ProductName": "Microsoft® .NET Framework",
|
||||
"ProductVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "äbFileVersi on",
|
||||
Version: "4.6.25512.01",
|
||||
PURL: "pkg:nuget/%C3%A4bFileVersi%20on@4.6.25512.01",
|
||||
Metadata: pkg.DotnetPortableExecutableEntry{
|
||||
LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
InternalName: "äb\x01FileVersion",
|
||||
CompanyName: "Microsoft Corporation",
|
||||
ProductName: "Microsoft® .NET Framework",
|
||||
ProductVersion: "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "System.Data.Linq.dll",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"FileDescription": "System.Data.Linq.dll",
|
||||
"FileVersion": "4.7.3190.0 built by: NET472REL1LAST_C",
|
||||
"InternalName": "System.Data.Linq.dll",
|
||||
"LegalCopyright": "© Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "System.Data.Linq.dll",
|
||||
"ProductName": "Microsoft® .NET Framework",
|
||||
"ProductVersion": "4.7.3190.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "System.Data.Linq.dll",
|
||||
Version: "4.7.3190.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "curl",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "curl, https://curl.se/",
|
||||
"FileDescription": "The curl executable",
|
||||
"FileVersion": "8.4.0",
|
||||
"InternalName": "curl",
|
||||
"LegalCopyright": "© Daniel Stenberg, <daniel@haxx.se>.",
|
||||
"OriginalFilename": "curl.exe",
|
||||
"ProductName": "The curl executable",
|
||||
"ProductVersion": "8.4.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "The curl executable",
|
||||
Version: "8.4.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Prometheus",
|
||||
versionResources: map[string]string{
|
||||
"AssemblyVersion": "8.0.0.0",
|
||||
"CompanyName": "",
|
||||
"FileDescription": "",
|
||||
"FileVersion": "8.0.1",
|
||||
"InternalName": "Prometheus.AspNetCore.dll",
|
||||
"OriginalFilename": "Prometheus.AspNetCore.dll",
|
||||
"ProductName": "",
|
||||
"ProductVersion": "8.0.1",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Prometheus.AspNetCore.dll",
|
||||
Version: "8.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Hidden Input",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Reads from stdin without leaking info to the terminal and outputs back to stdout",
|
||||
"FileVersion": "1, 0, 0, 0",
|
||||
"InternalName": "hiddeninput",
|
||||
"LegalCopyright": "Jordi Boggiano - 2012",
|
||||
"OriginalFilename": "hiddeninput.exe",
|
||||
"ProductName": "Hidden Input",
|
||||
"ProductVersion": "1, 0, 0, 0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Hidden Input",
|
||||
Version: "1, 0, 0, 0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SQLite3",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "SQLite Development Team",
|
||||
"FileDescription": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.",
|
||||
"FileVersion": "3.23.2",
|
||||
"InternalName": "sqlite3",
|
||||
"LegalCopyright": "http://www.sqlite.org/copyright.html",
|
||||
"ProductName": "SQLite",
|
||||
"ProductVersion": "3.23.2",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "SQLite",
|
||||
Version: "3.23.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Brave Browser",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Brave Software, Inc.",
|
||||
"FileDescription": "Brave Browser",
|
||||
"FileVersion": "80.1.7.92",
|
||||
"InternalName": "chrome_exe",
|
||||
"LegalCopyright": "Copyright 2016 The Brave Authors. All rights reserved.",
|
||||
"OriginalFilename": "chrome.exe",
|
||||
"ProductName": "Brave Browser",
|
||||
"ProductVersion": "80.1.7.92",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Brave Browser",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Better product version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Better version",
|
||||
"FileVersion": "80.1.7",
|
||||
"ProductVersion": "80.1.7.92",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Better version",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Better file version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Better version",
|
||||
"FileVersion": "80.1.7.92",
|
||||
"ProductVersion": "80.1.7",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Better version",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Higher semantic version Product Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Higher semantic version Product Version",
|
||||
"FileVersion": "3.0.0.0",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Higher semantic version Product Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Higher semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Higher semantic version File Version",
|
||||
"FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
"ProductVersion": "3.0.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Higher semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version File Version",
|
||||
"FileVersion": "A",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version File Version",
|
||||
"FileVersion": "A",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version Product Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version Product Version",
|
||||
"FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
"ProductVersion": "A",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version Product Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Semantically equal falls through, chooses File Version with more components",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Semantically equal falls through, chooses File Version with more components",
|
||||
"FileVersion": "3.0.0.0",
|
||||
"ProductVersion": "3.0.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Semantically equal falls through, chooses File Version with more components",
|
||||
Version: "3.0.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
location := file.NewLocation("")
|
||||
got := newDotnetBinaryPackage(tc.versionResources, location)
|
||||
|
||||
// ignore certain metadata
|
||||
if tc.expectedPackage.Metadata == nil {
|
||||
got.Metadata = nil
|
||||
}
|
||||
// set known defaults
|
||||
if tc.expectedPackage.Type == "" {
|
||||
tc.expectedPackage.Type = pkg.DotnetPkg
|
||||
}
|
||||
if tc.expectedPackage.Language == "" {
|
||||
tc.expectedPackage.Language = pkg.Dotnet
|
||||
}
|
||||
if tc.expectedPackage.PURL == "" {
|
||||
tc.expectedPackage.PURL = binaryPackageURL(tc.expectedPackage.Name, tc.expectedPackage.Version)
|
||||
}
|
||||
tc.expectedPackage.Locations = file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
|
||||
pkgtest.AssertPackagesEqual(t, tc.expectedPackage, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
var _ generic.Parser = parseDotnetDeps
|
||||
|
||||
type dotnetDeps struct {
|
||||
RuntimeTarget dotnetRuntimeTarget `json:"runtimeTarget"`
|
||||
Targets map[string]map[string]dotnetDepsTarget `json:"targets"`
|
||||
Libraries map[string]dotnetDepsLibrary `json:"libraries"`
|
||||
}
|
||||
|
||||
type dotnetRuntimeTarget struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type dotnetDepsTarget struct {
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
Runtime map[string]struct{} `json:"runtime"`
|
||||
}
|
||||
|
||||
type dotnetDepsLibrary struct {
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path"`
|
||||
Sha512 string `json:"sha512"`
|
||||
HashPath string `json:"hashPath"`
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func parseDotnetDeps(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var pkgMap = make(map[string]pkg.Package)
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var depsDoc dotnetDeps
|
||||
if err := dec.Decode(&depsDoc); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err)
|
||||
}
|
||||
|
||||
rootName := getDepsJSONFilePrefix(reader.Path())
|
||||
if rootName == "" {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.Path())
|
||||
}
|
||||
var rootPkg *pkg.Package
|
||||
for nameVersion, lib := range depsDoc.Libraries {
|
||||
name, _ := extractNameAndVersion(nameVersion)
|
||||
if lib.Type == "project" && name == rootName {
|
||||
rootPkg = newDotnetDepsPackage(
|
||||
nameVersion,
|
||||
lib,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
}
|
||||
}
|
||||
if rootPkg == nil {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.Path())
|
||||
}
|
||||
pkgs = append(pkgs, *rootPkg)
|
||||
pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg
|
||||
|
||||
var names []string
|
||||
for nameVersion := range depsDoc.Libraries {
|
||||
names = append(names, nameVersion)
|
||||
}
|
||||
// sort the names so that the order of the packages is deterministic
|
||||
sort.Strings(names)
|
||||
|
||||
for _, nameVersion := range names {
|
||||
// skip the root package
|
||||
name, version := extractNameAndVersion(nameVersion)
|
||||
if name == rootPkg.Name && version == rootPkg.Version {
|
||||
continue
|
||||
}
|
||||
|
||||
lib := depsDoc.Libraries[nameVersion]
|
||||
dotnetPkg := newDotnetDepsPackage(
|
||||
nameVersion,
|
||||
lib,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
|
||||
if dotnetPkg != nil {
|
||||
pkgs = append(pkgs, *dotnetPkg)
|
||||
pkgMap[nameVersion] = *dotnetPkg
|
||||
}
|
||||
}
|
||||
|
||||
for pkgNameVersion, target := range depsDoc.Targets[depsDoc.RuntimeTarget.Name] {
|
||||
for depName, depVersion := range target.Dependencies {
|
||||
depNameVersion := createNameAndVersion(depName, depVersion)
|
||||
depPkg, ok := pkgMap[depNameVersion]
|
||||
if !ok {
|
||||
log.Debug("unable to find package in map", depNameVersion)
|
||||
continue
|
||||
}
|
||||
p, ok := pkgMap[pkgNameVersion]
|
||||
if !ok {
|
||||
log.Debug("unable to find package in map", pkgNameVersion)
|
||||
continue
|
||||
}
|
||||
rel := artifact.Relationship{
|
||||
From: depPkg,
|
||||
To: p,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
relationships = append(relationships, rel)
|
||||
}
|
||||
}
|
||||
|
||||
// sort the relationships for deterministic output
|
||||
// TODO: ideally this would be replaced with artifact.SortRelationships when one exists and is type agnostic.
|
||||
// this will only consider package-to-package relationships.
|
||||
relationship.Sort(relationships)
|
||||
|
||||
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
@ -1,366 +0,0 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_corruptDotnetDeps(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/glob-paths/src/something.deps.json").
|
||||
WithError().
|
||||
TestParser(t, parseDotnetDeps)
|
||||
}
|
||||
|
||||
func TestParseDotnetDeps(t *testing.T) {
|
||||
fixture := "test-fixtures/TestLibrary.deps.json"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
rootPkg := pkg.Package{
|
||||
Name: "TestLibrary",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:nuget/TestLibrary@1.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "TestLibrary",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
testCommon := pkg.Package{
|
||||
Name: "TestCommon",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:nuget/TestCommon@1.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "TestCommon",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
awssdkcore := pkg.Package{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==",
|
||||
Path: "awssdk.core/3.7.10.6",
|
||||
HashPath: "awssdk.core.3.7.10.6.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftDependencyInjectionAbstractions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==",
|
||||
Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftDependencyInjection := pkg.Package{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==",
|
||||
Path: "microsoft.extensions.dependencyinjection/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftLoggingAbstractions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==",
|
||||
Path: "microsoft.extensions.logging.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsLogging := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==",
|
||||
Path: "microsoft.extensions.logging/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsOptions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==",
|
||||
Path: "microsoft.extensions.options/6.0.0",
|
||||
HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsPrimitives := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==",
|
||||
Path: "microsoft.extensions.primitives/6.0.0",
|
||||
HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
newtonsoftJson := pkg.Package{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
PURL: "pkg:nuget/Newtonsoft.Json@13.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
|
||||
Path: "newtonsoft.json/13.0.1",
|
||||
HashPath: "newtonsoft.json.13.0.1.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
serilogSinksConsole := pkg.Package{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==",
|
||||
Path: "serilog.sinks.console/4.0.1",
|
||||
HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
serilog := pkg.Package{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
PURL: "pkg:nuget/Serilog@2.10.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==",
|
||||
Path: "serilog/2.10.0",
|
||||
HashPath: "serilog.2.10.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
systemDiagnosticsDiagnosticsource := pkg.Package{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==",
|
||||
Path: "system.diagnostics.diagnosticsource/6.0.0",
|
||||
HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
systemRuntimeCompilerServicesUnsafe := pkg.Package{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
Metadata: pkg.DotnetDepsEntry{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
|
||||
Path: "system.runtime.compilerservices.unsafe/6.0.0",
|
||||
HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
||||
}}
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
awssdkcore,
|
||||
msftDependencyInjection,
|
||||
msftDependencyInjectionAbstractions,
|
||||
msftExtensionsLogging,
|
||||
msftLoggingAbstractions,
|
||||
msftExtensionsOptions,
|
||||
msftExtensionsPrimitives,
|
||||
newtonsoftJson,
|
||||
serilog,
|
||||
serilogSinksConsole,
|
||||
systemDiagnosticsDiagnosticsource,
|
||||
systemRuntimeCompilerServicesUnsafe,
|
||||
testCommon,
|
||||
rootPkg,
|
||||
}
|
||||
|
||||
// ┌── (✓ = is represented in the test)
|
||||
// ↓
|
||||
//
|
||||
// ✓ TestLibrary/1.0.0 (project)
|
||||
// ✓ ├── [a] Microsoft.Extensions.DependencyInjection/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── [b] Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ └── [c!] System.Runtime.CompilerServices.Unsafe/6.0.0 [NO TARGET INFO]
|
||||
// ✓ ├── Microsoft.Extensions.Logging/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── Microsoft.Extensions.DependencyInjection/6.0.0 ...to [a]
|
||||
// ✓ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b]
|
||||
// ✓ │ ├── Microsoft.Extensions.Logging.Abstractions/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── Microsoft.Extensions.Options/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b]
|
||||
// ✓ │ │ └── Microsoft.Extensions.Primitives/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!]
|
||||
// ✓ │ └── System.Diagnostics.DiagnosticSource/6.0.0 [NO RUNTIME INFO]
|
||||
// ✓ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!]
|
||||
// ✓ ├── Newtonsoft.Json/13.0.1 [file version: 13.0.1.25517]
|
||||
// ✓ ├── [d] Serilog/2.10.0 [file version: 2.10.0.0]
|
||||
// ✓ ├── Serilog.Sinks.Console/4.0.1 [file version: 4.0.1.0]
|
||||
// ✓ │ └── Serilog/2.10.0 ...to [d]
|
||||
// ✓ └── [e!] TestCommon/1.0.0 [NOT SERVICEABLE / NO SHA]
|
||||
// ✓ └── AWSSDK.Core/3.7.10.6 [file version: 3.7.10.6]
|
||||
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: awssdkcore,
|
||||
To: testCommon,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjection,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjection,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftDependencyInjection,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftExtensionsOptions,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsLogging,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftLoggingAbstractions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsOptions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsPrimitives,
|
||||
To: msftExtensionsOptions,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: newtonsoftJson,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilog,
|
||||
To: serilogSinksConsole,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilog,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilogSinksConsole,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemDiagnosticsDiagnosticsource,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: msftDependencyInjection,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: msftExtensionsPrimitives,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: systemDiagnosticsDiagnosticsource,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: testCommon,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
@ -1,248 +0,0 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/saferwall/pe"
|
||||
|
||||
version "github.com/anchore/go-version"
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
type dotnetPortableExecutableParser struct {
|
||||
cfg CatalogerConfig
|
||||
}
|
||||
|
||||
func (p dotnetPortableExecutableParser) parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generic.Environment, f file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
by, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read file: %w", err)
|
||||
}
|
||||
|
||||
peFile, err := pe.NewBytes(by, &pe.Options{DisableCertValidation: !p.cfg.EnableCertificateValidation})
|
||||
if err != nil {
|
||||
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = peFile.Parse()
|
||||
if err != nil {
|
||||
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
versionResources, err := peFile.ParseVersionResources()
|
||||
if err != nil {
|
||||
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
|
||||
return nil, nil, fmt.Errorf("unable to parse version resources in PE file: %w", err)
|
||||
}
|
||||
|
||||
dotNetPkg, err := buildDotNetPackage(versionResources, f)
|
||||
if err != nil {
|
||||
log.Tracef("unable to build dotnet package for: %v %v", f.RealPath, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return []pkg.Package{dotNetPkg}, nil, nil
|
||||
}
|
||||
|
||||
func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) {
|
||||
name := findName(versionResources)
|
||||
if name == "" {
|
||||
return dnpkg, fmt.Errorf("unable to find PE name in file")
|
||||
}
|
||||
|
||||
version := findVersion(versionResources)
|
||||
if version == "" {
|
||||
return dnpkg, fmt.Errorf("unable to find PE version in file")
|
||||
}
|
||||
|
||||
metadata := pkg.DotnetPortableExecutableEntry{
|
||||
AssemblyVersion: versionResources["Assembly Version"],
|
||||
LegalCopyright: versionResources["LegalCopyright"],
|
||||
Comments: versionResources["Comments"],
|
||||
InternalName: versionResources["InternalName"],
|
||||
CompanyName: versionResources["CompanyName"],
|
||||
ProductName: versionResources["ProductName"],
|
||||
ProductVersion: versionResources["ProductVersion"],
|
||||
}
|
||||
|
||||
dnpkg = pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: file.NewLocationSet(f.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Type: pkg.DotnetPkg,
|
||||
Language: pkg.Dotnet,
|
||||
PURL: portableExecutablePackageURL(name, version),
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
dnpkg.SetID()
|
||||
|
||||
return dnpkg, nil
|
||||
}
|
||||
|
||||
func portableExecutablePackageURL(name, version string) string {
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeNuget, // See explanation in syft/pkg/cataloger/dotnet/package.go as to why this was chosen.
|
||||
"",
|
||||
name,
|
||||
version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func extractVersion(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
|
||||
out := ""
|
||||
|
||||
// some example versions are: "1, 0, 0, 0", "Release 73" or "4.7.4076.0 built by: NET472REL1LAST_B"
|
||||
// so try to split it and take the first parts that look numeric
|
||||
for i, f := range strings.Fields(version) {
|
||||
// if the output already has a number but the current segment does not have a number,
|
||||
// return what we found for the version
|
||||
if containsNumber(out) && !containsNumber(f) {
|
||||
return out
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
out = f
|
||||
} else {
|
||||
out += " " + f
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func keepGreaterSemanticVersion(productVersion string, fileVersion string) string {
|
||||
semanticProductVersion, err := version.NewVersion(productVersion)
|
||||
|
||||
if err != nil || semanticProductVersion == nil {
|
||||
log.Tracef("Unable to create semantic version from portable executable product version %s", productVersion)
|
||||
return ""
|
||||
}
|
||||
|
||||
semanticFileVersion, err := version.NewVersion(fileVersion)
|
||||
|
||||
if err != nil || semanticFileVersion == nil {
|
||||
log.Tracef("Unable to create semantic version from portable executable file version %s", fileVersion)
|
||||
return productVersion
|
||||
}
|
||||
|
||||
// Make no choice when they are semantically equal so that it falls
|
||||
// through to the other comparison cases
|
||||
if semanticProductVersion.Equal(semanticFileVersion) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if semanticFileVersion.GreaterThan(semanticProductVersion) {
|
||||
return fileVersion
|
||||
}
|
||||
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func findVersion(versionResources map[string]string) string {
|
||||
productVersion := extractVersion(versionResources["ProductVersion"])
|
||||
fileVersion := extractVersion(versionResources["FileVersion"])
|
||||
|
||||
semanticVersionCompareResult := keepGreaterSemanticVersion(productVersion, fileVersion)
|
||||
|
||||
if semanticVersionCompareResult != "" {
|
||||
return semanticVersionCompareResult
|
||||
}
|
||||
|
||||
productVersionDetail := punctuationCount(productVersion)
|
||||
fileVersionDetail := punctuationCount(fileVersion)
|
||||
|
||||
if containsNumber(productVersion) && productVersionDetail >= fileVersionDetail {
|
||||
return productVersion
|
||||
}
|
||||
|
||||
if containsNumber(fileVersion) && fileVersionDetail > 0 {
|
||||
return fileVersion
|
||||
}
|
||||
|
||||
if containsNumber(productVersion) {
|
||||
return productVersion
|
||||
}
|
||||
|
||||
if containsNumber(fileVersion) {
|
||||
return fileVersion
|
||||
}
|
||||
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func containsNumber(s string) bool {
|
||||
return numberRegex.MatchString(s)
|
||||
}
|
||||
|
||||
func punctuationCount(s string) int {
|
||||
return len(versionPunctuationRegex.FindAllString(s, -1))
|
||||
}
|
||||
|
||||
var (
|
||||
// spaceRegex includes nbsp (#160) considered to be a space character
|
||||
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
|
||||
numberRegex = regexp.MustCompile(`\d`)
|
||||
versionPunctuationRegex = regexp.MustCompile(`[.,]+`)
|
||||
)
|
||||
|
||||
func findName(versionResources map[string]string) string {
|
||||
// PE files found in the wild _not_ authored by Microsoft seem to use ProductName as a clear
|
||||
// identifier of the software
|
||||
nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"}
|
||||
|
||||
if isMicrosoft(versionResources) {
|
||||
// Microsoft seems to be consistent using the FileDescription, with a few that are blank and have
|
||||
// fallbacks to ProductName last, as this is often something very broad like "Microsoft Windows"
|
||||
nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"}
|
||||
}
|
||||
|
||||
for _, field := range nameFields {
|
||||
value := spaceNormalize(versionResources[field])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// normalizes a string to a trimmed version with all contigous whitespace collapsed to a single space character
|
||||
func spaceNormalize(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
// ensure valid utf8 text
|
||||
value = strings.ToValidUTF8(value, "")
|
||||
// consolidate all space characters
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
// remove other non-space, non-printable characters
|
||||
value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "")
|
||||
// consolidate all space characters again in case other non-printables were in-between
|
||||
value = spaceRegex.ReplaceAllString(value, " ")
|
||||
// finally, remove any remaining surrounding whitespace
|
||||
value = strings.TrimSpace(value)
|
||||
return value
|
||||
}
|
||||
|
||||
func isMicrosoft(versionResources map[string]string) bool {
|
||||
return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") ||
|
||||
strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft")
|
||||
}
|
||||
@ -1,358 +0,0 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestParseDotnetPortableExecutable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
versionResources map[string]string
|
||||
expectedPackage pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "dotnet package with extra version info",
|
||||
versionResources: map[string]string{
|
||||
"InternalName": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
"FileVersion": "3.14.40721.0918 xxxfffdddjjjj",
|
||||
"FileDescription": "Active Directory Authentication Library",
|
||||
"ProductName": "Active Directory Authentication Library",
|
||||
"Comments": "",
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"LegalTrademarks": "",
|
||||
"LegalCopyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
"ProductVersion": "c61f043686a544863efc014114c42e844f905336",
|
||||
"Assembly Version": "3.14.2.11",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Active Directory Authentication Library",
|
||||
Version: "3.14.40721.0918",
|
||||
Metadata: pkg.DotnetPortableExecutableEntry{
|
||||
AssemblyVersion: "3.14.2.11",
|
||||
LegalCopyright: "Copyright (c) Microsoft Corporation. All rights reserved.",
|
||||
InternalName: "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
|
||||
CompanyName: "Microsoft Corporation",
|
||||
ProductName: "Active Directory Authentication Library",
|
||||
ProductVersion: "c61f043686a544863efc014114c42e844f905336",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dotnet package with malformed field and extended version",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"FileDescription": "äbFile\xa0\xa1Versi on",
|
||||
"FileVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
"InternalName": "äbFileVersion",
|
||||
"LegalCopyright": "© Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "TProductName",
|
||||
"ProductName": "Microsoft® .NET Framework",
|
||||
"ProductVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "äbFileVersi on",
|
||||
Version: "4.6.25512.01",
|
||||
PURL: "pkg:nuget/%C3%A4bFileVersi%20on@4.6.25512.01",
|
||||
Metadata: pkg.DotnetPortableExecutableEntry{
|
||||
LegalCopyright: "© Microsoft Corporation. All rights reserved.",
|
||||
InternalName: "äb\x01FileVersion",
|
||||
CompanyName: "Microsoft Corporation",
|
||||
ProductName: "Microsoft® .NET Framework",
|
||||
ProductVersion: "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "System.Data.Linq.dll",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Microsoft Corporation",
|
||||
"FileDescription": "System.Data.Linq.dll",
|
||||
"FileVersion": "4.7.3190.0 built by: NET472REL1LAST_C",
|
||||
"InternalName": "System.Data.Linq.dll",
|
||||
"LegalCopyright": "© Microsoft Corporation. All rights reserved.",
|
||||
"OriginalFilename": "System.Data.Linq.dll",
|
||||
"ProductName": "Microsoft® .NET Framework",
|
||||
"ProductVersion": "4.7.3190.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "System.Data.Linq.dll",
|
||||
Version: "4.7.3190.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "curl",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "curl, https://curl.se/",
|
||||
"FileDescription": "The curl executable",
|
||||
"FileVersion": "8.4.0",
|
||||
"InternalName": "curl",
|
||||
"LegalCopyright": "© Daniel Stenberg, <daniel@haxx.se>.",
|
||||
"OriginalFilename": "curl.exe",
|
||||
"ProductName": "The curl executable",
|
||||
"ProductVersion": "8.4.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "The curl executable",
|
||||
Version: "8.4.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Prometheus",
|
||||
versionResources: map[string]string{
|
||||
"AssemblyVersion": "8.0.0.0",
|
||||
"CompanyName": "",
|
||||
"FileDescription": "",
|
||||
"FileVersion": "8.0.1",
|
||||
"InternalName": "Prometheus.AspNetCore.dll",
|
||||
"OriginalFilename": "Prometheus.AspNetCore.dll",
|
||||
"ProductName": "",
|
||||
"ProductVersion": "8.0.1",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Prometheus.AspNetCore.dll",
|
||||
Version: "8.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Hidden Input",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Reads from stdin without leaking info to the terminal and outputs back to stdout",
|
||||
"FileVersion": "1, 0, 0, 0",
|
||||
"InternalName": "hiddeninput",
|
||||
"LegalCopyright": "Jordi Boggiano - 2012",
|
||||
"OriginalFilename": "hiddeninput.exe",
|
||||
"ProductName": "Hidden Input",
|
||||
"ProductVersion": "1, 0, 0, 0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Hidden Input",
|
||||
Version: "1, 0, 0, 0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SQLite3",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "SQLite Development Team",
|
||||
"FileDescription": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.",
|
||||
"FileVersion": "3.23.2",
|
||||
"InternalName": "sqlite3",
|
||||
"LegalCopyright": "http://www.sqlite.org/copyright.html",
|
||||
"ProductName": "SQLite",
|
||||
"ProductVersion": "3.23.2",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "SQLite",
|
||||
Version: "3.23.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Brave Browser",
|
||||
versionResources: map[string]string{
|
||||
"CompanyName": "Brave Software, Inc.",
|
||||
"FileDescription": "Brave Browser",
|
||||
"FileVersion": "80.1.7.92",
|
||||
"InternalName": "chrome_exe",
|
||||
"LegalCopyright": "Copyright 2016 The Brave Authors. All rights reserved.",
|
||||
"OriginalFilename": "chrome.exe",
|
||||
"ProductName": "Brave Browser",
|
||||
"ProductVersion": "80.1.7.92",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Brave Browser",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Better product version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Better version",
|
||||
"FileVersion": "80.1.7",
|
||||
"ProductVersion": "80.1.7.92",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Better version",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Better file version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Better version",
|
||||
"FileVersion": "80.1.7.92",
|
||||
"ProductVersion": "80.1.7",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Better version",
|
||||
Version: "80.1.7.92",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Higher semantic version Product Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Higher semantic version Product Version",
|
||||
"FileVersion": "3.0.0.0",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Higher semantic version Product Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Higher semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Higher semantic version File Version",
|
||||
"FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
"ProductVersion": "3.0.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Higher semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version File Version",
|
||||
"FileVersion": "A",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version File Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version File Version",
|
||||
"FileVersion": "A",
|
||||
"ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version File Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid semantic version Product Version",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Invalid semantic version Product Version",
|
||||
"FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
"ProductVersion": "A",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Invalid semantic version Product Version",
|
||||
Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Semantically equal falls through, chooses File Version with more components",
|
||||
versionResources: map[string]string{
|
||||
"FileDescription": "Semantically equal falls through, chooses File Version with more components",
|
||||
"FileVersion": "3.0.0.0",
|
||||
"ProductVersion": "3.0.0",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Semantically equal falls through, chooses File Version with more components",
|
||||
Version: "3.0.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
location := file.NewLocation("")
|
||||
f := file.LocationReadCloser{
|
||||
Location: location,
|
||||
}
|
||||
got, err := buildDotNetPackage(tc.versionResources, f)
|
||||
assert.NoErrorf(t, err, "failed to build package from version resources: %+v", tc.versionResources)
|
||||
|
||||
// ignore certain metadata
|
||||
if tc.expectedPackage.Metadata == nil {
|
||||
got.Metadata = nil
|
||||
}
|
||||
// set known defaults
|
||||
if tc.expectedPackage.Type == "" {
|
||||
tc.expectedPackage.Type = pkg.DotnetPkg
|
||||
}
|
||||
if tc.expectedPackage.Language == "" {
|
||||
tc.expectedPackage.Language = pkg.Dotnet
|
||||
}
|
||||
if tc.expectedPackage.PURL == "" {
|
||||
tc.expectedPackage.PURL = portableExecutablePackageURL(tc.expectedPackage.Name, tc.expectedPackage.Version)
|
||||
}
|
||||
tc.expectedPackage.Locations = file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
|
||||
pkgtest.AssertPackagesEqual(t, tc.expectedPackage, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_corruptDotnetPE(t *testing.T) {
|
||||
p := dotnetPortableExecutableParser{
|
||||
cfg: DefaultCatalogerConfig(),
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/glob-paths/src/something.exe").
|
||||
WithError().
|
||||
TestParser(t, p.parseDotnetPortableExecutable)
|
||||
}
|
||||
|
||||
func Test_extractVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: "1, 0, 0, 0",
|
||||
expected: "1, 0, 0, 0",
|
||||
},
|
||||
{
|
||||
input: "Release 73",
|
||||
expected: "Release 73",
|
||||
},
|
||||
{
|
||||
input: "4.7.4076.0 built by: NET472REL1LAST_B",
|
||||
expected: "4.7.4076.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
got := extractVersion(test.input)
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_spaceNormalize(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
expected: "some spaces apart",
|
||||
input: " some spaces\n\t\t \n\rapart\n",
|
||||
},
|
||||
{
|
||||
expected: "söme ¡nvalid characters",
|
||||
input: "\rsöme \u0001¡nvalid\t characters\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
got := spaceNormalize(test.input)
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ func Test_corruptDotnetPackagesLock(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, "test-fixtures/glob-paths/src/packages.lock.json").
|
||||
WithError().
|
||||
TestParser(t, parseDotnetDeps)
|
||||
TestParser(t, parseDotnetPackagesLock)
|
||||
}
|
||||
|
||||
func TestParseDotnetPackagesLock(t *testing.T) {
|
||||
740
syft/pkg/cataloger/dotnet/pe.go
Normal file
740
syft/pkg/cataloger/dotnet/pe.go
Normal file
@ -0,0 +1,740 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/scylladb/go-set/u32set"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
const peMaxAllowedDirectoryEntries = 0x1000
|
||||
|
||||
var imageDirectoryEntryIndexes = []int{
|
||||
pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored
|
||||
pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored
|
||||
}
|
||||
|
||||
// logicalPE does not directly represent a binary shape to be parsed, instead it represents the
|
||||
// information of interest extracted from a PE file.
|
||||
type logicalPE struct {
|
||||
// Location is where the PE file was found
|
||||
Location file.Location
|
||||
|
||||
// TargetPath is the path is the deps.json target entry. This is not present in the PE file
|
||||
// but instead is used in downstream processing to track associations between the PE file and the deps.json file.
|
||||
TargetPath string
|
||||
|
||||
// CLR is the information about the CLR (common language runtime) version found in the PE file which helps
|
||||
// understand if this executable is even a .NET application.
|
||||
CLR *clrEvidence
|
||||
|
||||
// VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory.
|
||||
VersionResources map[string]string
|
||||
}
|
||||
|
||||
// clrEvidence is basic info about the CLR (common language runtime) version from the COM descriptor.
|
||||
// This is not a complete representation of the CLR version, but rather a subset of the information that is
|
||||
// useful to us.
|
||||
type clrEvidence struct {
|
||||
// HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO").
|
||||
HasClrResourceNames bool
|
||||
|
||||
// MajorVersion is the minimum supported major version of the CLR.
|
||||
MajorVersion uint16
|
||||
|
||||
// MinorVersion is the minimum supported minor version of the CLR.
|
||||
MinorVersion uint16
|
||||
}
|
||||
|
||||
// hasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version.
|
||||
func (c *clrEvidence) hasEvidenceOfCLR() bool {
|
||||
return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames)
|
||||
}
|
||||
|
||||
type peDosHeader struct {
|
||||
Magic [2]byte // "MZ"
|
||||
Unused [58]byte
|
||||
AddressOfNewEXEHeader uint32 // offset to PE header
|
||||
}
|
||||
|
||||
// peImageCore20 represents the .NET Core 2.0 header structure.
|
||||
// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775
|
||||
type peImageCore20 struct {
|
||||
Cb uint32
|
||||
MajorRuntimeVersion uint16
|
||||
MinorRuntimeVersion uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectory represents the resource directory structure.
|
||||
type peImageResourceDirectory struct {
|
||||
Characteristics uint32
|
||||
TimeDateStamp uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
NumberOfNamedEntries uint16
|
||||
NumberOfIDEntries uint16
|
||||
}
|
||||
|
||||
// peImageResourceDirectoryEntry represents an entry in the resource directory entries.
|
||||
type peImageResourceDirectoryEntry struct {
|
||||
Name uint32
|
||||
OffsetToData uint32
|
||||
}
|
||||
|
||||
// peImageResourceDataEntry is the unit of raw data in the Resource Data area.
|
||||
type peImageResourceDataEntry struct {
|
||||
OffsetToData uint32
|
||||
Size uint32
|
||||
CodePage uint32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// peVsFixedFileInfo represents the fixed file information structure.
|
||||
type peVsFixedFileInfo struct {
|
||||
Signature uint32
|
||||
StructVersion uint32
|
||||
FileVersionMS uint32
|
||||
FileVersionLS uint32
|
||||
ProductVersionMS uint32
|
||||
ProductVersionLS uint32
|
||||
FileFlagsMask uint32
|
||||
FileFlags uint32
|
||||
FileOS uint32
|
||||
FileType uint32
|
||||
FileSubtype uint32
|
||||
FileDateMS uint32
|
||||
FileDateLS uint32
|
||||
}
|
||||
|
||||
type peVsVersionInfo peLenValLenType
|
||||
|
||||
type peStringFileInfo peLenValLenType
|
||||
|
||||
type peStringTable peLenValLenType
|
||||
|
||||
type peString peLenValLenType
|
||||
|
||||
type peLenValLenType struct {
|
||||
Length uint16
|
||||
ValueLength uint16
|
||||
Type uint16
|
||||
}
|
||||
|
||||
type extractedSection struct {
|
||||
RVA uint32
|
||||
BaseRVA uint32
|
||||
Size uint32
|
||||
Reader *bytes.Reader
|
||||
}
|
||||
|
||||
func (s extractedSection) exists() bool {
|
||||
return s.RVA != 0 && s.Size != 0
|
||||
}
|
||||
|
||||
func directoryName(i int) string {
|
||||
switch i {
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE:
|
||||
return "Resource"
|
||||
case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
|
||||
return "COM Descriptor"
|
||||
}
|
||||
return fmt.Sprintf("Unknown (%d)", i)
|
||||
}
|
||||
|
||||
func getLogicalDotnetPE(f file.LocationReadCloser) (*logicalPE, error) {
|
||||
r, err := unionreader.GetUnionReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sections, _, err := parsePEFile(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE sections: %w", err)
|
||||
}
|
||||
|
||||
dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases)
|
||||
versionResources := make(map[string]string) // map of version resource keys to their values
|
||||
resourceNames := strset.New() // set of resource names found in the PE file
|
||||
err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err)
|
||||
}
|
||||
|
||||
return &logicalPE{
|
||||
Location: f.Location,
|
||||
CLR: c,
|
||||
VersionResources: versionResources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parsePEFile creates readers for targeted sections of the binary used by downstream processing.
|
||||
func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
fileHeader, magic, err := parsePEHeader(file)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing PE header: %w", err)
|
||||
}
|
||||
|
||||
soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing section headers: %w", err)
|
||||
}
|
||||
|
||||
for i, sec := range soi {
|
||||
if !sec.exists() {
|
||||
continue
|
||||
}
|
||||
data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err)
|
||||
}
|
||||
sec.Reader = data
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator
|
||||
// for downstream logic to determine 32/64 bit parsing.
|
||||
func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) {
|
||||
var dosHeader peDosHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading DOS header: %w", err)
|
||||
}
|
||||
if string(dosHeader.Magic[:]) != "MZ" {
|
||||
return nil, 0, fmt.Errorf("invalid DOS header magic")
|
||||
}
|
||||
|
||||
peOffset := int64(dosHeader.AddressOfNewEXEHeader)
|
||||
if _, err := file.Seek(peOffset, io.SeekStart); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking to PE header: %w", err)
|
||||
}
|
||||
|
||||
var signature [4]byte
|
||||
if err := binary.Read(file, binary.LittleEndian, &signature); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading PE signature: %w", err)
|
||||
}
|
||||
if !bytes.Equal(signature[:], []byte("PE\x00\x00")) {
|
||||
return nil, 0, fmt.Errorf("invalid PE signature")
|
||||
}
|
||||
|
||||
var fileHeader pe.FileHeader
|
||||
if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading file header: %w", err)
|
||||
}
|
||||
|
||||
var magic uint16
|
||||
if err := binary.Read(file, binary.LittleEndian, &magic); err != nil {
|
||||
return nil, 0, fmt.Errorf("error reading optional header magic: %w", err)
|
||||
}
|
||||
|
||||
// seek back to before reading magic (since that value is in the header)
|
||||
if _, err := file.Seek(-2, io.SeekCurrent); err != nil {
|
||||
return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err)
|
||||
}
|
||||
|
||||
return &fileHeader, magic, nil
|
||||
}
|
||||
|
||||
// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size
|
||||
// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing.
|
||||
func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) {
|
||||
soi := make(map[int]*extractedSection)
|
||||
switch magic {
|
||||
case 0x10B: // PE32
|
||||
var optHeader pe.OptionalHeader32
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
case 0x20B: // PE32+ (64 bit)
|
||||
var optHeader pe.OptionalHeader64
|
||||
if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err)
|
||||
}
|
||||
|
||||
for _, i := range imageDirectoryEntryIndexes {
|
||||
sectionHeader := optHeader.DataDirectory[i]
|
||||
if sectionHeader.Size == 0 {
|
||||
continue
|
||||
}
|
||||
soi[i] = &extractedSection{
|
||||
RVA: sectionHeader.VirtualAddress,
|
||||
Size: sectionHeader.Size,
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic)
|
||||
}
|
||||
|
||||
// read section headers
|
||||
headers := make([]pe.SectionHeader32, numberOfSections)
|
||||
for i := 0; i < int(numberOfSections); i++ {
|
||||
if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil {
|
||||
return nil, nil, fmt.Errorf("error reading section header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return soi, headers, nil
|
||||
}
|
||||
|
||||
// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes
|
||||
// present/not-present determination based on the presence of CLR resource names.
|
||||
func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*clrEvidence, error) {
|
||||
hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO")
|
||||
if sec == nil || sec.Reader == nil {
|
||||
return &clrEvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
reader := sec.Reader
|
||||
var c peImageCore20
|
||||
if err := binary.Read(reader, binary.LittleEndian, &c); err != nil {
|
||||
return nil, fmt.Errorf("error reading CLR header: %w", err)
|
||||
}
|
||||
|
||||
return &clrEvidence{
|
||||
HasClrResourceNames: hasCLRDebugResourceNames,
|
||||
MajorVersion: c.MajorRuntimeVersion,
|
||||
MinorVersion: c.MinorRuntimeVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rvaToFileOffset is a helper function to convert RVA to file offset using section headers
|
||||
func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) {
|
||||
for _, section := range sections {
|
||||
if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize {
|
||||
return section.PointerToRawData + (rva - section.VirtualAddress), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("RVA 0x%x not found in any section", rva)
|
||||
}
|
||||
|
||||
// readDataFromRVA will read data from a specific RVA in the PE file
|
||||
func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) {
|
||||
if size == 0 {
|
||||
return nil, fmt.Errorf("zero size specified")
|
||||
}
|
||||
|
||||
offset, err := rvaToFileOffset(rva, sections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := file.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("error seeking to data: %w", err)
|
||||
}
|
||||
|
||||
data := make([]byte, size)
|
||||
if _, err := io.ReadFull(file, data); err != nil {
|
||||
return nil, fmt.Errorf("error reading data: %w", err)
|
||||
}
|
||||
|
||||
return bytes.NewReader(data), nil
|
||||
}
|
||||
|
||||
// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of
|
||||
// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already
|
||||
// parsed, and the map to populate discovered version resource values.
|
||||
//
|
||||
// .rsrc Section
|
||||
// +------------------------------+
|
||||
// | Resource Directory Table |
|
||||
// +------------------------------+
|
||||
// | Resource Directory Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Subdirectory or Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Resource Data Entries |
|
||||
// | +------------------------+ |
|
||||
// | | Resource Data | |
|
||||
// | +------------------------+ |
|
||||
// +------------------------------+
|
||||
// | Actual Resource Data |
|
||||
// +------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section
|
||||
// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources
|
||||
func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
if sec == nil || sec.Size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sec.Reader == nil {
|
||||
return errors.New("resource section not found")
|
||||
}
|
||||
|
||||
baseRVA := sec.BaseRVA
|
||||
if baseRVA == 0 {
|
||||
baseRVA = sec.RVA
|
||||
}
|
||||
|
||||
offset := int64(sec.RVA - baseRVA)
|
||||
if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to directory offset: %w", err)
|
||||
}
|
||||
|
||||
var directoryHeader peImageResourceDirectory
|
||||
if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil {
|
||||
return fmt.Errorf("error reading directory header: %w", err)
|
||||
}
|
||||
|
||||
numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries)
|
||||
switch {
|
||||
case numEntries > peMaxAllowedDirectoryEntries:
|
||||
return fmt.Errorf("too many entries in resource directory: %d", numEntries)
|
||||
case numEntries == 0:
|
||||
return fmt.Errorf("no entries in resource directory")
|
||||
case numEntries < 0:
|
||||
return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries)
|
||||
}
|
||||
|
||||
for i := 0; i < numEntries; i++ {
|
||||
var entry peImageResourceDirectoryEntry
|
||||
|
||||
entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry))
|
||||
if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil {
|
||||
log.Tracef("error seeking to PE entry offset: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := readIntoStruct(sec.Reader, &entry); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil {
|
||||
log.Tracef("error processing resource entry: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error {
|
||||
// if the high bit is set, this is a directory entry, otherwise it is a data entry
|
||||
isDirectory := entry.OffsetToData&0x80000000 != 0
|
||||
|
||||
// note: the offset is relative to the beginning of the resource section, not an RVA
|
||||
entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF
|
||||
|
||||
nameIsString := entry.Name&0x80000000 != 0
|
||||
nameOffset := entry.Name & 0x7FFFFFFF
|
||||
|
||||
// read the string name of the resource directory
|
||||
if nameIsString {
|
||||
currentPos, err := sec.Reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting current reader position: %w", err)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
|
||||
name, err := readUTF16WithLength(sec.Reader)
|
||||
if err == nil {
|
||||
names.Add(name)
|
||||
}
|
||||
|
||||
if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error restoring reader position: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if isDirectory {
|
||||
subRVA := baseRVA + entryOffsetToData
|
||||
if dirs.Has(subRVA) {
|
||||
// some malware uses recursive PE references to evade analysis
|
||||
return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA)
|
||||
}
|
||||
|
||||
dirs.Add(subRVA)
|
||||
err := parseResourceDirectory(
|
||||
&extractedSection{
|
||||
RVA: subRVA,
|
||||
BaseRVA: baseRVA,
|
||||
Size: sec.Size - (sec.RVA - baseRVA),
|
||||
Reader: sec.Reader,
|
||||
},
|
||||
dirs, fields, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields)
|
||||
}
|
||||
|
||||
func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error {
|
||||
var dataEntry peImageResourceDataEntry
|
||||
offset := int64(rva - baseRVA)
|
||||
|
||||
if _, err := reader.Seek(offset, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to data entry offset: %w", err)
|
||||
}
|
||||
|
||||
if err := readIntoStruct(reader, &dataEntry); err != nil {
|
||||
return fmt.Errorf("error reading resource data entry: %w", err)
|
||||
}
|
||||
|
||||
if remainingSize < dataEntry.Size {
|
||||
return fmt.Errorf("resource data entry size exceeds remaining size")
|
||||
}
|
||||
|
||||
data := make([]byte, dataEntry.Size)
|
||||
if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("error seeking to resource data: %w", err)
|
||||
}
|
||||
|
||||
if _, err := reader.Read(data); err != nil {
|
||||
return fmt.Errorf("error reading resource data: %w", err)
|
||||
}
|
||||
|
||||
return parseVersionResourceSection(bytes.NewReader(data), fields)
|
||||
}
|
||||
|
||||
// parseVersionResourceSection parses a PE version resource section from within a resource directory.
|
||||
//
|
||||
// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the
|
||||
// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information.
|
||||
// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary."
|
||||
//
|
||||
// "VS_VERSIONINFO" (utf16)
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("VS_VERSION_INFO") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// +---------------------------------------------------+
|
||||
// | VS_FIXEDFILEINFO (52 bytes) |
|
||||
// +---------------------------------------------------+
|
||||
// | "StringFileInfo" (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | wLength (2 bytes) |
|
||||
// | wValueLength (2 bytes) |
|
||||
// | wType (2 bytes) |
|
||||
// | szKey ("StringFileInfo") (utf16) |
|
||||
// | Padding (to DWORD) |
|
||||
// | StringTable |
|
||||
// | +--------------------------------------------+ |
|
||||
// | | wLength (2 bytes) | |
|
||||
// | | wValueLength (2 bytes) | |
|
||||
// | | wType (2 bytes) | |
|
||||
// | | szKey ("040904b0") | |
|
||||
// | | Padding (to DWORD) | |
|
||||
// | | String | |
|
||||
// | | +--------------------------------------+ | |
|
||||
// | | | wLength (2 bytes) | | |
|
||||
// | | | wValueLength (2 bytes) | | |
|
||||
// | | | wType (2 bytes) | | |
|
||||
// | | | szKey ("FileVersion") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | | | szValue ("15.00.0913.015") | | |
|
||||
// | | | Padding (to DWORD) | | |
|
||||
// | +--------------------------------------------+ |
|
||||
// +---------------------------------------------------+
|
||||
// | VarFileInfo (utf16) |
|
||||
// +---------------------------------------------------+
|
||||
// | (skip!) |
|
||||
// +---------------------------------------------------+
|
||||
//
|
||||
// sources:
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo
|
||||
// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable
|
||||
func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error {
|
||||
offset := 0
|
||||
|
||||
var info peVsVersionInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE version info: %v", err)
|
||||
} else if szKey != "VS_VERSION_INFO" {
|
||||
// this is a resource section, but not the version resources
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error aligning past PE version info: %w", err)
|
||||
}
|
||||
|
||||
var fixedFileInfo peVsFixedFileInfo
|
||||
if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE FixedFileInfo: %v", err)
|
||||
}
|
||||
|
||||
for reader.Len() > 0 {
|
||||
if err := alignAndSeek(reader, &offset); err != nil {
|
||||
return fmt.Errorf("error seeking to PE StringFileInfo: %w", err)
|
||||
}
|
||||
|
||||
var sfiHeader peStringFileInfo
|
||||
if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil {
|
||||
return fmt.Errorf("error reading PE string file info header: %v", err)
|
||||
} else if szKey != "StringFileInfo" {
|
||||
// we only care about extracting strings from any string tables, skip this
|
||||
offset += int(sfiHeader.ValueLength)
|
||||
continue
|
||||
}
|
||||
|
||||
var stOffset int
|
||||
|
||||
// note: the szKey for the prStringTable is the language
|
||||
var stHeader peStringTable
|
||||
if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error reading PE string table header: %v", err)
|
||||
}
|
||||
|
||||
for stOffset < int(stHeader.Length) {
|
||||
var stringHeader peString
|
||||
if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := readUTF16(reader, &offset, &stOffset)
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table value: %w", err)
|
||||
}
|
||||
|
||||
var value string
|
||||
if stringHeader.ValueLength > 0 {
|
||||
value = readUTF16(reader, &offset, &stOffset)
|
||||
}
|
||||
|
||||
fields[key] = value
|
||||
|
||||
if err := alignAndSeek(reader, &offset, &stOffset); err != nil {
|
||||
return fmt.Errorf("error aligning to next PE string table key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields["FileVersion"] == "" {
|
||||
// we can derive the file version from the fixed file info if it is not already specified as a string entry... neat!
|
||||
fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d",
|
||||
fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF,
|
||||
fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value.
|
||||
// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context).
|
||||
func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) {
|
||||
if err := readIntoStruct(reader, data, offsets...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return readUTF16(reader, offsets...), nil
|
||||
}
|
||||
|
||||
// readIntoStruct reads a struct from the reader and updates the offsets if provided.
|
||||
func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error {
|
||||
if err := binary.Read(reader, binary.LittleEndian, data); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += binary.Size(*data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets).
|
||||
func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error {
|
||||
ogOffset := *offset
|
||||
*offset = alignToDWORD(*offset)
|
||||
diff := *offset - ogOffset
|
||||
for i := range trackOffsets {
|
||||
*trackOffsets[i] += diff
|
||||
}
|
||||
_, err := reader.Seek(int64(*offset), io.SeekStart)
|
||||
return err
|
||||
}
|
||||
|
||||
// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary)
|
||||
func alignToDWORD(offset int) int {
|
||||
return (offset + 3) & ^3
|
||||
}
|
||||
|
||||
// readUTF16 is a helper function to read a null-terminated UTF16 string
|
||||
func readUTF16(reader *bytes.Reader, offsets ...*int) string {
|
||||
startPos, err := reader.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var result []rune
|
||||
for {
|
||||
var char uint16
|
||||
err := binary.Read(reader, binary.LittleEndian, &char)
|
||||
if err != nil || char == 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, rune(char))
|
||||
}
|
||||
|
||||
// calculate how many bytes we've actually read (including null terminator)
|
||||
endPos, _ := reader.Seek(0, io.SeekCurrent)
|
||||
bytesRead := int(endPos - startPos)
|
||||
|
||||
for i := range offsets {
|
||||
*offsets[i] += bytesRead
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// readUTF16WithLength reads a length-prefixed UTF-16 string from reader.
|
||||
// The first 2 bytes represent the number of UTF-16 code units.
|
||||
func readUTF16WithLength(reader *bytes.Reader) (string, error) {
|
||||
var length uint16
|
||||
if err := binary.Read(reader, binary.LittleEndian, &length); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// read length UTF-16 code units.
|
||||
codes := make([]uint16, length)
|
||||
if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(utf16.Decode(codes)), nil
|
||||
}
|
||||
163
syft/pkg/cataloger/dotnet/pe_test.go
Normal file
163
syft/pkg/cataloger/dotnet/pe_test.go
Normal file
@ -0,0 +1,163 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func Test_getLogicalDotnetPE(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
path string
|
||||
wantVR map[string]string
|
||||
wantCLR bool
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "newtonsoft",
|
||||
path: "/app/Newtonsoft.Json.dll",
|
||||
fixture: "image-net8-app",
|
||||
wantCLR: true,
|
||||
wantVR: map[string]string{
|
||||
// the numbers are the field parse order, which helped for debugging and understanding corrupted fields
|
||||
"Comments": "Json.NET is a popular high-performance JSON framework for .NET", // 1
|
||||
"CompanyName": "Newtonsoft", // 2
|
||||
"FileDescription": "Json.NET .NET 6.0", // 3
|
||||
"FileVersion": "13.0.3.27908", // 4
|
||||
"InternalName": "Newtonsoft.Json.dll", // 5
|
||||
"LegalCopyright": "Copyright © James Newton-King 2008", // 6
|
||||
"LegalTrademarks": "", // 7 (empty value actually exists in the string table)
|
||||
"OriginalFilename": "Newtonsoft.Json.dll", // 8
|
||||
"ProductName": "Json.NET", // 9
|
||||
"ProductVersion": "13.0.3+0a2e291c0d9c0c7675d445703e51750363a549ef", // 10
|
||||
"Assembly Version": "13.0.0.0", // 11
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "humanizer",
|
||||
path: "/app/Humanizer.dll",
|
||||
fixture: "image-net8-app",
|
||||
wantCLR: true,
|
||||
wantVR: map[string]string{
|
||||
"Comments": "A micro-framework that turns your normal strings, type names, enum fields, date fields ETC into a human friendly format",
|
||||
"CompanyName": "Mehdi Khalili, Claire Novotny",
|
||||
"FileDescription": "Humanizer",
|
||||
"FileVersion": "2.14.1.48190",
|
||||
"InternalName": "Humanizer.dll",
|
||||
"LegalCopyright": "Copyright © .NET Foundation and Contributors",
|
||||
"OriginalFilename": "Humanizer.dll",
|
||||
"ProductName": "Humanizer (net6.0)",
|
||||
"ProductVersion": "2.14.1+3ebc38de58",
|
||||
"Assembly Version": "2.14.0.0",
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "dotnetapp",
|
||||
path: "/app/dotnetapp.dll",
|
||||
fixture: "image-net8-app",
|
||||
wantCLR: true,
|
||||
wantVR: map[string]string{
|
||||
"CompanyName": "dotnetapp",
|
||||
"FileDescription": "dotnetapp",
|
||||
"FileVersion": "1.0.0.0",
|
||||
"InternalName": "dotnetapp.dll",
|
||||
"LegalCopyright": " ",
|
||||
"OriginalFilename": "dotnetapp.dll",
|
||||
"ProductName": "dotnetapp",
|
||||
"ProductVersion": "1.0.0",
|
||||
"Assembly Version": "1.0.0.0",
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "jruby",
|
||||
path: "/app/jruby_windows_9_3_15_0.exe",
|
||||
fixture: "image-net8-app",
|
||||
wantCLR: false, // important!
|
||||
wantVR: map[string]string{
|
||||
"CompanyName": "JRuby Dev Team",
|
||||
"FileDescription": "JRuby",
|
||||
"FileVersion": "9.3.15.0",
|
||||
"InternalName": "jruby",
|
||||
"LegalCopyright": "JRuby Dev Team",
|
||||
"OriginalFilename": "jruby_windows-x32_9_3_15_0.exe",
|
||||
"ProductName": "JRuby",
|
||||
"ProductVersion": "9.3.15.0",
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "single file deployment",
|
||||
path: "/app/dotnetapp.exe",
|
||||
fixture: "image-net8-app-single-file",
|
||||
// single file deployment does not have CLR metadata embedded in the COM descriptor. Instead we need
|
||||
// to look for evidence of the CLR in other resources directory names, specifically for "CLRDEBUGINFO".
|
||||
wantCLR: true,
|
||||
wantVR: map[string]string{
|
||||
"CompanyName": "dotnetapp",
|
||||
"FileDescription": "dotnetapp",
|
||||
"FileVersion": "1.0.0.0",
|
||||
"InternalName": "dotnetapp.dll",
|
||||
"LegalCopyright": " ",
|
||||
"OriginalFilename": "dotnetapp.dll",
|
||||
"ProductName": "dotnetapp",
|
||||
"ProductVersion": "1.0.0",
|
||||
"Assembly Version": "1.0.0.0",
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
reader := fixtureFile(t, tt.fixture, tt.path)
|
||||
|
||||
got, err := getLogicalDotnetPE(reader)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tt.wantVR, got.VersionResources); d != "" {
|
||||
t.Errorf("unexpected version resources (-want +got): %s", d)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantCLR, got.CLR.hasEvidenceOfCLR())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fixtureFile(t *testing.T, fixture, path string) file.LocationReadCloser {
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", fixture)
|
||||
|
||||
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: fixture,
|
||||
})
|
||||
|
||||
r, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
locs, err := r.FilesByPath(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, locs, 1)
|
||||
loc := locs[0]
|
||||
|
||||
reader, err := r.FileContentsByLocation(loc)
|
||||
require.NoError(t, err)
|
||||
return file.NewLocationReadCloser(loc, reader)
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
!*.dll
|
||||
!*.exe
|
||||
!*.exe
|
||||
/cache
|
||||
21
syft/pkg/cataloger/dotnet/test-fixtures/Makefile
Normal file
21
syft/pkg/cataloger/dotnet/test-fixtures/Makefile
Normal file
@ -0,0 +1,21 @@
|
||||
FINGERPRINT_FILE=cache.fingerprint
|
||||
|
||||
|
||||
.DEFAULT_GOAL := fixtures
|
||||
|
||||
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||
fixtures:
|
||||
@echo "nothing to do"
|
||||
|
||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||
fingerprint: $(FINGERPRINT_FILE)
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find Makefile **/Dockerfile **/src/** -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||
@#cat $(FINGERPRINT_FILE) | sha256sum | awk '{print $$1}'
|
||||
|
||||
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||
clean:
|
||||
rm -f $(FINGERPRINT_FILE)
|
||||
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/.gitignore
vendored
Normal file
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/app
|
||||
@ -0,0 +1,22 @@
|
||||
# This is the same as the net8-app image, but without the .deps.json file.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
COPY src/packages.lock.json .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode
|
||||
|
||||
# copy and publish app and libraries
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME --no-restore -o /app
|
||||
|
||||
# important!
|
||||
RUN rm /app/*.deps.json
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using static System.Console;
|
||||
|
||||
WriteLine("Runtime and Environment Information");
|
||||
|
||||
// OS and .NET information
|
||||
WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}");
|
||||
WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}");
|
||||
WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}");
|
||||
WriteLine();
|
||||
|
||||
// Environment information
|
||||
WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}");
|
||||
WriteLine($"HostName: {Dns.GetHostName()}");
|
||||
WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}");
|
||||
WriteLine();
|
||||
|
||||
// Memory information
|
||||
WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}");
|
||||
|
||||
string GetInBestUnit(long size)
|
||||
{
|
||||
const double Mebi = 1024 * 1024;
|
||||
const double Gibi = Mebi * 1024;
|
||||
|
||||
if (size < Mebi) return $"{size} bytes";
|
||||
if (size < Gibi) return $"{size / Mebi:F} MiB";
|
||||
return $"{size / Gibi:F} GiB";
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,459 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Humanizer": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.14.1, )",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core.af": "2.14.1",
|
||||
"Humanizer.Core.ar": "2.14.1",
|
||||
"Humanizer.Core.az": "2.14.1",
|
||||
"Humanizer.Core.bg": "2.14.1",
|
||||
"Humanizer.Core.bn-BD": "2.14.1",
|
||||
"Humanizer.Core.cs": "2.14.1",
|
||||
"Humanizer.Core.da": "2.14.1",
|
||||
"Humanizer.Core.de": "2.14.1",
|
||||
"Humanizer.Core.el": "2.14.1",
|
||||
"Humanizer.Core.es": "2.14.1",
|
||||
"Humanizer.Core.fa": "2.14.1",
|
||||
"Humanizer.Core.fi-FI": "2.14.1",
|
||||
"Humanizer.Core.fr": "2.14.1",
|
||||
"Humanizer.Core.fr-BE": "2.14.1",
|
||||
"Humanizer.Core.he": "2.14.1",
|
||||
"Humanizer.Core.hr": "2.14.1",
|
||||
"Humanizer.Core.hu": "2.14.1",
|
||||
"Humanizer.Core.hy": "2.14.1",
|
||||
"Humanizer.Core.id": "2.14.1",
|
||||
"Humanizer.Core.is": "2.14.1",
|
||||
"Humanizer.Core.it": "2.14.1",
|
||||
"Humanizer.Core.ja": "2.14.1",
|
||||
"Humanizer.Core.ko-KR": "2.14.1",
|
||||
"Humanizer.Core.ku": "2.14.1",
|
||||
"Humanizer.Core.lv": "2.14.1",
|
||||
"Humanizer.Core.ms-MY": "2.14.1",
|
||||
"Humanizer.Core.mt": "2.14.1",
|
||||
"Humanizer.Core.nb": "2.14.1",
|
||||
"Humanizer.Core.nb-NO": "2.14.1",
|
||||
"Humanizer.Core.nl": "2.14.1",
|
||||
"Humanizer.Core.pl": "2.14.1",
|
||||
"Humanizer.Core.pt": "2.14.1",
|
||||
"Humanizer.Core.ro": "2.14.1",
|
||||
"Humanizer.Core.ru": "2.14.1",
|
||||
"Humanizer.Core.sk": "2.14.1",
|
||||
"Humanizer.Core.sl": "2.14.1",
|
||||
"Humanizer.Core.sr": "2.14.1",
|
||||
"Humanizer.Core.sr-Latn": "2.14.1",
|
||||
"Humanizer.Core.sv": "2.14.1",
|
||||
"Humanizer.Core.th-TH": "2.14.1",
|
||||
"Humanizer.Core.tr": "2.14.1",
|
||||
"Humanizer.Core.uk": "2.14.1",
|
||||
"Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
|
||||
"Humanizer.Core.uz-Latn-UZ": "2.14.1",
|
||||
"Humanizer.Core.vi": "2.14.1",
|
||||
"Humanizer.Core.zh-CN": "2.14.1",
|
||||
"Humanizer.Core.zh-Hans": "2.14.1",
|
||||
"Humanizer.Core.zh-Hant": "2.14.1"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||
},
|
||||
"Humanizer.Core.af": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ar": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.az": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bg": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bn-BD": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.cs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.da": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.de": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.el": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.es": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fa": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fi-FI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr-BE": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.he": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hu": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hy": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.id": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.is": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.it": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ja": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ko-KR": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ku": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.lv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ms-MY": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.mt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb-NO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ro": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ru": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr-Latn": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.th-TH": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.tr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Cyrl-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Latn-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.vi": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-CN": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hans": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hant": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/app
|
||||
/extract.sh
|
||||
@ -0,0 +1,21 @@
|
||||
# This is the same as the net8-app image, however, the .NET runtime is copied to the target directory tree. There
|
||||
# is no bundling to include the runtime within a single archive/binary.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
COPY src/packages.lock.json .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode
|
||||
|
||||
# copy and publish app and libraries (self-contained!)
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME --self-contained --no-restore -o /app
|
||||
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using static System.Console;
|
||||
|
||||
WriteLine("Runtime and Environment Information");
|
||||
|
||||
// OS and .NET information
|
||||
WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}");
|
||||
WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}");
|
||||
WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}");
|
||||
WriteLine();
|
||||
|
||||
// Environment information
|
||||
WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}");
|
||||
WriteLine($"HostName: {Dns.GetHostName()}");
|
||||
WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}");
|
||||
WriteLine();
|
||||
|
||||
// Memory information
|
||||
WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}");
|
||||
|
||||
string GetInBestUnit(long size)
|
||||
{
|
||||
const double Mebi = 1024 * 1024;
|
||||
const double Gibi = Mebi * 1024;
|
||||
|
||||
if (size < Mebi) return $"{size} bytes";
|
||||
if (size < Gibi) return $"{size / Mebi:F} MiB";
|
||||
return $"{size / Gibi:F} GiB";
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,459 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Humanizer": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.14.1, )",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core.af": "2.14.1",
|
||||
"Humanizer.Core.ar": "2.14.1",
|
||||
"Humanizer.Core.az": "2.14.1",
|
||||
"Humanizer.Core.bg": "2.14.1",
|
||||
"Humanizer.Core.bn-BD": "2.14.1",
|
||||
"Humanizer.Core.cs": "2.14.1",
|
||||
"Humanizer.Core.da": "2.14.1",
|
||||
"Humanizer.Core.de": "2.14.1",
|
||||
"Humanizer.Core.el": "2.14.1",
|
||||
"Humanizer.Core.es": "2.14.1",
|
||||
"Humanizer.Core.fa": "2.14.1",
|
||||
"Humanizer.Core.fi-FI": "2.14.1",
|
||||
"Humanizer.Core.fr": "2.14.1",
|
||||
"Humanizer.Core.fr-BE": "2.14.1",
|
||||
"Humanizer.Core.he": "2.14.1",
|
||||
"Humanizer.Core.hr": "2.14.1",
|
||||
"Humanizer.Core.hu": "2.14.1",
|
||||
"Humanizer.Core.hy": "2.14.1",
|
||||
"Humanizer.Core.id": "2.14.1",
|
||||
"Humanizer.Core.is": "2.14.1",
|
||||
"Humanizer.Core.it": "2.14.1",
|
||||
"Humanizer.Core.ja": "2.14.1",
|
||||
"Humanizer.Core.ko-KR": "2.14.1",
|
||||
"Humanizer.Core.ku": "2.14.1",
|
||||
"Humanizer.Core.lv": "2.14.1",
|
||||
"Humanizer.Core.ms-MY": "2.14.1",
|
||||
"Humanizer.Core.mt": "2.14.1",
|
||||
"Humanizer.Core.nb": "2.14.1",
|
||||
"Humanizer.Core.nb-NO": "2.14.1",
|
||||
"Humanizer.Core.nl": "2.14.1",
|
||||
"Humanizer.Core.pl": "2.14.1",
|
||||
"Humanizer.Core.pt": "2.14.1",
|
||||
"Humanizer.Core.ro": "2.14.1",
|
||||
"Humanizer.Core.ru": "2.14.1",
|
||||
"Humanizer.Core.sk": "2.14.1",
|
||||
"Humanizer.Core.sl": "2.14.1",
|
||||
"Humanizer.Core.sr": "2.14.1",
|
||||
"Humanizer.Core.sr-Latn": "2.14.1",
|
||||
"Humanizer.Core.sv": "2.14.1",
|
||||
"Humanizer.Core.th-TH": "2.14.1",
|
||||
"Humanizer.Core.tr": "2.14.1",
|
||||
"Humanizer.Core.uk": "2.14.1",
|
||||
"Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
|
||||
"Humanizer.Core.uz-Latn-UZ": "2.14.1",
|
||||
"Humanizer.Core.vi": "2.14.1",
|
||||
"Humanizer.Core.zh-CN": "2.14.1",
|
||||
"Humanizer.Core.zh-Hans": "2.14.1",
|
||||
"Humanizer.Core.zh-Hant": "2.14.1"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||
},
|
||||
"Humanizer.Core.af": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ar": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.az": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bg": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bn-BD": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.cs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.da": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.de": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.el": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.es": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fa": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fi-FI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr-BE": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.he": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hu": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hy": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.id": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.is": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.it": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ja": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ko-KR": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ku": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.lv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ms-MY": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.mt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb-NO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ro": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ru": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr-Latn": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.th-TH": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.tr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Cyrl-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Latn-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.vi": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-CN": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hans": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hant": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/app
|
||||
/extract.sh
|
||||
@ -0,0 +1,20 @@
|
||||
# This is the same as the net8-app image, however, the entire .NET runtime is compiled into a single binary, residing with the application.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
COPY src/packages.lock.json .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode
|
||||
|
||||
# copy and publish app and libraries (single file)
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true --no-restore -o /app
|
||||
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace IndirectDependencyExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
string runtimeInfo = "hello world!\n";
|
||||
Console.WriteLine(runtimeInfo);
|
||||
|
||||
|
||||
Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}");
|
||||
|
||||
const string jsonString = @"
|
||||
{
|
||||
""message"": ""Hello from JSON!"",
|
||||
""details"": {
|
||||
""timestamp"": ""2025-03-26T12:00:00Z"",
|
||||
""version"": ""1.0.0"",
|
||||
""metadata"": {
|
||||
""author"": ""Claude"",
|
||||
""environment"": ""Development""
|
||||
}
|
||||
},
|
||||
""items"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""name"": ""Item One""
|
||||
},
|
||||
{
|
||||
""id"": 2,
|
||||
""name"": ""Item Two""
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
JObject jsonObject = JObject.Parse(jsonString);
|
||||
|
||||
string message = (string)jsonObject["message"];
|
||||
Console.WriteLine($"Message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,459 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Humanizer": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.14.1, )",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core.af": "2.14.1",
|
||||
"Humanizer.Core.ar": "2.14.1",
|
||||
"Humanizer.Core.az": "2.14.1",
|
||||
"Humanizer.Core.bg": "2.14.1",
|
||||
"Humanizer.Core.bn-BD": "2.14.1",
|
||||
"Humanizer.Core.cs": "2.14.1",
|
||||
"Humanizer.Core.da": "2.14.1",
|
||||
"Humanizer.Core.de": "2.14.1",
|
||||
"Humanizer.Core.el": "2.14.1",
|
||||
"Humanizer.Core.es": "2.14.1",
|
||||
"Humanizer.Core.fa": "2.14.1",
|
||||
"Humanizer.Core.fi-FI": "2.14.1",
|
||||
"Humanizer.Core.fr": "2.14.1",
|
||||
"Humanizer.Core.fr-BE": "2.14.1",
|
||||
"Humanizer.Core.he": "2.14.1",
|
||||
"Humanizer.Core.hr": "2.14.1",
|
||||
"Humanizer.Core.hu": "2.14.1",
|
||||
"Humanizer.Core.hy": "2.14.1",
|
||||
"Humanizer.Core.id": "2.14.1",
|
||||
"Humanizer.Core.is": "2.14.1",
|
||||
"Humanizer.Core.it": "2.14.1",
|
||||
"Humanizer.Core.ja": "2.14.1",
|
||||
"Humanizer.Core.ko-KR": "2.14.1",
|
||||
"Humanizer.Core.ku": "2.14.1",
|
||||
"Humanizer.Core.lv": "2.14.1",
|
||||
"Humanizer.Core.ms-MY": "2.14.1",
|
||||
"Humanizer.Core.mt": "2.14.1",
|
||||
"Humanizer.Core.nb": "2.14.1",
|
||||
"Humanizer.Core.nb-NO": "2.14.1",
|
||||
"Humanizer.Core.nl": "2.14.1",
|
||||
"Humanizer.Core.pl": "2.14.1",
|
||||
"Humanizer.Core.pt": "2.14.1",
|
||||
"Humanizer.Core.ro": "2.14.1",
|
||||
"Humanizer.Core.ru": "2.14.1",
|
||||
"Humanizer.Core.sk": "2.14.1",
|
||||
"Humanizer.Core.sl": "2.14.1",
|
||||
"Humanizer.Core.sr": "2.14.1",
|
||||
"Humanizer.Core.sr-Latn": "2.14.1",
|
||||
"Humanizer.Core.sv": "2.14.1",
|
||||
"Humanizer.Core.th-TH": "2.14.1",
|
||||
"Humanizer.Core.tr": "2.14.1",
|
||||
"Humanizer.Core.uk": "2.14.1",
|
||||
"Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
|
||||
"Humanizer.Core.uz-Latn-UZ": "2.14.1",
|
||||
"Humanizer.Core.vi": "2.14.1",
|
||||
"Humanizer.Core.zh-CN": "2.14.1",
|
||||
"Humanizer.Core.zh-Hans": "2.14.1",
|
||||
"Humanizer.Core.zh-Hant": "2.14.1"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||
},
|
||||
"Humanizer.Core.af": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ar": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.az": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bg": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bn-BD": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.cs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.da": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.de": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.el": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.es": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fa": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fi-FI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr-BE": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.he": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hu": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hy": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.id": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.is": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.it": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ja": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ko-KR": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ku": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.lv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ms-MY": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.mt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb-NO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ro": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ru": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr-Latn": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.th-TH": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.tr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Cyrl-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Latn-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.vi": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-CN": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hans": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hant": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/app
|
||||
/extract.sh
|
||||
@ -0,0 +1,82 @@
|
||||
# This represents a basic .NET project build where the project dependencies are downloaded and the project is built.
|
||||
# The output is a directory tree of DLLs, a project.lock.json (not used in these tests), a .deps.json file, and
|
||||
# a .runtimeconfig.json file (not used in these tests). With this deployment strategy there is no bundled runtime.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
COPY src/packages.lock.json .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode
|
||||
|
||||
# copy and publish app and libraries
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME --no-restore -o /app
|
||||
|
||||
# $ dotnet list package --include-transitive
|
||||
# Project 'dotnetapp' has the following package references
|
||||
# [net8.0]:
|
||||
# Top-level Package Requested Resolved
|
||||
# > Humanizer 2.14.1 2.14.1
|
||||
# > Newtonsoft.Json 13.0.3 13.0.3
|
||||
#
|
||||
# Transitive Package Resolved
|
||||
# > Humanizer.Core 2.14.1
|
||||
# > Humanizer.Core.af 2.14.1
|
||||
# > Humanizer.Core.ar 2.14.1
|
||||
# > Humanizer.Core.az 2.14.1
|
||||
# > Humanizer.Core.bg 2.14.1
|
||||
# > Humanizer.Core.bn-BD 2.14.1
|
||||
# > Humanizer.Core.cs 2.14.1
|
||||
# > Humanizer.Core.da 2.14.1
|
||||
# > Humanizer.Core.de 2.14.1
|
||||
# > Humanizer.Core.el 2.14.1
|
||||
# > Humanizer.Core.es 2.14.1
|
||||
# > Humanizer.Core.fa 2.14.1
|
||||
# > Humanizer.Core.fi-FI 2.14.1
|
||||
# > Humanizer.Core.fr 2.14.1
|
||||
# > Humanizer.Core.fr-BE 2.14.1
|
||||
# > Humanizer.Core.he 2.14.1
|
||||
# > Humanizer.Core.hr 2.14.1
|
||||
# > Humanizer.Core.hu 2.14.1
|
||||
# > Humanizer.Core.hy 2.14.1
|
||||
# > Humanizer.Core.id 2.14.1
|
||||
# > Humanizer.Core.is 2.14.1
|
||||
# > Humanizer.Core.it 2.14.1
|
||||
# > Humanizer.Core.ja 2.14.1
|
||||
# > Humanizer.Core.ko-KR 2.14.1
|
||||
# > Humanizer.Core.ku 2.14.1
|
||||
# > Humanizer.Core.lv 2.14.1
|
||||
# > Humanizer.Core.ms-MY 2.14.1
|
||||
# > Humanizer.Core.mt 2.14.1
|
||||
# > Humanizer.Core.nb 2.14.1
|
||||
# > Humanizer.Core.nb-NO 2.14.1
|
||||
# > Humanizer.Core.nl 2.14.1
|
||||
# > Humanizer.Core.pl 2.14.1
|
||||
# > Humanizer.Core.pt 2.14.1
|
||||
# > Humanizer.Core.ro 2.14.1
|
||||
# > Humanizer.Core.ru 2.14.1
|
||||
# > Humanizer.Core.sk 2.14.1
|
||||
# > Humanizer.Core.sl 2.14.1
|
||||
# > Humanizer.Core.sr 2.14.1
|
||||
# > Humanizer.Core.sr-Latn 2.14.1
|
||||
# > Humanizer.Core.sv 2.14.1
|
||||
# > Humanizer.Core.th-TH 2.14.1
|
||||
# > Humanizer.Core.tr 2.14.1
|
||||
# > Humanizer.Core.uk 2.14.1
|
||||
# > Humanizer.Core.uz-Cyrl-UZ 2.14.1
|
||||
# > Humanizer.Core.uz-Latn-UZ 2.14.1
|
||||
# > Humanizer.Core.vi 2.14.1
|
||||
# > Humanizer.Core.zh-CN 2.14.1
|
||||
# > Humanizer.Core.zh-Hans 2.14.1
|
||||
# > Humanizer.Core.zh-Hant 2.14.1
|
||||
|
||||
# lets pull in a file that is not related at all and in fact is not a .NET binary either (this should be ignored)
|
||||
RUN wget -O /app/jruby_windows_9_3_15_0.exe https://s3.amazonaws.com/jruby.org/downloads/9.3.15.0/jruby_windows_9_3_15_0.exe
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using static System.Console;
|
||||
|
||||
WriteLine("Runtime and Environment Information");
|
||||
|
||||
// OS and .NET information
|
||||
WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}");
|
||||
WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}");
|
||||
WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}");
|
||||
WriteLine();
|
||||
|
||||
// Environment information
|
||||
WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}");
|
||||
WriteLine($"HostName: {Dns.GetHostName()}");
|
||||
WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}");
|
||||
WriteLine();
|
||||
|
||||
// Memory information
|
||||
WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}");
|
||||
|
||||
string GetInBestUnit(long size)
|
||||
{
|
||||
const double Mebi = 1024 * 1024;
|
||||
const double Gibi = Mebi * 1024;
|
||||
|
||||
if (size < Mebi) return $"{size} bytes";
|
||||
if (size < Gibi) return $"{size / Mebi:F} MiB";
|
||||
return $"{size / Gibi:F} GiB";
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,459 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0": {
|
||||
"Humanizer": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.14.1, )",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core.af": "2.14.1",
|
||||
"Humanizer.Core.ar": "2.14.1",
|
||||
"Humanizer.Core.az": "2.14.1",
|
||||
"Humanizer.Core.bg": "2.14.1",
|
||||
"Humanizer.Core.bn-BD": "2.14.1",
|
||||
"Humanizer.Core.cs": "2.14.1",
|
||||
"Humanizer.Core.da": "2.14.1",
|
||||
"Humanizer.Core.de": "2.14.1",
|
||||
"Humanizer.Core.el": "2.14.1",
|
||||
"Humanizer.Core.es": "2.14.1",
|
||||
"Humanizer.Core.fa": "2.14.1",
|
||||
"Humanizer.Core.fi-FI": "2.14.1",
|
||||
"Humanizer.Core.fr": "2.14.1",
|
||||
"Humanizer.Core.fr-BE": "2.14.1",
|
||||
"Humanizer.Core.he": "2.14.1",
|
||||
"Humanizer.Core.hr": "2.14.1",
|
||||
"Humanizer.Core.hu": "2.14.1",
|
||||
"Humanizer.Core.hy": "2.14.1",
|
||||
"Humanizer.Core.id": "2.14.1",
|
||||
"Humanizer.Core.is": "2.14.1",
|
||||
"Humanizer.Core.it": "2.14.1",
|
||||
"Humanizer.Core.ja": "2.14.1",
|
||||
"Humanizer.Core.ko-KR": "2.14.1",
|
||||
"Humanizer.Core.ku": "2.14.1",
|
||||
"Humanizer.Core.lv": "2.14.1",
|
||||
"Humanizer.Core.ms-MY": "2.14.1",
|
||||
"Humanizer.Core.mt": "2.14.1",
|
||||
"Humanizer.Core.nb": "2.14.1",
|
||||
"Humanizer.Core.nb-NO": "2.14.1",
|
||||
"Humanizer.Core.nl": "2.14.1",
|
||||
"Humanizer.Core.pl": "2.14.1",
|
||||
"Humanizer.Core.pt": "2.14.1",
|
||||
"Humanizer.Core.ro": "2.14.1",
|
||||
"Humanizer.Core.ru": "2.14.1",
|
||||
"Humanizer.Core.sk": "2.14.1",
|
||||
"Humanizer.Core.sl": "2.14.1",
|
||||
"Humanizer.Core.sr": "2.14.1",
|
||||
"Humanizer.Core.sr-Latn": "2.14.1",
|
||||
"Humanizer.Core.sv": "2.14.1",
|
||||
"Humanizer.Core.th-TH": "2.14.1",
|
||||
"Humanizer.Core.tr": "2.14.1",
|
||||
"Humanizer.Core.uk": "2.14.1",
|
||||
"Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
|
||||
"Humanizer.Core.uz-Latn-UZ": "2.14.1",
|
||||
"Humanizer.Core.vi": "2.14.1",
|
||||
"Humanizer.Core.zh-CN": "2.14.1",
|
||||
"Humanizer.Core.zh-Hans": "2.14.1",
|
||||
"Humanizer.Core.zh-Hant": "2.14.1"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.3, )",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"Humanizer.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||
},
|
||||
"Humanizer.Core.af": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ar": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.az": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bg": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.bn-BD": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.cs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.da": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.de": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.el": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.es": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fa": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fi-FI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.fr-BE": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.he": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hu": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.hy": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.id": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.is": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.it": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ja": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ko-KR": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ku": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.lv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ms-MY": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.mt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nb-NO": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.nl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.pt": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ro": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.ru": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sr-Latn": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.sv": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.th-TH": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.tr": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uk": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Cyrl-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.uz-Latn-UZ": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.vi": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-CN": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hans": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
},
|
||||
"Humanizer.Core.zh-Hant": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.14.1",
|
||||
"contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "[2.14.1]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/app
|
||||
/extract.sh
|
||||
@ -0,0 +1,18 @@
|
||||
# This is the same as the net8-app image, but without the .deps.json file.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal
|
||||
|
||||
# copy and publish app and libraries
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME --no-restore -o /app
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
3
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/.gitignore
vendored
Normal file
3
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/bin
|
||||
/obj
|
||||
/app
|
||||
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace IndirectDependencyExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
string runtimeInfo = "hello world!\n";
|
||||
Console.WriteLine(runtimeInfo);
|
||||
|
||||
|
||||
Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}");
|
||||
|
||||
const string jsonString = @"
|
||||
{
|
||||
""message"": ""Hello from JSON!"",
|
||||
""details"": {
|
||||
""timestamp"": ""2025-03-26T12:00:00Z"",
|
||||
""version"": ""1.0.0"",
|
||||
""metadata"": {
|
||||
""author"": ""Claude"",
|
||||
""environment"": ""Development""
|
||||
}
|
||||
},
|
||||
""items"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""name"": ""Item One""
|
||||
},
|
||||
{
|
||||
""id"": 2,
|
||||
""name"": ""Item Two""
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
JObject jsonObject = JObject.Parse(jsonString);
|
||||
|
||||
string message = (string)jsonObject["message"];
|
||||
Console.WriteLine($"Message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ILRepackTargetConfigurations>Debug;Release</ILRepackTargetConfigurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" PrivateAssets="all" />
|
||||
<PackageReference Include="ILRepack.FullAuto" Version="1.6.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/app
|
||||
/extract.sh
|
||||
@ -0,0 +1,18 @@
|
||||
# This is the same as the net8-app image, but without the .deps.json file.
|
||||
FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build
|
||||
ARG RUNTIME=win-x64
|
||||
WORKDIR /src
|
||||
|
||||
# copy csproj and restore as distinct layers
|
||||
COPY src/*.csproj .
|
||||
RUN dotnet restore -r $RUNTIME --verbosity normal
|
||||
|
||||
# copy and publish app and libraries
|
||||
COPY src/ .
|
||||
RUN dotnet publish -r $RUNTIME --no-restore -o /app
|
||||
|
||||
FROM busybox
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
# just a nice to have for later...
|
||||
#COPY --from=build /src/packages.lock.json .
|
||||
3
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/.gitignore
vendored
Normal file
3
syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/bin
|
||||
/obj
|
||||
/app
|
||||
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace IndirectDependencyExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
string runtimeInfo = "hello world!\n";
|
||||
Console.WriteLine(runtimeInfo);
|
||||
|
||||
|
||||
Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}");
|
||||
|
||||
const string jsonString = @"
|
||||
{
|
||||
""message"": ""Hello from JSON!"",
|
||||
""details"": {
|
||||
""timestamp"": ""2025-03-26T12:00:00Z"",
|
||||
""version"": ""1.0.0"",
|
||||
""metadata"": {
|
||||
""author"": ""Claude"",
|
||||
""environment"": ""Development""
|
||||
}
|
||||
},
|
||||
""items"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""name"": ""Item One""
|
||||
},
|
||||
{
|
||||
""id"": 2,
|
||||
""name"": ""Item Two""
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
JObject jsonObject = JObject.Parse(jsonString);
|
||||
|
||||
string message = (string)jsonObject["message"];
|
||||
Console.WriteLine($"Message: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Humanizer" Version="2.14.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -7,6 +7,8 @@ type DotnetDepsEntry struct {
|
||||
Path string `mapstructure:"path" json:"path"`
|
||||
Sha512 string `mapstructure:"sha512" json:"sha512"`
|
||||
HashPath string `mapstructure:"hashPath" json:"hashPath"`
|
||||
|
||||
Executables map[string]DotnetPortableExecutableEntry `json:"executables,omitempty"`
|
||||
}
|
||||
|
||||
// DotnetPackagesLockEntry is a struct that represents a single entry found in the "dependencies" section in a .NET packages.lock.json file.
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
const (
|
||||
// this is the number of packages that should be found in the image-pkg-coverage fixture image
|
||||
// when analyzed with the squashed scope.
|
||||
coverageImageSquashedPackageCount = 30
|
||||
coverageImageSquashedPackageCount = 42
|
||||
)
|
||||
|
||||
func TestPackagesCmdFlags(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user