From 8e893dfc207addeadaa0112bd5d0818b619aaabb Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:19:49 -0400 Subject: [PATCH] feat: use originator logic to fill supplier (#1980) * feat: use Originator to fill supplier for NTIA minimum --------- Signed-off-by: Christopher Phillips --- .../common/spdxhelpers/to_format_model.go | 21 ++++++++++++++++-- .../spdxhelpers/to_format_model_test.go | 20 +++++++++++++++-- .../TestSPDXJSONDirectoryEncoder.golden | 3 +++ .../snapshot/TestSPDXJSONImageEncoder.golden | 3 +++ .../snapshot/TestSPDXRelationshipOrder.golden | 3 +++ .../stereoscope-fixture-image-simple.golden | Bin 15360 -> 15360 bytes .../snapshot/TestSPDXJSONSPDXIDs.golden | 4 ++++ .../snapshot/TestSPDXRelationshipOrder.golden | 3 +++ .../TestSPDXTagValueDirectoryEncoder.golden | 3 +++ .../TestSPDXTagValueImageEncoder.golden | 3 +++ .../stereoscope-fixture-image-simple.golden | Bin 15360 -> 15360 bytes 11 files changed, 59 insertions(+), 4 deletions(-) diff --git a/syft/formats/common/spdxhelpers/to_format_model.go b/syft/formats/common/spdxhelpers/to_format_model.go index 459f5b72872..9ad5a2d858a 100644 --- a/syft/formats/common/spdxhelpers/to_format_model.go +++ b/syft/formats/common/spdxhelpers/to_format_model.go @@ -240,9 +240,11 @@ func toRootPackage(s source.Description) *spdx.Package { PackageSPDXIdentifier: spdx.ElementID(SanitizeElementID(fmt.Sprintf("DocumentRoot-%s-%s", prefix, name))), PackageVersion: version, PackageChecksums: checksums, - PackageSupplier: nil, PackageExternalReferences: nil, PrimaryPackagePurpose: purpose, + PackageSupplier: &spdx.Supplier{ + Supplier: NOASSERTION, + }, } if purl != nil { @@ -357,7 +359,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag // 7.6: Package Originator: may have single result for either Person or Organization, // or NOASSERTION // Cardinality: optional, one - PackageSupplier: nil, + PackageSupplier: toPackageSupplier(p), PackageOriginator: toPackageOriginator(p), @@ -514,6 +516,21 @@ func toPackageOriginator(p pkg.Package) *spdx.Originator { } } +func toPackageSupplier(p pkg.Package) *spdx.Supplier { + // this uses the Originator function for now until + // a better distinction can be made for supplier + kind, supplier := Originator(p) + if kind == "" || supplier == "" { + return &spdx.Supplier{ + Supplier: NOASSERTION, + } + } + return &spdx.Supplier{ + Supplier: supplier, + SupplierType: kind, + } +} + func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference) { for _, ref := range ExternalRefs(p) { refs = append(refs, &spdx.PackageExternalReference{ diff --git a/syft/formats/common/spdxhelpers/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go index 2ca5809507e..311cd5c41ab 100644 --- a/syft/formats/common/spdxhelpers/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -51,12 +51,14 @@ func Test_toFormatModel(t *testing.T) { SPDXVersion: spdx.Version, DataLicense: spdx.DataLicense, DocumentName: "alpine", - Packages: []*spdx.Package{ { PackageSPDXIdentifier: "Package-pkg-1-pkg-1", PackageName: "pkg-1", PackageVersion: "version-1", + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, { PackageSPDXIdentifier: "DocumentRoot-Image-alpine", @@ -71,6 +73,9 @@ func Test_toFormatModel(t *testing.T) { Locator: "pkg:oci/alpine@sha256:d34db33f?arch=&tag=latest", }, }, + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, }, Relationships: []*spdx.Relationship{ @@ -122,12 +127,18 @@ func Test_toFormatModel(t *testing.T) { PackageSPDXIdentifier: "Package-pkg-1-pkg-1", PackageName: "pkg-1", PackageVersion: "version-1", + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, { PackageSPDXIdentifier: "DocumentRoot-Directory-some-directory", PackageName: "some/directory", PackageVersion: "", PrimaryPackagePurpose: "FILE", + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, }, Relationships: []*spdx.Relationship{ @@ -180,12 +191,14 @@ func Test_toFormatModel(t *testing.T) { SPDXVersion: spdx.Version, DataLicense: spdx.DataLicense, DocumentName: "path/to/some.file", - Packages: []*spdx.Package{ { PackageSPDXIdentifier: "Package-pkg-1-pkg-1", PackageName: "pkg-1", PackageVersion: "version-1", + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, { PackageSPDXIdentifier: "DocumentRoot-File-path-to-some.file", @@ -193,6 +206,9 @@ func Test_toFormatModel(t *testing.T) { PackageVersion: "sha256:d34db33f", PrimaryPackagePurpose: "FILE", PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}}, + PackageSupplier: &spdx.Supplier{ + Supplier: "NOASSERTION", + }, }, }, Relationships: []*spdx.Relationship{ diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden index d51b754ce11..7107f906ded 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONDirectoryEncoder.golden @@ -17,6 +17,7 @@ "name": "package-1", "SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a", "versionInfo": "1.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1", @@ -40,6 +41,7 @@ "name": "package-2", "SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3", "versionInfo": "2.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1", @@ -62,6 +64,7 @@ { "name": "some/path", "SPDXID": "SPDXRef-DocumentRoot-Directory-some-path", + "supplier": "NOASSERTION", "downloadLocation": "", "filesAnalyzed": false, "primaryPackagePurpose": "FILE" diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden index 49466ae7d44..955d910a63b 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXJSONImageEncoder.golden @@ -17,6 +17,7 @@ "name": "package-1", "SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "versionInfo": "1.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt", @@ -40,6 +41,7 @@ "name": "package-2", "SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4", "versionInfo": "2.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", @@ -63,6 +65,7 @@ "name": "user-image-input", "SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input", "versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", + "supplier": "NOASSERTION", "downloadLocation": "", "filesAnalyzed": false, "checksums": [ diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index 6ee33b0f41e..d6fff364e37 100644 --- a/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdxjson/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -17,6 +17,7 @@ "name": "package-1", "SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "versionInfo": "1.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt", @@ -40,6 +41,7 @@ "name": "package-2", "SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4", "versionInfo": "2.0.1", + "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt", @@ -63,6 +65,7 @@ "name": "user-image-input", "SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input", "versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", + "supplier": "NOASSERTION", "downloadLocation": "", "filesAnalyzed": false, "checksums": [ diff --git a/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxjson/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index c799acd2481e7b489335c6cd773fd8f81404ef55..d7e3532665c6533e289fb1ce3e04874b541b60ca 100644 GIT binary patch literal 15360 zcmeHOZExE)5ccQ&3QzkQ+k6*kU>~}sKnoO0(Pka6A}A=nBwB6BkmRC4kpI4;>?XFi z#7$+p=}IApMUgz7p3@!gPA5dYrphqkGKaL|IJJ}*ZiO_dp-vb>2t~|b#+f26MbU73r#ai*D9E*U3!Y6VM<5>pmFKTpP2$+O~PGJco5dUE>w*U`ML zMw8O$$*9iqV!YD6Y2J3UmljPpP0o&zKg;SvUYsB26)ZJ^WloCU=J~`X<6=IUT*I=J z(~X06LJ*+?lny99#d6HV7_(uj6y^-c^e>otk=IRGT>@`c+on;95hV(TY0DMfXD~B) za(vwP`NR$Iu=&*VM|C-MEA3zUS>WzN-n58Jr>m628c z|CN%UueR` z7*P%y4_3iqg}?%6%y1?$X0=ja2~v_W0`@>zCy~(92+19DglkY<09gXnB_*&eUR$fc zkXS*8l@1$+QZ10p1oPBU%>-B$pK&L=Q1Fj4#vDx%_EP!e?E3mZdhnwzBaKkRP)vpK znixq0RTxtSk`_`4osvv)EVQ;pDE`NepD&qcsAEvwN!^6&=&#qXB zbiM%so%MeQ{y!%h_Md>WmPY+Q#N%+4!#4t;OGLe z<8lfEZrgvo92u{XcPinVvt(=3azj@JBFm-wHw1Eg>d44i;#X6m~ zby65$VmYRbqe7T-lsug-Bc7w=#Z;d!8GTz$=2Hi*_+rpntOVRXE1K$ZR^}lDS&Uc< zo{o~2I&+id^bktjL~xH7j~&I84+k^^pASX!j!=0e)cWqim9C0EY9q;E$f4YMZ1MjX z^l$iom~a~H|DAyDu;V7-(dHR;NO*9~|NC0F zA2bhY=btX?L=v=%Z$TgueC=lCsXkxSlsB$&Wo^ot8~FTF_{d<8hmw~;olj>IHV z2pLT*u`#jzoz)jY6bZ(5(DYjVZ|?43N(o|2@TMN5NVwBst3|i3F{)}=!jRfZT7I~; z^0w3-x7GjK=O?cZkN^DIa1lm`Q@X78yF4HlF6kdLj(0k(b@H`A-Ie|9`tNN8sUkj02Vu?Ynu zN=95TrLYq=RC(#DHUlGwgk40t1fSw~ zfYBfnJ>Eofz7v)TH$GL_<%<4bSuYy57O4Hq7 zKZ9qRCJ!@tJ{#!UA{}M6x+{5Cl$S|Wied zx*nB{%4B9Z1u+F+9;8V=x(;sFVw77H($?_%sT|v~*X*Icc&+gN^z;2c2}ADv{}%yW zV?kA9)+MR!l3smXuTk&=Q7iP1;Ji26|A!$Q{dN$>!t4J5fE!Fsb>C@LQCPjJUpLs5 z7wr-72zUfM0#68mwuiM1#eYL0eEfGC)1Wif6G(o`5g{PbS%cO1&xJ8yt8BTXMd#2Er zwrsXfy(<*l?yelj0(0p<2QqmN|6m}~{38bfjF_xY$y?M&{FAaI!n`~g>+vCP>dSZ6smlJS&t60Q%Z2)h z`x`x5B*N?lWl_`)m@zj&Jsvi6nF&**f@&sl?qg!0?0ryONW%C)Dozm4!(wH%AQ6|2 zGY(!S0wyp87iFyi_X8fuNiBi-#dYYI1Ol03)(Ig%>P(Wu5eg&96mZIfiK*e1TCFWm zN-za}J9Ai5r($8i<3W#MOr+qBvWPeyDiVCS{ozPFE&N0#^TgSz{y%>FTf14s|6nY9 zzy(JW{)gv(ekOFXf|;#8hb?Yp+6Eh=3MZPz8&4RfxWG?d3~ zm1M)z4kCs)e0YLU6e(kfpiamrwsCCXvym7%4y81!dp?&E)( oQpbYao1j`@q0I{3+DO;HFu%QhnijMGr_Xo%vApigX diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden index 960fdc8ac49..2916b240f00 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXJSONSPDXIDs.golden @@ -12,6 +12,7 @@ Created: redacted PackageName: foobar/baz SPDXID: SPDXRef-DocumentRoot-Directory-foobar-baz +PackageSupplier: NOASSERTION PrimaryPackagePurpose: FILE FilesAnalyzed: false @@ -19,6 +20,7 @@ FilesAnalyzed: false PackageName: @at-sign SPDXID: SPDXRef-Package--at-sign-3732f7a5679bdec4 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from the following paths: @@ -30,6 +32,7 @@ PackageCopyrightText: NOASSERTION PackageName: some/slashes SPDXID: SPDXRef-Package-some-slashes-1345166d4801153b +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from the following paths: @@ -41,6 +44,7 @@ PackageCopyrightText: NOASSERTION PackageName: under_scores SPDXID: SPDXRef-Package-under-scores-290d5c77210978c1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from the following paths: diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden index e7bf336a6a2..ceda8d5eaf7 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXRelationshipOrder.golden @@ -51,6 +51,7 @@ LicenseConcluded: NOASSERTION PackageName: user-image-input SPDXID: SPDXRef-DocumentRoot-Image-user-image-input PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368 +PackageSupplier: NOASSERTION PrimaryPackagePurpose: CONTAINER FilesAnalyzed: false PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368 @@ -61,6 +62,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951 PackageName: package-2 SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4 PackageVersion: 2.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt @@ -75,6 +77,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1 PackageName: package-1 SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7 PackageVersion: 1.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden index 117f3c53b98..ff168e71fbb 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueDirectoryEncoder.golden @@ -12,6 +12,7 @@ Created: redacted PackageName: some/path SPDXID: SPDXRef-DocumentRoot-Directory-some-path +PackageSupplier: NOASSERTION PrimaryPackagePurpose: FILE FilesAnalyzed: false @@ -20,6 +21,7 @@ FilesAnalyzed: false PackageName: package-2 SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3 PackageVersion: 2.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1 @@ -34,6 +36,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1 PackageName: package-1 SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a PackageVersion: 1.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1 diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden index ae1be5286b9..34d428afcd9 100644 --- a/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden +++ b/syft/formats/spdxtagvalue/test-fixtures/snapshot/TestSPDXTagValueImageEncoder.golden @@ -13,6 +13,7 @@ Created: redacted PackageName: user-image-input SPDXID: SPDXRef-DocumentRoot-Image-user-image-input PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368 +PackageSupplier: NOASSERTION PrimaryPackagePurpose: CONTAINER FilesAnalyzed: false PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368 @@ -23,6 +24,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951 PackageName: package-2 SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4 PackageVersion: 2.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt @@ -37,6 +39,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1 PackageName: package-1 SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7 PackageVersion: 1.0.1 +PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt diff --git a/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden b/syft/formats/spdxtagvalue/test-fixtures/snapshot/stereoscope-fixture-image-simple.golden index f4aa1e7bb74e9d8c2cf36a69cd7a6d910a9a7087..d7e3532665c6533e289fb1ce3e04874b541b60ca 100644 GIT binary patch literal 15360 zcmeHOZExE)5ccQ&3QzkQ+k6*kU>~}sKnoO0(Pka6A}A=nBwB6BkmRC4kpI4;>?XFi z#7$+p=}IApMUgz7p3@!gPA5dYrphqkGKaL|IJJ}*ZiO_dp-vb>2t~|b#+f26MbU73r#ai*D9E*U3!Y6VM<5>pmFKTpP2$+O~PGJco5dUE>w*U`ML zMw8O$$*9iqV!YD6Y2J3UmljPpP0o&zKg;SvUYsB26)ZJ^WloCU=J~`X<6=IUT*I=J z(~X06LJ*+?lny99#d6HV7_(uj6y^-c^e>otk=IRGT>@`c+on;95hV(TY0DMfXD~B) za(vwP`NR$Iu=&*VM|C-MEA3zUS>WzN-n58Jr>m628c z|CN%UueR` z7*P%y4_3iqg}?%6%y1?$X0=ja2~v_W0`@>zCy~(92+19DglkY<09gXnB_*&eUR$fc zkXS*8l@1$+QZ10p1oPBU%>-B$pK&L=Q1Fj4#vDx%_EP!e?E3mZdhnwzBaKkRP)vpK znixq0RTxtSk`_`4osvv)EVQ;pDE`NepD&qcsAEvwN!^6&=&#qXB zbiM%so%MeQ{y!%h_Md>WmPY+Q#N%+4!#4t;OGLe z<8lfEZrgvo92u{XcPinVvt(=3azj@JBFm-wHw1Eg>d44i;#X6m~ zby65$VmYRbqe7T-lsug-Bc7w=#Z;d!8GTz$=2Hi*_+rpntOVRXE1K$ZR^}lDS&Uc< zo{o~2I&+id^bktjL~xH7j~&I84+k^^pASX!j!=0e)cWqim9C0EY9q;E$f4YMZ1MjX z^l$iom~a~H|DAyDu;V7-(dHR;NO*9~|NC0F zA2bhY=btX?L=v=%Z$TgueC=lCsXkxSlsB$&Wo^ot8~FTF_{d<8hmw~;olj>IH>+Ccv_P>8ZPoz`f`H;B(PB%6Bo_$`|Mwlqj$^w^ zv_!U(q7)36*F*9p$TkutO) zGMZRoBVzjp%@0BhNzM<@{Mr1kQV%d=1aU4!RSrUg9&~uK==LQhMX7S=Qd>!zgKH~q zPxWzI{eL<6^!dZ-?@x6XVT1%@n|i;G19Egj|CkGW&}prcr#f|?_oM5-w-=P}(f0pu z41w16|4)&n4P$Dsf6U3|`i~hV-u@e4-AeW}NK7yY+D?PuFc_=crX|SYM54%0$w{n2 zA$c5QCv6l*1`9{9V-`8eonl!GO+a+Yy<@!;(r!4pXVHvrP9}* zOOvF*z^P$j4})b|Hi5xxCfXdTE)vhmyJh%vY}0i)b?h|N(1}I)lk{saNGHSLVeoDQ zW19y*jMUYlqhGS&WMqr`oWHXCCP}Z3lSOY&(lWmrXGsO}yzXiG+awt-MSWDU9WJWR zYGmF%9_PUyM`!PU?N5rlKg_fm_KP@42P^Sq^dX~8nnxiRT)YI(KFo~gBC2wIos?FW zle|)y8X3VrOa_<-!z7*D2D7agd{D0oFwgZ>em_ZItqO5x~tfH0O`|0@9QFgVwJr+SOR+q?LAhfR6W z9s!SlN5CWSh7f3bSlf~RmlEmyzx$Xbt@Xbf|1TBX@BjCqf9(Cg`+)h)w$a-D+xvf! zAb9ip55chh@8!$H5B~jU;s0$r4Jt?M(r1e!iMj~G?+l?q{qvsMr#!W<=s;|%_#OHG zF!SGk_VUhH`v352`v1f?{yzr%55NBJehQS=NztjCfSqeqEZu;+Z`Zfr5%36j1Uv$- zi-4k9V52c*hB%62hPmd55@sb288OmDSP;mHR2VT4T8<+cQi@{g3{|ez{``jt+dTgv z#+iTr?_*|6gGWdW4{Vy6x3VRk#u}%kqF}61SSr%W@)X&~{DGZIndQL2IiEIFLsDSM zIj@hQ^A;{+s2Lm`AJ@B|ZoU2O^w+n;onJa}MX_G{j?ClF>~2I(3k`lv+s^j zvk#-)VjClsCe9Y+|5?Xh+H4d5gLAsV1xGdi!}C91iuUvB!P@z5!J7=WvKIjB|76G6 znYx;kQrQ`x@|-v75Nb*#yq21SE~R=bAQM!E%t`E2$!Eoco+AMpry L1Uv%Y4FdlHH$Ie?