From 0976ad828ad478fb7cc35e1f275a4e227c327cf7 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 13 Jan 2023 08:54:15 +1300 Subject: [PATCH] feat: support parsing PyPi `Pipfile.lock` lockfiles (#166) --- README.md | 1 + main_test.go | 1 + pkg/lockfile/ecosystems_test.go | 8 +- pkg/lockfile/fixtures/pipenv/empty.json | 20 +++ pkg/lockfile/fixtures/pipenv/no-version.json | 26 ++++ pkg/lockfile/fixtures/pipenv/not-json.txt | 1 + .../fixtures/pipenv/one-package-dev.json | 67 +++++++++ pkg/lockfile/fixtures/pipenv/one-package.json | 67 +++++++++ .../fixtures/pipenv/two-packages-alt.json | 75 ++++++++++ .../fixtures/pipenv/two-packages.json | 76 ++++++++++ pkg/lockfile/parse-pipenv-lock.go | 65 +++++++++ pkg/lockfile/parse-pipenv-lock_test.go | 136 ++++++++++++++++++ pkg/lockfile/parse.go | 1 + pkg/lockfile/parse_test.go | 2 + 14 files changed, 543 insertions(+), 3 deletions(-) create mode 100644 pkg/lockfile/fixtures/pipenv/empty.json create mode 100644 pkg/lockfile/fixtures/pipenv/no-version.json create mode 100644 pkg/lockfile/fixtures/pipenv/not-json.txt create mode 100644 pkg/lockfile/fixtures/pipenv/one-package-dev.json create mode 100644 pkg/lockfile/fixtures/pipenv/one-package.json create mode 100644 pkg/lockfile/fixtures/pipenv/two-packages-alt.json create mode 100644 pkg/lockfile/fixtures/pipenv/two-packages.json create mode 100644 pkg/lockfile/parse-pipenv-lock.go create mode 100644 pkg/lockfile/parse-pipenv-lock_test.go diff --git a/README.md b/README.md index c4d30492..6c42de5c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ The detector supports parsing the following lockfiles: | `gradle.lockfile` | `Maven` | `gradle` | | `mix.lock` | `Hex` | `mix` | | `poetry.lock` | `PyPI` | `poetry` | +| `Pipfile.lock` | `PyPI` | `pipenv` | | `pubspec.lock` | `Pub` | `pub` | | `pom.xml`\* | `Maven` | `maven` | | `requirements.txt`\* | `PyPI` | `pip` | diff --git a/main_test.go b/main_test.go index 5ef4c65e..f2c63ad5 100644 --- a/main_test.go +++ b/main_test.go @@ -140,6 +140,7 @@ func TestRun(t *testing.T) { mix.lock package-lock.json packages.lock.json + Pipfile.lock pnpm-lock.yaml poetry.lock pom.xml diff --git a/pkg/lockfile/ecosystems_test.go b/pkg/lockfile/ecosystems_test.go index f745e38e..ca20ae0f 100644 --- a/pkg/lockfile/ecosystems_test.go +++ b/pkg/lockfile/ecosystems_test.go @@ -33,9 +33,11 @@ func TestKnownEcosystems(t *testing.T) { expectedCount := numberOfLockfileParsers(t) - // npm, yarn, and pnpm, and pip and poetry, and maven and gradle, all - // use the same ecosystem so "ignore" those parsers in the count - expectedCount -= 4 + // - npm, yarn, and pnpm, + // - pip, poetry, and pipenv, + // - maven and gradle, + // all use the same ecosystem so "ignore" those parsers in the count + expectedCount -= 5 ecosystems := lockfile.KnownEcosystems() diff --git a/pkg/lockfile/fixtures/pipenv/empty.json b/pkg/lockfile/fixtures/pipenv/empty.json new file mode 100644 index 00000000..dc24d3e9 --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/empty.json @@ -0,0 +1,20 @@ +{ + "_meta": { + "hash": { + "sha256": "7f7606f08e0544d8d012ef4d097dabdd6df6843a28793eb6551245d4b2db4242" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": {} +} diff --git a/pkg/lockfile/fixtures/pipenv/no-version.json b/pkg/lockfile/fixtures/pipenv/no-version.json new file mode 100644 index 00000000..0b572f3a --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/no-version.json @@ -0,0 +1,26 @@ +{ + "_meta": { + "hash": { + "sha256": "6689fbb19241c5123bd89c9a39c1289cfb133a8298b57731af508066a9622ec7" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "markupsafe": { + "editable": true, + "git": "https://github.com/pallets/markupsafe", + "ref": "b36054111bc1e8bbadb5d0d60158feb72926f467" + } + }, + "develop": {} +} diff --git a/pkg/lockfile/fixtures/pipenv/not-json.txt b/pkg/lockfile/fixtures/pipenv/not-json.txt new file mode 100644 index 00000000..3ae3a213 --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/not-json.txt @@ -0,0 +1 @@ +this is not json! diff --git a/pkg/lockfile/fixtures/pipenv/one-package-dev.json b/pkg/lockfile/fixtures/pipenv/one-package-dev.json new file mode 100644 index 00000000..ddbd0b8f --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/one-package-dev.json @@ -0,0 +1,67 @@ +{ + "_meta": { + "hash": { + "sha256": "3231c8267be08eae5fb3173e9569d9c42c78821d6bbd9ad91f14b50ab541280a" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + } + } +} diff --git a/pkg/lockfile/fixtures/pipenv/one-package.json b/pkg/lockfile/fixtures/pipenv/one-package.json new file mode 100644 index 00000000..ba099231 --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/one-package.json @@ -0,0 +1,67 @@ +{ + "_meta": { + "hash": { + "sha256": "3231c8267be08eae5fb3173e9569d9c42c78821d6bbd9ad91f14b50ab541280a" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + } + }, + "develop": {} +} diff --git a/pkg/lockfile/fixtures/pipenv/two-packages-alt.json b/pkg/lockfile/fixtures/pipenv/two-packages-alt.json new file mode 100644 index 00000000..a7237e19 --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/two-packages-alt.json @@ -0,0 +1,75 @@ +{ + "_meta": { + "hash": { + "sha256": "3809f002b3b58ea51dc4258d4e00b7ebae6b28c46d7cb25ab8d4540a7c5f0d7b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "index": "pypi", + "version": "==2.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + } + }, + "develop": {} +} diff --git a/pkg/lockfile/fixtures/pipenv/two-packages.json b/pkg/lockfile/fixtures/pipenv/two-packages.json new file mode 100644 index 00000000..a9d37fb0 --- /dev/null +++ b/pkg/lockfile/fixtures/pipenv/two-packages.json @@ -0,0 +1,76 @@ +{ + "_meta": { + "hash": { + "sha256": "0233fe866c2c839807e391fd3b91553a8a60798c72d33a420b8edb6cbd88882a" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "index": "pypi", + "version": "==2.1.2" + } + }, + "develop": { + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + } + } +} diff --git a/pkg/lockfile/parse-pipenv-lock.go b/pkg/lockfile/parse-pipenv-lock.go new file mode 100644 index 00000000..ddc03d6c --- /dev/null +++ b/pkg/lockfile/parse-pipenv-lock.go @@ -0,0 +1,65 @@ +package lockfile + +import ( + "encoding/json" + "fmt" + "os" +) + +type PipenvPackage struct { + Version string `json:"version"` +} + +type PipenvLock struct { + Packages map[string]PipenvPackage `json:"default"` + PackagesDev map[string]PipenvPackage `json:"develop"` +} + +const PipenvEcosystem = PipEcosystem + +func ParsePipenvLock(pathToLockfile string) ([]PackageDetails, error) { + var parsedLockfile *PipenvLock + + lockfileContents, err := os.ReadFile(pathToLockfile) + + if err != nil { + return []PackageDetails{}, fmt.Errorf("could not read %s: %w", pathToLockfile, err) + } + + err = json.Unmarshal(lockfileContents, &parsedLockfile) + + if err != nil { + return []PackageDetails{}, fmt.Errorf("could not parse %s: %w", pathToLockfile, err) + } + + packages := make( + []PackageDetails, + 0, + // len cannot return negative numbers, but the types can't reflect that + uint64(len(parsedLockfile.Packages))+uint64(len(parsedLockfile.PackagesDev)), + ) + + for name, pipenvPackage := range parsedLockfile.Packages { + if pipenvPackage.Version == "" { + continue + } + + packages = append(packages, PackageDetails{ + Name: name, + Version: pipenvPackage.Version[2:], + Ecosystem: PipenvEcosystem, + CompareAs: PipenvEcosystem, + }) + } + + for name, pipenvPackage := range parsedLockfile.PackagesDev { + packages = append(packages, PackageDetails{ + Name: name, + Version: pipenvPackage.Version[2:], + Ecosystem: PipenvEcosystem, + CompareAs: PipenvEcosystem, + }) + } + + return packages, nil +} diff --git a/pkg/lockfile/parse-pipenv-lock_test.go b/pkg/lockfile/parse-pipenv-lock_test.go new file mode 100644 index 00000000..ca7ffb88 --- /dev/null +++ b/pkg/lockfile/parse-pipenv-lock_test.go @@ -0,0 +1,136 @@ +package lockfile_test + +import ( + "github.com/g-rath/osv-detector/pkg/lockfile" + "testing" +) + +func TestParsePipenvLock_FileDoesNotExist(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/does-not-exist") + + expectErrContaining(t, err, "could not read") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParsePipenvLock_InvalidJson(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/not-json.txt") + + expectErrContaining(t, err, "could not parse") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParsePipenvLock_NoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/empty.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParsePipenvLock_OnePackage(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/one-package.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "markupsafe", + Version: "2.1.1", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + }) +} + +func TestParsePipenvLock_OnePackageDev(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/one-package-dev.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "markupsafe", + Version: "2.1.1", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + }) +} + +func TestParsePipenvLock_TwoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/two-packages.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "itsdangerous", + Version: "2.1.2", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + { + Name: "markupsafe", + Version: "2.1.1", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + }) +} + +func TestParsePipenvLock_TwoPackagesAlt(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/two-packages-alt.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "itsdangerous", + Version: "2.1.2", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + { + Name: "markupsafe", + Version: "2.1.1", + Ecosystem: lockfile.PipenvEcosystem, + CompareAs: lockfile.PipenvEcosystem, + }, + }) +} + +func TestParsePipenvLock_PackageWithoutVersion(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePipenvLock("fixtures/pipenv/no-version.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{}) +} diff --git a/pkg/lockfile/parse.go b/pkg/lockfile/parse.go index 6b24c9ec..a9d985f3 100644 --- a/pkg/lockfile/parse.go +++ b/pkg/lockfile/parse.go @@ -27,6 +27,7 @@ var parsers = map[string]PackageDetailsParser{ "packages.lock.json": ParseNuGetLock, "pnpm-lock.yaml": ParsePnpmLock, "poetry.lock": ParsePoetryLock, + "Pipfile.lock": ParsePipenvLock, "pom.xml": ParseMavenLock, "pubspec.lock": ParsePubspecLock, "requirements.txt": ParseRequirementsTxt, diff --git a/pkg/lockfile/parse_test.go b/pkg/lockfile/parse_test.go index 241d037c..7806722a 100644 --- a/pkg/lockfile/parse_test.go +++ b/pkg/lockfile/parse_test.go @@ -54,6 +54,7 @@ func TestFindParser(t *testing.T) { "pom.xml", "poetry.lock", "pubspec.lock", + "Pipfile.lock", "requirements.txt", } @@ -101,6 +102,7 @@ func TestParse_FindsExpectedParsers(t *testing.T) { "mix.lock", "pom.xml", "poetry.lock", + "Pipfile.lock", "pubspec.lock", "requirements.txt", }