diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt index b1af4522a9d0e..cf24794ccc78f 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssConfig.kt @@ -25,11 +25,11 @@ import org.ossreviewtoolkit.plugins.api.OrtPluginOption import org.ossreviewtoolkit.plugins.api.Secret data class ScanOssConfig( - /** The URL of the ScanOSS server. */ + /** The URL of the SCANOSS server. */ @OrtPluginOption(defaultValue = ScanApi.DEFAULT_BASE_URL) val apiUrl: String, - /** The API key used to authenticate with the ScanOSS server. */ + /** The API key used to authenticate with the SCANOSS server. */ @OrtPluginOption(defaultValue = "") val apiKey: Secret, diff --git a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt index 9522a0b3e0016..c46917fe8aeaa 100644 --- a/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt +++ b/plugins/scanners/scanoss/src/main/kotlin/ScanOssResultParser.kt @@ -22,9 +22,13 @@ package org.ossreviewtoolkit.plugins.scanners.scanoss import com.scanoss.dto.ScanFileDetails import com.scanoss.dto.ScanFileResult import com.scanoss.dto.enums.MatchType +import com.scanoss.dto.enums.StatusType +import java.lang.invoke.MethodHandles import java.time.Instant +import org.apache.logging.log4j.kotlin.loggerOf + import org.ossreviewtoolkit.downloader.VcsHost import org.ossreviewtoolkit.model.CopyrightFinding import org.ossreviewtoolkit.model.LicenseFinding @@ -38,6 +42,8 @@ import org.ossreviewtoolkit.utils.spdx.SpdxExpression import org.ossreviewtoolkit.utils.spdx.SpdxLicenseIdExpression import org.ossreviewtoolkit.utils.spdx.toExpression +private val logger = loggerOf(MethodHandles.lookup().lookupClass()) + /** * Generate a summary from the given SCANOSS [result], using [startTime], [endTime] as metadata. This variant can be * used if the result is not read from a local file. @@ -51,21 +57,19 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List result.fileDetails.forEach { details -> when (details.matchType) { MatchType.file -> { - licenseFindings += getLicenseFindings(details) - copyrightFindings += getCopyrightFindings(details) + val localFile = requireNotNull(result.filePath) + licenseFindings += getLicenseFindings(details, localFile) + copyrightFindings += getCopyrightFindings(details, localFile) } MatchType.snippet -> { - val file = requireNotNull(details.file) - val lines = requireNotNull(details.lines) - val sourceLocations = convertLines(file, lines) - val snippets = getSnippets(details) - - snippets.forEach { snippet -> - sourceLocations.forEach { sourceLocation -> - // TODO: Aggregate the snippet by source file location. - snippetFindings += SnippetFinding(sourceLocation, setOf(snippet)) - } + val localFile = requireNotNull(result.filePath) + if (details.status == StatusType.pending) { + snippetFindings += createSnippetFindings(details, localFile) + } else { + logger.info { "File '$localFile' is identified, not including in snippet findings." } + licenseFindings += getLicenseFindings(details, result.filePath) + copyrightFindings += getCopyrightFindings(details, result.filePath) } } @@ -90,8 +94,7 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List /** * Get the license findings from the given [details]. */ -private fun getLicenseFindings(details: ScanFileDetails): List { - val path = details.file ?: return emptyList() +private fun getLicenseFindings(details: ScanFileDetails, path: String): List { val score = details.matched?.removeSuffix("%")?.toFloatOrNull() return details.licenseDetails.orEmpty().map { license -> @@ -118,9 +121,7 @@ private fun getLicenseFindings(details: ScanFileDetails): List { /** * Get the copyright findings from the given [details]. */ -private fun getCopyrightFindings(details: ScanFileDetails): List { - val path = details.file ?: return emptyList() - +private fun getCopyrightFindings(details: ScanFileDetails, path: String): List { return details.copyrightDetails.orEmpty().map { copyright -> CopyrightFinding( statement = copyright.name, @@ -134,39 +135,57 @@ private fun getCopyrightFindings(details: ScanFileDetails): List { +private fun createSnippetFindings(details: ScanFileDetails, localFilePath: String): Set { val matched = requireNotNull(details.matched) - val fileUrl = requireNotNull(details.fileUrl) + val ossFile = requireNotNull(details.file) val ossLines = requireNotNull(details.ossLines) + val localLines = requireNotNull(details.lines) val url = requireNotNull(details.url) - val purls = requireNotNull(details.purls) - - val licenses = details.licenseDetails.orEmpty().mapTo(mutableSetOf()) { license -> - SpdxExpression.parse(license.name) - } + val purls = requireNotNull(details.purls).toMutableList() val score = matched.substringBeforeLast("%").toFloat() - val locations = convertLines(fileUrl, ossLines) + val primaryPurl = purls.removeFirstOrNull().orEmpty() + + val license = details.licenseDetails.orEmpty() + .map { license -> SpdxExpression.parse(license.name) } + .toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) + // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ? val vcsInfo = VcsHost.parseUrl(url.takeUnless { it == "none" }.orEmpty()) val provenance = RepositoryProvenance(vcsInfo, ".") - return buildSet { - purls.forEach { purl -> - locations.forEach { snippetLocation -> - val license = licenses.toExpression()?.sorted() ?: SpdxLicenseIdExpression(SpdxConstants.NOASSERTION) + val additionalData = purls.associateWith { "" } - add(Snippet(score, snippetLocation, provenance, purl, license)) - } + // Convert both local and OSS line ranges to source locations. + val sourceLocations = convertLines(localFilePath, localLines) + val ossLocations = convertLines(ossFile, ossLines) + + // The number of source locations should match the number of oss locations. + if (sourceLocations.size != ossLocations.size) { + logger.warn { + "Unexpected mismatch in '$localFilePath': " + + "${sourceLocations.size} source locations vs ${ossLocations.size} oss locations. " + + "This indicates a potential issue with line range conversion." } } + + // Directly pair source locations with their corresponding OSS locations and create a SnippetFinding. + return sourceLocations.zip(ossLocations).mapTo(mutableSetOf()) { (sourceLocation, ossLocation) -> + SnippetFinding( + sourceLocation, + setOf( + Snippet(score, ossLocation, provenance, primaryPurl, license, additionalData) + ) + ) + } } /** - * Split [lineRanges] returned by ScanOSS such as "32-105,117-199" into [TextLocation]s for the given [file]. + * Split [lineRanges] returned by SCANOSS such as "32-105,117-199" into [TextLocation]s for the given [file]. */ private fun convertLines(file: String, lineRanges: String): List = lineRanges.split(',').map { lineRange -> diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt index bb8716529bc20..662c807ec891d 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssResultParserTest.kt @@ -27,6 +27,7 @@ import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.collections.haveSize import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.should +import io.kotest.matchers.shouldBe import java.time.Instant @@ -38,7 +39,9 @@ import org.ossreviewtoolkit.model.SnippetFinding import org.ossreviewtoolkit.model.TextLocation import org.ossreviewtoolkit.model.VcsInfo import org.ossreviewtoolkit.model.VcsType +import org.ossreviewtoolkit.utils.spdx.SpdxConstants import org.ossreviewtoolkit.utils.spdx.SpdxExpression +import org.ossreviewtoolkit.utils.spdx.toSpdx import org.ossreviewtoolkit.utils.test.readResource class ScanOssResultParserTest : WordSpec({ @@ -63,7 +66,8 @@ class ScanOssResultParserTest : WordSpec({ summary.licenseFindings shouldContain LicenseFinding( license = "Apache-2.0", location = TextLocation( - path = "hopscotch-rails-0.1.2.1/vendor/assets/javascripts/hopscotch.js", + path = "/tmp/ort-ScanOss2759786101559527642/Maven/junit/junit/4.12/src/site/resources/" + + "scripts/hopscotch-0.1.2.min.js", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ), @@ -74,7 +78,8 @@ class ScanOssResultParserTest : WordSpec({ summary.copyrightFindings shouldContain CopyrightFinding( statement = "Copyright 2013 LinkedIn Corp.", location = TextLocation( - path = "hopscotch-rails-0.1.2.1/vendor/assets/javascripts/hopscotch.js", + path = "/tmp/ort-ScanOss2759786101559527642/Maven/junit/junit/4.12/src/site/resources/" + + "scripts/hopscotch-0.1.2.min.js", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ) @@ -101,7 +106,7 @@ class ScanOssResultParserTest : WordSpec({ summary.licenseFindings shouldContain LicenseFinding( license = "Apache-2.0", location = TextLocation( - path = "com/vdurmont/semver4j/Range.java", + path = "src/main/java/com/vdurmont/semver4j/Range.java", startLine = TextLocation.UNKNOWN_LINE, endLine = TextLocation.UNKNOWN_LINE ), @@ -116,7 +121,7 @@ class ScanOssResultParserTest : WordSpec({ Snippet( 98.0f, TextLocation( - "https://osskb.org/api/file_contents/6ff2427335b985212c9b79dfa795799f", + "src/main/java/com/vdurmont/semver4j/Requirement.java", 1, 710 ), @@ -131,5 +136,107 @@ class ScanOssResultParserTest : WordSpec({ ) ) } + + "handle multiple PURLs by extracting first as primary and storing remaining in additionalData" { + val results = readResource("/scanoss-multiple-purls.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify we have one finding per source location, not per PURL. + summary.snippetFindings should haveSize(2) + + with(summary.snippetFindings.first()) { + // Check source location (local file). + sourceLocation shouldBe TextLocation("hung_task.c", 12, 150) + + // Verify first PURL is extracted as primary identifier. + snippets should haveSize(1) + snippets.first().purl shouldBe "pkg:github/kdrag0n/proton_bluecross" + + // Verify remaining PURLs are stored in additionalData. + snippets.first().additionalData shouldBe + mapOf( + "pkg:github/fake/fake_repository" to "" + ) + + // Check OSS location. + snippets.first().location shouldBe + TextLocation("kernel/hung_task.c", 10, 148) + } + + // Verify same behavior for second snippet. + with(summary.snippetFindings.last()) { + sourceLocation shouldBe TextLocation("hung_task.c", 540, 561) + snippets.first().purl shouldBe "pkg:github/kdrag0n/proton_bluecross" + snippets.first().location shouldBe + TextLocation("kernel/hung_task.c", 86, 107) + } + } + + "combine the same license from different sources into a single expression" { + // When the same license appears in multiple sources (like scancode and file_header), + // combine them into a single expression rather than duplicating. + val results = readResource("/scanoss-snippet-same-license-multiple-sources.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify the snippet finding. + summary.snippetFindings should haveSize(1) + val snippet = summary.snippetFindings.first().snippets.first() + + // Consolidate the license into a single expression + // even though it came from both "scancode" and "file_header" sources. + snippet.license shouldBe "LGPL-2.1-or-later".toSpdx() + + // Preserve other snippet details correctly. + with(summary.snippetFindings.first()) { + sourceLocation.path shouldBe "src/check_error.c" + sourceLocation.startLine shouldBe 16 + sourceLocation.endLine shouldBe 24 + } + } + + "handle empty license array with NOASSERTION" { + val results = readResource("/scanoss-snippet-no-license-data.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Verify the snippet finding. + summary.snippetFindings should haveSize(1) + val snippet = summary.snippetFindings.first().snippets.first() + + // Use NOASSERTION when no licenses are provided. + snippet.license shouldBe SpdxConstants.NOASSERTION.toSpdx() + + // Preserve other snippet details correctly. + with(summary.snippetFindings.first()) { + sourceLocation.path shouldBe "fake_file.c" + sourceLocation.startLine shouldBe 16 + sourceLocation.endLine shouldBe 24 + } + } + + "exclude identified snippets from snippet findings" { + // The scanoss-identified-snippet.json contains two snippets, but one is identified. + // Only unidentified snippets should be included in the SnippetFindings. + val results = readResource("/scanoss-identified-snippet.json").let { + JsonUtils.toScanFileResultsFromObject(JsonUtils.toJsonObject(it)) + } + + val time = Instant.now() + val summary = generateSummary(time, time, results) + + // Should have only one finding because the identified snippet is excluded + summary.snippetFindings should haveSize(1) + } } }) diff --git a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt index 6a1a134bc7df7..acbf4247a27b8 100644 --- a/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt +++ b/plugins/scanners/scanoss/src/test/kotlin/ScanOssScannerDirectoryTest.kt @@ -103,7 +103,7 @@ class ScanOssScannerDirectoryTest : StringSpec({ Snippet( 99.0f, TextLocation( - "https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095", + "examples/example.rules.kts", 128, 367 ), @@ -158,7 +158,7 @@ class ScanOssScannerDirectoryTest : StringSpec({ // [fingerprint data for the file] // --boundary-- - // Extract included filenames using a regex pattern from the ScanOSS HTTP POST. + // Extract included filenames using a regex pattern from the SCANOSS HTTP POST. // The pattern matches lines starting with "file=" followed by hash and size, then captures the filename. val filenamePattern = "file=.*?,.*?,(.+)".toRegex(RegexOption.MULTILINE) val includedFiles = requestBodies.flatMap { body -> diff --git a/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json b/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json index 317e7ae388d1e..d722c62243dae 100644 --- a/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json +++ b/plugins/scanners/scanoss/src/test/resources/scanMulti/mappings/scanoss-multi-response.json @@ -10,7 +10,7 @@ }, "response" : { "status" : 200, - "body" : "{ \"utils/src/main/kotlin/random-data-05-06-11.kt\": [ { \"id\": \"snippet\", \"status\": \"pending\", \"lines\": \"1-240\", \"oss_lines\": \"128-367\", \"matched\": \"99%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"utils/src/main/kotlin/random-data-05-06-11.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"871fb0c5188c2f620d9b997e225b0095\", \"source_hash\": \"2e91edbe430c4eb195a977d326d6d6c0\", \"file_url\": \"https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ], \"5530105e-0752-4750-9c07-4e4604b879a5\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", + "body" : "{ \"utils/src/main/kotlin/random-data-05-06-11.kt\": [ { \"id\": \"snippet\", \"status\": \"pending\", \"lines\": \"1-240\", \"oss_lines\": \"128-367\", \"matched\": \"99%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"examples/example.rules.kts\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"871fb0c5188c2f620d9b997e225b0095\", \"source_hash\": \"2e91edbe430c4eb195a977d326d6d6c0\", \"file_url\": \"https://osskb.org/api/file_contents/871fb0c5188c2f620d9b997e225b0095\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ], \"scanner/src/main/kotlin/random-data-05-07-04.kt\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", "headers" : { "Server" : "nginx/1.14.2", "Date" : "Wed, 16 Mar 2022 13:07:04 GMT", diff --git a/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json b/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json index 2dcfa12b6ba83..416d2555071de 100644 --- a/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json +++ b/plugins/scanners/scanoss/src/test/resources/scanSingle/mappings/scanoss-response.json @@ -10,7 +10,7 @@ }, "response" : { "status" : 200, - "body" : "{ \"bf5401e9-03b3-4c91-906c-cadb90487b8c\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"scanner/src/main/kotlin/random-data-05-07-04.kt\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", + "body" : "{ \"scanner/src/main/kotlin/random-data-05-07-04.kt\": [ { \"id\": \"file\", \"status\": \"pending\", \"lines\": \"all\", \"oss_lines\": \"all\", \"matched\": \"100%\", \"purl\": [ \"pkg:github/scanoss/ort\" ], \"vendor\": \"scanoss\", \"component\": \"ort\", \"version\": \"e654028\", \"latest\": \"b12f8ee\", \"url\": \"https://github.com/scanoss/ort\", \"release_date\": \"2021-03-18\", \"file\": \"examples/example.rules.kts\", \"url_hash\": \"37faa38a820322fa93bf7a8fa8290bb8\", \"file_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"source_hash\": \"5c8ab9be40df937e46c53509481107cd\", \"file_url\": \"https://osskb.org/api/file_contents/5c8ab9be40df937e46c53509481107cd\", \"licenses\": [ { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"file_spdx_tag\" }, { \"name\": \"Apache-2.0\", \"patent_hints\": \"yes\", \"copyleft\": \"no\", \"checklist_url\": \"https://www.osadl.org/fileadmin/checklists/unreflicenses/Apache-2.0.txt\", \"osadl_updated\": \"2022-03-17 13:38\", \"source\": \"scancode\" } ], \"server\": { \"version\": \"4.4.2\", \"kb_version\": { \"monthly\": \"22.02\", \"daily\": \"22.03.25\" } } } ]}", "headers" : { "Server" : "nginx/1.14.2", "Date" : "Wed, 16 Mar 2022 13:07:04 GMT", diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json b/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json new file mode 100644 index 0000000000000..f1e34a76d8264 --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-identified-snippet.json @@ -0,0 +1,133 @@ +{ + "main.c": [ + { + "id": "snippet", + "lines": "14-22", + "oss_lines": "34-42", + "matched": "19%", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "source_hash": "f5aef06745de4d711838ea21198f2fc1", + "quality": [ + { + "score": "4/5", + "source": "best_practices" + } + ], + "cryptography": [], + "purl": [ + "pkg:sourceforge/check" + ], + "vendor": "check", + "component": "check", + "version": "0.8.1", + "latest": "0.8.1", + "url": "https://sourceforge.net/projects/check", + "status": "identified", + "release_date": "2002-03-02", + "file": "src/check_error.c", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "licenses": [ + { + "name": "LGPL-2.1-or-later", + "patent_hints": "yes", + "copyleft": "yes", + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "osadl_updated": "2025-02-10T14:26:00+0000", + "source": "scancode", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + }, + { + "name": "LGPL-2.1-or-later", + "patent_hints": "yes", + "copyleft": "yes", + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "osadl_updated": "2025-02-10T14:26:00+0000", + "source": "file_header", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + } + ], + "url_stats": {}, + "dependencies": [], + "copyrights": [ + { + "name": "Copyright (C) 2001; 2002 Arien Malec", + "source": "file_header" + }, + { + "name": "Copyright (c) 2001; 2002 Arien Malec", + "source": "scancode" + } + ], + "vulnerabilities": [], + "server": { + "version": "5.4.10", + "kb_version": { + "monthly": "25.04", + "daily": "25.05.07" + }, + "hostname": "d2", + "flags": "16384", + "elapsed": "0.107563s" + } + } + ], + "hung_task.c": [ + { + "component": "proton_bluecross", + "file": "kernel/hung_task.c", + "file_hash": "581734935cfbe570d280a1265aaa2a6b", + "file_url": "https://api.scanoss.com/file_contents/581734935cfbe570d280a1265aaa2a6b", + "id": "snippet", + "latest": "17", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "name": "GPL-2.0-only WITH Linux-syscall-note", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only WITH Linux-syscall-note.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "12-150", + "matched": "35%", + "oss_lines": "10-148", + "purl": [ + "pkg:github/kdrag0n/proton_bluecross" + ], + "release_date": "2019-02-21", + "server": { + "kb_version": { + "daily": "25.03.27", + "monthly": "25.03" + }, + "version": "5.4.10" + }, + "source_hash": "45dd1e50621a8a32f88fbe0251a470ab", + "status": "pending", + "url": "https://github.com/kdrag0n/proton_bluecross", + "url_hash": "a9c1c67f0930dc42dbd40c29e565bcdd", + "vendor": "kdrag0n", + "version": "15" + } + ] +} diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json b/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json new file mode 100644 index 0000000000000..7e02115508b8a --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-multiple-purls.json @@ -0,0 +1,60 @@ +{ + "hung_task.c": [ + { + "component": "proton_bluecross", + "file": "kernel/hung_task.c", + "file_hash": "581734935cfbe570d280a1265aaa2a6b", + "file_url": "https://api.scanoss.com/file_contents/581734935cfbe570d280a1265aaa2a6b", + "id": "snippet", + "latest": "17", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "name": "GPL-2.0-only WITH Linux-syscall-note", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only WITH Linux-syscall-note.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "12-150,540-561", + "matched": "35%", + "oss_lines": "10-148,86-107", + "purl": [ + "pkg:github/kdrag0n/proton_bluecross", + "pkg:github/fake/fake_repository" + ], + "release_date": "2019-02-21", + "server": { + "kb_version": { + "daily": "25.03.27", + "monthly": "25.03" + }, + "version": "5.4.10" + }, + "source_hash": "45dd1e50621a8a32f88fbe0251a470ab", + "status": "pending", + "url": "https://github.com/kdrag0n/proton_bluecross", + "url_hash": "a9c1c67f0930dc42dbd40c29e565bcdd", + "vendor": "kdrag0n", + "version": "15" + } + ] +} diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json new file mode 100644 index 0000000000000..de8f4de06cb1b --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-no-license-data.json @@ -0,0 +1,33 @@ +{ + "fake_file.c": [ + { + "component": "check", + "file": "fake_file.c", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "file_url": "https://api.scanoss.com/file_contents//4597ef1de00849bb96d42e78f2cfc3a7", + "id": "snippet", + "latest": "0.8.1", + "licenses": [], + "lines": "16-24", + "matched": "15%", + "oss_lines": "34-42", + "purl": [ + "pkg:sourceforge/check" + ], + "release_date": "2002-03-02", + "server": { + "kb_version": { + "daily": "25.05.14", + "monthly": "25.04" + }, + "version": "5.4.10" + }, + "source_hash": "74c49597c2934e08b2ce8797f4aa7454", + "status": "pending", + "url": "https://sourceforge.net/projects/check", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "vendor": "check", + "version": "0.8.1" + } + ] +} diff --git a/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json new file mode 100644 index 0000000000000..89f954ca0260c --- /dev/null +++ b/plugins/scanners/scanoss/src/test/resources/scanoss-snippet-same-license-multiple-sources.json @@ -0,0 +1,54 @@ +{ + "src/check_error.c": [ + { + "component": "check", + "file": "src/check_error.c", + "file_hash": "4597ef1de00849bb96d42e78f2cfc3a7", + "file_url": "https://api.scanoss.com/file_contents//4597ef1de00849bb96d42e78f2cfc3a7", + "id": "snippet", + "latest": "0.8.1", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "LGPL-2.1-or-later", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/LGPL-2.1-or-later.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "LGPL-2.1-or-later", + "osadl_updated": "2025-02-10T14:26:00+0000", + "patent_hints": "yes", + "source": "file_header", + "url": "https://spdx.org/licenses/LGPL-2.1-or-later.html" + } + ], + "lines": "16-24", + "matched": "15%", + "oss_lines": "34-42", + "purl": [ + "pkg:sourceforge/check" + ], + "release_date": "2002-03-02", + "server": { + "kb_version": { + "daily": "25.05.14", + "monthly": "25.04" + }, + "version": "5.4.10" + }, + "source_hash": "74c49597c2934e08b2ce8797f4aa7454", + "status": "pending", + "url": "https://sourceforge.net/projects/check", + "url_hash": "d81953e1dca4c498140c44f5d6fa92d6", + "vendor": "check", + "version": "0.8.1" + } + ] +}