| 1 | // SPDX-FileCopyrightText: 2020-2021 HH Partners |
| 2 | // |
| 3 | // SPDX-License-Identifier: MIT |
| 4 | |
| 5 | use serde::{Deserialize, Serialize}; |
| 6 | use spdx_expression::SpdxExpression; |
| 7 | |
| 8 | use super::{Algorithm, Checksum}; |
| 9 | |
| 10 | /// ## File Information |
| 11 | /// |
| 12 | /// SPDX's [File Information](https://spdx.github.io/spdx-spec/4-file-information/) |
| 13 | #[derive (Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] |
| 14 | #[serde(rename_all = "camelCase" )] |
| 15 | pub struct FileInformation { |
| 16 | /// <https://spdx.github.io/spdx-spec/4-file-information/#41-file-name> |
| 17 | pub file_name: String, |
| 18 | |
| 19 | /// <https://spdx.github.io/spdx-spec/4-file-information/#42-file-spdx-identifier> |
| 20 | #[serde(rename = "SPDXID" )] |
| 21 | pub file_spdx_identifier: String, |
| 22 | |
| 23 | /// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type> |
| 24 | #[serde(rename = "fileTypes" , skip_serializing_if = "Vec::is_empty" , default)] |
| 25 | pub file_type: Vec<FileType>, |
| 26 | |
| 27 | /// <https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum> |
| 28 | #[serde(rename = "checksums" )] |
| 29 | pub file_checksum: Vec<Checksum>, |
| 30 | |
| 31 | /// <https://spdx.github.io/spdx-spec/4-file-information/#45-concluded-license> |
| 32 | #[serde( |
| 33 | rename = "licenseConcluded" , |
| 34 | skip_serializing_if = "Option::is_none" , |
| 35 | default |
| 36 | )] |
| 37 | pub concluded_license: Option<SpdxExpression>, |
| 38 | |
| 39 | /// <https://spdx.github.io/spdx-spec/4-file-information/#46-license-information-in-file> |
| 40 | #[serde( |
| 41 | rename = "licenseInfoInFiles" , |
| 42 | skip_serializing_if = "Vec::is_empty" , |
| 43 | default |
| 44 | )] |
| 45 | pub license_information_in_file: Vec<SpdxExpression>, |
| 46 | |
| 47 | /// <https://spdx.github.io/spdx-spec/4-file-information/#47-comments-on-license> |
| 48 | #[serde( |
| 49 | rename = "licenseComments" , |
| 50 | skip_serializing_if = "Option::is_none" , |
| 51 | default |
| 52 | )] |
| 53 | pub comments_on_license: Option<String>, |
| 54 | |
| 55 | /// <https://spdx.github.io/spdx-spec/4-file-information/#48-copyright-text> |
| 56 | #[serde( |
| 57 | rename = "copyrightText" , |
| 58 | skip_serializing_if = "Option::is_none" , |
| 59 | default |
| 60 | )] |
| 61 | pub copyright_text: Option<String>, |
| 62 | |
| 63 | /// <https://spdx.github.io/spdx-spec/4-file-information/#412-file-comment> |
| 64 | #[serde(rename = "comment" , skip_serializing_if = "Option::is_none" , default)] |
| 65 | pub file_comment: Option<String>, |
| 66 | |
| 67 | /// <https://spdx.github.io/spdx-spec/4-file-information/#413-file-notice> |
| 68 | #[serde( |
| 69 | rename = "noticeText" , |
| 70 | skip_serializing_if = "Option::is_none" , |
| 71 | default |
| 72 | )] |
| 73 | pub file_notice: Option<String>, |
| 74 | |
| 75 | /// <https://spdx.github.io/spdx-spec/4-file-information/#414-file-contributor> |
| 76 | #[serde( |
| 77 | rename = "fileContributors" , |
| 78 | skip_serializing_if = "Vec::is_empty" , |
| 79 | default |
| 80 | )] |
| 81 | pub file_contributor: Vec<String>, |
| 82 | |
| 83 | /// <https://spdx.github.io/spdx-spec/4-file-information/#415-file-attribution-text> |
| 84 | #[serde(skip_serializing_if = "Option::is_none" , default)] |
| 85 | pub file_attribution_text: Option<Vec<String>>, |
| 86 | // TODO: Snippet Information. |
| 87 | } |
| 88 | |
| 89 | impl Default for FileInformation { |
| 90 | fn default() -> Self { |
| 91 | Self { |
| 92 | file_name: "NOASSERTION" .to_string(), |
| 93 | file_spdx_identifier: "NOASSERTION" .to_string(), |
| 94 | file_type: Vec::new(), |
| 95 | file_checksum: Vec::new(), |
| 96 | concluded_license: None, |
| 97 | license_information_in_file: Vec::new(), |
| 98 | comments_on_license: None, |
| 99 | copyright_text: None, |
| 100 | file_comment: None, |
| 101 | file_notice: None, |
| 102 | file_contributor: Vec::new(), |
| 103 | file_attribution_text: None, |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | impl FileInformation { |
| 109 | /// Create new file. |
| 110 | pub fn new(name: &str, id: &mut i32) -> Self { |
| 111 | *id += 1; |
| 112 | Self { |
| 113 | file_name: name.to_string(), |
| 114 | file_spdx_identifier: format!("SPDXRef- {id}" ), |
| 115 | ..Self::default() |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /// Check if hash equals. |
| 120 | pub fn equal_by_hash(&self, algorithm: Algorithm, value: &str) -> bool { |
| 121 | let checksum = self |
| 122 | .file_checksum |
| 123 | .iter() |
| 124 | .find(|&checksum| checksum.algorithm == algorithm); |
| 125 | |
| 126 | checksum.map_or(false, |checksum| { |
| 127 | checksum.value.to_ascii_lowercase() == value.to_ascii_lowercase() |
| 128 | }) |
| 129 | } |
| 130 | |
| 131 | /// Get checksum |
| 132 | pub fn checksum(&self, algorithm: Algorithm) -> Option<&str> { |
| 133 | let checksum = self |
| 134 | .file_checksum |
| 135 | .iter() |
| 136 | .find(|&checksum| checksum.algorithm == algorithm); |
| 137 | |
| 138 | checksum.map(|checksum| checksum.value.as_str()) |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type> |
| 143 | #[derive (Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Clone, Copy)] |
| 144 | #[serde(rename_all = "UPPERCASE" )] |
| 145 | pub enum FileType { |
| 146 | Source, |
| 147 | Binary, |
| 148 | Archive, |
| 149 | Application, |
| 150 | Audio, |
| 151 | Image, |
| 152 | Text, |
| 153 | Video, |
| 154 | Documentation, |
| 155 | SPDX, |
| 156 | Other, |
| 157 | } |
| 158 | |
| 159 | #[cfg (test)] |
| 160 | mod test { |
| 161 | use std::fs::read_to_string; |
| 162 | |
| 163 | use super::*; |
| 164 | use crate::models::{Checksum, FileType, SPDX}; |
| 165 | |
| 166 | #[test ] |
| 167 | fn checksum_equality() { |
| 168 | let mut id = 1; |
| 169 | let mut file_sha256 = FileInformation::new("sha256" , &mut id); |
| 170 | file_sha256 |
| 171 | .file_checksum |
| 172 | .push(Checksum::new(Algorithm::SHA256, "test" )); |
| 173 | |
| 174 | assert!(file_sha256.equal_by_hash(Algorithm::SHA256, "test" )); |
| 175 | assert!(!file_sha256.equal_by_hash(Algorithm::SHA256, "no_test" )); |
| 176 | |
| 177 | let mut file_md5 = FileInformation::new("md5" , &mut id); |
| 178 | file_md5 |
| 179 | .file_checksum |
| 180 | .push(Checksum::new(Algorithm::MD5, "test" )); |
| 181 | assert!(file_md5.equal_by_hash(Algorithm::MD5, "test" )); |
| 182 | assert!(!file_md5.equal_by_hash(Algorithm::MD5, "no_test" )); |
| 183 | assert!(!file_md5.equal_by_hash(Algorithm::SHA1, "test" )); |
| 184 | } |
| 185 | |
| 186 | #[test ] |
| 187 | fn get_checksum() { |
| 188 | let mut id = 1; |
| 189 | let mut file_sha256 = FileInformation::new("sha256" , &mut id); |
| 190 | file_sha256 |
| 191 | .file_checksum |
| 192 | .push(Checksum::new(Algorithm::SHA256, "test" )); |
| 193 | |
| 194 | assert_eq!(file_sha256.checksum(Algorithm::SHA256), Some("test" )); |
| 195 | assert_eq!(file_sha256.checksum(Algorithm::MD2), None); |
| 196 | |
| 197 | let mut file_md5 = FileInformation::new("md5" , &mut id); |
| 198 | file_md5 |
| 199 | .file_checksum |
| 200 | .push(Checksum::new(Algorithm::MD5, "test" )); |
| 201 | |
| 202 | assert_eq!(file_md5.checksum(Algorithm::MD5), Some("test" )); |
| 203 | } |
| 204 | |
| 205 | #[test ] |
| 206 | fn file_name() { |
| 207 | let spdx: SPDX = serde_json::from_str( |
| 208 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 209 | ) |
| 210 | .unwrap(); |
| 211 | assert_eq!( |
| 212 | spdx.file_information[0].file_name, |
| 213 | "./src/org/spdx/parser/DOAPProject.java" |
| 214 | ); |
| 215 | } |
| 216 | #[test ] |
| 217 | fn file_spdx_identifier() { |
| 218 | let spdx: SPDX = serde_json::from_str( |
| 219 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 220 | ) |
| 221 | .unwrap(); |
| 222 | assert_eq!( |
| 223 | spdx.file_information[0].file_spdx_identifier, |
| 224 | "SPDXRef-DoapSource" |
| 225 | ); |
| 226 | } |
| 227 | #[test ] |
| 228 | fn file_type() { |
| 229 | let spdx: SPDX = serde_json::from_str( |
| 230 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 231 | ) |
| 232 | .unwrap(); |
| 233 | assert_eq!(spdx.file_information[0].file_type, vec![FileType::Source]); |
| 234 | } |
| 235 | #[test ] |
| 236 | fn file_checksum() { |
| 237 | let spdx: SPDX = serde_json::from_str( |
| 238 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 239 | ) |
| 240 | .unwrap(); |
| 241 | assert_eq!( |
| 242 | spdx.file_information[0].file_checksum, |
| 243 | vec![Checksum { |
| 244 | algorithm: Algorithm::SHA1, |
| 245 | value: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" .to_string() |
| 246 | }] |
| 247 | ); |
| 248 | } |
| 249 | #[test ] |
| 250 | fn concluded_license() { |
| 251 | let spdx: SPDX = serde_json::from_str( |
| 252 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 253 | ) |
| 254 | .unwrap(); |
| 255 | assert_eq!( |
| 256 | spdx.file_information[0].concluded_license, |
| 257 | Some(SpdxExpression::parse("Apache-2.0" ).unwrap()) |
| 258 | ); |
| 259 | } |
| 260 | #[test ] |
| 261 | fn license_information_in_file() { |
| 262 | let spdx: SPDX = serde_json::from_str( |
| 263 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 264 | ) |
| 265 | .unwrap(); |
| 266 | assert_eq!( |
| 267 | spdx.file_information[0].license_information_in_file, |
| 268 | vec![SpdxExpression::parse("Apache-2.0" ).unwrap()] |
| 269 | ); |
| 270 | } |
| 271 | #[test ] |
| 272 | fn comments_on_license() { |
| 273 | let spdx: SPDX = serde_json::from_str( |
| 274 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 275 | ) |
| 276 | .unwrap(); |
| 277 | assert_eq!( |
| 278 | spdx.file_information[2].comments_on_license, |
| 279 | Some("This license is used by Jena" .to_string()) |
| 280 | ); |
| 281 | } |
| 282 | #[test ] |
| 283 | fn copyright_text() { |
| 284 | let spdx: SPDX = serde_json::from_str( |
| 285 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 286 | ) |
| 287 | .unwrap(); |
| 288 | assert_eq!( |
| 289 | spdx.file_information[0] |
| 290 | .copyright_text |
| 291 | .as_ref() |
| 292 | .unwrap() |
| 293 | .clone(), |
| 294 | "Copyright 2010, 2011 Source Auditor Inc." .to_string() |
| 295 | ); |
| 296 | } |
| 297 | #[test ] |
| 298 | fn file_comment() { |
| 299 | let spdx: SPDX = serde_json::from_str( |
| 300 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 301 | ) |
| 302 | .unwrap(); |
| 303 | assert_eq!( |
| 304 | spdx.file_information[1].file_comment, |
| 305 | Some("This file is used by Jena" .to_string()) |
| 306 | ); |
| 307 | } |
| 308 | #[test ] |
| 309 | fn file_notice() { |
| 310 | let spdx: SPDX = serde_json::from_str( |
| 311 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 312 | ) |
| 313 | .unwrap(); |
| 314 | assert_eq!( |
| 315 | spdx.file_information[1].file_notice, |
| 316 | 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()) |
| 317 | ); |
| 318 | } |
| 319 | #[test ] |
| 320 | fn file_contributor() { |
| 321 | let spdx: SPDX = serde_json::from_str( |
| 322 | &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json" ).unwrap(), |
| 323 | ) |
| 324 | .unwrap(); |
| 325 | assert_eq!( |
| 326 | spdx.file_information[1].file_contributor, |
| 327 | vec!["Apache Software Foundation" .to_string()] |
| 328 | ); |
| 329 | } |
| 330 | } |
| 331 | |