1// SPDX-FileCopyrightText: 2020-2021 HH Partners
2//
3// SPDX-License-Identifier: MIT
4
5use serde::{Deserialize, Serialize};
6use spdx_expression::{SimpleExpression, SpdxExpression};
7
8use 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")]
15pub 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<SimpleExpression>,
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
89impl 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
108impl 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")]
145pub 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)]
160mod 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![SimpleExpression::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