diff options
Diffstat (limited to 'vendor/spdx-rs/src')
-rw-r--r-- | vendor/spdx-rs/src/error.rs | 33 | ||||
-rw-r--r-- | vendor/spdx-rs/src/lib.rs | 17 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/annotation.rs | 114 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/checksum.rs | 46 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/document_creation_information.rs | 295 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/file_information.rs | 313 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/mod.rs | 24 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/other_licensing_information_detected.rs | 97 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/package_information.rs | 555 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/relationship.rs | 138 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/snippet.rs | 237 | ||||
-rw-r--r-- | vendor/spdx-rs/src/models/spdx_document.rs | 330 | ||||
-rw-r--r-- | vendor/spdx-rs/src/parsers/mod.rs | 1100 | ||||
-rw-r--r-- | vendor/spdx-rs/src/parsers/tag_value.rs | 669 |
14 files changed, 3968 insertions, 0 deletions
diff --git a/vendor/spdx-rs/src/error.rs b/vendor/spdx-rs/src/error.rs new file mode 100644 index 000000000..ae6c4b4e1 --- /dev/null +++ b/vendor/spdx-rs/src/error.rs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use std::io; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SpdxError { + #[error("Error parsing the SPDX Expression.")] + Parse { + #[from] + source: spdx_expression::SpdxExpressionError, + }, + + #[error("Path {0} doesn't have an extension.")] + PathExtension(String), + + #[error("Error with file I/O.")] + Io { + #[from] + source: io::Error, + }, + + #[error("Error while parsing date.")] + DateTimeParse { + #[from] + source: chrono::ParseError, + }, + + #[error("Error parsing tag-value: {0}")] + TagValueParse(String), +} diff --git a/vendor/spdx-rs/src/lib.rs b/vendor/spdx-rs/src/lib.rs new file mode 100644 index 000000000..906ff435b --- /dev/null +++ b/vendor/spdx-rs/src/lib.rs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2020 HH Partners +// +// SPDX-License-Identifier: MIT + +#![doc = include_str!("../README.md")] +#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] +#![allow( + clippy::must_use_candidate, + clippy::module_name_repetitions, + clippy::non_ascii_literal, + clippy::missing_const_for_fn, + clippy::use_self +)] + +pub mod error; +pub mod models; +pub mod parsers; diff --git a/vendor/spdx-rs/src/models/annotation.rs b/vendor/spdx-rs/src/models/annotation.rs new file mode 100644 index 000000000..f5396aa1d --- /dev/null +++ b/vendor/spdx-rs/src/models/annotation.rs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// <https://spdx.github.io/spdx-spec/8-annotations/> +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Annotation { + /// <https://spdx.github.io/spdx-spec/8-annotations/#81-annotator> + pub annotator: String, + + /// <https://spdx.github.io/spdx-spec/8-annotations/#82-annotation-date> + pub annotation_date: DateTime<Utc>, + + /// <https://spdx.github.io/spdx-spec/8-annotations/#83-annotation-type> + pub annotation_type: AnnotationType, + + /// <https://spdx.github.io/spdx-spec/8-annotations/#84-spdx-identifier-reference> + // TODO: According to the spec this is mandatory, but the example file doesn't + // have it. + pub spdx_identifier_reference: Option<String>, + + /// <https://spdx.github.io/spdx-spec/8-annotations/#85-annotation-comment> + #[serde(rename = "comment")] + pub annotation_comment: String, +} + +impl Annotation { + pub fn new( + annotator: String, + annotation_date: DateTime<Utc>, + annotation_type: AnnotationType, + spdx_identifier_reference: Option<String>, + annotation_comment: String, + ) -> Self { + Self { + annotator, + annotation_date, + annotation_type, + spdx_identifier_reference, + annotation_comment, + } + } +} + +/// <https://spdx.github.io/spdx-spec/8-annotations/#83-annotation-type> +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AnnotationType { + Review, + Other, +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use chrono::TimeZone; + + use crate::models::SPDX; + + use super::*; + + #[test] + fn annotator() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx_file.annotations[0].annotator, + "Person: Jane Doe ()".to_string() + ); + } + + #[test] + fn annotation_date() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx_file.annotations[0].annotation_date, + Utc.ymd(2010, 1, 29).and_hms(18, 30, 22) + ); + } + + #[test] + fn annotation_type() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx_file.annotations[0].annotation_type, + AnnotationType::Other + ); + } + + #[test] + fn annotation_comment() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx_file.annotations[0].annotation_comment, + "Document level annotation" + ); + } +} diff --git a/vendor/spdx-rs/src/models/checksum.rs b/vendor/spdx-rs/src/models/checksum.rs new file mode 100644 index 000000000..bd91f4c7a --- /dev/null +++ b/vendor/spdx-rs/src/models/checksum.rs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; + +/// Representation of SPDX's +/// [Package Checksum](https://spdx.github.io/spdx-spec/3-package-information/#310-package-checksum) +/// and +/// [File Checksum](https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum). +/// According to the spec, SHA1 is mandatory but we don't currently enforce that. +#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd, Clone)] +pub struct Checksum { + /// Algorithm used to calculate the checksum + pub algorithm: Algorithm, + + /// The checksum value. + #[serde(rename = "checksumValue")] + pub value: String, +} + +impl Checksum { + /// Create new checksum. + pub fn new(algorithm: Algorithm, value: &str) -> Self { + Self { + algorithm, + value: value.to_lowercase(), + } + } +} + +/// Possible algorithms to be used for SPDX's +/// [package checksum](https://spdx.github.io/spdx-spec/3-package-information/#310-package-checksum) +/// and [file checksum](https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum). +#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd, Clone, Copy)] +pub enum Algorithm { + SHA1, + SHA224, + SHA256, + SHA384, + SHA512, + MD2, + MD4, + MD5, + MD6, +} diff --git a/vendor/spdx-rs/src/models/document_creation_information.rs b/vendor/spdx-rs/src/models/document_creation_information.rs new file mode 100644 index 000000000..610803354 --- /dev/null +++ b/vendor/spdx-rs/src/models/document_creation_information.rs @@ -0,0 +1,295 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use super::Checksum; + +/// ## Document Creation Information +/// +/// SPDX's [Document Creation Information](https://spdx.github.io/spdx-spec/2-document-creation-information/) +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DocumentCreationInformation { + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#21-spdx-version> + pub spdx_version: String, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#22-data-license> + pub data_license: String, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#23-spdx-identifier> + #[serde(rename = "SPDXID")] + pub spdx_identifier: String, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#24-document-name> + #[serde(rename = "name")] + pub document_name: String, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#25-spdx-document-namespace> + #[serde(rename = "documentNamespace")] + pub spdx_document_namespace: String, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#26-external-document-references> + #[serde( + rename = "externalDocumentRefs", + skip_serializing_if = "Vec::is_empty", + default + )] + pub external_document_references: Vec<ExternalDocumentReference>, + + pub creation_info: CreationInfo, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#211-document-comment> + #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)] + pub document_comment: Option<String>, + + /// Doesn't seem to be in spec, but the example contains it. + /// <https://github.com/spdx/spdx-spec/issues/395> + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub document_describes: Vec<String>, +} + +impl Default for DocumentCreationInformation { + fn default() -> Self { + Self { + // Current version is 2.2. Might need to support more verisons + // in the future. + spdx_version: "SPDX-2.2".to_string(), + data_license: "CC0-1.0".to_string(), + spdx_identifier: "SPDXRef-DOCUMENT".to_string(), + document_name: "NOASSERTION".to_string(), + spdx_document_namespace: "NOASSERTION".to_string(), + external_document_references: Vec::new(), + document_comment: None, + creation_info: CreationInfo::default(), + document_describes: Vec::new(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CreationInfo { + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#27-license-list-version> + #[serde(skip_serializing_if = "Option::is_none", default)] + pub license_list_version: Option<String>, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#28-creator> + pub creators: Vec<String>, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#29-created> + pub created: DateTime<Utc>, + + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/#210-creator-comment> + #[serde(skip_serializing_if = "Option::is_none", default)] + #[serde(rename = "comment")] + pub creator_comment: Option<String>, +} + +impl Default for CreationInfo { + fn default() -> Self { + Self { + license_list_version: None, + creators: vec![ + "Person: Jane Doe ()".into(), + "Organization: ExampleCodeInspect ()".into(), + "Tool: LicenseFind-1.0".into(), + ], + created: chrono::offset::Utc::now(), + creator_comment: None, + } + } +} + +/// <https://spdx.github.io/spdx-spec/2-document-creation-information/#26-external-document-references> +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] +pub struct ExternalDocumentReference { + /// Unique ID string of the reference. + #[serde(rename = "externalDocumentId")] + pub id_string: String, + + /// Unique ID for the external document. + #[serde(rename = "spdxDocument")] + pub spdx_document_uri: String, + + /// Checksum of the external document following the checksum format defined + /// in <https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum.> + pub checksum: Checksum, +} + +impl ExternalDocumentReference { + pub const fn new(id_string: String, spdx_document_uri: String, checksum: Checksum) -> Self { + Self { + id_string, + spdx_document_uri, + checksum, + } + } +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use chrono::{TimeZone, Utc}; + + use super::*; + use crate::models::{Algorithm, SPDX}; + + #[test] + fn spdx_version() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + assert_eq!( + spdx.document_creation_information.spdx_version, + "SPDX-2.2".to_string() + ); + } + #[test] + fn data_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + assert_eq!(spdx.document_creation_information.data_license, "CC0-1.0"); + } + #[test] + fn spdx_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information.spdx_identifier, + "SPDXRef-DOCUMENT".to_string() + ); + } + #[test] + fn document_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information.document_name, + "SPDX-Tools-v2.0".to_string() + ); + } + #[test] + fn spdx_document_namespace() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information.spdx_document_namespace, + "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301" + .to_string() + ); + } + #[test] + fn license_list_version() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information + .creation_info + .license_list_version, + Some("3.9".to_string()) + ); + } + #[test] + fn creators() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!(spdx + .document_creation_information + .creation_info + .creators + .contains(&"Tool: LicenseFind-1.0".to_string())); + assert!(spdx + .document_creation_information + .creation_info + .creators + .contains(&"Organization: ExampleCodeInspect ()".to_string())); + assert!(spdx + .document_creation_information + .creation_info + .creators + .contains(&"Person: Jane Doe ()".to_string())); + } + #[test] + fn created() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information.creation_info.created, + Utc.ymd(2010, 1, 29).and_hms(18, 30, 22) + ); + } + #[test] + fn creator_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information + .creation_info + .creator_comment, + Some( + r#"This package has been shipped in source and binary form. +The binaries were created with gcc 4.5.1 and expect to link to +compatible system run time libraries."# + .to_string() + ) + ); + } + #[test] + fn document_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.document_creation_information.document_comment, + Some( + "This document was created using SPDX 2.0 using licenses from the web site." + .to_string() + ) + ); + } + + #[test] + fn external_document_references() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!(spdx + .document_creation_information + .external_document_references + .contains(&ExternalDocumentReference { + id_string: "DocumentRef-spdx-tool-1.2".to_string(), + checksum: Checksum { + algorithm: Algorithm::SHA1, + value: "d6a770ba38583ed4bb4525bd96e50461655d2759".to_string() + }, + spdx_document_uri: + "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + .to_string() + })); + } +} diff --git a/vendor/spdx-rs/src/models/file_information.rs b/vendor/spdx-rs/src/models/file_information.rs new file mode 100644 index 000000000..f2db6e030 --- /dev/null +++ b/vendor/spdx-rs/src/models/file_information.rs @@ -0,0 +1,313 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +use spdx_expression::{SimpleExpression, SpdxExpression}; + +use super::{Algorithm, Checksum}; + +/// ## File Information +/// +/// SPDX's [File Information](https://spdx.github.io/spdx-spec/4-file-information/) +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct FileInformation { + /// <https://spdx.github.io/spdx-spec/4-file-information/#41-file-name> + pub file_name: String, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#42-file-spdx-identifier> + #[serde(rename = "SPDXID")] + pub file_spdx_identifier: String, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type> + #[serde(rename = "fileTypes", skip_serializing_if = "Vec::is_empty", default)] + pub file_type: Vec<FileType>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum> + #[serde(rename = "checksums")] + pub file_checksum: Vec<Checksum>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#45-concluded-license> + #[serde(rename = "licenseConcluded")] + pub concluded_license: SpdxExpression, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#46-license-information-in-file> + #[serde(rename = "licenseInfoInFiles")] + pub license_information_in_file: Vec<SimpleExpression>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#47-comments-on-license> + #[serde( + rename = "licenseComments", + skip_serializing_if = "Option::is_none", + default + )] + pub comments_on_license: Option<String>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#48-copyright-text> + pub copyright_text: String, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#412-file-comment> + #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)] + pub file_comment: Option<String>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#413-file-notice> + #[serde( + rename = "noticeText", + skip_serializing_if = "Option::is_none", + default + )] + pub file_notice: Option<String>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#414-file-contributor> + #[serde( + rename = "fileContributors", + skip_serializing_if = "Vec::is_empty", + default + )] + pub file_contributor: Vec<String>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/#415-file-attribution-text> + #[serde(skip_serializing_if = "Option::is_none", default)] + pub file_attribution_text: Option<Vec<String>>, + // TODO: Snippet Information. +} + +impl Default for FileInformation { + fn default() -> Self { + Self { + file_name: "NOASSERTION".to_string(), + file_spdx_identifier: "NOASSERTION".to_string(), + file_type: Vec::new(), + file_checksum: Vec::new(), + concluded_license: SpdxExpression::parse("NOASSERTION").expect("will always succeed"), + license_information_in_file: Vec::new(), + comments_on_license: None, + copyright_text: "NOASSERTION".to_string(), + file_comment: None, + file_notice: None, + file_contributor: Vec::new(), + file_attribution_text: None, + } + } +} + +impl FileInformation { + /// Create new file. + pub fn new(name: &str, id: &mut i32) -> Self { + *id += 1; + Self { + file_name: name.to_string(), + file_spdx_identifier: format!("SPDXRef-{}", id), + ..Self::default() + } + } + + /// Check if hash equals. + pub fn equal_by_hash(&self, algorithm: Algorithm, value: &str) -> bool { + let checksum = self + .file_checksum + .iter() + .find(|&checksum| checksum.algorithm == algorithm); + + checksum.map_or(false, |checksum| { + checksum.value.to_ascii_lowercase() == value.to_ascii_lowercase() + }) + } + + /// Get checksum + pub fn checksum(&self, algorithm: Algorithm) -> Option<&str> { + let checksum = self + .file_checksum + .iter() + .find(|&checksum| checksum.algorithm == algorithm); + + checksum.map(|checksum| checksum.value.as_str()) + } +} + +/// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type> +#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd, Clone, Copy)] +#[serde(rename_all = "UPPERCASE")] +pub enum FileType { + Source, + Binary, + Archive, + Application, + Audio, + Image, + Text, + Video, + Documentation, + SPDX, + Other, +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use super::*; + use crate::models::{Checksum, FileType, SPDX}; + + #[test] + fn checksum_equality() { + let mut id = 1; + let mut file_sha256 = FileInformation::new("sha256", &mut id); + file_sha256 + .file_checksum + .push(Checksum::new(Algorithm::SHA256, "test")); + + assert!(file_sha256.equal_by_hash(Algorithm::SHA256, "test")); + assert!(!file_sha256.equal_by_hash(Algorithm::SHA256, "no_test")); + + let mut file_md5 = FileInformation::new("md5", &mut id); + file_md5 + .file_checksum + .push(Checksum::new(Algorithm::MD5, "test")); + assert!(file_md5.equal_by_hash(Algorithm::MD5, "test")); + assert!(!file_md5.equal_by_hash(Algorithm::MD5, "no_test")); + assert!(!file_md5.equal_by_hash(Algorithm::SHA1, "test")); + } + + #[test] + fn get_checksum() { + let mut id = 1; + let mut file_sha256 = FileInformation::new("sha256", &mut id); + file_sha256 + .file_checksum + .push(Checksum::new(Algorithm::SHA256, "test")); + + assert_eq!(file_sha256.checksum(Algorithm::SHA256), Some("test")); + assert_eq!(file_sha256.checksum(Algorithm::MD2), None); + + let mut file_md5 = FileInformation::new("md5", &mut id); + file_md5 + .file_checksum + .push(Checksum::new(Algorithm::MD5, "test")); + + assert_eq!(file_md5.checksum(Algorithm::MD5), Some("test")); + } + + #[test] + fn file_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].file_name, + "./src/org/spdx/parser/DOAPProject.java" + ); + } + #[test] + fn file_spdx_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].file_spdx_identifier, + "SPDXRef-DoapSource" + ); + } + #[test] + fn file_type() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!(spdx.file_information[0].file_type, vec![FileType::Source]); + } + #[test] + fn file_checksum() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].file_checksum, + vec![Checksum { + algorithm: Algorithm::SHA1, + value: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12".to_string() + }] + ); + } + #[test] + fn concluded_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].concluded_license, + SpdxExpression::parse("Apache-2.0").unwrap() + ); + } + #[test] + fn license_information_in_file() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].license_information_in_file, + vec![SimpleExpression::parse("Apache-2.0").unwrap()] + ); + } + #[test] + fn comments_on_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[2].comments_on_license, + Some("This license is used by Jena".to_string()) + ); + } + #[test] + fn copyright_text() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[0].copyright_text, + "Copyright 2010, 2011 Source Auditor Inc.".to_string() + ); + } + #[test] + fn file_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[1].file_comment, + Some("This file is used by Jena".to_string()) + ); + } + #[test] + fn file_notice() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[1].file_notice, + Some("Apache Commons Lang\nCopyright 2001-2011 The Apache Software Foundation\n\nThis product includes software developed by\nThe Apache Software Foundation (http://www.apache.org/).\n\nThis product includes software from the Spring Framework,\nunder the Apache License 2.0 (see: StringUtils.containsWhitespace())".to_string()) + ); + } + #[test] + fn file_contributor() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.file_information[1].file_contributor, + vec!["Apache Software Foundation".to_string()] + ); + } +} diff --git a/vendor/spdx-rs/src/models/mod.rs b/vendor/spdx-rs/src/models/mod.rs new file mode 100644 index 000000000..2eed75db2 --- /dev/null +++ b/vendor/spdx-rs/src/models/mod.rs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2021 HH Partners +// +// SPDX-License-Identifier: MIT + +mod annotation; +mod checksum; +mod document_creation_information; +mod file_information; +mod other_licensing_information_detected; +mod package_information; +mod relationship; +mod snippet; +mod spdx_document; + +pub use annotation::*; +pub use checksum::*; +pub use document_creation_information::*; +pub use file_information::*; +pub use other_licensing_information_detected::*; +pub use package_information::*; +pub use relationship::*; +pub use snippet::*; +pub use spdx_document::*; +pub use spdx_expression::*; diff --git a/vendor/spdx-rs/src/models/other_licensing_information_detected.rs b/vendor/spdx-rs/src/models/other_licensing_information_detected.rs new file mode 100644 index 000000000..30bd1d318 --- /dev/null +++ b/vendor/spdx-rs/src/models/other_licensing_information_detected.rs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; + +/// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/> +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct OtherLicensingInformationDetected { + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/#61-license-identifier> + #[serde(rename = "licenseId")] + pub license_identifier: String, + + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/#62-extracted-text> + pub extracted_text: String, + + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/#63-license-name> + #[serde(rename = "name")] + #[serde(default = "default_noassertion")] + pub license_name: String, + + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/#64-license-cross-reference> + #[serde(rename = "seeAlsos", skip_serializing_if = "Vec::is_empty", default)] + pub license_cross_reference: Vec<String>, + + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/#65-license-comment> + #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)] + pub license_comment: Option<String>, +} + +fn default_noassertion() -> String { + "NOASSERTION".into() +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use crate::models::SPDX; + + #[test] + fn license_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.other_licensing_information_detected[0].license_identifier, + "LicenseRef-Beerware-4.2".to_string() + ); + } + #[test] + fn extracted_text() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!(spdx.other_licensing_information_detected[0].extracted_text, "\"THE BEER-WARE LICENSE\" (Revision 42):\nphk@FreeBSD.ORG wrote this file. As long as you retain this notice you\ncan do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp </\nLicenseName: Beer-Ware License (Version 42)\nLicenseCrossReference: http://people.freebsd.org/~phk/\nLicenseComment: \nThe beerware license has a couple of other standard variants."); + } + #[test] + fn license_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.other_licensing_information_detected[2].license_name, + "CyberNeko License".to_string() + ); + } + #[test] + fn license_cross_reference() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.other_licensing_information_detected[2].license_cross_reference, + vec![ + "http://people.apache.org/~andyc/neko/LICENSE".to_string(), + "http://justasample.url.com".to_string() + ] + ); + } + #[test] + fn license_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.other_licensing_information_detected[2].license_comment, + Some("This is tye CyperNeko License".to_string()) + ); + } +} diff --git a/vendor/spdx-rs/src/models/package_information.rs b/vendor/spdx-rs/src/models/package_information.rs new file mode 100644 index 000000000..1cfd938f8 --- /dev/null +++ b/vendor/spdx-rs/src/models/package_information.rs @@ -0,0 +1,555 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +use spdx_expression::SpdxExpression; + +use super::Annotation; + +use super::{Checksum, FileInformation}; + +/// ## Package Information +/// +/// SPDX's [Package Information](https://spdx.github.io/spdx-spec/3-package-information/). +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct PackageInformation { + /// <https://spdx.github.io/spdx-spec/3-package-information/#31-package-name> + #[serde(rename = "name")] + pub package_name: String, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#32-package-spdx-identifier> + #[serde(rename = "SPDXID")] + pub package_spdx_identifier: String, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#33-package-version> + #[serde( + rename = "versionInfo", + skip_serializing_if = "Option::is_none", + default + )] + pub package_version: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#34-package-file-name> + #[serde(skip_serializing_if = "Option::is_none", default)] + pub package_file_name: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#35-package-supplier> + #[serde(rename = "supplier", skip_serializing_if = "Option::is_none", default)] + pub package_supplier: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#36-package-originator> + #[serde( + rename = "originator", + skip_serializing_if = "Option::is_none", + default + )] + pub package_originator: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#37-package-download-location> + #[serde(rename = "downloadLocation")] + pub package_download_location: String, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#38-files-analyzed> + #[serde(skip_serializing_if = "Option::is_none", default)] + pub files_analyzed: Option<bool>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#39-package-verification-code> + #[serde(skip_serializing_if = "Option::is_none", default)] + pub package_verification_code: Option<PackageVerificationCode>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#310-package-checksum> + #[serde(rename = "checksums", skip_serializing_if = "Vec::is_empty", default)] + pub package_checksum: Vec<Checksum>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#311-package-home-page> + #[serde(rename = "homepage", skip_serializing_if = "Option::is_none", default)] + pub package_home_page: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#312-source-information> + #[serde( + rename = "sourceInfo", + skip_serializing_if = "Option::is_none", + default + )] + pub source_information: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license> + #[serde(rename = "licenseConcluded")] + pub concluded_license: SpdxExpression, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#314-all-licenses-information-from-files> + #[serde( + rename = "licenseInfoFromFiles", + skip_serializing_if = "Vec::is_empty", + default + )] + pub all_licenses_information_from_files: Vec<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#315-declared-license> + #[serde(rename = "licenseDeclared")] + pub declared_license: SpdxExpression, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#316-comments-on-license> + #[serde( + rename = "licenseComments", + skip_serializing_if = "Option::is_none", + default + )] + pub comments_on_license: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#317-copyright-text> + pub copyright_text: String, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#318-package-summary-description> + #[serde(rename = "summary", skip_serializing_if = "Option::is_none", default)] + pub package_summary_description: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#319-package-detailed-description> + #[serde( + rename = "description", + skip_serializing_if = "Option::is_none", + default + )] + pub package_detailed_description: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#320-package-comment> + #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)] + pub package_comment: Option<String>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#321-external-reference> + #[serde( + rename = "externalRefs", + skip_serializing_if = "Vec::is_empty", + default + )] + pub external_reference: Vec<ExternalPackageReference>, + + /// <https://spdx.github.io/spdx-spec/3-package-information/#323-package-attribution-text> + #[serde( + rename = "attributionTexts", + skip_serializing_if = "Vec::is_empty", + default + )] + pub package_attribution_text: Vec<String>, + + /// List of "files in the package". Not sure which relationship type this maps to. + /// Info: <https://github.com/spdx/spdx-spec/issues/487> + // Valid SPDX? + #[serde(rename = "hasFiles", skip_serializing_if = "Vec::is_empty", default)] + pub files: Vec<String>, + + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub annotations: Vec<Annotation>, +} + +impl Default for PackageInformation { + fn default() -> Self { + Self { + package_name: "NOASSERTION".to_string(), + package_spdx_identifier: "NOASSERTION".to_string(), + package_version: None, + package_file_name: None, + package_supplier: None, + package_originator: None, + package_download_location: "NOASSERTION".to_string(), + files_analyzed: None, + package_verification_code: None, + package_checksum: Vec::new(), + package_home_page: None, + source_information: None, + concluded_license: SpdxExpression::parse("NONE").expect("will always succeed"), + all_licenses_information_from_files: Vec::new(), + declared_license: SpdxExpression::parse("NONE").expect("will always succeed"), + comments_on_license: None, + copyright_text: "NOASSERTION".to_string(), + package_summary_description: None, + package_detailed_description: None, + package_comment: None, + external_reference: Vec::new(), + package_attribution_text: Vec::new(), + files: Vec::new(), + annotations: Vec::new(), + } + } +} + +impl PackageInformation { + /// Create new package. + pub fn new(name: &str, id: &mut i32) -> Self { + *id += 1; + Self { + package_name: name.to_string(), + package_spdx_identifier: format!("SPDXRef-{}", id), + ..Self::default() + } + } + + /// Find all files of the package. + pub fn find_files_for_package<'a>( + &'a self, + files: &'a [FileInformation], + ) -> Vec<&'a FileInformation> { + self.files + .iter() + .filter_map(|file| { + files + .iter() + .find(|file_information| &file_information.file_spdx_identifier == file) + }) + .collect() + } +} + +/// <https://spdx.github.io/spdx-spec/3-package-information/#39-package-verification-code> +#[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd, Clone)] +pub struct PackageVerificationCode { + /// Value of the verification code. + #[serde(rename = "packageVerificationCodeValue")] + pub value: String, + + /// Files that were excluded when calculating the verification code. + #[serde( + rename = "packageVerificationCodeExcludedFiles", + skip_serializing_if = "Vec::is_empty", + default + )] + pub excludes: Vec<String>, +} + +impl PackageVerificationCode { + pub fn new(value: String, excludes: Vec<String>) -> Self { + Self { value, excludes } + } +} + +/// <https://spdx.github.io/spdx-spec/3-package-information/#321-external-reference> +#[derive(Serialize, Deserialize, Debug, PartialEq, PartialOrd, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExternalPackageReference { + pub reference_category: ExternalPackageReferenceCategory, + pub reference_type: String, + pub reference_locator: String, + #[serde(rename = "comment")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub reference_comment: Option<String>, +} + +impl ExternalPackageReference { + pub const fn new( + reference_category: ExternalPackageReferenceCategory, + reference_type: String, + reference_locator: String, + reference_comment: Option<String>, + ) -> Self { + Self { + reference_category, + reference_type, + reference_locator, + reference_comment, + } + } +} + +/// <https://spdx.github.io/spdx-spec/3-package-information/#321-external-reference> +#[derive(Serialize, Deserialize, Debug, PartialEq, PartialOrd, Clone)] +#[serde(rename_all = "SCREAMING-KEBAB-CASE")] +pub enum ExternalPackageReferenceCategory { + Security, + PackageManager, + PersistentID, + Other, +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use crate::models::{Algorithm, SPDX}; + + use super::*; + + #[test] + fn all_packages_are_deserialized() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!(spdx.package_information.len(), 4); + } + #[test] + fn package_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_name, + "glibc".to_string() + ); + } + #[test] + fn package_spdx_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_spdx_identifier, + "SPDXRef-Package".to_string() + ); + } + #[test] + fn package_version() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_version, + Some("2.11.1".to_string()) + ); + } + #[test] + fn package_file_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_file_name, + Some("glibc-2.11.1.tar.gz".to_string()) + ); + } + #[test] + fn package_supplier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_supplier, + Some("Person: Jane Doe (jane.doe@example.com)".to_string()) + ); + } + #[test] + fn package_originator() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_originator, + Some("Organization: ExampleCodeInspect (contact@example.com)".to_string()) + ); + } + #[test] + fn package_download_location() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_download_location, + "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz".to_string() + ); + } + #[test] + fn files_analyzed() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!(spdx.package_information[0].files_analyzed, Some(true)); + } + #[test] + fn package_verification_code() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_verification_code, + Some(PackageVerificationCode { + value: "d6a770ba38583ed4bb4525bd96e50461655d2758".to_string(), + excludes: vec!["./package.spdx".to_string()] + }) + ); + } + #[test] + fn package_chekcsum() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!(spdx.package_information[0] + .package_checksum + .contains(&Checksum::new( + Algorithm::SHA1, + "85ed0817af83a24ad8da68c2b5094de69833983c" + ))); + assert!(spdx.package_information[0] + .package_checksum + .contains(&Checksum::new( + Algorithm::MD5, + "624c1abb3664f4b35547e7c73864ad24" + ))); + assert!(spdx.package_information[0] + .package_checksum + .contains(&Checksum::new( + Algorithm::SHA256, + "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd" + ))); + } + #[test] + fn package_home_page() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_home_page, + Some("http://ftp.gnu.org/gnu/glibc".to_string()) + ); + } + #[test] + fn source_information() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].source_information, + Some("uses glibc-2_11-branch from git://sourceware.org/git/glibc.git.".to_string()) + ); + } + #[test] + fn concluded_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].concluded_license, + SpdxExpression::parse("(LGPL-2.0-only OR LicenseRef-3)").unwrap() + ); + } + #[test] + fn all_licenses_information_from_files() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!(spdx.package_information[0] + .all_licenses_information_from_files + .contains(&"GPL-2.0-only".to_string())); + assert!(spdx.package_information[0] + .all_licenses_information_from_files + .contains(&"LicenseRef-2".to_string())); + assert!(spdx.package_information[0] + .all_licenses_information_from_files + .contains(&"LicenseRef-1".to_string())); + } + #[test] + fn declared_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].declared_license, + SpdxExpression::parse("(LGPL-2.0-only AND LicenseRef-3)").unwrap() + ); + } + #[test] + fn comments_on_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].comments_on_license, + Some("The license for this project changed with the release of version x.y. The version of the project included here post-dates the license change.".to_string()) + ); + } + #[test] + fn copyright_text() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].copyright_text, + "Copyright 2008-2010 John Smith".to_string() + ); + } + #[test] + fn package_summary_description() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_summary_description, + Some("GNU C library.".to_string()) + ); + } + #[test] + fn package_detailed_description() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[0].package_detailed_description, + Some("The GNU C Library defines functions that are specified by the ISO C standard, as well as additional features specific to POSIX and other derivatives of the Unix operating system, and extensions specific to GNU systems.".to_string()) + ); + } + #[test] + fn package_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.package_information[1].package_comment, + Some("This package was converted from a DOAP Project by the same name".to_string()) + ); + } + #[test] + fn external_reference() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!( + spdx.package_information[0].external_reference.contains(&ExternalPackageReference { + reference_comment: Some("This is the external ref for Acme".to_string()), + reference_category: ExternalPackageReferenceCategory::Other, + reference_locator: "acmecorp/acmenator/4.1.3-alpha".to_string(), + reference_type: "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#LocationRef-acmeforge".to_string() + }) + ); + assert!(spdx.package_information[0].external_reference.contains( + &ExternalPackageReference { + reference_comment: None, + reference_category: ExternalPackageReferenceCategory::Security, + reference_locator: + "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*".to_string(), + reference_type: "http://spdx.org/rdf/references/cpe23Type".to_string() + } + )); + } + #[test] + fn package_attribution_text() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert!( + spdx.package_information[0].package_attribution_text.contains(&"The GNU C Library is free software. See the file COPYING.LIB for copying conditions, and LICENSES for notices about a few contributions that require these additional notices to be distributed. License copyright years may be listed using range notation, e.g., 1996-2015, indicating that every year in the range, inclusive, is a copyrightable year that would otherwise be listed individually.".to_string()) + ); + } +} diff --git a/vendor/spdx-rs/src/models/relationship.rs b/vendor/spdx-rs/src/models/relationship.rs new file mode 100644 index 000000000..6430a796f --- /dev/null +++ b/vendor/spdx-rs/src/models/relationship.rs @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +use strum_macros::AsRefStr; + +/// <https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/#71-relationship> +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct Relationship { + /// SPDX ID of the element. + pub spdx_element_id: String, + + /// SPDX ID of the related element. + pub related_spdx_element: String, + + /// Type of the relationship. + pub relationship_type: RelationshipType, + + /// <https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/#72-relationship-comment> + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub comment: Option<String>, +} + +impl Relationship { + /// Create a new relationship. + pub fn new( + spdx_element_id: &str, + related_spdx_element: &str, + relationship_type: RelationshipType, + comment: Option<String>, + ) -> Self { + Self { + spdx_element_id: spdx_element_id.to_string(), + related_spdx_element: related_spdx_element.to_string(), + relationship_type, + comment, + } + } +} + +/// <https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/#71-relationship> +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, AsRefStr, Eq, Hash)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RelationshipType { + Describes, + DescribedBy, + Contains, + ContainedBy, + DependsOn, + DependencyOf, + DependencyManifestOf, + BuildDependencyOf, + DevDependencyOf, + OptionalDependencyOf, + ProvidedDependencyOf, + TestDependencyOf, + RuntimeDependencyOf, + ExampleOf, + Generates, + GeneratedFrom, + AncestorOf, + DescendantOf, + VariantOf, + DistributionArtifact, + PatchFor, + PatchApplied, + CopyOf, + FileAdded, + FileDeleted, + FileModified, + ExpandedFromArchive, + DynamicLink, + StaticLink, + DataFileOf, + TestCaseOf, + BuildToolOf, + DevToolOf, + TestOf, + TestToolOf, + DocumentationOf, + OptionalComponentOf, + MetafileOf, + PackageOf, + Amends, + PrerequisiteFor, + HasPrerequisite, + Other, +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use crate::models::SPDX; + + use super::*; + + #[test] + fn spdx_element_id() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.relationships[0].spdx_element_id, + "SPDXRef-DOCUMENT".to_string() + ); + } + #[test] + fn related_spdx_element() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.relationships[0].related_spdx_element, + "SPDXRef-Package".to_string() + ); + } + #[test] + fn relationship_type() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.relationships[0].relationship_type, + RelationshipType::Contains + ); + assert_eq!( + spdx.relationships[2].relationship_type, + RelationshipType::CopyOf + ); + } +} diff --git a/vendor/spdx-rs/src/models/snippet.rs b/vendor/spdx-rs/src/models/snippet.rs new file mode 100644 index 000000000..e4a644ac5 --- /dev/null +++ b/vendor/spdx-rs/src/models/snippet.rs @@ -0,0 +1,237 @@ +// SPDX-FileCopyrightText: 2020-2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +use spdx_expression::SpdxExpression; + +/// <https://spdx.github.io/spdx-spec/5-snippet-information/> +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct Snippet { + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#51-snippet-spdx-identifier> + #[serde(rename = "SPDXID")] + pub snippet_spdx_identifier: String, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#52-snippet-from-file-spdx-identifier> + #[serde(rename = "snippetFromFile")] + pub snippet_from_file_spdx_identifier: String, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#53-snippet-byte-range> + pub ranges: Vec<Range>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#55-snippet-concluded-license> + #[serde(rename = "licenseConcluded")] + pub snippet_concluded_license: SpdxExpression, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#56-license-information-in-snippet> + #[serde( + rename = "licenseInfoInSnippets", + skip_serializing_if = "Vec::is_empty", + default + )] + pub license_information_in_snippet: Vec<String>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#57-snippet-comments-on-license> + #[serde( + rename = "licenseComments", + skip_serializing_if = "Option::is_none", + default + )] + pub snippet_comments_on_license: Option<String>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#58-snippet-copyright-text> + #[serde(rename = "copyrightText")] + pub snippet_copyright_text: String, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#59-snippet-comment> + #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)] + pub snippet_comment: Option<String>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#510-snippet-name> + #[serde(rename = "name", skip_serializing_if = "Option::is_none", default)] + pub snippet_name: Option<String>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/#511-snippet-attribution-text> + #[serde( + rename = "attributionText", + skip_serializing_if = "Option::is_none", + default + )] + pub snippet_attribution_text: Option<String>, +} + +/// <https://spdx.github.io/spdx-spec/5-snippet-information/#53-snippet-byte-range> +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Range { + pub start_pointer: Pointer, + pub end_pointer: Pointer, +} + +impl Range { + pub fn new(start_pointer: Pointer, end_pointer: Pointer) -> Self { + Self { + start_pointer, + end_pointer, + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(untagged)] +pub enum Pointer { + Byte { + reference: Option<String>, + offset: i32, + }, + Line { + reference: Option<String>, + #[serde(rename = "lineNumber")] + line_number: i32, + }, +} + +impl Pointer { + /// Create a new [`Pointer::Byte`]. + pub fn new_byte(reference: Option<String>, offset: i32) -> Self { + Self::Byte { reference, offset } + } + + /// Create a new [`Pointer::Line`]. + pub fn new_line(reference: Option<String>, line_number: i32) -> Self { + Self::Line { + reference, + line_number, + } + } +} + +#[cfg(test)] +mod test { + use std::fs::read_to_string; + + use crate::models::SPDX; + + use super::*; + + #[test] + fn snippet_spdx_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_spdx_identifier, + "SPDXRef-Snippet".to_string() + ); + } + #[test] + fn snippet_from_file_spdx_identifier() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_from_file_spdx_identifier, + "SPDXRef-DoapSource".to_string() + ); + } + #[test] + fn ranges() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].ranges, + vec![ + Range { + end_pointer: Pointer::Line { + line_number: 23, + reference: Some("SPDXRef-DoapSource".to_string()), + }, + start_pointer: Pointer::Line { + line_number: 5, + reference: Some("SPDXRef-DoapSource".to_string()), + }, + }, + Range { + end_pointer: Pointer::Byte { + offset: 420, + reference: Some("SPDXRef-DoapSource".to_string()), + }, + start_pointer: Pointer::Byte { + offset: 310, + reference: Some("SPDXRef-DoapSource".to_string()), + }, + }, + ] + ); + } + #[test] + fn snippet_concluded_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_concluded_license, + SpdxExpression::parse("GPL-2.0-only").unwrap() + ); + } + #[test] + fn license_information_in_snippet() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].license_information_in_snippet, + vec!["GPL-2.0-only".to_string()] + ); + } + #[test] + fn snippet_comments_on_license() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_comments_on_license, + Some("The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.".to_string()) + ); + } + #[test] + fn snippet_copyright_text() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_copyright_text, + "Copyright 2008-2010 John Smith".to_string() + ); + } + #[test] + fn snippet_comment() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_comment, + Some("This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0.".to_string()) + ); + } + #[test] + fn snippet_name() { + let spdx: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + assert_eq!( + spdx.snippet_information[0].snippet_name, + Some("from linux kernel".to_string()) + ); + } +} diff --git a/vendor/spdx-rs/src/models/spdx_document.rs b/vendor/spdx-rs/src/models/spdx_document.rs new file mode 100644 index 000000000..d7db1009a --- /dev/null +++ b/vendor/spdx-rs/src/models/spdx_document.rs @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: 2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use std::collections::HashSet; + +use log::info; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::{ + Algorithm, Annotation, DocumentCreationInformation, FileInformation, + OtherLicensingInformationDetected, PackageInformation, Relationship, Snippet, +}; + +/// A representation of an [SPDX Document] +/// +/// This is the main struct of this crate. The struct implements [`Serialize`] and [`Deserialize`] +/// to allow it to be serialized into and deserialized from any data format supported by [Serde]. +/// +/// # SPDX specification version +/// +/// The crate has been developed around SPDX version 2.2.1. Fields deprecated in 2.2.1, like +/// [review information] are not supported. The plan is to support newer versions as they are +/// released. +/// +/// # Data formats +/// +/// The crate has been developed for usage with JSON SPDX documents. The naming of the fields should +/// conform to the spec for at least JSON. Other formats, like YAML may work, but no guarantees are +/// made. +/// +/// The crate also allows for deserializing the struct from SPDX documents in [tag-value format] +/// with [`crate::parsers::spdx_from_tag_value`]. +/// +/// [SPDX Document]: https://spdx.github.io/spdx-spec/composition-of-an-SPDX-document/ +/// [Serde]: https://serde.rs +/// [review information]: https://spdx.github.io/spdx-spec/review-information-deprecated/ +/// [tag-value format]: https://spdx.github.io/spdx-spec/conformance/ +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct SPDX { + /// <https://spdx.github.io/spdx-spec/2-document-creation-information/> + #[serde(flatten)] + pub document_creation_information: DocumentCreationInformation, + + /// <https://spdx.github.io/spdx-spec/3-package-information/> + #[serde(rename = "packages")] + #[serde(default)] + pub package_information: Vec<PackageInformation>, + + /// <https://spdx.github.io/spdx-spec/6-other-licensing-information-detected/> + #[serde(rename = "hasExtractedLicensingInfos")] + #[serde(default)] + pub other_licensing_information_detected: Vec<OtherLicensingInformationDetected>, + + /// <https://spdx.github.io/spdx-spec/4-file-information/> + #[serde(rename = "files")] + #[serde(default)] + pub file_information: Vec<FileInformation>, + + /// <https://spdx.github.io/spdx-spec/5-snippet-information/> + #[serde(rename = "snippets")] + #[serde(default)] + pub snippet_information: Vec<Snippet>, + + /// <https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/> + #[serde(default)] + pub relationships: Vec<Relationship>, + + /// <https://spdx.github.io/spdx-spec/8-annotations/> + #[serde(default)] + pub annotations: Vec<Annotation>, + + /// Counter for creating SPDXRefs. Is not part of the spec, so don't serialize. + #[serde(skip)] + pub spdx_ref_counter: i32, +} + +impl SPDX { + /// Create new SPDX struct. + pub fn new(name: &str) -> Self { + info!("Creating SPDX."); + + Self { + document_creation_information: DocumentCreationInformation { + document_name: name.to_string(), + spdx_document_namespace: format!( + "http://spdx.org/spdxdocs/{}-{}", + name, + Uuid::new_v4() + ), + ..DocumentCreationInformation::default() + }, + package_information: Vec::new(), + other_licensing_information_detected: Vec::new(), + file_information: Vec::new(), + relationships: Vec::new(), + spdx_ref_counter: 0, + annotations: Vec::new(), + snippet_information: Vec::new(), + } + } + + /// Get unique hashes for all files the SPDX. + pub fn get_unique_hashes(&self, algorithm: Algorithm) -> HashSet<String> { + info!("Getting unique hashes for files in SPDX."); + + let mut unique_hashes: HashSet<String> = HashSet::new(); + + for file_information in &self.file_information { + if let Some(checksum) = file_information.checksum(algorithm) { + unique_hashes.insert(checksum.to_string()); + } + } + + unique_hashes + } + + /// Find related files of the package with the provided id. + pub fn get_files_for_package( + &self, + package_spdx_id: &str, + ) -> Vec<(&FileInformation, &Relationship)> { + info!("Finding related files for package {}.", &package_spdx_id); + + let relationships = self + .relationships + .iter() + .filter(|relationship| relationship.spdx_element_id == package_spdx_id); + + let mut result: Vec<(&FileInformation, &Relationship)> = Vec::new(); + + for relationship in relationships { + let file = self + .file_information + .iter() + .find(|file| file.file_spdx_identifier == relationship.related_spdx_element); + if let Some(file) = file { + result.push((file, relationship)); + }; + } + + result + } + + /// Get all license identifiers from the SPDX. + /// + /// # Errors + /// + /// Returns [`SpdxError`] if parsing of the expressions fails. + pub fn get_license_ids(&self) -> HashSet<String> { + info!("Getting all license identifiers from SPDX."); + + let mut license_ids = HashSet::new(); + + for file in &self.file_information { + for license in &file.concluded_license.identifiers() { + if license != "NOASSERTION" && license != "NONE" { + license_ids.insert(license.clone()); + } + } + } + + license_ids + } + + /// Get all relationships where the given SPDX ID is the SPDX element id. + pub fn relationships_for_spdx_id(&self, spdx_id: &str) -> Vec<&Relationship> { + self.relationships + .iter() + .filter(|relationship| relationship.spdx_element_id == spdx_id) + .collect() + } + + /// Get all relationships where the given SPDX ID is the related SPDX element id. + pub fn relationships_for_related_spdx_id(&self, spdx_id: &str) -> Vec<&Relationship> { + self.relationships + .iter() + .filter(|relationship| relationship.related_spdx_element == spdx_id) + .collect() + } +} + +#[cfg(test)] +mod test { + use std::{fs::read_to_string, iter::FromIterator}; + + use spdx_expression::SpdxExpression; + + use crate::models::RelationshipType; + + use super::*; + + #[test] + fn deserialize_simple_spdx() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + assert_eq!( + spdx_file.document_creation_information.document_name, + "SPDX-Tools-v2.0".to_string() + ); + } + + #[test] + fn find_related_files_for_package() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + let package_1_files = spdx_file.get_files_for_package("SPDXRef-Package"); + + assert_eq!(package_1_files.len(), 1); + + let file = package_1_files + .iter() + .find(|package_and_relationship| { + package_and_relationship.0.file_name == *"./lib-source/jena-2.6.3-sources.jar" + }) + .expect("Should always be found"); + + assert_eq!(file.0.file_spdx_identifier, "SPDXRef-JenaLib"); + assert_eq!(file.1.relationship_type, RelationshipType::Contains); + + assert_eq!( + file.0.concluded_license, + SpdxExpression::parse("LicenseRef-1").unwrap() + ); + } + + #[test] + fn get_all_licenses_from_spdx() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + let actual = spdx_file.get_license_ids(); + + let expected = HashSet::from_iter([ + "Apache-2.0".into(), + "LicenseRef-1".into(), + "LGPL-2.0-only".into(), + "LicenseRef-2".into(), + ]); + + assert_eq!(expected, actual); + } + + #[test] + fn get_relationships_for_spdx_id() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + let relationships = spdx_file.relationships_for_spdx_id("SPDXRef-Package"); + let relationship_1 = Relationship { + spdx_element_id: "SPDXRef-Package".into(), + related_spdx_element: "SPDXRef-Saxon".into(), + relationship_type: RelationshipType::DynamicLink, + comment: None, + }; + let relationship_2 = Relationship { + spdx_element_id: "SPDXRef-Package".into(), + related_spdx_element: "SPDXRef-JenaLib".into(), + relationship_type: RelationshipType::Contains, + comment: None, + }; + let expected_relationships = vec![&relationship_1, &relationship_2]; + + assert_eq!(relationships, expected_relationships); + } + + #[test] + fn get_relationships_for_related_spdx_id() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + + let relationships = spdx_file.relationships_for_related_spdx_id("SPDXRef-Package"); + let relationship_1 = Relationship { + spdx_element_id: "SPDXRef-DOCUMENT".into(), + related_spdx_element: "SPDXRef-Package".into(), + relationship_type: RelationshipType::Contains, + comment: None, + }; + let relationship_2 = Relationship { + spdx_element_id: "SPDXRef-DOCUMENT".into(), + related_spdx_element: "SPDXRef-Package".into(), + relationship_type: RelationshipType::Describes, + comment: None, + }; + let relationship_3 = Relationship { + spdx_element_id: "SPDXRef-JenaLib".into(), + related_spdx_element: "SPDXRef-Package".into(), + relationship_type: RelationshipType::Contains, + comment: None, + }; + let expected_relationships = vec![&relationship_1, &relationship_2, &relationship_3]; + + assert_eq!(relationships, expected_relationships); + } + + #[test] + fn get_unique_hashes_for_files() { + let spdx_file: SPDX = serde_json::from_str( + &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(), + ) + .unwrap(); + let hashes = spdx_file.get_unique_hashes(Algorithm::SHA1); + + let expected = [ + "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12".to_string(), + "c2b4e1c67a2d28fced849ee1bb76e7391b93f125".to_string(), + "3ab4e1c67a2d28fced849ee1bb76e7391b93f125".to_string(), + "d6a770ba38583ed4bb4525bd96e50461655d2758".to_string(), + ] + .iter() + .cloned() + .collect::<HashSet<_>>(); + + assert_eq!(hashes, expected); + } +} diff --git a/vendor/spdx-rs/src/parsers/mod.rs b/vendor/spdx-rs/src/parsers/mod.rs new file mode 100644 index 000000000..59ce03bae --- /dev/null +++ b/vendor/spdx-rs/src/parsers/mod.rs @@ -0,0 +1,1100 @@ +// SPDX-FileCopyrightText: 2021 HH Partners +// +// SPDX-License-Identifier: MIT + +//! Parsers for deserializing [`SPDX`] from different data formats. +//! +//! The SPDX spec supports some data formats that are not supported by [Serde], so parsing from JSON +//! (and YAML) is achieved with the data format specific crates: +//! +//! ```rust +//! # use spdx_rs::error::SpdxError; +//! use spdx_rs::models::SPDX; +//! # fn main() -> Result<(), SpdxError> { +//! +//! let spdx_file = std::fs::read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json")?; +//! let spdx_document: SPDX = serde_json::from_str(&spdx_file).unwrap(); +//! +//! assert_eq!( +//! spdx_document.document_creation_information.document_name, +//! "SPDX-Tools-v2.0" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! [Serde]: https://serde.rs + +use std::collections::HashSet; + +use chrono::{DateTime, Utc}; +use spdx_expression::{SimpleExpression, SpdxExpression}; + +use crate::{ + error::SpdxError, + models::{ + Annotation, AnnotationType, DocumentCreationInformation, ExternalPackageReference, + FileInformation, OtherLicensingInformationDetected, PackageInformation, Pointer, Range, + Relationship, Snippet, SPDX, + }, + parsers::tag_value::{atoms, Atom}, +}; + +mod tag_value; + +/// Parse a tag-value SPDX document to [`SPDX`]. +/// +/// # Usage +/// +/// ``` +/// # use spdx_rs::error::SpdxError; +/// use spdx_rs::parsers::spdx_from_tag_value; +/// # fn main() -> Result<(), SpdxError> { +/// +/// let spdx_file = std::fs::read_to_string("tests/data/SPDXTagExample-v2.2.spdx")?; +/// let spdx_document = spdx_from_tag_value(&spdx_file)?; +/// +/// assert_eq!( +/// spdx_document.document_creation_information.document_name, +/// "SPDX-Tools-v2.0" +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// +/// - If parsing of the tag-value fails. +/// - If parsing of some of the values fail. +pub fn spdx_from_tag_value(input: &str) -> Result<SPDX, SpdxError> { + let (_, atoms) = atoms(input).map_err(|err| SpdxError::TagValueParse(err.to_string()))?; + + let spdx = spdx_from_atoms(&atoms)?; + + Ok(spdx) +} + +#[allow(clippy::cognitive_complexity, clippy::too_many_lines)] +fn spdx_from_atoms(atoms: &[Atom]) -> Result<SPDX, SpdxError> { + let mut document_creation_information_in_progress = + Some(DocumentCreationInformation::default()); + let mut document_creation_information_final: Option<DocumentCreationInformation> = None; + + let mut package_information: Vec<PackageInformation> = Vec::new(); + let mut package_in_progress: Option<PackageInformation> = None; + let mut external_package_ref_in_progress: Option<ExternalPackageReference> = None; + + let mut other_licensing_information_detected: Vec<OtherLicensingInformationDetected> = + Vec::new(); + let mut license_info_in_progress: Option<OtherLicensingInformationDetected> = None; + + let mut file_information: Vec<FileInformation> = Vec::new(); + let mut file_in_progress: Option<FileInformation> = None; + + let mut snippet_information: Vec<Snippet> = Vec::new(); + let mut snippet_in_progress: Option<Snippet> = None; + + let mut relationships: HashSet<Relationship> = HashSet::new(); + let mut relationship_in_progress: Option<Relationship> = None; + + let mut annotations: Vec<Annotation> = Vec::new(); + let mut annotation_in_progress = AnnotationInProgress::default(); + + for atom in atoms { + let document_creation_information = process_atom_for_document_creation_information( + atom, + &mut document_creation_information_in_progress, + )?; + if let Some(document_creation_information) = document_creation_information { + document_creation_information_final = Some(document_creation_information); + document_creation_information_in_progress = None; + } + process_atom_for_packages( + atom, + &mut package_information, + &mut package_in_progress, + &mut external_package_ref_in_progress, + ); + process_atom_for_files( + atom, + &mut file_in_progress, + &mut file_information, + &package_in_progress, + &mut relationships, + ); + process_atom_for_snippets(atom, &mut snippet_information, &mut snippet_in_progress); + process_atom_for_relationships(atom, &mut relationships, &mut relationship_in_progress); + process_atom_for_annotations(atom, &mut annotations, &mut annotation_in_progress)?; + process_atom_for_license_info( + atom, + &mut other_licensing_information_detected, + &mut license_info_in_progress, + )?; + } + if let Some(file) = file_in_progress { + file_information.push(file); + } + if let Some(snippet) = &mut snippet_in_progress { + snippet_information.push(snippet.clone()); + } + + if let Some(package) = package_in_progress { + package_information.push(package); + } + + if let Some(relationship) = relationship_in_progress { + relationships.insert(relationship); + } + + if let Some(license_info) = license_info_in_progress { + other_licensing_information_detected.push(license_info); + } + + if document_creation_information_in_progress.is_some() { + document_creation_information_final = document_creation_information_in_progress; + } + + process_annotation(&mut annotation_in_progress, &mut annotations); + + Ok(SPDX { + document_creation_information: document_creation_information_final + // TODO: Proper error handling + .expect("If this doesn't exist, the document is not valid."), + package_information, + other_licensing_information_detected, + file_information, + snippet_information, + relationships: relationships.into_iter().collect(), + annotations, + // TODO: This should probably be removed. + spdx_ref_counter: 0, + }) +} + +fn process_atom_for_document_creation_information( + atom: &Atom, + mut document_creation_information_in_progress: &mut Option<DocumentCreationInformation>, +) -> Result<Option<DocumentCreationInformation>, SpdxError> { + // Get document creation information. + let mut final_creation_information = None; + match atom { + Atom::SpdxVersion(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.spdx_version = value.to_string(); + } + } + Atom::DataLicense(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.data_license = value.to_string(); + } + } + Atom::SPDXID(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.spdx_identifier = value.to_string(); + } + } + Atom::DocumentName(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.document_name = value.to_string(); + } + } + Atom::DocumentNamespace(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.spdx_document_namespace = value.to_string(); + } + } + Atom::ExternalDocumentRef(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information + .external_document_references + .push(value.clone()); + } + } + Atom::LicenseListVersion(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information + .creation_info + .license_list_version = Some(value.to_string()); + } + } + Atom::Creator(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information + .creation_info + .creators + .push(value.to_string()); + } + } + Atom::Created(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.creation_info.created = + DateTime::parse_from_rfc3339(value)?.with_timezone(&Utc); + } + } + Atom::CreatorComment(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.creation_info.creator_comment = + Some(value.to_string()); + } + } + Atom::DocumentComment(value) => { + if let Some(document_creation_information) = + &mut document_creation_information_in_progress + { + document_creation_information.document_comment = Some(value.to_string()); + } + } + Atom::TVComment(_) => {} + _ => { + if let Some(document_creation_information) = document_creation_information_in_progress { + final_creation_information = Some(document_creation_information.clone()); + } + } + } + Ok(final_creation_information) +} + +#[allow(clippy::too_many_lines, clippy::cognitive_complexity)] +fn process_atom_for_packages( + atom: &Atom, + packages: &mut Vec<PackageInformation>, + mut package_in_progress: &mut Option<PackageInformation>, + mut external_package_ref_in_progress: &mut Option<ExternalPackageReference>, +) { + match atom { + Atom::PackageName(value) => { + if let Some(package) = &mut package_in_progress { + if let Some(pkg_ref) = &mut external_package_ref_in_progress { + package.external_reference.push(pkg_ref.clone()); + *external_package_ref_in_progress = None; + } + packages.push(package.clone()); + } + *package_in_progress = Some(PackageInformation::default()); + + if let Some(package) = &mut package_in_progress { + package.package_name = value.to_string(); + } + } + Atom::SPDXID(value) => { + if let Some(package) = &mut package_in_progress { + if package.package_spdx_identifier == "NOASSERTION" { + package.package_spdx_identifier = value.to_string(); + } + } + } + Atom::PackageVersion(value) => { + if let Some(package) = &mut package_in_progress { + package.package_version = Some(value.to_string()); + } + } + Atom::PackageFileName(value) => { + if let Some(package) = &mut package_in_progress { + package.package_file_name = Some(value.to_string()); + } + } + Atom::PackageSupplier(value) => { + if let Some(package) = &mut package_in_progress { + package.package_supplier = Some(value.to_string()); + } + } + Atom::PackageOriginator(value) => { + if let Some(package) = &mut package_in_progress { + package.package_originator = Some(value.to_string()); + } + } + Atom::PackageDownloadLocation(value) => { + if let Some(package) = &mut package_in_progress { + package.package_download_location = value.to_string(); + } + } + Atom::PackageVerificationCode(value) => { + if let Some(package) = &mut package_in_progress { + package.package_verification_code = Some(value.clone()); + } + } + Atom::PackageChecksum(value) => { + if let Some(package) = &mut package_in_progress { + package.package_checksum.push(value.clone()); + } + } + Atom::PackageHomePage(value) => { + if let Some(package) = &mut package_in_progress { + package.package_home_page = Some(value.clone()); + } + } + Atom::PackageSourceInfo(value) => { + if let Some(package) = &mut package_in_progress { + package.source_information = Some(value.clone()); + } + } + Atom::PackageLicenseConcluded(value) => { + if let Some(package) = &mut package_in_progress { + package.concluded_license = SpdxExpression::parse(value).unwrap(); + } + } + Atom::PackageLicenseInfoFromFiles(value) => { + if let Some(package) = &mut package_in_progress { + package + .all_licenses_information_from_files + .push(value.clone()); + } + } + Atom::PackageLicenseDeclared(value) => { + if let Some(package) = &mut package_in_progress { + package.declared_license = SpdxExpression::parse(value).unwrap(); + } + } + Atom::PackageLicenseComments(value) => { + if let Some(package) = &mut package_in_progress { + package.comments_on_license = Some(value.clone()); + } + } + Atom::PackageCopyrightText(value) => { + if let Some(package) = &mut package_in_progress { + package.copyright_text = value.clone(); + } + } + Atom::PackageSummary(value) => { + if let Some(package) = &mut package_in_progress { + package.package_summary_description = Some(value.clone()); + } + } + Atom::PackageDescription(value) => { + if let Some(package) = &mut package_in_progress { + package.package_detailed_description = Some(value.clone()); + } + } + Atom::PackageAttributionText(value) => { + if let Some(package) = &mut package_in_progress { + package.package_attribution_text.push(value.clone()); + } + } + Atom::ExternalRef(value) => { + if let Some(pkg_ref) = &mut external_package_ref_in_progress { + if let Some(package) = &mut package_in_progress { + package.external_reference.push(pkg_ref.clone()); + } + } + *external_package_ref_in_progress = Some(value.clone()); + } + Atom::ExternalRefComment(value) => { + if let Some(pkg_ref) = &mut external_package_ref_in_progress { + pkg_ref.reference_comment = Some(value.clone()); + } + } + _ => {} + } +} + +fn process_atom_for_files( + atom: &Atom, + mut file_in_progress: &mut Option<FileInformation>, + files: &mut Vec<FileInformation>, + package_in_progress: &Option<PackageInformation>, + relationships: &mut HashSet<Relationship>, +) { + match atom { + Atom::PackageName(_) => { + if let Some(file) = &mut file_in_progress { + files.push(file.clone()); + *file_in_progress = None; + } + } + Atom::FileName(value) => { + if let Some(file) = &mut file_in_progress { + files.push(file.clone()); + } + *file_in_progress = Some(FileInformation::default()); + + if let Some(file) = &mut file_in_progress { + file.file_name = value.to_string(); + } + } + Atom::SPDXID(value) => { + if let Some(file) = &mut file_in_progress { + file.file_spdx_identifier = value.to_string(); + if let Some(package) = package_in_progress { + relationships.insert(Relationship::new( + &package.package_spdx_identifier, + value, + crate::models::RelationshipType::Contains, + None, + )); + } + } + } + Atom::FileComment(value) => { + if let Some(file) = &mut file_in_progress { + file.file_comment = Some(value.to_string()); + } + } + Atom::FileType(value) => { + if let Some(file) = &mut file_in_progress { + file.file_type.push(*value); + } + } + Atom::FileChecksum(value) => { + if let Some(file) = &mut file_in_progress { + file.file_checksum.push(value.clone()); + } + } + Atom::LicenseConcluded(value) => { + if let Some(file) = &mut file_in_progress { + file.concluded_license = SpdxExpression::parse(value).unwrap(); + } + } + Atom::LicenseInfoInFile(value) => { + if let Some(file) = &mut file_in_progress { + file.license_information_in_file + .push(SimpleExpression::parse(value).unwrap()); + } + } + Atom::LicenseComments(value) => { + if let Some(file) = &mut file_in_progress { + file.comments_on_license = Some(value.clone()); + } + } + Atom::FileCopyrightText(value) => { + if let Some(file) = &mut file_in_progress { + file.copyright_text = value.clone(); + } + } + Atom::FileNotice(value) => { + if let Some(file) = &mut file_in_progress { + file.file_notice = Some(value.clone()); + } + } + Atom::FileContributor(value) => { + if let Some(file) = &mut file_in_progress { + file.file_contributor.push(value.clone()); + } + } + _ => {} + } +} + +fn process_atom_for_snippets( + atom: &Atom, + snippets: &mut Vec<Snippet>, + mut snippet_in_progress: &mut Option<Snippet>, +) { + match atom { + Atom::SnippetSPDXID(value) => { + if let Some(snippet) = &snippet_in_progress { + snippets.push(snippet.clone()); + } + + *snippet_in_progress = Some(Snippet::default()); + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_spdx_identifier = value.to_string(); + } + } + Atom::SnippetFromFileSPDXID(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_from_file_spdx_identifier = value.to_string(); + } + } + Atom::SnippetByteRange(value) => { + if let Some(snippet) = &mut snippet_in_progress { + let start_pointer = Pointer::new_byte(None, value.0); + let end_pointer = Pointer::new_byte(None, value.1); + let range = Range::new(start_pointer, end_pointer); + snippet.ranges.push(range); + } + } + Atom::SnippetLineRange(value) => { + if let Some(snippet) = &mut snippet_in_progress { + let start_pointer = Pointer::new_line(None, value.0); + let end_pointer = Pointer::new_line(None, value.1); + let range = Range::new(start_pointer, end_pointer); + snippet.ranges.push(range); + } + } + Atom::SnippetLicenseConcluded(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_concluded_license = SpdxExpression::parse(value).unwrap(); + } + } + Atom::LicenseInfoInSnippet(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet + .license_information_in_snippet + .push(value.to_string()); + } + } + Atom::SnippetLicenseComments(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_comments_on_license = Some(value.to_string()); + } + } + Atom::SnippetCopyrightText(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_copyright_text = value.to_string(); + } + } + Atom::SnippetComment(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_comment = Some(value.to_string()); + } + } + Atom::SnippetName(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_name = Some(value.to_string()); + } + } + Atom::SnippetAttributionText(value) => { + if let Some(snippet) = &mut snippet_in_progress { + snippet.snippet_attribution_text = Some(value.to_string()); + } + } + _ => {} + } +} + +#[allow(clippy::unnecessary_wraps)] +fn process_atom_for_relationships( + atom: &Atom, + relationships: &mut HashSet<Relationship>, + mut relationship_in_progress: &mut Option<Relationship>, +) { + match atom { + Atom::Relationship(value) => { + if let Some(relationship) = relationship_in_progress { + relationships.insert(relationship.clone()); + } + *relationship_in_progress = Some(value.clone()); + } + Atom::RelationshipComment(value) => { + if let Some(relationship) = &mut relationship_in_progress { + relationship.comment = Some(value.to_string()); + } + } + _ => {} + } +} + +#[derive(Debug, Default)] +struct AnnotationInProgress { + annotator_in_progress: Option<String>, + date_in_progress: Option<DateTime<Utc>>, + comment_in_progress: Option<String>, + type_in_progress: Option<AnnotationType>, + spdxref_in_progress: Option<String>, +} + +fn process_annotation( + mut annotation_in_progress: &mut AnnotationInProgress, + + annotations: &mut Vec<Annotation>, +) { + if let AnnotationInProgress { + annotator_in_progress: Some(annotator), + date_in_progress: Some(date), + comment_in_progress: Some(comment), + type_in_progress: Some(annotation_type), + spdxref_in_progress: Some(spdxref), + } = &mut annotation_in_progress + { + let annotation = Annotation::new( + annotator.clone(), + *date, + *annotation_type, + Some(spdxref.clone()), + comment.clone(), + ); + *annotation_in_progress = AnnotationInProgress { + annotator_in_progress: None, + comment_in_progress: None, + date_in_progress: None, + spdxref_in_progress: None, + type_in_progress: None, + }; + annotations.push(annotation); + } +} + +fn process_atom_for_annotations( + atom: &Atom, + annotations: &mut Vec<Annotation>, + mut annotation_in_progress: &mut AnnotationInProgress, +) -> Result<(), SpdxError> { + process_annotation(annotation_in_progress, annotations); + + match atom { + Atom::Annotator(value) => { + annotation_in_progress.annotator_in_progress = Some(value.clone()); + } + Atom::AnnotationDate(value) => { + annotation_in_progress.date_in_progress = + Some(DateTime::parse_from_rfc3339(value)?.with_timezone(&Utc)); + } + Atom::AnnotationComment(value) => { + annotation_in_progress.comment_in_progress = Some(value.clone()); + } + Atom::AnnotationType(value) => { + annotation_in_progress.type_in_progress = Some(*value); + } + Atom::SPDXREF(value) => { + annotation_in_progress.spdxref_in_progress = Some(value.clone()); + } + _ => {} + } + + Ok(()) +} + +#[allow(clippy::unnecessary_wraps)] +fn process_atom_for_license_info( + atom: &Atom, + license_infos: &mut Vec<OtherLicensingInformationDetected>, + mut license_info_in_progress: &mut Option<OtherLicensingInformationDetected>, +) -> Result<(), SpdxError> { + match atom { + Atom::LicenseID(value) => { + if let Some(license_info) = &mut license_info_in_progress { + license_infos.push(license_info.clone()); + } + *license_info_in_progress = Some(OtherLicensingInformationDetected::default()); + + if let Some(license_info) = &mut license_info_in_progress { + license_info.license_identifier = value.to_string(); + } + } + Atom::ExtractedText(value) => { + if let Some(license_info) = &mut license_info_in_progress { + license_info.extracted_text = value.to_string(); + } + } + Atom::LicenseName(value) => { + if let Some(license_info) = &mut license_info_in_progress { + license_info.license_name = value.to_string(); + } + } + Atom::LicenseCrossReference(value) => { + if let Some(license_info) = &mut license_info_in_progress { + license_info.license_cross_reference.push(value.to_string()); + } + } + Atom::LicenseComment(value) => { + if let Some(license_info) = &mut license_info_in_progress { + license_info.license_comment = Some(value.to_string()); + } + } + _ => {} + } + + Ok(()) +} + +#[cfg(test)] +#[allow(clippy::too_many_lines)] +mod test_super { + use std::{fs::read_to_string, iter::FromIterator}; + + use chrono::TimeZone; + + use crate::models::{ + Algorithm, Checksum, ExternalDocumentReference, ExternalPackageReferenceCategory, FileType, + }; + + use super::*; + + #[test] + fn whole_spdx_is_parsed() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + assert_eq!(spdx.package_information.len(), 4); + assert_eq!(spdx.file_information.len(), 4); + } + + #[test] + fn spdx_creation_info_is_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let document_creation_information = spdx.document_creation_information; + assert_eq!(document_creation_information.spdx_version, "SPDX-2.2"); + assert_eq!(document_creation_information.data_license, "CC0-1.0"); + assert_eq!( + document_creation_information.spdx_document_namespace, + "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301" + ); + assert_eq!( + document_creation_information.document_name, + "SPDX-Tools-v2.0" + ); + assert_eq!( + document_creation_information.spdx_identifier, + "SPDXRef-DOCUMENT" + ); + assert_eq!( + document_creation_information.document_comment, + Some( + "This document was created using SPDX 2.0 using licenses from the web site." + .to_string() + ) + ); + assert_eq!( + document_creation_information.external_document_references, + vec![ExternalDocumentReference::new( + "spdx-tool-1.2".to_string(), + "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + .to_string(), + Checksum::new(Algorithm::SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759") + )] + ); + assert!(document_creation_information + .creation_info + .creators + .contains(&"Tool: LicenseFind-1.0".to_string())); + assert!(document_creation_information + .creation_info + .creators + .contains(&"Organization: ExampleCodeInspect ()".to_string())); + assert!(document_creation_information + .creation_info + .creators + .contains(&"Person: Jane Doe ()".to_string())); + assert_eq!( + document_creation_information.creation_info.created, + Utc.ymd(2010, 1, 29).and_hms(18, 30, 22) + ); + assert_eq!( + document_creation_information.creation_info.creator_comment, + Some( + "This package has been shipped in source and binary form. +The binaries were created with gcc 4.5.1 and expect to link to +compatible system run time libraries." + .to_string() + ) + ); + assert_eq!( + document_creation_information + .creation_info + .license_list_version, + Some("3.9".to_string()) + ); + } + + #[test] + fn package_info_is_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let packages = spdx.package_information; + assert_eq!(packages.len(), 4); + + let glibc = packages.iter().find(|p| p.package_name == "glibc").unwrap(); + assert_eq!(glibc.package_spdx_identifier, "SPDXRef-Package"); + assert_eq!(glibc.package_version, Some("2.11.1".to_string())); + assert_eq!( + glibc.package_file_name, + Some("glibc-2.11.1.tar.gz".to_string()) + ); + assert_eq!( + glibc.package_supplier, + Some("Person: Jane Doe (jane.doe@example.com)".to_string()) + ); + assert_eq!( + glibc.package_originator, + Some("Organization: ExampleCodeInspect (contact@example.com)".to_string()) + ); + assert_eq!( + glibc.package_download_location, + "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz".to_string() + ); + assert_eq!( + glibc.package_verification_code.as_ref().unwrap().value, + "d6a770ba38583ed4bb4525bd96e50461655d2758".to_string() + ); + assert_eq!( + glibc.package_verification_code.as_ref().unwrap().excludes, + vec!["./package.spdx"] + ); + assert_eq!( + glibc.package_checksum, + vec![ + Checksum::new(Algorithm::MD5, "624c1abb3664f4b35547e7c73864ad24"), + Checksum::new(Algorithm::SHA1, "85ed0817af83a24ad8da68c2b5094de69833983c"), + Checksum::new( + Algorithm::SHA256, + "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd" + ), + ] + ); + assert_eq!( + glibc.package_home_page, + Some("http://ftp.gnu.org/gnu/glibc".to_string()) + ); + assert_eq!( + glibc.source_information, + Some("uses glibc-2_11-branch from git://sourceware.org/git/glibc.git.".to_string()) + ); + assert_eq!( + glibc.concluded_license.identifiers(), + HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-3".to_string()]) + ); + assert_eq!( + glibc.all_licenses_information_from_files, + vec!["GPL-2.0-only", "LicenseRef-2", "LicenseRef-1"] + ); + assert_eq!( + glibc.declared_license.identifiers(), + HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-3".to_string()]) + ); + assert_eq!(glibc.comments_on_license, Some("The license for this project changed with the release of version x.y. The version of the project included here post-dates the license change.".to_string())); + assert_eq!(glibc.copyright_text, "Copyright 2008-2010 John Smith"); + assert_eq!( + glibc.package_summary_description, + Some("GNU C library.".to_string()) + ); + assert_eq!(glibc.package_detailed_description, Some("The GNU C Library defines functions that are specified by the ISO C standard, as well as additional features specific to POSIX and other derivatives of the Unix operating system, and extensions specific to GNU systems.".to_string())); + assert_eq!( + glibc.package_attribution_text, + vec!["The GNU C Library is free software. See the file COPYING.LIB for copying conditions, and LICENSES for notices about a few contributions that require these additional notices to be distributed. License copyright years may be listed using range notation, e.g., 1996-2015, indicating that every year in the range, inclusive, is a copyrightable year that would otherwise be listed individually.".to_string()] + ); + assert_eq!( + glibc.external_reference, + vec![ + ExternalPackageReference::new( + ExternalPackageReferenceCategory::Security, + "cpe23Type".to_string(), + "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*".to_string(), + None + ), + ExternalPackageReference::new( + ExternalPackageReferenceCategory::Other, + "LocationRef-acmeforge".to_string(), + "acmecorp/acmenator/4.1.3-alpha".to_string(), + Some("This is the external ref for Acme".to_string()) + ), + ] + ); + let jena = packages.iter().find(|p| p.package_name == "Jena").unwrap(); + assert_eq!(jena.package_spdx_identifier, "SPDXRef-fromDoap-0"); + assert_eq!( + jena.external_reference, + vec![ExternalPackageReference::new( + ExternalPackageReferenceCategory::PackageManager, + "purl".to_string(), + "pkg:maven/org.apache.jena/apache-jena@3.12.0".to_string(), + None + ),] + ); + } + + #[test] + fn file_info_is_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let files = spdx.file_information; + assert_eq!(files.len(), 4); + + let fooc = files + .iter() + .find(|p| p.file_name == "./package/foo.c") + .unwrap(); + assert_eq!(fooc.file_spdx_identifier, "SPDXRef-File"); + assert_eq!(fooc.file_comment, Some("The concluded license was taken from the package level that the file was included in. +This information was found in the COPYING.txt file in the xyz directory.".to_string())); + assert_eq!(fooc.file_type, vec![FileType::Source]); + assert_eq!( + fooc.file_checksum, + vec![ + Checksum::new(Algorithm::SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"), + Checksum::new(Algorithm::MD5, "624c1abb3664f4b35547e7c73864ad24") + ] + ); + assert_eq!( + fooc.concluded_license.identifiers(), + HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-2".to_string(),]) + ); + assert_eq!( + fooc.license_information_in_file, + vec![ + SimpleExpression::parse("GPL-2.0-only").unwrap(), + SimpleExpression::parse("LicenseRef-2").unwrap() + ] + ); + assert_eq!(fooc.comments_on_license, Some("The concluded license was taken from the package level that the file was included in.".to_string())); + assert_eq!( + fooc.copyright_text, + "Copyright 2008-2010 John Smith".to_string() + ); + assert_eq!( + fooc.file_notice, + Some("Copyright (c) 2001 Aaron Lehmann aaroni@vitelus.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the �Software�), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED �AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.".to_string()) + ); + assert_eq!( + fooc.file_contributor, + vec![ + "The Regents of the University of California".to_string(), + "Modified by Paul Mundt lethal@linux-sh.org".to_string(), + "IBM Corporation".to_string(), + ] + ); + let doap = files + .iter() + .find(|p| p.file_name == "./src/org/spdx/parser/DOAPProject.java") + .unwrap(); + + assert_eq!(doap.file_spdx_identifier, "SPDXRef-DoapSource"); + } + + #[test] + fn snippet_info_is_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let snippets = spdx.snippet_information; + assert_eq!(snippets.len(), 1); + + let snippet = snippets[0].clone(); + + assert_eq!(snippet.snippet_spdx_identifier, "SPDXRef-Snippet"); + assert_eq!( + snippet.snippet_from_file_spdx_identifier, + "SPDXRef-DoapSource" + ); + assert_eq!(snippet.ranges.len(), 2); + assert!(snippet + .ranges + .iter() + .any(|snip| snip.start_pointer == Pointer::new_byte(None, 310))); + assert!(snippet + .ranges + .iter() + .any(|snip| snip.end_pointer == Pointer::new_byte(None, 420))); + assert!(snippet + .ranges + .iter() + .any(|snip| snip.start_pointer == Pointer::new_line(None, 5))); + assert!(snippet + .ranges + .iter() + .any(|snip| snip.end_pointer == Pointer::new_line(None, 23))); + assert_eq!( + snippet.snippet_concluded_license, + SpdxExpression::parse("GPL-2.0-only").unwrap() + ); + assert_eq!(snippet.license_information_in_snippet, vec!["GPL-2.0-only"]); + assert_eq!(snippet.snippet_comments_on_license, Some("The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.".to_string())); + assert_eq!( + snippet.snippet_copyright_text, + "Copyright 2008-2010 John Smith" + ); + assert_eq!(snippet.snippet_comment, Some("This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0.".to_string())); + assert_eq!(snippet.snippet_name, Some("from linux kernel".to_string())); + } + + #[test] + fn relationships_are_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let relationships = spdx.relationships; + assert_eq!(relationships.len(), 11); + + assert!(relationships.contains(&Relationship::new( + "SPDXRef-DOCUMENT", + "SPDXRef-Package", + crate::models::RelationshipType::Contains, + None + ))); + assert!(relationships.contains(&Relationship::new( + "SPDXRef-CommonsLangSrc", + "NOASSERTION", + crate::models::RelationshipType::GeneratedFrom, + None + ))); + + // Implied relationship by the file following the package in tag-value. + assert!(relationships.contains(&Relationship::new( + "SPDXRef-Package", + "SPDXRef-DoapSource", + crate::models::RelationshipType::Contains, + None + ))); + } + + #[test] + fn annotations_are_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let annotations = spdx.annotations; + assert_eq!(annotations.len(), 5); + + assert_eq!( + annotations[2], + Annotation::new( + "Person: Suzanne Reviewer".to_string(), + Utc.ymd(2011, 3, 13).and_hms(0, 0, 0), + AnnotationType::Review, + Some("SPDXRef-DOCUMENT".to_string()), + "Another example reviewer.".to_string() + ) + ); + } + + #[test] + fn license_info_is_retrieved() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + let license_info = spdx.other_licensing_information_detected; + assert_eq!(license_info.len(), 5); + assert_eq!(license_info[1].license_identifier, "LicenseRef-2"); + assert_eq!( + license_info[3].license_name, + "Beer-Ware License (Version 42)" + ); + assert_eq!( + license_info[3].license_cross_reference, + vec!["http://people.freebsd.org/~phk/"] + ); + assert_eq!( + license_info[3].license_comment, + Some("The beerware license has a couple of other standard variants.".to_string()) + ); + assert!(license_info[3] + .extracted_text + .starts_with(r#""THE BEER-WARE"#)); + assert!(license_info[3] + .extracted_text + .ends_with("Poul-Henning Kamp")); + } + + #[test] + fn tag_value_is_parsed() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let spdx = spdx_from_tag_value(&file).unwrap(); + + assert_eq!(spdx.package_information.len(), 4); + assert_eq!(spdx.file_information.len(), 4); + assert_eq!(spdx.snippet_information.len(), 1); + assert_eq!(spdx.relationships.len(), 11); + assert_eq!(spdx.annotations.len(), 5); + assert_eq!(spdx.other_licensing_information_detected.len(), 5); + } +} diff --git a/vendor/spdx-rs/src/parsers/tag_value.rs b/vendor/spdx-rs/src/parsers/tag_value.rs new file mode 100644 index 000000000..0c8fdb4e3 --- /dev/null +++ b/vendor/spdx-rs/src/parsers/tag_value.rs @@ -0,0 +1,669 @@ +// SPDX-FileCopyrightText: 2021 HH Partners +// +// SPDX-License-Identifier: MIT + +use std::{num::ParseIntError, str::FromStr}; + +use nom::{ + branch::alt, + bytes::complete::{tag, take_until, take_while}, + character::complete::{alphanumeric0, char, digit1, multispace0, not_line_ending}, + combinator::{map, map_res, opt}, + error::{ParseError, VerboseError}, + multi::many0, + sequence::{delimited, preceded, separated_pair, tuple}, + AsChar, IResult, +}; + +use crate::models::{ + Algorithm, AnnotationType, Checksum, ExternalDocumentReference, ExternalPackageReference, + ExternalPackageReferenceCategory, FileType, PackageVerificationCode, Relationship, + RelationshipType, +}; + +#[derive(Debug, Clone, PartialEq)] +#[allow(clippy::upper_case_acronyms)] +pub(super) enum Atom { + // Document Creation Information + SpdxVersion(String), + DataLicense(String), + SPDXID(String), + DocumentName(String), + DocumentNamespace(String), + ExternalDocumentRef(ExternalDocumentReference), + LicenseListVersion(String), + Creator(String), + Created(String), + CreatorComment(String), + DocumentComment(String), + + // Package Information + PackageName(String), + PackageVersion(String), + PackageFileName(String), + PackageSupplier(String), + PackageOriginator(String), + PackageDownloadLocation(String), + FilesAnalyzed(String), + PackageVerificationCode(PackageVerificationCode), + PackageChecksum(Checksum), + PackageHomePage(String), + PackageSourceInfo(String), + PackageLicenseConcluded(String), + PackageLicenseInfoFromFiles(String), + PackageLicenseDeclared(String), + PackageLicenseComments(String), + PackageCopyrightText(String), + PackageSummary(String), + PackageDescription(String), + PackageComment(String), + ExternalRef(ExternalPackageReference), + ExternalRefComment(String), + PackageAttributionText(String), + + // File Information + FileName(String), + FileType(FileType), + FileChecksum(Checksum), + LicenseConcluded(String), + LicenseInfoInFile(String), + LicenseComments(String), + FileCopyrightText(String), + FileComment(String), + FileNotice(String), + FileContributor(String), + FileAttributionText(String), + + // Snippet Information + SnippetSPDXID(String), + SnippetFromFileSPDXID(String), + SnippetByteRange((i32, i32)), + SnippetLineRange((i32, i32)), + SnippetLicenseConcluded(String), + LicenseInfoInSnippet(String), + SnippetLicenseComments(String), + SnippetCopyrightText(String), + SnippetComment(String), + SnippetName(String), + SnippetAttributionText(String), + + // Other Licensing Information Detected + LicenseID(String), + ExtractedText(String), + LicenseName(String), + LicenseCrossReference(String), + LicenseComment(String), + + // Relationship + Relationship(Relationship), + RelationshipComment(String), + + // Annotation + Annotator(String), + AnnotationDate(String), + AnnotationType(AnnotationType), + SPDXREF(String), + AnnotationComment(String), + + /// Comment in the document. Not part of the final SPDX. + TVComment(String), +} + +pub(super) fn atoms(i: &str) -> IResult<&str, Vec<Atom>, VerboseError<&str>> { + many0(alt((ws(tv_comment), ws(tag_value_to_atom))))(i) +} + +fn tag_value_to_atom(i: &str) -> IResult<&str, Atom, VerboseError<&str>> { + let (i, key_value) = tag_value(i)?; + match key_value.0 { + // Document Creation Information + "SPDXVersion" => Ok((i, Atom::SpdxVersion(key_value.1.to_string()))), + "DataLicense" => Ok((i, Atom::DataLicense(key_value.1.to_string()))), + "SPDXID" => Ok((i, Atom::SPDXID(key_value.1.to_string()))), + "DocumentName" => Ok((i, Atom::DocumentName(key_value.1.to_string()))), + "DocumentNamespace" => Ok((i, Atom::DocumentNamespace(key_value.1.to_string()))), + "ExternalDocumentRef" => { + let (_, value) = external_document_reference(key_value.1)?; + Ok((i, Atom::ExternalDocumentRef(value))) + } + "LicenseListVersion" => Ok((i, Atom::LicenseListVersion(key_value.1.to_string()))), + "Creator" => Ok((i, Atom::Creator(key_value.1.to_string()))), + "Created" => Ok((i, Atom::Created(key_value.1.to_string()))), + "CreatorComment" => Ok((i, Atom::CreatorComment(key_value.1.to_string()))), + "DocumentComment" => Ok((i, Atom::DocumentComment(key_value.1.to_string()))), + + // Package Information + "PackageName" => Ok((i, Atom::PackageName(key_value.1.to_string()))), + "PackageVersion" => Ok((i, Atom::PackageVersion(key_value.1.to_string()))), + "PackageFileName" => Ok((i, Atom::PackageFileName(key_value.1.to_string()))), + "PackageSupplier" => Ok((i, Atom::PackageSupplier(key_value.1.to_string()))), + "PackageOriginator" => Ok((i, Atom::PackageOriginator(key_value.1.to_string()))), + "PackageDownloadLocation" => { + Ok((i, Atom::PackageDownloadLocation(key_value.1.to_string()))) + } + "FilesAnalyzed" => Ok((i, Atom::FilesAnalyzed(key_value.1.to_string()))), + "PackageVerificationCode" => { + let (_, value) = package_verification_code(key_value.1)?; + Ok((i, Atom::PackageVerificationCode(value))) + } + "PackageChecksum" => Ok((i, Atom::PackageChecksum(checksum(key_value.1)?.1))), + "PackageHomePage" => Ok((i, Atom::PackageHomePage(key_value.1.to_string()))), + "PackageSourceInfo" => Ok((i, Atom::PackageSourceInfo(key_value.1.to_string()))), + "PackageLicenseConcluded" => { + Ok((i, Atom::PackageLicenseConcluded(key_value.1.to_string()))) + } + "PackageLicenseInfoFromFiles" => Ok(( + i, + Atom::PackageLicenseInfoFromFiles(key_value.1.to_string()), + )), + "PackageLicenseDeclared" => Ok((i, Atom::PackageLicenseDeclared(key_value.1.to_string()))), + "PackageLicenseComments" => Ok((i, Atom::PackageLicenseComments(key_value.1.to_string()))), + "PackageCopyrightText" => Ok((i, Atom::PackageCopyrightText(key_value.1.to_string()))), + "PackageSummary" => Ok((i, Atom::PackageSummary(key_value.1.to_string()))), + "PackageDescription" => Ok((i, Atom::PackageDescription(key_value.1.to_string()))), + "PackageComment" => Ok((i, Atom::PackageComment(key_value.1.to_string()))), + "ExternalRef" => Ok(( + i, + Atom::ExternalRef(external_package_reference(key_value.1)?.1), + )), + "ExternalRefComment" => Ok((i, Atom::ExternalRefComment(key_value.1.to_string()))), + "PackageAttributionText" => Ok((i, Atom::PackageAttributionText(key_value.1.to_string()))), + + // File Information + "FileName" => Ok((i, Atom::FileName(key_value.1.to_string()))), + "FileType" => Ok((i, Atom::FileType(file_type(key_value.1)?.1))), + "FileChecksum" => Ok((i, Atom::FileChecksum(checksum(key_value.1)?.1))), + "LicenseConcluded" => Ok((i, Atom::LicenseConcluded(key_value.1.to_string()))), + "LicenseInfoInFile" => Ok((i, Atom::LicenseInfoInFile(key_value.1.to_string()))), + "LicenseComments" => Ok((i, Atom::LicenseComments(key_value.1.to_string()))), + "FileCopyrightText" => Ok((i, Atom::FileCopyrightText(key_value.1.to_string()))), + "FileComment" => Ok((i, Atom::FileComment(key_value.1.to_string()))), + "FileNotice" => Ok((i, Atom::FileNotice(key_value.1.to_string()))), + "FileContributor" => Ok((i, Atom::FileContributor(key_value.1.to_string()))), + "FileAttributionText" => Ok((i, Atom::FileAttributionText(key_value.1.to_string()))), + + // Snippet Information + "SnippetSPDXID" => Ok((i, Atom::SnippetSPDXID(key_value.1.to_string()))), + "SnippetFromFileSPDXID" => Ok((i, Atom::SnippetFromFileSPDXID(key_value.1.to_string()))), + "SnippetByteRange" => Ok((i, Atom::SnippetByteRange(range(key_value.1)?.1))), + "SnippetLineRange" => Ok((i, Atom::SnippetLineRange(range(key_value.1)?.1))), + "SnippetLicenseConcluded" => { + Ok((i, Atom::SnippetLicenseConcluded(key_value.1.to_string()))) + } + "LicenseInfoInSnippet" => Ok((i, Atom::LicenseInfoInSnippet(key_value.1.to_string()))), + "SnippetLicenseComments" => Ok((i, Atom::SnippetLicenseComments(key_value.1.to_string()))), + "SnippetCopyrightText" => Ok((i, Atom::SnippetCopyrightText(key_value.1.to_string()))), + "SnippetComment" => Ok((i, Atom::SnippetComment(key_value.1.to_string()))), + "SnippetName" => Ok((i, Atom::SnippetName(key_value.1.to_string()))), + "SnippetAttributionText" => Ok((i, Atom::SnippetAttributionText(key_value.1.to_string()))), + + // Other Licensing Information Detected + "LicenseID" => Ok((i, Atom::LicenseID(key_value.1.to_string()))), + "ExtractedText" => Ok((i, Atom::ExtractedText(key_value.1.to_string()))), + "LicenseName" => Ok((i, Atom::LicenseName(key_value.1.to_string()))), + "LicenseCrossReference" => Ok((i, Atom::LicenseCrossReference(key_value.1.to_string()))), + "LicenseComment" => Ok((i, Atom::LicenseComment(key_value.1.to_string()))), + + // Relationship + "Relationship" => Ok((i, Atom::Relationship(relationship(key_value.1)?.1))), + "RelationshipComment" => Ok((i, Atom::RelationshipComment(key_value.1.to_string()))), + + // Annotation + "Annotator" => Ok((i, Atom::Annotator(key_value.1.to_string()))), + "AnnotationDate" => Ok((i, Atom::AnnotationDate(key_value.1.to_string()))), + "AnnotationType" => Ok((i, Atom::AnnotationType(annotation_type(key_value.1)?.1))), + "SPDXREF" => Ok((i, Atom::SPDXREF(key_value.1.to_string()))), + "AnnotationComment" => Ok((i, Atom::AnnotationComment(key_value.1.to_string()))), + v => { + dbg!(v); + unimplemented!() + } + } +} + +fn external_document_reference( + i: &str, +) -> IResult<&str, ExternalDocumentReference, VerboseError<&str>> { + map( + tuple(( + document_ref, + ws(take_while(|c: char| !c.is_whitespace())), + ws(checksum), + )), + |(id_string, spdx_document_uri, checksum)| { + ExternalDocumentReference::new( + id_string.to_string(), + spdx_document_uri.to_string(), + checksum, + ) + }, + )(i) +} + +fn annotation_type(i: &str) -> IResult<&str, AnnotationType, VerboseError<&str>> { + match ws(not_line_ending)(i) { + Ok((i, value)) => match value { + "REVIEW" => Ok((i, AnnotationType::Review)), + "OTHER" => Ok((i, AnnotationType::Other)), + // Proper error + _ => todo!(), + }, + Err(err) => Err(err), + } +} + +fn file_type(i: &str) -> IResult<&str, FileType, VerboseError<&str>> { + match ws(not_line_ending)(i) { + Ok((i, value)) => match value { + "SOURCE" => Ok((i, FileType::Source)), + "BINARY" => Ok((i, FileType::Binary)), + "ARCHIVE" => Ok((i, FileType::Archive)), + "APPLICATION" => Ok((i, FileType::Application)), + "AUDIO" => Ok((i, FileType::Audio)), + "IMAGE" => Ok((i, FileType::Image)), + "TEXT" => Ok((i, FileType::Text)), + "VIDEO" => Ok((i, FileType::Video)), + "DOCUMENTATION" => Ok((i, FileType::Documentation)), + "SPDX" => Ok((i, FileType::SPDX)), + "OTHER" => Ok((i, FileType::Other)), + // Proper error + _ => todo!(), + }, + Err(err) => Err(err), + } +} + +fn document_ref<'a>(i: &'a str) -> IResult<&'a str, &str, VerboseError<&'a str>> { + preceded(tag("DocumentRef-"), ws(idstring))(i) +} + +fn relationship(i: &str) -> IResult<&str, Relationship, VerboseError<&str>> { + map( + tuple(( + ws(take_while(|c: char| !c.is_whitespace())), + ws(take_while(|c: char| !c.is_whitespace())), + ws(not_line_ending), + )), + |(item1, relationship_type, item2)| { + let relationship_type = relationship_type.to_uppercase(); + let relationship_type = match relationship_type.as_str() { + "DESCRIBES" => RelationshipType::Describes, + "DESCRIBED_BY" => RelationshipType::DescribedBy, + "CONTAINS" => RelationshipType::Contains, + "CONTAINED_BY" => RelationshipType::ContainedBy, + "DEPENDS_ON" => RelationshipType::DependsOn, + "DEPENDENCY_OF" => RelationshipType::DependencyOf, + "DEPENDENCY_MANIFEST_OF" => RelationshipType::DependencyManifestOf, + "BUILD_DEPENDENCY_OF" => RelationshipType::BuildDependencyOf, + "DEV_DEPENDENCY_OF" => RelationshipType::DevDependencyOf, + "OPTIONAL_DEPENDENCY_OF" => RelationshipType::OptionalDependencyOf, + "PROVIDED_DEPENDENCY_OF" => RelationshipType::ProvidedDependencyOf, + "TEST_DEPENDENCY_OF" => RelationshipType::TestDependencyOf, + "RUNTIME_DEPENDENCY_OF" => RelationshipType::RuntimeDependencyOf, + "EXAMPLE_OF" => RelationshipType::ExampleOf, + "GENERATES" => RelationshipType::Generates, + "GENERATED_FROM" => RelationshipType::GeneratedFrom, + "ANCESTOR_OF" => RelationshipType::AncestorOf, + "DESCENDANT_OF" => RelationshipType::DescendantOf, + "VARIANT_OF" => RelationshipType::VariantOf, + "DISTRIBUTION_ARTIFACT" => RelationshipType::DistributionArtifact, + "PATCH_FOR" => RelationshipType::PatchFor, + "PATCH_APPLIED" => RelationshipType::PatchApplied, + "COPY_OF" => RelationshipType::CopyOf, + "FILE_ADDED" => RelationshipType::FileAdded, + "FILE_DELETED" => RelationshipType::FileDeleted, + "FILE_MODIFIED" => RelationshipType::FileModified, + "EXPANDED_FROM_ARCHIVE" => RelationshipType::ExpandedFromArchive, + "DYNAMIC_LINK" => RelationshipType::DynamicLink, + "STATIC_LINK" => RelationshipType::StaticLink, + "DATA_FILE_OF" => RelationshipType::DataFileOf, + "TEST_CASE_OF" => RelationshipType::TestCaseOf, + "BUILD_TOOL_OF" => RelationshipType::BuildToolOf, + "DEV_TOOL_OF" => RelationshipType::DevToolOf, + "TEST_OF" => RelationshipType::TestOf, + "TEST_TOOL_OF" => RelationshipType::TestToolOf, + "DOCUMENTATION_OF" => RelationshipType::DocumentationOf, + "OPTIONAL_COMPONENT_OF" => RelationshipType::OptionalComponentOf, + "METAFILE_OF" => RelationshipType::MetafileOf, + "PACKAGE_OF" => RelationshipType::PackageOf, + "AMENDS" => RelationshipType::Amends, + "PREREQUISITE_FOR" => RelationshipType::PrerequisiteFor, + "HAS_PREREQUISITE" => RelationshipType::HasPrerequisite, + "OTHER" => RelationshipType::Other, + // TODO: Proper error. + _ => { + dbg!(relationship_type); + todo!() + } + }; + Relationship::new(item1, item2, relationship_type, None) + }, + )(i) +} + +fn external_package_reference( + i: &str, +) -> IResult<&str, ExternalPackageReference, VerboseError<&str>> { + map( + tuple(( + ws(take_while(|c: char| !c.is_whitespace())), + ws(take_while(|c: char| !c.is_whitespace())), + ws(not_line_ending), + )), + |(category, ref_type, locator)| { + let category = match category { + "SECURITY" => ExternalPackageReferenceCategory::Security, + "PACKAGE-MANAGER" => ExternalPackageReferenceCategory::PackageManager, + "PERSISTENT-ID" => ExternalPackageReferenceCategory::PersistentID, + "OTHER" => ExternalPackageReferenceCategory::Other, + // TODO: Proper error handling + _ => todo!(), + }; + ExternalPackageReference::new(category, ref_type.to_string(), locator.to_string(), None) + }, + )(i) +} + +fn package_verification_code( + i: &str, +) -> IResult<&str, PackageVerificationCode, VerboseError<&str>> { + map( + alt(( + separated_pair( + ws(take_until("(excludes:")), + ws(tag("(excludes:")), + opt(take_until(")")), + ), + map(ws(not_line_ending), |v| (v, None)), + )), + |(value, exclude)| { + #[allow(clippy::option_if_let_else)] + let excludes = if let Some(exclude) = exclude { + vec![exclude.to_string()] + } else { + Vec::new() + }; + PackageVerificationCode::new(value.to_string(), excludes) + }, + )(i) +} + +fn range(i: &str) -> IResult<&str, (i32, i32), VerboseError<&str>> { + map_res::<_, _, _, _, ParseIntError, _, _>( + separated_pair(digit1, char(':'), digit1), + |(left, right)| Ok((i32::from_str(left)?, i32::from_str(right)?)), + )(i) +} + +fn idstring<'a>(i: &'a str) -> IResult<&'a str, &str, VerboseError<&'a str>> { + take_while(|c: char| c.is_alphanum() || c == '.' || c == '-' || c == '+')(i) +} + +fn checksum(i: &str) -> IResult<&str, Checksum, VerboseError<&str>> { + map( + separated_pair(ws(take_until(":")), char(':'), ws(not_line_ending)), + |(algorithm, value)| { + let checksum_algorithm = match algorithm { + "SHA1" => Algorithm::SHA1, + "SHA224" => Algorithm::SHA224, + "SHA256" => Algorithm::SHA256, + "SHA384" => Algorithm::SHA384, + "SHA512" => Algorithm::SHA512, + "MD2" => Algorithm::MD2, + "MD4" => Algorithm::MD4, + "MD5" => Algorithm::MD5, + "MD6" => Algorithm::MD6, + // TODO: Use proper error. + _ => todo!(), + }; + Checksum::new(checksum_algorithm, value) + }, + )(i) +} + +fn tv_comment(i: &str) -> IResult<&str, Atom, VerboseError<&str>> { + map(preceded(ws(tag("#")), ws(not_line_ending)), |v| { + Atom::TVComment(v.to_string()) + })(i) +} + +fn tag_value<'a>(i: &'a str) -> IResult<&'a str, (&str, &str), VerboseError<&'a str>> { + separated_pair( + ws(alphanumeric0), + tag(":"), + alt((ws(multiline_text), ws(not_line_ending))), + )(i) +} + +fn multiline_text<'a>(i: &'a str) -> IResult<&'a str, &str, VerboseError<&'a str>> { + delimited(tag("<text>"), take_until("</text>"), tag("</text>"))(i) +} + +/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and +/// trailing whitespace, returning the output of `inner`. +fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +where + F: Fn(&'a str) -> IResult<&'a str, O, E> + 'a, +{ + delimited(multispace0, inner, multispace0) +} + +#[cfg(test)] +mod tests { + use std::fs::read_to_string; + + use crate::{ + models::{Algorithm, AnnotationType, ExternalPackageReferenceCategory, Relationship}, + parsers::tag_value::{ + annotation_type, checksum, document_ref, external_document_reference, + external_package_reference, package_verification_code, range, relationship, + }, + }; + + use super::{atoms, tag_value, tag_value_to_atom, Atom}; + + #[test] + fn version_can_be_parsed() { + let (_, value) = tag_value_to_atom("SPDXVersion: SPDX-1.2").unwrap(); + assert_eq!(value, Atom::SpdxVersion("SPDX-1.2".to_string())); + } + + #[test] + fn range_can_be_parsed() { + let (_, value) = range("310:420").unwrap(); + assert_eq!(value, (310, 420)); + } + + #[test] + fn annotation_type_can_be_parsed() { + let (_, value) = annotation_type("REVIEW").unwrap(); + assert_eq!(value, AnnotationType::Review); + let (_, value) = annotation_type("OTHER").unwrap(); + assert_eq!(value, AnnotationType::Other); + } + + #[test] + fn relationship_can_be_parsed() { + let (_, value) = relationship("SPDXRef-JenaLib CONTAINS SPDXRef-Package").unwrap(); + let expected = Relationship::new( + "SPDXRef-JenaLib", + "SPDXRef-Package", + crate::models::RelationshipType::Contains, + None, + ); + assert_eq!(value, expected); + } + + #[test] + fn data_license_can_be_parsed() { + let (_, value) = tag_value_to_atom("DataLicense: CC0-1.0").unwrap(); + assert_eq!(value, Atom::DataLicense("CC0-1.0".to_string())); + } + + #[test] + fn package_verification_code_can_be_parsed() { + let (_, value) = package_verification_code( + "d6a770ba38583ed4bb4525bd96e50461655d2758(excludes: ./package.spdx)", + ) + .unwrap(); + assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758"); + assert_eq!(value.excludes, vec!["./package.spdx"]); + } + + #[test] + fn package_verification_code_without_excludes_can_be_parsed() { + let (_, value) = + package_verification_code("d6a770ba38583ed4bb4525bd96e50461655d2758").unwrap(); + assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758"); + let expected: Vec<String> = Vec::new(); + assert_eq!(value.excludes, expected); + } + + #[test] + fn external_package_ref_can_be_parsed() { + let (_, value) = external_package_reference( + "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", + ) + .unwrap(); + assert_eq!( + value.reference_category, + ExternalPackageReferenceCategory::Security + ); + assert_eq!(value.reference_type, "cpe23Type"); + assert_eq!( + value.reference_locator, + "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*" + ); + } + + #[test] + fn external_document_reference_can_be_parsed() { + let (_, value) = external_document_reference("DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759").unwrap(); + assert_eq!(value.id_string, "spdx-tool-1.2"); + assert_eq!( + value.spdx_document_uri, + "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + ); + assert_eq!(value.checksum.algorithm, Algorithm::SHA1); + assert_eq!( + value.checksum.value, + "d6a770ba38583ed4bb4525bd96e50461655d2759" + ); + } + + #[test] + fn document_ref_can_be_parsed() { + let (_, value) = document_ref("DocumentRef-spdx-tool-1.2").unwrap(); + assert_eq!(value, "spdx-tool-1.2"); + } + + #[test] + fn checksum_can_be_parsed() { + let (_, value) = checksum("SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759").unwrap(); + assert_eq!(value.algorithm, Algorithm::SHA1); + assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2759"); + } + + #[test] + fn document_comment_can_be_parsed() { + let (_, value) = tag_value_to_atom("DocumentComment: <text>Sample Comment</text>").unwrap(); + assert_eq!(value, Atom::DocumentComment("Sample Comment".to_string())); + } + + #[test] + fn multiline_document_comment_can_be_parsed() { + let (_, value) = tag_value_to_atom( + "DocumentComment: <text>Sample +Comment</text>", + ) + .unwrap(); + assert_eq!(value, Atom::DocumentComment("Sample\nComment".to_string())); + } + + #[test] + fn multiple_key_values_can_be_parsed() { + let input = "SPDXVersion: SPDX-1.2 + DataLicense: CC0-1.0 + DocumentComment: <text>Sample Comment</text>"; + + let (_, value) = atoms(input).unwrap(); + assert_eq!( + value, + vec![ + Atom::SpdxVersion("SPDX-1.2".to_string()), + Atom::DataLicense("CC0-1.0".to_string()), + Atom::DocumentComment("Sample Comment".to_string()) + ] + ); + } + + #[test] + fn multiple_key_values_with_comment_can_be_parsed() { + let input = "SPDXVersion: SPDX-1.2 + # A comment + DataLicense: CC0-1.0 + DocumentComment: <text>Sample Comment</text>"; + + let (_, value) = atoms(input).unwrap(); + assert_eq!( + value, + vec![ + Atom::SpdxVersion("SPDX-1.2".to_string()), + Atom::TVComment("A comment".to_string()), + Atom::DataLicense("CC0-1.0".to_string()), + Atom::DocumentComment("Sample Comment".to_string()) + ] + ); + } + + #[test] + fn multiple_key_values_with_space_can_be_parsed() { + let input = "SPDXVersion: SPDX-1.2 + + DataLicense: CC0-1.0 + DocumentComment: <text>Sample Comment</text>"; + + let (_, value) = atoms(input).unwrap(); + assert_eq!( + value, + vec![ + Atom::SpdxVersion("SPDX-1.2".to_string()), + Atom::DataLicense("CC0-1.0".to_string()), + Atom::DocumentComment("Sample Comment".to_string()) + ] + ); + } + + #[test] + fn key_value_pair_is_detected() { + let (_, value) = tag_value("SPDXVersion: SPDX-1.2").unwrap(); + assert_eq!(value, ("SPDXVersion", "SPDX-1.2")); + } + + #[test] + fn get_tag_values_from_simple_example_file() { + let file = read_to_string("tests/data/SPDXSimpleTag.tag").unwrap(); + let (remains, result) = atoms(&file).unwrap(); + assert_eq!(remains.len(), 0); + assert!(result.contains(&Atom::SpdxVersion("SPDX-1.2".to_string()))); + assert!(result.contains(&Atom::PackageName("Test".to_string()))); + assert!(result.contains(&Atom::PackageDescription("A package.".to_string()))); + } + + #[test] + fn get_tag_values_from_example_file() { + let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap(); + let (remains, result) = atoms(&file).unwrap(); + assert_eq!(remains.len(), 0); + assert!(result.contains(&Atom::SpdxVersion("SPDX-2.2".to_string()))); + assert!(result.contains(&Atom::LicenseListVersion("3.9".to_string()))); + assert!(result.contains(&Atom::PackageLicenseDeclared("MPL-1.0".to_string()))); + } + + #[test] + fn relationship_case() { + relationship("SPDXRef-DOCUMENT DESCRIBES SPDXRef-File").expect("Caps is expected"); + relationship("SPDXRef-DOCUMENT describes SPDXRef-File") + .expect("At least reuse-tool emits lowercase"); + } +} |