1 | // SPDX-FileCopyrightText: 2021 HH Partners |
2 | // |
3 | // SPDX-License-Identifier: MIT |
4 | |
5 | use std::{num::ParseIntError, str::FromStr}; |
6 | |
7 | use nom::{ |
8 | branch::alt, |
9 | bytes::complete::{tag, take_until, take_while}, |
10 | character::complete::{alphanumeric0, char, digit1, multispace0, not_line_ending}, |
11 | combinator::{map, map_res, opt}, |
12 | error::{ParseError, VerboseError}, |
13 | multi::many0, |
14 | sequence::{delimited, preceded, separated_pair, tuple}, |
15 | AsChar, IResult, |
16 | }; |
17 | |
18 | use crate::models::{ |
19 | Algorithm, AnnotationType, Checksum, ExternalDocumentReference, ExternalPackageReference, |
20 | ExternalPackageReferenceCategory, FileType, PackageVerificationCode, Relationship, |
21 | RelationshipType, |
22 | }; |
23 | |
24 | #[derive (Debug, Clone, PartialEq)] |
25 | #[allow (clippy::upper_case_acronyms)] |
26 | pub(super) enum Atom { |
27 | // Document Creation Information |
28 | SpdxVersion(String), |
29 | DataLicense(String), |
30 | SPDXID(String), |
31 | DocumentName(String), |
32 | DocumentNamespace(String), |
33 | ExternalDocumentRef(ExternalDocumentReference), |
34 | LicenseListVersion(String), |
35 | Creator(String), |
36 | Created(String), |
37 | CreatorComment(String), |
38 | DocumentComment(String), |
39 | |
40 | // Package Information |
41 | PackageName(String), |
42 | PackageVersion(String), |
43 | PackageFileName(String), |
44 | PackageSupplier(String), |
45 | PackageOriginator(String), |
46 | PackageDownloadLocation(String), |
47 | FilesAnalyzed(String), |
48 | PackageVerificationCode(PackageVerificationCode), |
49 | PackageChecksum(Checksum), |
50 | PackageHomePage(String), |
51 | PackageSourceInfo(String), |
52 | PackageLicenseConcluded(String), |
53 | PackageLicenseInfoFromFiles(String), |
54 | PackageLicenseDeclared(String), |
55 | PackageLicenseComments(String), |
56 | PackageCopyrightText(String), |
57 | PackageSummary(String), |
58 | PackageDescription(String), |
59 | PackageComment(String), |
60 | ExternalRef(ExternalPackageReference), |
61 | ExternalRefComment(String), |
62 | PackageAttributionText(String), |
63 | PrimaryPackagePurpose(String), |
64 | BuiltDate(String), |
65 | ReleaseDate(String), |
66 | ValidUntilDate(String), |
67 | |
68 | // File Information |
69 | FileName(String), |
70 | FileType(FileType), |
71 | FileChecksum(Checksum), |
72 | LicenseConcluded(String), |
73 | LicenseInfoInFile(String), |
74 | LicenseComments(String), |
75 | FileCopyrightText(String), |
76 | FileComment(String), |
77 | FileNotice(String), |
78 | FileContributor(String), |
79 | FileAttributionText(String), |
80 | |
81 | // Snippet Information |
82 | SnippetSPDXID(String), |
83 | SnippetFromFileSPDXID(String), |
84 | SnippetByteRange((i32, i32)), |
85 | SnippetLineRange((i32, i32)), |
86 | SnippetLicenseConcluded(String), |
87 | LicenseInfoInSnippet(String), |
88 | SnippetLicenseComments(String), |
89 | SnippetCopyrightText(String), |
90 | SnippetComment(String), |
91 | SnippetName(String), |
92 | SnippetAttributionText(String), |
93 | |
94 | // Other Licensing Information Detected |
95 | LicenseID(String), |
96 | ExtractedText(String), |
97 | LicenseName(String), |
98 | LicenseCrossReference(String), |
99 | LicenseComment(String), |
100 | |
101 | // Relationship |
102 | Relationship(Relationship), |
103 | RelationshipComment(String), |
104 | |
105 | // Annotation |
106 | Annotator(String), |
107 | AnnotationDate(String), |
108 | AnnotationType(AnnotationType), |
109 | SPDXREF(String), |
110 | AnnotationComment(String), |
111 | |
112 | /// Comment in the document. Not part of the final SPDX. |
113 | TVComment(String), |
114 | } |
115 | |
116 | pub(super) fn atoms(i: &str) -> IResult<&str, Vec<Atom>, VerboseError<&str>> { |
117 | many0(alt((ws(inner:tv_comment), ws(inner:tag_value_to_atom))))(i) |
118 | } |
119 | |
120 | fn tag_value_to_atom(i: &str) -> IResult<&str, Atom, VerboseError<&str>> { |
121 | let (i, key_value) = tag_value(i)?; |
122 | match key_value.0 { |
123 | // Document Creation Information |
124 | "SPDXVersion" => Ok((i, Atom::SpdxVersion(key_value.1.to_string()))), |
125 | "DataLicense" => Ok((i, Atom::DataLicense(key_value.1.to_string()))), |
126 | "SPDXID" => Ok((i, Atom::SPDXID(key_value.1.to_string()))), |
127 | "DocumentName" => Ok((i, Atom::DocumentName(key_value.1.to_string()))), |
128 | "DocumentNamespace" => Ok((i, Atom::DocumentNamespace(key_value.1.to_string()))), |
129 | "ExternalDocumentRef" => { |
130 | let (_, value) = external_document_reference(key_value.1)?; |
131 | Ok((i, Atom::ExternalDocumentRef(value))) |
132 | } |
133 | "LicenseListVersion" => Ok((i, Atom::LicenseListVersion(key_value.1.to_string()))), |
134 | "Creator" => Ok((i, Atom::Creator(key_value.1.to_string()))), |
135 | "Created" => Ok((i, Atom::Created(key_value.1.to_string()))), |
136 | "CreatorComment" => Ok((i, Atom::CreatorComment(key_value.1.to_string()))), |
137 | "DocumentComment" => Ok((i, Atom::DocumentComment(key_value.1.to_string()))), |
138 | |
139 | // Package Information |
140 | "PackageName" => Ok((i, Atom::PackageName(key_value.1.to_string()))), |
141 | "PackageVersion" => Ok((i, Atom::PackageVersion(key_value.1.to_string()))), |
142 | "PackageFileName" => Ok((i, Atom::PackageFileName(key_value.1.to_string()))), |
143 | "PackageSupplier" => Ok((i, Atom::PackageSupplier(key_value.1.to_string()))), |
144 | "PackageOriginator" => Ok((i, Atom::PackageOriginator(key_value.1.to_string()))), |
145 | "PackageDownloadLocation" => { |
146 | Ok((i, Atom::PackageDownloadLocation(key_value.1.to_string()))) |
147 | } |
148 | "FilesAnalyzed" => Ok((i, Atom::FilesAnalyzed(key_value.1.to_string()))), |
149 | "PackageVerificationCode" => { |
150 | let (_, value) = package_verification_code(key_value.1)?; |
151 | Ok((i, Atom::PackageVerificationCode(value))) |
152 | } |
153 | "PackageChecksum" => Ok((i, Atom::PackageChecksum(checksum(key_value.1)?.1))), |
154 | "PackageHomePage" => Ok((i, Atom::PackageHomePage(key_value.1.to_string()))), |
155 | "PackageSourceInfo" => Ok((i, Atom::PackageSourceInfo(key_value.1.to_string()))), |
156 | "PackageLicenseConcluded" => { |
157 | Ok((i, Atom::PackageLicenseConcluded(key_value.1.to_string()))) |
158 | } |
159 | "PackageLicenseInfoFromFiles" => Ok(( |
160 | i, |
161 | Atom::PackageLicenseInfoFromFiles(key_value.1.to_string()), |
162 | )), |
163 | "PackageLicenseDeclared" => Ok((i, Atom::PackageLicenseDeclared(key_value.1.to_string()))), |
164 | "PackageLicenseComments" => Ok((i, Atom::PackageLicenseComments(key_value.1.to_string()))), |
165 | "PackageCopyrightText" => Ok((i, Atom::PackageCopyrightText(key_value.1.to_string()))), |
166 | "PackageSummary" => Ok((i, Atom::PackageSummary(key_value.1.to_string()))), |
167 | "PackageDescription" => Ok((i, Atom::PackageDescription(key_value.1.to_string()))), |
168 | "PackageComment" => Ok((i, Atom::PackageComment(key_value.1.to_string()))), |
169 | "ExternalRef" => Ok(( |
170 | i, |
171 | Atom::ExternalRef(external_package_reference(key_value.1)?.1), |
172 | )), |
173 | "ExternalRefComment" => Ok((i, Atom::ExternalRefComment(key_value.1.to_string()))), |
174 | "PackageAttributionText" => Ok((i, Atom::PackageAttributionText(key_value.1.to_string()))), |
175 | "PrimaryPackagePurpose" => Ok((i, Atom::PrimaryPackagePurpose(key_value.1.to_string()))), |
176 | "BuiltDate" => Ok((i, Atom::BuiltDate(key_value.1.to_string()))), |
177 | "ReleaseDate" => Ok((i, Atom::ReleaseDate(key_value.1.to_string()))), |
178 | "ValidUntilDate" => Ok((i, Atom::ValidUntilDate(key_value.1.to_string()))), |
179 | |
180 | // File Information |
181 | "FileName" => Ok((i, Atom::FileName(key_value.1.to_string()))), |
182 | "FileType" => Ok((i, Atom::FileType(file_type(key_value.1)?.1))), |
183 | "FileChecksum" => Ok((i, Atom::FileChecksum(checksum(key_value.1)?.1))), |
184 | "LicenseConcluded" => Ok((i, Atom::LicenseConcluded(key_value.1.to_string()))), |
185 | "LicenseInfoInFile" => Ok((i, Atom::LicenseInfoInFile(key_value.1.to_string()))), |
186 | "LicenseComments" => Ok((i, Atom::LicenseComments(key_value.1.to_string()))), |
187 | "FileCopyrightText" => Ok((i, Atom::FileCopyrightText(key_value.1.to_string()))), |
188 | "FileComment" => Ok((i, Atom::FileComment(key_value.1.to_string()))), |
189 | "FileNotice" => Ok((i, Atom::FileNotice(key_value.1.to_string()))), |
190 | "FileContributor" => Ok((i, Atom::FileContributor(key_value.1.to_string()))), |
191 | "FileAttributionText" => Ok((i, Atom::FileAttributionText(key_value.1.to_string()))), |
192 | |
193 | // Snippet Information |
194 | "SnippetSPDXID" => Ok((i, Atom::SnippetSPDXID(key_value.1.to_string()))), |
195 | "SnippetFromFileSPDXID" => Ok((i, Atom::SnippetFromFileSPDXID(key_value.1.to_string()))), |
196 | "SnippetByteRange" => Ok((i, Atom::SnippetByteRange(range(key_value.1)?.1))), |
197 | "SnippetLineRange" => Ok((i, Atom::SnippetLineRange(range(key_value.1)?.1))), |
198 | "SnippetLicenseConcluded" => { |
199 | Ok((i, Atom::SnippetLicenseConcluded(key_value.1.to_string()))) |
200 | } |
201 | "LicenseInfoInSnippet" => Ok((i, Atom::LicenseInfoInSnippet(key_value.1.to_string()))), |
202 | "SnippetLicenseComments" => Ok((i, Atom::SnippetLicenseComments(key_value.1.to_string()))), |
203 | "SnippetCopyrightText" => Ok((i, Atom::SnippetCopyrightText(key_value.1.to_string()))), |
204 | "SnippetComment" => Ok((i, Atom::SnippetComment(key_value.1.to_string()))), |
205 | "SnippetName" => Ok((i, Atom::SnippetName(key_value.1.to_string()))), |
206 | "SnippetAttributionText" => Ok((i, Atom::SnippetAttributionText(key_value.1.to_string()))), |
207 | |
208 | // Other Licensing Information Detected |
209 | "LicenseID" => Ok((i, Atom::LicenseID(key_value.1.to_string()))), |
210 | "ExtractedText" => Ok((i, Atom::ExtractedText(key_value.1.to_string()))), |
211 | "LicenseName" => Ok((i, Atom::LicenseName(key_value.1.to_string()))), |
212 | "LicenseCrossReference" => Ok((i, Atom::LicenseCrossReference(key_value.1.to_string()))), |
213 | "LicenseComment" => Ok((i, Atom::LicenseComment(key_value.1.to_string()))), |
214 | |
215 | // Relationship |
216 | "Relationship" => Ok((i, Atom::Relationship(relationship(key_value.1)?.1))), |
217 | "RelationshipComment" => Ok((i, Atom::RelationshipComment(key_value.1.to_string()))), |
218 | |
219 | // Annotation |
220 | "Annotator" => Ok((i, Atom::Annotator(key_value.1.to_string()))), |
221 | "AnnotationDate" => Ok((i, Atom::AnnotationDate(key_value.1.to_string()))), |
222 | "AnnotationType" => Ok((i, Atom::AnnotationType(annotation_type(key_value.1)?.1))), |
223 | "SPDXREF" => Ok((i, Atom::SPDXREF(key_value.1.to_string()))), |
224 | "AnnotationComment" => Ok((i, Atom::AnnotationComment(key_value.1.to_string()))), |
225 | v => { |
226 | dbg!(v); |
227 | unimplemented!() |
228 | } |
229 | } |
230 | } |
231 | |
232 | fn external_document_reference( |
233 | i: &str, |
234 | ) -> IResult<&str, ExternalDocumentReference, VerboseError<&str>> { |
235 | map( |
236 | parser:tuple(( |
237 | document_ref, |
238 | ws(take_while(|c: char| !c.is_whitespace())), |
239 | ws(checksum), |
240 | )), |
241 | |(id_string: &str, spdx_document_uri: &str, checksum: Checksum)| { |
242 | ExternalDocumentReference::new( |
243 | id_string:id_string.to_string(), |
244 | spdx_document_uri:spdx_document_uri.to_string(), |
245 | checksum, |
246 | ) |
247 | }, |
248 | )(i) |
249 | } |
250 | |
251 | fn annotation_type(i: &str) -> IResult<&str, AnnotationType, VerboseError<&str>> { |
252 | match ws(inner:not_line_ending)(i) { |
253 | Ok((i: &str, value: &str)) => match value { |
254 | "REVIEW" => Ok((i, AnnotationType::Review)), |
255 | "OTHER" => Ok((i, AnnotationType::Other)), |
256 | // Proper error |
257 | _ => todo!(), |
258 | }, |
259 | Err(err: Err>) => Err(err), |
260 | } |
261 | } |
262 | |
263 | fn file_type(i: &str) -> IResult<&str, FileType, VerboseError<&str>> { |
264 | match ws(inner:not_line_ending)(i) { |
265 | Ok((i: &str, value: &str)) => match value { |
266 | "SOURCE" => Ok((i, FileType::Source)), |
267 | "BINARY" => Ok((i, FileType::Binary)), |
268 | "ARCHIVE" => Ok((i, FileType::Archive)), |
269 | "APPLICATION" => Ok((i, FileType::Application)), |
270 | "AUDIO" => Ok((i, FileType::Audio)), |
271 | "IMAGE" => Ok((i, FileType::Image)), |
272 | "TEXT" => Ok((i, FileType::Text)), |
273 | "VIDEO" => Ok((i, FileType::Video)), |
274 | "DOCUMENTATION" => Ok((i, FileType::Documentation)), |
275 | "SPDX" => Ok((i, FileType::SPDX)), |
276 | "OTHER" => Ok((i, FileType::Other)), |
277 | // Proper error |
278 | _ => todo!(), |
279 | }, |
280 | Err(err: Err>) => Err(err), |
281 | } |
282 | } |
283 | |
284 | fn document_ref(i: &str) -> IResult<&str, &str, VerboseError<&str>> { |
285 | preceded(first:tag("DocumentRef-" ), second:ws(inner:idstring))(i) |
286 | } |
287 | |
288 | fn relationship(i: &str) -> IResult<&str, Relationship, VerboseError<&str>> { |
289 | map( |
290 | tuple(( |
291 | ws(take_while(|c: char| !c.is_whitespace())), |
292 | ws(take_while(|c: char| !c.is_whitespace())), |
293 | ws(not_line_ending), |
294 | )), |
295 | |(item1, relationship_type, item2)| { |
296 | let relationship_type = relationship_type.to_uppercase(); |
297 | let relationship_type = match relationship_type.as_str() { |
298 | "DESCRIBES" => RelationshipType::Describes, |
299 | "DESCRIBED_BY" => RelationshipType::DescribedBy, |
300 | "CONTAINS" => RelationshipType::Contains, |
301 | "CONTAINED_BY" => RelationshipType::ContainedBy, |
302 | "DEPENDS_ON" => RelationshipType::DependsOn, |
303 | "DEPENDENCY_OF" => RelationshipType::DependencyOf, |
304 | "DEPENDENCY_MANIFEST_OF" => RelationshipType::DependencyManifestOf, |
305 | "BUILD_DEPENDENCY_OF" => RelationshipType::BuildDependencyOf, |
306 | "DEV_DEPENDENCY_OF" => RelationshipType::DevDependencyOf, |
307 | "OPTIONAL_DEPENDENCY_OF" => RelationshipType::OptionalDependencyOf, |
308 | "PROVIDED_DEPENDENCY_OF" => RelationshipType::ProvidedDependencyOf, |
309 | "TEST_DEPENDENCY_OF" => RelationshipType::TestDependencyOf, |
310 | "RUNTIME_DEPENDENCY_OF" => RelationshipType::RuntimeDependencyOf, |
311 | "EXAMPLE_OF" => RelationshipType::ExampleOf, |
312 | "GENERATES" => RelationshipType::Generates, |
313 | "GENERATED_FROM" => RelationshipType::GeneratedFrom, |
314 | "ANCESTOR_OF" => RelationshipType::AncestorOf, |
315 | "DESCENDANT_OF" => RelationshipType::DescendantOf, |
316 | "VARIANT_OF" => RelationshipType::VariantOf, |
317 | "DISTRIBUTION_ARTIFACT" => RelationshipType::DistributionArtifact, |
318 | "PATCH_FOR" => RelationshipType::PatchFor, |
319 | "PATCH_APPLIED" => RelationshipType::PatchApplied, |
320 | "COPY_OF" => RelationshipType::CopyOf, |
321 | "FILE_ADDED" => RelationshipType::FileAdded, |
322 | "FILE_DELETED" => RelationshipType::FileDeleted, |
323 | "FILE_MODIFIED" => RelationshipType::FileModified, |
324 | "EXPANDED_FROM_ARCHIVE" => RelationshipType::ExpandedFromArchive, |
325 | "DYNAMIC_LINK" => RelationshipType::DynamicLink, |
326 | "STATIC_LINK" => RelationshipType::StaticLink, |
327 | "DATA_FILE_OF" => RelationshipType::DataFileOf, |
328 | "TEST_CASE_OF" => RelationshipType::TestCaseOf, |
329 | "BUILD_TOOL_OF" => RelationshipType::BuildToolOf, |
330 | "DEV_TOOL_OF" => RelationshipType::DevToolOf, |
331 | "TEST_OF" => RelationshipType::TestOf, |
332 | "TEST_TOOL_OF" => RelationshipType::TestToolOf, |
333 | "DOCUMENTATION_OF" => RelationshipType::DocumentationOf, |
334 | "OPTIONAL_COMPONENT_OF" => RelationshipType::OptionalComponentOf, |
335 | "METAFILE_OF" => RelationshipType::MetafileOf, |
336 | "PACKAGE_OF" => RelationshipType::PackageOf, |
337 | "AMENDS" => RelationshipType::Amends, |
338 | "PREREQUISITE_FOR" => RelationshipType::PrerequisiteFor, |
339 | "HAS_PREREQUISITE" => RelationshipType::HasPrerequisite, |
340 | "SPECIFICATION_FOR" => RelationshipType::SpecificationFor, |
341 | "REQUIREMENT_DESCRIPTION_FOR" => RelationshipType::RequirementDescriptionFor, |
342 | "OTHER" => RelationshipType::Other, |
343 | // TODO: Proper error. |
344 | _ => { |
345 | dbg!(relationship_type); |
346 | todo!() |
347 | } |
348 | }; |
349 | Relationship::new(item1, item2, relationship_type, None) |
350 | }, |
351 | )(i) |
352 | } |
353 | |
354 | fn external_package_reference( |
355 | i: &str, |
356 | ) -> IResult<&str, ExternalPackageReference, VerboseError<&str>> { |
357 | map( |
358 | parser:tuple(( |
359 | ws(take_while(|c: char| !c.is_whitespace())), |
360 | ws(take_while(|c: char| !c.is_whitespace())), |
361 | ws(not_line_ending), |
362 | )), |
363 | |(category: &str, ref_type: &str, locator: &str)| { |
364 | let category: ExternalPackageReferenceCategory = match category { |
365 | "SECURITY" => ExternalPackageReferenceCategory::Security, |
366 | "PACKAGE-MANAGER" => ExternalPackageReferenceCategory::PackageManager, |
367 | "PERSISTENT-ID" => ExternalPackageReferenceCategory::PersistentID, |
368 | "OTHER" => ExternalPackageReferenceCategory::Other, |
369 | // TODO: Proper error handling |
370 | _ => todo!(), |
371 | }; |
372 | ExternalPackageReference::new(reference_category:category, reference_type:ref_type.to_string(), reference_locator:locator.to_string(), reference_comment:None) |
373 | }, |
374 | )(i) |
375 | } |
376 | |
377 | fn package_verification_code( |
378 | i: &str, |
379 | ) -> IResult<&str, PackageVerificationCode, VerboseError<&str>> { |
380 | map( |
381 | parser:alt(( |
382 | separated_pair( |
383 | ws(take_until("(excludes:" )), |
384 | ws(tag("(excludes:" )), |
385 | opt(take_until(")" )), |
386 | ), |
387 | map(ws(not_line_ending), |v| (v, None)), |
388 | )), |
389 | |(value: &str, exclude: Option<&str>)| { |
390 | #[allow (clippy::option_if_let_else)] |
391 | let excludes: Vec = if let Some(exclude: &str) = exclude { |
392 | vec![exclude.to_string()] |
393 | } else { |
394 | Vec::new() |
395 | }; |
396 | PackageVerificationCode::new(value:value.to_string(), excludes) |
397 | }, |
398 | )(i) |
399 | } |
400 | |
401 | fn range(i: &str) -> IResult<&str, (i32, i32), VerboseError<&str>> { |
402 | map_res::<_, _, _, _, ParseIntError, _, _>( |
403 | parser:separated_pair(digit1, char(':' ), digit1), |
404 | |(left: &str, right: &str)| Ok((i32::from_str(left)?, i32::from_str(right)?)), |
405 | )(i) |
406 | } |
407 | |
408 | fn idstring(i: &str) -> IResult<&str, &str, VerboseError<&str>> { |
409 | take_while(|c: char| c.is_alphanum() || c == '.' || c == '-' || c == '+' )(i) |
410 | } |
411 | |
412 | fn checksum(i: &str) -> IResult<&str, Checksum, VerboseError<&str>> { |
413 | map( |
414 | separated_pair(ws(take_until(":" )), char(':' ), ws(not_line_ending)), |
415 | |(algorithm, value)| { |
416 | let checksum_algorithm = match algorithm { |
417 | "SHA1" => Algorithm::SHA1, |
418 | "SHA224" => Algorithm::SHA224, |
419 | "SHA256" => Algorithm::SHA256, |
420 | "SHA384" => Algorithm::SHA384, |
421 | "SHA512" => Algorithm::SHA512, |
422 | "MD2" => Algorithm::MD2, |
423 | "MD4" => Algorithm::MD4, |
424 | "MD5" => Algorithm::MD5, |
425 | "MD6" => Algorithm::MD6, |
426 | "SHA3-256" => Algorithm::SHA3256, |
427 | "SHA3-384" => Algorithm::SHA3384, |
428 | "SHA3-512" => Algorithm::SHA3512, |
429 | "BLAKE2b-256" => Algorithm::BLAKE2B256, |
430 | "BLAKE2b-384" => Algorithm::BLAKE2B384, |
431 | "BLAKE2b-512" => Algorithm::BLAKE2B512, |
432 | "BLAKE3" => Algorithm::BLAKE3, |
433 | "ADLER32" => Algorithm::ADLER32, |
434 | // TODO: Use proper error. |
435 | _ => todo!(), |
436 | }; |
437 | Checksum::new(checksum_algorithm, value) |
438 | }, |
439 | )(i) |
440 | } |
441 | |
442 | fn tv_comment(i: &str) -> IResult<&str, Atom, VerboseError<&str>> { |
443 | map(parser:preceded(ws(tag("#" )), ws(not_line_ending)), |v: &str| { |
444 | Atom::TVComment(v.to_string()) |
445 | })(i) |
446 | } |
447 | |
448 | fn tag_value(i: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> { |
449 | separated_pair( |
450 | first:ws(alphanumeric0), |
451 | sep:tag(":" ), |
452 | second:alt((ws(inner:multiline_text), ws(inner:not_line_ending))), |
453 | )(i) |
454 | } |
455 | |
456 | fn multiline_text(i: &str) -> IResult<&str, &str, VerboseError<&str>> { |
457 | delimited(first:tag("<text>" ), second:take_until("</text>" ), third:tag("</text>" ))(i) |
458 | } |
459 | |
460 | /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and |
461 | /// trailing whitespace, returning the output of `inner`. |
462 | fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> |
463 | where |
464 | F: Fn(&'a str) -> IResult<&'a str, O, E> + 'a, |
465 | { |
466 | delimited(first:multispace0, second:inner, third:multispace0) |
467 | } |
468 | |
469 | #[cfg (test)] |
470 | mod tests { |
471 | use std::fs::read_to_string; |
472 | |
473 | use crate::{ |
474 | models::{Algorithm, AnnotationType, ExternalPackageReferenceCategory, Relationship}, |
475 | parsers::tag_value::{ |
476 | annotation_type, checksum, document_ref, external_document_reference, |
477 | external_package_reference, package_verification_code, range, relationship, |
478 | }, |
479 | }; |
480 | |
481 | use super::{atoms, tag_value, tag_value_to_atom, Atom}; |
482 | |
483 | #[test ] |
484 | fn version_can_be_parsed() { |
485 | let (_, value) = tag_value_to_atom("SPDXVersion: SPDX-1.2" ).unwrap(); |
486 | assert_eq!(value, Atom::SpdxVersion("SPDX-1.2" .to_string())); |
487 | } |
488 | |
489 | #[test ] |
490 | fn range_can_be_parsed() { |
491 | let (_, value) = range("310:420" ).unwrap(); |
492 | assert_eq!(value, (310, 420)); |
493 | } |
494 | |
495 | #[test ] |
496 | fn annotation_type_can_be_parsed() { |
497 | let (_, value) = annotation_type("REVIEW" ).unwrap(); |
498 | assert_eq!(value, AnnotationType::Review); |
499 | let (_, value) = annotation_type("OTHER" ).unwrap(); |
500 | assert_eq!(value, AnnotationType::Other); |
501 | } |
502 | |
503 | #[test ] |
504 | fn relationship_can_be_parsed() { |
505 | let (_, value) = relationship("SPDXRef-JenaLib CONTAINS SPDXRef-Package" ).unwrap(); |
506 | let expected = Relationship::new( |
507 | "SPDXRef-JenaLib" , |
508 | "SPDXRef-Package" , |
509 | crate::models::RelationshipType::Contains, |
510 | None, |
511 | ); |
512 | assert_eq!(value, expected); |
513 | } |
514 | |
515 | #[test ] |
516 | fn data_license_can_be_parsed() { |
517 | let (_, value) = tag_value_to_atom("DataLicense: CC0-1.0" ).unwrap(); |
518 | assert_eq!(value, Atom::DataLicense("CC0-1.0" .to_string())); |
519 | } |
520 | |
521 | #[test ] |
522 | fn package_verification_code_can_be_parsed() { |
523 | let (_, value) = package_verification_code( |
524 | "d6a770ba38583ed4bb4525bd96e50461655d2758(excludes: ./package.spdx)" , |
525 | ) |
526 | .unwrap(); |
527 | assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758" ); |
528 | assert_eq!(value.excludes, vec!["./package.spdx" ]); |
529 | } |
530 | |
531 | #[test ] |
532 | fn package_verification_code_without_excludes_can_be_parsed() { |
533 | let (_, value) = |
534 | package_verification_code("d6a770ba38583ed4bb4525bd96e50461655d2758" ).unwrap(); |
535 | assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758" ); |
536 | let expected: Vec<String> = Vec::new(); |
537 | assert_eq!(value.excludes, expected); |
538 | } |
539 | |
540 | #[test ] |
541 | fn external_package_ref_can_be_parsed() { |
542 | let (_, value) = external_package_reference( |
543 | "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*" , |
544 | ) |
545 | .unwrap(); |
546 | assert_eq!( |
547 | value.reference_category, |
548 | ExternalPackageReferenceCategory::Security |
549 | ); |
550 | assert_eq!(value.reference_type, "cpe23Type" ); |
551 | assert_eq!( |
552 | value.reference_locator, |
553 | "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*" |
554 | ); |
555 | } |
556 | |
557 | #[test ] |
558 | fn external_document_reference_can_be_parsed() { |
559 | 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(); |
560 | assert_eq!(value.id_string, "spdx-tool-1.2" ); |
561 | assert_eq!( |
562 | value.spdx_document_uri, |
563 | "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" |
564 | ); |
565 | assert_eq!(value.checksum.algorithm, Algorithm::SHA1); |
566 | assert_eq!( |
567 | value.checksum.value, |
568 | "d6a770ba38583ed4bb4525bd96e50461655d2759" |
569 | ); |
570 | } |
571 | |
572 | #[test ] |
573 | fn document_ref_can_be_parsed() { |
574 | let (_, value) = document_ref("DocumentRef-spdx-tool-1.2" ).unwrap(); |
575 | assert_eq!(value, "spdx-tool-1.2" ); |
576 | } |
577 | |
578 | #[test ] |
579 | fn checksum_can_be_parsed() { |
580 | let (_, value) = checksum("SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759" ).unwrap(); |
581 | assert_eq!(value.algorithm, Algorithm::SHA1); |
582 | assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2759" ); |
583 | } |
584 | |
585 | #[test ] |
586 | fn document_comment_can_be_parsed() { |
587 | let (_, value) = tag_value_to_atom("DocumentComment: <text>Sample Comment</text>" ).unwrap(); |
588 | assert_eq!(value, Atom::DocumentComment("Sample Comment" .to_string())); |
589 | } |
590 | |
591 | #[test ] |
592 | fn multiline_document_comment_can_be_parsed() { |
593 | let (_, value) = tag_value_to_atom( |
594 | "DocumentComment: <text>Sample |
595 | Comment</text>" , |
596 | ) |
597 | .unwrap(); |
598 | assert_eq!(value, Atom::DocumentComment("Sample \nComment" .to_string())); |
599 | } |
600 | |
601 | #[test ] |
602 | fn multiple_key_values_can_be_parsed() { |
603 | let input = "SPDXVersion: SPDX-1.2 |
604 | DataLicense: CC0-1.0 |
605 | DocumentComment: <text>Sample Comment</text>" ; |
606 | |
607 | let (_, value) = atoms(input).unwrap(); |
608 | assert_eq!( |
609 | value, |
610 | vec![ |
611 | Atom::SpdxVersion("SPDX-1.2" .to_string()), |
612 | Atom::DataLicense("CC0-1.0" .to_string()), |
613 | Atom::DocumentComment("Sample Comment" .to_string()) |
614 | ] |
615 | ); |
616 | } |
617 | |
618 | #[test ] |
619 | fn multiple_key_values_with_comment_can_be_parsed() { |
620 | let input = "SPDXVersion: SPDX-1.2 |
621 | # A comment |
622 | DataLicense: CC0-1.0 |
623 | DocumentComment: <text>Sample Comment</text>" ; |
624 | |
625 | let (_, value) = atoms(input).unwrap(); |
626 | assert_eq!( |
627 | value, |
628 | vec![ |
629 | Atom::SpdxVersion("SPDX-1.2" .to_string()), |
630 | Atom::TVComment("A comment" .to_string()), |
631 | Atom::DataLicense("CC0-1.0" .to_string()), |
632 | Atom::DocumentComment("Sample Comment" .to_string()) |
633 | ] |
634 | ); |
635 | } |
636 | |
637 | #[test ] |
638 | fn multiple_key_values_with_space_can_be_parsed() { |
639 | let input = "SPDXVersion: SPDX-1.2 |
640 | |
641 | DataLicense: CC0-1.0 |
642 | DocumentComment: <text>Sample Comment</text>" ; |
643 | |
644 | let (_, value) = atoms(input).unwrap(); |
645 | assert_eq!( |
646 | value, |
647 | vec![ |
648 | Atom::SpdxVersion("SPDX-1.2" .to_string()), |
649 | Atom::DataLicense("CC0-1.0" .to_string()), |
650 | Atom::DocumentComment("Sample Comment" .to_string()) |
651 | ] |
652 | ); |
653 | } |
654 | |
655 | #[test ] |
656 | fn key_value_pair_is_detected() { |
657 | let (_, value) = tag_value("SPDXVersion: SPDX-1.2" ).unwrap(); |
658 | assert_eq!(value, ("SPDXVersion" , "SPDX-1.2" )); |
659 | } |
660 | |
661 | #[test ] |
662 | fn get_tag_values_from_simple_example_file() { |
663 | let file = read_to_string("tests/data/SPDXSimpleTag.tag" ).unwrap(); |
664 | let (remains, result) = atoms(&file).unwrap(); |
665 | assert_eq!(remains.len(), 0); |
666 | assert!(result.contains(&Atom::SpdxVersion("SPDX-1.2" .to_string()))); |
667 | assert!(result.contains(&Atom::PackageName("Test" .to_string()))); |
668 | assert!(result.contains(&Atom::PackageDescription("A package." .to_string()))); |
669 | } |
670 | |
671 | #[test ] |
672 | fn get_tag_values_from_example_file() { |
673 | let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx" ).unwrap(); |
674 | let (remains, result) = atoms(&file).unwrap(); |
675 | assert_eq!(remains.len(), 0); |
676 | assert!(result.contains(&Atom::SpdxVersion("SPDX-2.2" .to_string()))); |
677 | assert!(result.contains(&Atom::LicenseListVersion("3.9" .to_string()))); |
678 | assert!(result.contains(&Atom::PackageLicenseDeclared("MPL-1.0" .to_string()))); |
679 | } |
680 | |
681 | #[test ] |
682 | fn relationship_case() { |
683 | relationship("SPDXRef-DOCUMENT DESCRIBES SPDXRef-File" ).expect("Caps is expected" ); |
684 | relationship("SPDXRef-DOCUMENT describes SPDXRef-File" ) |
685 | .expect("At least reuse-tool emits lowercase" ); |
686 | } |
687 | } |
688 | |