diff --git a/README.md b/README.md index 861d1b6122..7c9e9c9d2e 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ A wide range of lockfiles are supported by utilizing this [lockfile package](htt - `buildscript-gradle.lockfile` - `Cargo.lock` - `composer.lock` +- `conan.lock` - `Gemfile.lock` - `go.mod` - `gradle.lockfile` diff --git a/internal/semantic/parse.go b/internal/semantic/parse.go index 99c57c0687..871fc03157 100644 --- a/internal/semantic/parse.go +++ b/internal/semantic/parse.go @@ -41,6 +41,8 @@ func Parse(str string, ecosystem Ecosystem) (Version, error) { return parsePyPIVersion(str), nil case "Pub": return parseSemverVersion(str), nil + case "ConanCenter": + return parseSemverVersion(str), nil } return nil, fmt.Errorf("%w %s", ErrUnsupportedEcosystem, ecosystem) diff --git a/pkg/lockfile/ecosystems.go b/pkg/lockfile/ecosystems.go index b4f456da9c..861373af80 100644 --- a/pkg/lockfile/ecosystems.go +++ b/pkg/lockfile/ecosystems.go @@ -12,6 +12,7 @@ func KnownEcosystems() []Ecosystem { MavenEcosystem, PipEcosystem, PubEcosystem, + ConanEcosystem, // Disabled temporarily, // see https://github.com/google/osv-scanner/pull/128 discussion for additional context // AlpineEcosystem, diff --git a/pkg/lockfile/fixtures/conan/empty.v1.json b/pkg/lockfile/fixtures/conan/empty.v1.json new file mode 100644 index 0000000000..0ff352b3a5 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/empty.v1.json @@ -0,0 +1,15 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False", + "path": "conanfile.py", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/empty.v1.revisions.json b/pkg/lockfile/fixtures/conan/empty.v1.revisions.json new file mode 100644 index 0000000000..c550aa5266 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/empty.v1.revisions.json @@ -0,0 +1,15 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False", + "path": "conanfile.py", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/empty.v2.json b/pkg/lockfile/fixtures/conan/empty.v2.json new file mode 100644 index 0000000000..e97a2242ee --- /dev/null +++ b/pkg/lockfile/fixtures/conan/empty.v2.json @@ -0,0 +1,6 @@ +{ + "version": "0.5", + "requires": [], + "build_requires": [], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/nested-dependencies.v1.json b/pkg/lockfile/fixtures/conan/nested-dependencies.v1.json new file mode 100644 index 0000000000..2974db6f57 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/nested-dependencies.v1.json @@ -0,0 +1,62 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nbrotli:enable_debug=False\nbrotli:enable_log=False\nbrotli:enable_portable=False\nbrotli:enable_rbit=True\nbrotli:endianness=None\nbrotli:fPIC=True\nbrotli:shared=False\nbrotli:target_bits=None\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nfreetype:fPIC=True\nfreetype:shared=False\nfreetype:subpixel=False\nfreetype:with_brotli=True\nfreetype:with_bzip2=True\nfreetype:with_png=True\nfreetype:with_zlib=True\nlibpng:api_prefix=\nlibpng:fPIC=True\nlibpng:shared=False\nlibpng:sse=True\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "freetype/2.12.1", + "options": "fPIC=True\nshared=False\nsubpixel=False\nwith_brotli=True\nwith_bzip2=True\nwith_png=True\nwith_zlib=True\nbrotli:enable_debug=False\nbrotli:enable_log=False\nbrotli:enable_portable=False\nbrotli:enable_rbit=True\nbrotli:endianness=None\nbrotli:fPIC=True\nbrotli:shared=False\nbrotli:target_bits=None\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nlibpng:api_prefix=\nlibpng:fPIC=True\nlibpng:shared=False\nlibpng:sse=True\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "bca7b8880d98719d556dd526ce612be20a815922", + "prev": "0", + "requires": [ + "2", + "3", + "4", + "5" + ], + "context": "host" + }, + "2": { + "ref": "libpng/1.6.39", + "options": "api_prefix=\nfPIC=True\nshared=False\nsse=True\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "d5b3dc27faecfb4eb94086722000dd65bb9e6bff", + "prev": "0", + "requires": [ + "3" + ], + "context": "host" + }, + "3": { + "ref": "zlib/1.2.13", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "0", + "context": "host" + }, + "4": { + "ref": "bzip2/1.0.8", + "options": "build_executable=True\nfPIC=True\nshared=False", + "package_id": "91a8b22c2c5a149bc617cfc06cdd21bf23b12567", + "prev": "0", + "context": "host" + }, + "5": { + "ref": "brotli/1.0.9", + "options": "enable_debug=False\nenable_log=False\nenable_portable=False\nenable_rbit=True\nendianness=None\nfPIC=True\nshared=False\ntarget_bits=None", + "package_id": "bfdbb855937046dc347fec082c59cb7f733e8855", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/nested-dependencies.v1.revisions.json b/pkg/lockfile/fixtures/conan/nested-dependencies.v1.revisions.json new file mode 100644 index 0000000000..12165b1f19 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/nested-dependencies.v1.revisions.json @@ -0,0 +1,62 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nbrotli:enable_debug=False\nbrotli:enable_log=False\nbrotli:enable_portable=False\nbrotli:enable_rbit=True\nbrotli:endianness=None\nbrotli:fPIC=True\nbrotli:shared=False\nbrotli:target_bits=None\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nfreetype:fPIC=True\nfreetype:shared=False\nfreetype:subpixel=False\nfreetype:with_brotli=True\nfreetype:with_bzip2=True\nfreetype:with_png=True\nfreetype:with_zlib=True\nlibpng:api_prefix=\nlibpng:fPIC=True\nlibpng:shared=False\nlibpng:sse=True\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "freetype/2.12.1#7e1b67634f54f38a979bbad44fd09a2c", + "options": "fPIC=True\nshared=False\nsubpixel=False\nwith_brotli=True\nwith_bzip2=True\nwith_png=True\nwith_zlib=True\nbrotli:enable_debug=False\nbrotli:enable_log=False\nbrotli:enable_portable=False\nbrotli:enable_rbit=True\nbrotli:endianness=None\nbrotli:fPIC=True\nbrotli:shared=False\nbrotli:target_bits=None\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nlibpng:api_prefix=\nlibpng:fPIC=True\nlibpng:shared=False\nlibpng:sse=True\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "bca7b8880d98719d556dd526ce612be20a815922", + "prev": "400c9a65b20f791ea05c47eb6817e80a", + "requires": [ + "2", + "3", + "4", + "5" + ], + "context": "host" + }, + "2": { + "ref": "libpng/1.6.39#7927e8ce5b2576a6ea497c6ca70e9751", + "options": "api_prefix=\nfPIC=True\nshared=False\nsse=True\nzlib:fPIC=True\nzlib:shared=False", + "package_id": "d5b3dc27faecfb4eb94086722000dd65bb9e6bff", + "prev": "3e3b7f79b03c52ab932089560ea2eb56", + "requires": [ + "3" + ], + "context": "host" + }, + "3": { + "ref": "zlib/1.2.13#13c96f538b52e1600c40b88994de240f", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "562e6cc3d7987119418780e5c5697342", + "context": "host" + }, + "4": { + "ref": "bzip2/1.0.8#464be69744fa6d48ed01928cfe470008", + "options": "build_executable=True\nfPIC=True\nshared=False", + "package_id": "91a8b22c2c5a149bc617cfc06cdd21bf23b12567", + "prev": "94d2f51be78e63879215a3b2ba014fda", + "context": "host" + }, + "5": { + "ref": "brotli/1.0.9#4bfbb302b87df342ccd6a2b5fdad307a", + "options": "enable_debug=False\nenable_log=False\nenable_portable=False\nenable_rbit=True\nendianness=None\nfPIC=True\nshared=False\ntarget_bits=None", + "package_id": "bfdbb855937046dc347fec082c59cb7f733e8855", + "prev": "c2eaa7784f2988c35d8b8925a783e73b", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/nested-dependencies.v2.json b/pkg/lockfile/fixtures/conan/nested-dependencies.v2.json new file mode 100644 index 0000000000..87b2bbdf65 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/nested-dependencies.v2.json @@ -0,0 +1,12 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.2.13#13c96f538b52e1600c40b88994de240f%1667396813.733", + "libpng/1.6.39#7927e8ce5b2576a6ea497c6ca70e9751%1669038072.946", + "freetype/2.12.1#7e1b67634f54f38a979bbad44fd09a2c%1669913185.923", + "bzip2/1.0.8#464be69744fa6d48ed01928cfe470008%1666580345.213", + "brotli/1.0.9#4bfbb302b87df342ccd6a2b5fdad307a%1661519995.45" + ], + "build_requires": [], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/no-name.v1.json b/pkg/lockfile/fixtures/conan/no-name.v1.json new file mode 100644 index 0000000000..f7494eeddb --- /dev/null +++ b/pkg/lockfile/fixtures/conan/no-name.v1.json @@ -0,0 +1,32 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1", "2" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "0", + "context": "host" + }, + "2": { + "ref": "1.2.3", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/no-name.v1.revisions.json b/pkg/lockfile/fixtures/conan/no-name.v1.revisions.json new file mode 100644 index 0000000000..5b13d8e2f7 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/no-name.v1.revisions.json @@ -0,0 +1,32 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1", "2" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11#ffa77daf83a57094149707928bdce823", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "a636df1594de20e55e1c393ffb1eb166", + "context": "host" + }, + "2": { + "ref": "1.2.3#ffa77daf83a57094149707928bdce823", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "a636df1594de20e55e1c393ffb1eb166", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/no-name.v2.json b/pkg/lockfile/fixtures/conan/no-name.v2.json new file mode 100644 index 0000000000..eca29891a1 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/no-name.v2.json @@ -0,0 +1,9 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.2.11#ffa77daf83a57094149707928bdce823%1667396813.184", + "1.2.3#ffa77daf83a57094149707928bdce823%1667396813.184" + ], + "build_requires": [], + "python_requires": [] +} diff --git a/pkg/lockfile/fixtures/conan/not-json.txt b/pkg/lockfile/fixtures/conan/not-json.txt new file mode 100644 index 0000000000..3ae3a213d5 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/not-json.txt @@ -0,0 +1 @@ +this is not json! diff --git a/pkg/lockfile/fixtures/conan/old-format-0.0.json b/pkg/lockfile/fixtures/conan/old-format-0.0.json new file mode 100644 index 0000000000..fcbbcacf7d --- /dev/null +++ b/pkg/lockfile/fixtures/conan/old-format-0.0.json @@ -0,0 +1,18 @@ +{ + "profile": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n", + "graph_lock": { + "nodes": { + "05b715be-7ec7-11ed-8a66-b537134a228d": { + "pref": null, + "options": "zlib:fPIC=True\nzlib:minizip=False\nzlib:shared=False", + "requires": { + "zlib/1.2.11@bincrafters/testing#5f4917ce0a630b102f472afd00102d40": "05b715bd-7ec7-11ed-8a66-b537134a228d" + } + }, + "05b715bd-7ec7-11ed-8a66-b537134a228d": { + "pref": "zlib/1.2.11@bincrafters/testing#5f4917ce0a630b102f472afd00102d40:19729b9559f3ae196cad45cb2b97468ccb75dcd1#58846c4ed127f63e9c88c5be5190a6d9", + "options": "fPIC=True\nminizip=False\nshared=False" + } + } + } + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/old-format-0.1.json b/pkg/lockfile/fixtures/conan/old-format-0.1.json new file mode 100644 index 0000000000..b9b41e43af --- /dev/null +++ b/pkg/lockfile/fixtures/conan/old-format-0.1.json @@ -0,0 +1,20 @@ +{ + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n", + "graph_lock": { + "nodes": { + "1058d05a-7ec6-11ed-8a66-b537134a228d": { + "pref": "test/1.0:2ce08bf790c58b729dda567426e810ed5e35e513", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:minizip=False\nzlib:shared=False", + "requires": { + "zlib/1.2.11#5f4917ce0a630b102f472afd00102d40": "1058d059-7ec6-11ed-8a66-b537134a228d" + }, + "path": "/home/sse4/projects/conan_test/v1/conanfile.py" + }, + "1058d059-7ec6-11ed-8a66-b537134a228d": { + "pref": "zlib/1.2.11#5f4917ce0a630b102f472afd00102d40:19729b9559f3ae196cad45cb2b97468ccb75dcd1#58846c4ed127f63e9c88c5be5190a6d9", + "options": "fPIC=True\nminizip=False\nshared=False" + } + } + }, + "version": "0.1" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/old-format-0.2.json b/pkg/lockfile/fixtures/conan/old-format-0.2.json new file mode 100644 index 0000000000..e62dbf77b5 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/old-format-0.2.json @@ -0,0 +1,20 @@ +{ + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n", + "graph_lock": { + "nodes": { + "0": { + "pref": "test/1.0:2ce08bf790c58b729dda567426e810ed5e35e513", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:minizip=False\nzlib:shared=False", + "requires": { + "zlib/1.2.11#5f4917ce0a630b102f472afd00102d40": "1" + }, + "path": "/home/sse4/projects/conan_test/v1/conanfile.py" + }, + "1": { + "pref": "zlib/1.2.11#5f4917ce0a630b102f472afd00102d40:19729b9559f3ae196cad45cb2b97468ccb75dcd1#58846c4ed127f63e9c88c5be5190a6d9", + "options": "fPIC=True\nminizip=False\nshared=False" + } + } + }, + "version": "0.2" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/old-format-0.3.json b/pkg/lockfile/fixtures/conan/old-format-0.3.json new file mode 100644 index 0000000000..fe9136bc43 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/old-format-0.3.json @@ -0,0 +1,21 @@ +{ + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n", + "graph_lock": { + "nodes": { + "0": { + "pref": "test/1.0:2ce08bf790c58b729dda567426e810ed5e35e513", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:minizip=False\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "/home/sse4/projects/conan_test/v1/conanfile.py" + }, + "1": { + "pref": "zlib/1.2.11#5f4917ce0a630b102f472afd00102d40:19729b9559f3ae196cad45cb2b97468ccb75dcd1#58846c4ed127f63e9c88c5be5190a6d9", + "options": "fPIC=True\nminizip=False\nshared=False", + "modified": "built" + } + } + }, + "version": "0.3" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package-dev.v1.json b/pkg/lockfile/fixtures/conan/one-package-dev.v1.json new file mode 100644 index 0000000000..d99f9bebd8 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package-dev.v1.json @@ -0,0 +1,25 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False", + "build_requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "ninja/1.11.1", + "options": "", + "package_id": "24647d9fe8ec489125dfbae4b3ebefaf7581674c", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package-dev.v1.revisions.json b/pkg/lockfile/fixtures/conan/one-package-dev.v1.revisions.json new file mode 100644 index 0000000000..9247e54225 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package-dev.v1.revisions.json @@ -0,0 +1,25 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False", + "build_requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "ninja/1.11.1#a2f0b832705907016f336839f96963f8", + "options": "", + "package_id": "24647d9fe8ec489125dfbae4b3ebefaf7581674c", + "prev": "d894345ae9996c9b97f1cb4150051c25", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package-dev.v2.json b/pkg/lockfile/fixtures/conan/one-package-dev.v2.json new file mode 100644 index 0000000000..e806573b01 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package-dev.v2.json @@ -0,0 +1,8 @@ +{ + "version": "0.5", + "requires": [], + "build_requires": [ + "ninja/1.11.1#a2f0b832705907016f336839f96963f8%1667050636.338" + ], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package.v1.json b/pkg/lockfile/fixtures/conan/one-package.v1.json new file mode 100644 index 0000000000..f5b9602d35 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package.v1.json @@ -0,0 +1,25 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package.v1.revisions.json b/pkg/lockfile/fixtures/conan/one-package.v1.revisions.json new file mode 100644 index 0000000000..755d9548f2 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package.v1.revisions.json @@ -0,0 +1,25 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11#ffa77daf83a57094149707928bdce823", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "a636df1594de20e55e1c393ffb1eb166", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/one-package.v2.json b/pkg/lockfile/fixtures/conan/one-package.v2.json new file mode 100644 index 0000000000..40895065cd --- /dev/null +++ b/pkg/lockfile/fixtures/conan/one-package.v2.json @@ -0,0 +1,8 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.2.11#ffa77daf83a57094149707928bdce823%1667396813.184" + ], + "build_requires": [], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/two-packages.v1.json b/pkg/lockfile/fixtures/conan/two-packages.v1.json new file mode 100644 index 0000000000..6fad91e1dc --- /dev/null +++ b/pkg/lockfile/fixtures/conan/two-packages.v1.json @@ -0,0 +1,33 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1", + "2" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "0", + "context": "host" + }, + "2": { + "ref": "bzip2/1.0.8", + "options": "build_executable=True\nfPIC=True\nshared=False", + "package_id": "91a8b22c2c5a149bc617cfc06cdd21bf23b12567", + "prev": "0", + "context": "host" + } + }, + "revisions_enabled": false + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/two-packages.v1.revisions.json b/pkg/lockfile/fixtures/conan/two-packages.v1.revisions.json new file mode 100644 index 0000000000..3306ffc6fd --- /dev/null +++ b/pkg/lockfile/fixtures/conan/two-packages.v1.revisions.json @@ -0,0 +1,33 @@ +{ + "graph_lock": { + "nodes": { + "0": { + "ref": "test/1.0", + "options": "fPIC=True\nshared=False\nbzip2:build_executable=True\nbzip2:fPIC=True\nbzip2:shared=False\nzlib:fPIC=True\nzlib:shared=False", + "requires": [ + "1", + "2" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "zlib/1.2.11#ffa77daf83a57094149707928bdce823", + "options": "fPIC=True\nshared=False", + "package_id": "19729b9559f3ae196cad45cb2b97468ccb75dcd1", + "prev": "a636df1594de20e55e1c393ffb1eb166", + "context": "host" + }, + "2": { + "ref": "bzip2/1.0.8#464be69744fa6d48ed01928cfe470008", + "options": "build_executable=True\nfPIC=True\nshared=False", + "package_id": "91a8b22c2c5a149bc617cfc06cdd21bf23b12567", + "prev": "94d2f51be78e63879215a3b2ba014fda", + "context": "host" + } + }, + "revisions_enabled": true + }, + "version": "0.4", + "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=10\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" + } \ No newline at end of file diff --git a/pkg/lockfile/fixtures/conan/two-packages.v2.json b/pkg/lockfile/fixtures/conan/two-packages.v2.json new file mode 100644 index 0000000000..6d9aa3db20 --- /dev/null +++ b/pkg/lockfile/fixtures/conan/two-packages.v2.json @@ -0,0 +1,9 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.2.11#ffa77daf83a57094149707928bdce823%1667396813.184", + "bzip2/1.0.8#464be69744fa6d48ed01928cfe470008%1666580345.213" + ], + "build_requires": [], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/lockfile/parse-conan-lock-v1-revisions_test.go b/pkg/lockfile/parse-conan-lock-v1-revisions_test.go new file mode 100644 index 0000000000..5d649fb9d6 --- /dev/null +++ b/pkg/lockfile/parse-conan-lock-v1-revisions_test.go @@ -0,0 +1,162 @@ +package lockfile_test + +import ( + "testing" + + "github.com/google/osv-scanner/pkg/lockfile" +) + +func TestParseConanLock_v1_revisions_FileDoesNotExist(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/does-not-exist") + + expectErrContaining(t, err, "could not read") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_revisions_InvalidJson(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/not-json.txt") + + expectErrContaining(t, err, "could not parse") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_revisions_NoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/empty.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_revisions_OnePackage(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_revisions_NoName(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/no-name.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_revisions_TwoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/two-packages.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_revisions_NestedDependencies(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/nested-dependencies.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.13", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "freetype", + Version: "2.12.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "libpng", + Version: "1.6.39", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "brotli", + Version: "1.0.9", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_revisions_OnePackageDev(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package-dev.v1.revisions.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "ninja", + Version: "1.11.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} diff --git a/pkg/lockfile/parse-conan-lock-v1_test.go b/pkg/lockfile/parse-conan-lock-v1_test.go new file mode 100644 index 0000000000..e3bc280d15 --- /dev/null +++ b/pkg/lockfile/parse-conan-lock-v1_test.go @@ -0,0 +1,238 @@ +package lockfile_test + +import ( + "testing" + + "github.com/google/osv-scanner/pkg/lockfile" +) + +func TestParseConanLock_v1_FileDoesNotExist(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/does-not-exist") + + expectErrContaining(t, err, "could not read") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_InvalidJson(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/not-json.txt") + + expectErrContaining(t, err, "could not parse") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_NoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/empty.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v1_OnePackage(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_NoName(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/no-name.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_TwoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/two-packages.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_NestedDependencies(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/nested-dependencies.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.13", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "freetype", + Version: "2.12.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "libpng", + Version: "1.6.39", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "brotli", + Version: "1.0.9", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_OnePackageDev(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package-dev.v1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "ninja", + Version: "1.11.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_OldFormat00(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/old-format-0.0.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_OldFormat01(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/old-format-0.1.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_OldFormat02(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/old-format-0.2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v1_OldFormat03(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/old-format-0.3.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} diff --git a/pkg/lockfile/parse-conan-lock-v2_test.go b/pkg/lockfile/parse-conan-lock-v2_test.go new file mode 100644 index 0000000000..873157bd30 --- /dev/null +++ b/pkg/lockfile/parse-conan-lock-v2_test.go @@ -0,0 +1,162 @@ +package lockfile_test + +import ( + "testing" + + "github.com/google/osv-scanner/pkg/lockfile" +) + +func TestParseConanLock_v2_FileDoesNotExist(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/does-not-exist") + + expectErrContaining(t, err, "could not read") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v2_InvalidJson(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/not-json.txt") + + expectErrContaining(t, err, "could not parse") + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v2_NoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/empty.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{}) +} + +func TestParseConanLock_v2_OnePackage(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v2_NoName(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/no-name.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v2_TwoPackages(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/two-packages.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.11", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v2_NestedDependencies(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/nested-dependencies.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "zlib", + Version: "1.2.13", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "bzip2", + Version: "1.0.8", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "freetype", + Version: "2.12.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "libpng", + Version: "1.6.39", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + { + Name: "brotli", + Version: "1.0.9", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} + +func TestParseConanLock_v2_OnePackageDev(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParseConanLock("fixtures/conan/one-package-dev.v2.json") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "ninja", + Version: "1.11.1", + Ecosystem: lockfile.ConanEcosystem, + CompareAs: lockfile.ConanEcosystem, + }, + }) +} diff --git a/pkg/lockfile/parse-conan-lock.go b/pkg/lockfile/parse-conan-lock.go new file mode 100644 index 0000000000..c10fcbac80 --- /dev/null +++ b/pkg/lockfile/parse-conan-lock.go @@ -0,0 +1,188 @@ +package lockfile + +import ( + "encoding/json" + "fmt" + "os" + "strings" +) + +type ConanReference struct { + Name string + Version string + Username string + Channel string + RecipeRevision string + PackageID string + PackageRevision string + TimeStamp string +} + +type ConanGraphNode struct { + Pref string `json:"pref"` + Ref string `json:"ref"` + Options string `json:"options"` + PackageId string `json:"package_id"` + Prev string `json:"prev"` + Path string `json:"path"` + Context string `json:"context"` +} + +type ConanGraphLock struct { + Nodes map[string]ConanGraphNode `json:"nodes"` +} + +type ConanLockFile struct { + Version string `json:"version"` + // conan v0.4- lockfiles use "graph_lock", "profile_host" and "profile_build" + GraphLock ConanGraphLock `json:"graph_lock,omitempty"` + ProfileHost string `json:"profile_host,omitempty"` + ProfileBuild string `json:"profile_build,omitempty"` + // conan v0.5+ lockfiles use "requires", "build_requires" and "python_requires" + Requires []string `json:"requires,omitempty"` + BuildRequires []string `json:"build_requires,omitempty"` + PythonRequires []string `json:"python_requires,omitempty"` +} + +// TODO this is tentative and subject to change depending on the OSV schema +const ConanEcosystem Ecosystem = "ConanCenter" + +func parseConanRenference(ref string) ConanReference { + // very flexible format name/version[@username[/channel]][#rrev][:pkgid[#prev]][%timestamp] + var reference ConanReference + + parts := strings.SplitN(ref, "%", 2) + if len(parts) == 2 { + ref = parts[0] + reference.TimeStamp = parts[1] + } + + parts = strings.SplitN(ref, ":", 2) + if len(parts) == 2 { + ref = parts[0] + parts = strings.SplitN(parts[1], "#", 2) + reference.PackageID = parts[0] + if len(parts) == 2 { + reference.PackageRevision = parts[1] + } + } + + parts = strings.SplitN(ref, "#", 2) + if len(parts) == 2 { + ref = parts[0] + reference.RecipeRevision = parts[1] + } + + parts = strings.SplitN(ref, "@", 2) + if len(parts) == 2 { + ref = parts[0] + UsernameChannel := parts[1] + + parts = strings.SplitN(UsernameChannel, "/", 2) + reference.Username = parts[0] + if len(parts) == 2 { + reference.Channel = parts[1] + } + } + + parts = strings.SplitN(ref, "/", 2) + if len(parts) == 2 { + reference.Name = parts[0] + reference.Version = parts[1] + } else { + // consumer conanfile.txt or conanfile.py might not have a name + reference.Name = "" + reference.Version = ref + } + return reference +} + +func parseConanV1Lock(lockfile ConanLockFile) []PackageDetails { + var reference ConanReference + packages := make([]PackageDetails, 0, len(lockfile.GraphLock.Nodes)) + + for _, node := range lockfile.GraphLock.Nodes { + if node.Path != "" { + // a local "conanfile.txt", skip + continue + } + + if node.Pref != "" { + // old format 0.3 (conan 1.27-) lockfiles use "pref" instead of "ref" + reference = parseConanRenference(node.Pref) + } else if node.Ref != "" { + reference = parseConanRenference(node.Ref) + } else { + continue + } + // skip entries with no name, they are most likely consumer's conanfiles + // and not dependencies to be searched in a database anyway + if reference.Name == "" { + continue + } + packages = append(packages, PackageDetails{ + Name: reference.Name, + Version: reference.Version, + Ecosystem: ConanEcosystem, + CompareAs: ConanEcosystem, + }) + } + + return packages +} + +func parseConanRequires(packages *[]PackageDetails, requires []string) { + for _, ref := range requires { + reference := parseConanRenference(ref) + // skip entries with no name, they are most likely consumer's conanfiles + // and not dependencies to be searched in a database anyway + if reference.Name == "" { + continue + } + + *packages = append(*packages, PackageDetails{ + Name: reference.Name, + Version: reference.Version, + Ecosystem: ConanEcosystem, + CompareAs: ConanEcosystem, + }) + } +} + +func parseConanV2Lock(lockfile ConanLockFile) []PackageDetails { + packages := make( + []PackageDetails, + 0, + uint64(len(lockfile.Requires))+uint64(len(lockfile.BuildRequires))+uint64(len(lockfile.PythonRequires)), + ) + + parseConanRequires(&packages, lockfile.Requires) + parseConanRequires(&packages, lockfile.BuildRequires) + parseConanRequires(&packages, lockfile.PythonRequires) + + return packages +} + +func parseConanLock(lockfile ConanLockFile) []PackageDetails { + if lockfile.GraphLock.Nodes != nil { + return parseConanV1Lock(lockfile) + } + + return parseConanV2Lock(lockfile) +} + +func ParseConanLock(pathToLockfile string) ([]PackageDetails, error) { + var parsedLockfile *ConanLockFile + + 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) + } + + return parseConanLock(*parsedLockfile), nil +} diff --git a/pkg/lockfile/parse.go b/pkg/lockfile/parse.go index 3a34b225a2..0fe9da497a 100644 --- a/pkg/lockfile/parse.go +++ b/pkg/lockfile/parse.go @@ -21,6 +21,7 @@ var parsers = map[string]PackageDetailsParser{ "buildscript-gradle.lockfile": ParseGradleLock, "Cargo.lock": ParseCargoLock, "composer.lock": ParseComposerLock, + "conan.lock": ParseConanLock, "Gemfile.lock": ParseGemfileLock, "go.mod": ParseGoLock, "gradle.lockfile": ParseGradleLock, diff --git a/pkg/lockfile/parse_test.go b/pkg/lockfile/parse_test.go index 9bf9cad312..1d74be8f86 100644 --- a/pkg/lockfile/parse_test.go +++ b/pkg/lockfile/parse_test.go @@ -93,6 +93,7 @@ func TestParse_FindsExpectedParsers(t *testing.T) { "buildscript-gradle.lockfile", "Cargo.lock", "composer.lock", + "conan.lock", "Gemfile.lock", "go.mod", "gradle.lockfile",