1 | // SPDX-FileCopyrightText: 2020-2021 HH Partners |
2 | // |
3 | // SPDX-License-Identifier: MIT |
4 | |
5 | use serde::{Deserialize, Serialize}; |
6 | use spdx_expression::{SimpleExpression, 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<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 | |
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![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 | |