1// SPDX-FileCopyrightText: 2021 HH Partners
2//
3// SPDX-License-Identifier: MIT
4
5//! Parsers for deserializing [`SPDX`] from different data formats.
6//!
7//! The SPDX spec supports some data formats that are not supported by [Serde], so parsing from JSON
8//! (and YAML) is achieved with the data format specific crates:
9//!
10//! ```rust
11//! # use spdx_rs::error::SpdxError;
12//! use spdx_rs::models::SPDX;
13//! # fn main() -> Result<(), SpdxError> {
14//!
15//! let spdx_file = std::fs::read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json")?;
16//! let spdx_document: SPDX = serde_json::from_str(&spdx_file).unwrap();
17//!
18//! assert_eq!(
19//! spdx_document.document_creation_information.document_name,
20//! "SPDX-Tools-v2.0"
21//! );
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! [Serde]: https://serde.rs
27
28use std::collections::HashSet;
29
30use chrono::{DateTime, Utc};
31use spdx_expression::{SimpleExpression, SpdxExpression};
32
33use crate::{
34 error::SpdxError,
35 models::{
36 Annotation, AnnotationType, DocumentCreationInformation, ExternalPackageReference,
37 FileInformation, OtherLicensingInformationDetected, PackageInformation, Pointer, Range,
38 Relationship, Snippet, SPDX,
39 },
40 parsers::tag_value::{atoms, Atom},
41};
42
43mod tag_value;
44
45/// Parse a tag-value SPDX document to [`SPDX`].
46///
47/// # Usage
48///
49/// ```
50/// # use spdx_rs::error::SpdxError;
51/// use spdx_rs::parsers::spdx_from_tag_value;
52/// # fn main() -> Result<(), SpdxError> {
53///
54/// let spdx_file = std::fs::read_to_string("tests/data/SPDXTagExample-v2.2.spdx")?;
55/// let spdx_document = spdx_from_tag_value(&spdx_file)?;
56///
57/// assert_eq!(
58/// spdx_document.document_creation_information.document_name,
59/// "SPDX-Tools-v2.0"
60/// );
61/// # Ok(())
62/// # }
63/// ```
64///
65/// # Errors
66///
67/// - If parsing of the tag-value fails.
68/// - If parsing of some of the values fail.
69pub fn spdx_from_tag_value(input: &str) -> Result<SPDX, SpdxError> {
70 let (_, atoms: Vec) = atoms(input).map_err(|err: Err>| SpdxError::TagValueParse(err.to_string()))?;
71
72 let spdx: SPDX = spdx_from_atoms(&atoms)?;
73
74 Ok(spdx)
75}
76
77#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
78fn spdx_from_atoms(atoms: &[Atom]) -> Result<SPDX, SpdxError> {
79 let mut document_creation_information_in_progress =
80 Some(DocumentCreationInformation::default());
81 let mut document_creation_information_final: Option<DocumentCreationInformation> = None;
82
83 let mut package_information: Vec<PackageInformation> = Vec::new();
84 let mut package_in_progress: Option<PackageInformation> = None;
85 let mut external_package_ref_in_progress: Option<ExternalPackageReference> = None;
86
87 let mut other_licensing_information_detected: Vec<OtherLicensingInformationDetected> =
88 Vec::new();
89 let mut license_info_in_progress: Option<OtherLicensingInformationDetected> = None;
90
91 let mut file_information: Vec<FileInformation> = Vec::new();
92 let mut file_in_progress: Option<FileInformation> = None;
93
94 let mut snippet_information: Vec<Snippet> = Vec::new();
95 let mut snippet_in_progress: Option<Snippet> = None;
96
97 let mut relationships: HashSet<Relationship> = HashSet::new();
98 let mut relationship_in_progress: Option<Relationship> = None;
99
100 let mut annotations: Vec<Annotation> = Vec::new();
101 let mut annotation_in_progress = AnnotationInProgress::default();
102
103 for atom in atoms {
104 let document_creation_information = process_atom_for_document_creation_information(
105 atom,
106 &mut document_creation_information_in_progress,
107 )?;
108 if let Some(document_creation_information) = document_creation_information {
109 document_creation_information_final = Some(document_creation_information);
110 document_creation_information_in_progress = None;
111 }
112 process_atom_for_packages(
113 atom,
114 &mut package_information,
115 &mut package_in_progress,
116 &mut external_package_ref_in_progress,
117 );
118 process_atom_for_files(
119 atom,
120 &mut file_in_progress,
121 &mut file_information,
122 &package_in_progress,
123 &mut relationships,
124 );
125 process_atom_for_snippets(atom, &mut snippet_information, &mut snippet_in_progress);
126 process_atom_for_relationships(atom, &mut relationships, &mut relationship_in_progress);
127 process_atom_for_annotations(atom, &mut annotations, &mut annotation_in_progress)?;
128 process_atom_for_license_info(
129 atom,
130 &mut other_licensing_information_detected,
131 &mut license_info_in_progress,
132 )?;
133 }
134 if let Some(file) = file_in_progress {
135 file_information.push(file);
136 }
137 if let Some(snippet) = &mut snippet_in_progress {
138 snippet_information.push(snippet.clone());
139 }
140
141 if let Some(package) = package_in_progress {
142 package_information.push(package);
143 }
144
145 if let Some(relationship) = relationship_in_progress {
146 relationships.insert(relationship);
147 }
148
149 if let Some(license_info) = license_info_in_progress {
150 other_licensing_information_detected.push(license_info);
151 }
152
153 if document_creation_information_in_progress.is_some() {
154 document_creation_information_final = document_creation_information_in_progress;
155 }
156
157 process_annotation(&mut annotation_in_progress, &mut annotations);
158
159 Ok(SPDX {
160 document_creation_information: document_creation_information_final
161 // TODO: Proper error handling
162 .expect("If this doesn't exist, the document is not valid."),
163 package_information,
164 other_licensing_information_detected,
165 file_information,
166 snippet_information,
167 relationships: relationships.into_iter().collect(),
168 annotations,
169 // TODO: This should probably be removed.
170 spdx_ref_counter: 0,
171 })
172}
173
174fn process_atom_for_document_creation_information(
175 atom: &Atom,
176 mut document_creation_information_in_progress: &mut Option<DocumentCreationInformation>,
177) -> Result<Option<DocumentCreationInformation>, SpdxError> {
178 // Get document creation information.
179 let mut final_creation_information = None;
180 match atom {
181 Atom::SpdxVersion(value) => {
182 if let Some(document_creation_information) =
183 &mut document_creation_information_in_progress
184 {
185 document_creation_information.spdx_version = value.to_string();
186 }
187 }
188 Atom::DataLicense(value) => {
189 if let Some(document_creation_information) =
190 &mut document_creation_information_in_progress
191 {
192 document_creation_information.data_license = value.to_string();
193 }
194 }
195 Atom::SPDXID(value) => {
196 if let Some(document_creation_information) =
197 &mut document_creation_information_in_progress
198 {
199 document_creation_information.spdx_identifier = value.to_string();
200 }
201 }
202 Atom::DocumentName(value) => {
203 if let Some(document_creation_information) =
204 &mut document_creation_information_in_progress
205 {
206 document_creation_information.document_name = value.to_string();
207 }
208 }
209 Atom::DocumentNamespace(value) => {
210 if let Some(document_creation_information) =
211 &mut document_creation_information_in_progress
212 {
213 document_creation_information.spdx_document_namespace = value.to_string();
214 }
215 }
216 Atom::ExternalDocumentRef(value) => {
217 if let Some(document_creation_information) =
218 &mut document_creation_information_in_progress
219 {
220 document_creation_information
221 .external_document_references
222 .push(value.clone());
223 }
224 }
225 Atom::LicenseListVersion(value) => {
226 if let Some(document_creation_information) =
227 &mut document_creation_information_in_progress
228 {
229 document_creation_information
230 .creation_info
231 .license_list_version = Some(value.to_string());
232 }
233 }
234 Atom::Creator(value) => {
235 if let Some(document_creation_information) =
236 &mut document_creation_information_in_progress
237 {
238 document_creation_information
239 .creation_info
240 .creators
241 .push(value.to_string());
242 }
243 }
244 Atom::Created(value) => {
245 if let Some(document_creation_information) =
246 &mut document_creation_information_in_progress
247 {
248 document_creation_information.creation_info.created =
249 DateTime::parse_from_rfc3339(value)?.with_timezone(&Utc);
250 }
251 }
252 Atom::CreatorComment(value) => {
253 if let Some(document_creation_information) =
254 &mut document_creation_information_in_progress
255 {
256 document_creation_information.creation_info.creator_comment =
257 Some(value.to_string());
258 }
259 }
260 Atom::DocumentComment(value) => {
261 if let Some(document_creation_information) =
262 &mut document_creation_information_in_progress
263 {
264 document_creation_information.document_comment = Some(value.to_string());
265 }
266 }
267 Atom::TVComment(_) => {}
268 _ => {
269 if let Some(document_creation_information) = document_creation_information_in_progress {
270 final_creation_information = Some(document_creation_information.clone());
271 }
272 }
273 }
274 Ok(final_creation_information)
275}
276
277#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
278fn process_atom_for_packages(
279 atom: &Atom,
280 packages: &mut Vec<PackageInformation>,
281 mut package_in_progress: &mut Option<PackageInformation>,
282 mut external_package_ref_in_progress: &mut Option<ExternalPackageReference>,
283) {
284 match atom {
285 Atom::PackageName(value) => {
286 if let Some(package) = &mut package_in_progress {
287 if let Some(pkg_ref) = &mut external_package_ref_in_progress {
288 package.external_reference.push(pkg_ref.clone());
289 *external_package_ref_in_progress = None;
290 }
291 packages.push(package.clone());
292 }
293 *package_in_progress = Some(PackageInformation::default());
294
295 if let Some(package) = &mut package_in_progress {
296 package.package_name = value.to_string();
297 }
298 }
299 Atom::SPDXID(value) => {
300 if let Some(package) = &mut package_in_progress {
301 if package.package_spdx_identifier == "NOASSERTION" {
302 package.package_spdx_identifier = value.to_string();
303 }
304 }
305 }
306 Atom::PackageVersion(value) => {
307 if let Some(package) = &mut package_in_progress {
308 package.package_version = Some(value.to_string());
309 }
310 }
311 Atom::PackageFileName(value) => {
312 if let Some(package) = &mut package_in_progress {
313 package.package_file_name = Some(value.to_string());
314 }
315 }
316 Atom::PackageSupplier(value) => {
317 if let Some(package) = &mut package_in_progress {
318 package.package_supplier = Some(value.to_string());
319 }
320 }
321 Atom::PackageOriginator(value) => {
322 if let Some(package) = &mut package_in_progress {
323 package.package_originator = Some(value.to_string());
324 }
325 }
326 Atom::PackageDownloadLocation(value) => {
327 if let Some(package) = &mut package_in_progress {
328 package.package_download_location = value.to_string();
329 }
330 }
331 Atom::PackageVerificationCode(value) => {
332 if let Some(package) = &mut package_in_progress {
333 package.package_verification_code = Some(value.clone());
334 }
335 }
336 Atom::PackageChecksum(value) => {
337 if let Some(package) = &mut package_in_progress {
338 package.package_checksum.push(value.clone());
339 }
340 }
341 Atom::PackageHomePage(value) => {
342 if let Some(package) = &mut package_in_progress {
343 package.package_home_page = Some(value.clone());
344 }
345 }
346 Atom::PackageSourceInfo(value) => {
347 if let Some(package) = &mut package_in_progress {
348 package.source_information = Some(value.clone());
349 }
350 }
351 Atom::PackageLicenseConcluded(value) => {
352 if let Some(package) = &mut package_in_progress {
353 package.concluded_license = Some(SpdxExpression::parse(value).unwrap());
354 }
355 }
356 Atom::PackageLicenseInfoFromFiles(value) => {
357 if let Some(package) = &mut package_in_progress {
358 package
359 .all_licenses_information_from_files
360 .push(value.clone());
361 }
362 }
363 Atom::PackageLicenseDeclared(value) => {
364 if let Some(package) = &mut package_in_progress {
365 package.declared_license = Some(SpdxExpression::parse(value).unwrap());
366 }
367 }
368 Atom::PackageLicenseComments(value) => {
369 if let Some(package) = &mut package_in_progress {
370 package.comments_on_license = Some(value.clone());
371 }
372 }
373 Atom::PackageCopyrightText(value) => {
374 if let Some(package) = &mut package_in_progress {
375 package.copyright_text = Some(value.clone());
376 }
377 }
378 Atom::PackageSummary(value) => {
379 if let Some(package) = &mut package_in_progress {
380 package.package_summary_description = Some(value.clone());
381 }
382 }
383 Atom::PackageDescription(value) => {
384 if let Some(package) = &mut package_in_progress {
385 package.package_detailed_description = Some(value.clone());
386 }
387 }
388 Atom::PackageAttributionText(value) => {
389 if let Some(package) = &mut package_in_progress {
390 package.package_attribution_text.push(value.clone());
391 }
392 }
393 Atom::ExternalRef(value) => {
394 if let Some(pkg_ref) = &mut external_package_ref_in_progress {
395 if let Some(package) = &mut package_in_progress {
396 package.external_reference.push(pkg_ref.clone());
397 }
398 }
399 *external_package_ref_in_progress = Some(value.clone());
400 }
401 Atom::ExternalRefComment(value) => {
402 if let Some(pkg_ref) = &mut external_package_ref_in_progress {
403 pkg_ref.reference_comment = Some(value.clone());
404 }
405 }
406 _ => {}
407 }
408}
409
410fn process_atom_for_files(
411 atom: &Atom,
412 mut file_in_progress: &mut Option<FileInformation>,
413 files: &mut Vec<FileInformation>,
414 package_in_progress: &Option<PackageInformation>,
415 relationships: &mut HashSet<Relationship>,
416) {
417 match atom {
418 Atom::PackageName(_) => {
419 if let Some(file) = &mut file_in_progress {
420 files.push(file.clone());
421 *file_in_progress = None;
422 }
423 }
424 Atom::FileName(value) => {
425 if let Some(file) = &mut file_in_progress {
426 files.push(file.clone());
427 }
428 *file_in_progress = Some(FileInformation::default());
429
430 if let Some(file) = &mut file_in_progress {
431 file.file_name = value.to_string();
432 }
433 }
434 Atom::SPDXID(value) => {
435 if let Some(file) = &mut file_in_progress {
436 file.file_spdx_identifier = value.to_string();
437 if let Some(package) = package_in_progress {
438 relationships.insert(Relationship::new(
439 &package.package_spdx_identifier,
440 value,
441 crate::models::RelationshipType::Contains,
442 None,
443 ));
444 }
445 }
446 }
447 Atom::FileComment(value) => {
448 if let Some(file) = &mut file_in_progress {
449 file.file_comment = Some(value.to_string());
450 }
451 }
452 Atom::FileType(value) => {
453 if let Some(file) = &mut file_in_progress {
454 file.file_type.push(*value);
455 }
456 }
457 Atom::FileChecksum(value) => {
458 if let Some(file) = &mut file_in_progress {
459 file.file_checksum.push(value.clone());
460 }
461 }
462 Atom::LicenseConcluded(value) => {
463 if let Some(file) = &mut file_in_progress {
464 file.concluded_license = Some(SpdxExpression::parse(value).unwrap());
465 }
466 }
467 Atom::LicenseInfoInFile(value) => {
468 if let Some(file) = &mut file_in_progress {
469 file.license_information_in_file
470 .push(SimpleExpression::parse(value).unwrap());
471 }
472 }
473 Atom::LicenseComments(value) => {
474 if let Some(file) = &mut file_in_progress {
475 file.comments_on_license = Some(value.clone());
476 }
477 }
478 Atom::FileCopyrightText(value) => {
479 if let Some(file) = &mut file_in_progress {
480 file.copyright_text = Some(value.clone());
481 }
482 }
483 Atom::FileNotice(value) => {
484 if let Some(file) = &mut file_in_progress {
485 file.file_notice = Some(value.clone());
486 }
487 }
488 Atom::FileContributor(value) => {
489 if let Some(file) = &mut file_in_progress {
490 file.file_contributor.push(value.clone());
491 }
492 }
493 _ => {}
494 }
495}
496
497fn process_atom_for_snippets(
498 atom: &Atom,
499 snippets: &mut Vec<Snippet>,
500 mut snippet_in_progress: &mut Option<Snippet>,
501) {
502 match atom {
503 Atom::SnippetSPDXID(value) => {
504 if let Some(snippet) = &snippet_in_progress {
505 snippets.push(snippet.clone());
506 }
507
508 *snippet_in_progress = Some(Snippet::default());
509 if let Some(snippet) = &mut snippet_in_progress {
510 snippet.snippet_spdx_identifier = value.to_string();
511 }
512 }
513 Atom::SnippetFromFileSPDXID(value) => {
514 if let Some(snippet) = &mut snippet_in_progress {
515 snippet.snippet_from_file_spdx_identifier = value.to_string();
516 }
517 }
518 Atom::SnippetByteRange(value) => {
519 if let Some(snippet) = &mut snippet_in_progress {
520 let start_pointer = Pointer::new_byte(None, value.0);
521 let end_pointer = Pointer::new_byte(None, value.1);
522 let range = Range::new(start_pointer, end_pointer);
523 snippet.ranges.push(range);
524 }
525 }
526 Atom::SnippetLineRange(value) => {
527 if let Some(snippet) = &mut snippet_in_progress {
528 let start_pointer = Pointer::new_line(None, value.0);
529 let end_pointer = Pointer::new_line(None, value.1);
530 let range = Range::new(start_pointer, end_pointer);
531 snippet.ranges.push(range);
532 }
533 }
534 Atom::SnippetLicenseConcluded(value) => {
535 if let Some(snippet) = &mut snippet_in_progress {
536 snippet.snippet_concluded_license = Some(SpdxExpression::parse(value).unwrap());
537 }
538 }
539 Atom::LicenseInfoInSnippet(value) => {
540 if let Some(snippet) = &mut snippet_in_progress {
541 snippet
542 .license_information_in_snippet
543 .push(value.to_string());
544 }
545 }
546 Atom::SnippetLicenseComments(value) => {
547 if let Some(snippet) = &mut snippet_in_progress {
548 snippet.snippet_comments_on_license = Some(value.to_string());
549 }
550 }
551 Atom::SnippetCopyrightText(value) => {
552 if let Some(snippet) = &mut snippet_in_progress {
553 snippet.snippet_copyright_text = Some(value.to_string());
554 }
555 }
556 Atom::SnippetComment(value) => {
557 if let Some(snippet) = &mut snippet_in_progress {
558 snippet.snippet_comment = Some(value.to_string());
559 }
560 }
561 Atom::SnippetName(value) => {
562 if let Some(snippet) = &mut snippet_in_progress {
563 snippet.snippet_name = Some(value.to_string());
564 }
565 }
566 Atom::SnippetAttributionText(value) => {
567 if let Some(snippet) = &mut snippet_in_progress {
568 snippet.snippet_attribution_text = Some(value.to_string());
569 }
570 }
571 _ => {}
572 }
573}
574
575#[allow(clippy::unnecessary_wraps)]
576fn process_atom_for_relationships(
577 atom: &Atom,
578 relationships: &mut HashSet<Relationship>,
579 mut relationship_in_progress: &mut Option<Relationship>,
580) {
581 match atom {
582 Atom::Relationship(value: &Relationship) => {
583 if let Some(relationship: &mut Relationship) = relationship_in_progress {
584 relationships.insert(relationship.clone());
585 }
586 *relationship_in_progress = Some(value.clone());
587 }
588 Atom::RelationshipComment(value: &String) => {
589 if let Some(relationship: &mut Relationship) = &mut relationship_in_progress {
590 relationship.comment = Some(value.to_string());
591 }
592 }
593 _ => {}
594 }
595}
596
597#[derive(Debug, Default)]
598struct AnnotationInProgress {
599 annotator_in_progress: Option<String>,
600 date_in_progress: Option<DateTime<Utc>>,
601 comment_in_progress: Option<String>,
602 type_in_progress: Option<AnnotationType>,
603 spdxref_in_progress: Option<String>,
604}
605
606fn process_annotation(
607 mut annotation_in_progress: &mut AnnotationInProgress,
608
609 annotations: &mut Vec<Annotation>,
610) {
611 if let AnnotationInProgress {
612 annotator_in_progress: Some(annotator),
613 date_in_progress: Some(date),
614 comment_in_progress: Some(comment),
615 type_in_progress: Some(annotation_type),
616 spdxref_in_progress: Some(spdxref),
617 } = &mut annotation_in_progress
618 {
619 let annotation = Annotation::new(
620 annotator.clone(),
621 *date,
622 *annotation_type,
623 Some(spdxref.clone()),
624 comment.clone(),
625 );
626 *annotation_in_progress = AnnotationInProgress {
627 annotator_in_progress: None,
628 comment_in_progress: None,
629 date_in_progress: None,
630 spdxref_in_progress: None,
631 type_in_progress: None,
632 };
633 annotations.push(annotation);
634 }
635}
636
637fn process_atom_for_annotations(
638 atom: &Atom,
639 annotations: &mut Vec<Annotation>,
640 mut annotation_in_progress: &mut AnnotationInProgress,
641) -> Result<(), SpdxError> {
642 process_annotation(annotation_in_progress, annotations);
643
644 match atom {
645 Atom::Annotator(value) => {
646 annotation_in_progress.annotator_in_progress = Some(value.clone());
647 }
648 Atom::AnnotationDate(value) => {
649 annotation_in_progress.date_in_progress =
650 Some(DateTime::parse_from_rfc3339(value)?.with_timezone(&Utc));
651 }
652 Atom::AnnotationComment(value) => {
653 annotation_in_progress.comment_in_progress = Some(value.clone());
654 }
655 Atom::AnnotationType(value) => {
656 annotation_in_progress.type_in_progress = Some(*value);
657 }
658 Atom::SPDXREF(value) => {
659 annotation_in_progress.spdxref_in_progress = Some(value.clone());
660 }
661 _ => {}
662 }
663
664 Ok(())
665}
666
667#[allow(clippy::unnecessary_wraps)]
668fn process_atom_for_license_info(
669 atom: &Atom,
670 license_infos: &mut Vec<OtherLicensingInformationDetected>,
671 mut license_info_in_progress: &mut Option<OtherLicensingInformationDetected>,
672) -> Result<(), SpdxError> {
673 match atom {
674 Atom::LicenseID(value) => {
675 if let Some(license_info) = &mut license_info_in_progress {
676 license_infos.push(license_info.clone());
677 }
678 *license_info_in_progress = Some(OtherLicensingInformationDetected::default());
679
680 if let Some(license_info) = &mut license_info_in_progress {
681 license_info.license_identifier = value.to_string();
682 }
683 }
684 Atom::ExtractedText(value) => {
685 if let Some(license_info) = &mut license_info_in_progress {
686 license_info.extracted_text = value.to_string();
687 }
688 }
689 Atom::LicenseName(value) => {
690 if let Some(license_info) = &mut license_info_in_progress {
691 license_info.license_name = value.to_string();
692 }
693 }
694 Atom::LicenseCrossReference(value) => {
695 if let Some(license_info) = &mut license_info_in_progress {
696 license_info.license_cross_reference.push(value.to_string());
697 }
698 }
699 Atom::LicenseComment(value) => {
700 if let Some(license_info) = &mut license_info_in_progress {
701 license_info.license_comment = Some(value.to_string());
702 }
703 }
704 _ => {}
705 }
706
707 Ok(())
708}
709
710#[cfg(test)]
711#[allow(clippy::too_many_lines)]
712mod test_super {
713 use std::{fs::read_to_string, iter::FromIterator};
714
715 use chrono::TimeZone;
716
717 use crate::models::{
718 Algorithm, Checksum, ExternalDocumentReference, ExternalPackageReferenceCategory, FileType,
719 };
720
721 use super::*;
722
723 #[test]
724 fn whole_spdx_is_parsed() {
725 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
726 let spdx = spdx_from_tag_value(&file).unwrap();
727 assert_eq!(spdx.package_information.len(), 4);
728 assert_eq!(spdx.file_information.len(), 4);
729 }
730
731 #[test]
732 fn spdx_creation_info_is_retrieved() {
733 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
734 let spdx = spdx_from_tag_value(&file).unwrap();
735 let document_creation_information = spdx.document_creation_information;
736 assert_eq!(document_creation_information.spdx_version, "SPDX-2.2");
737 assert_eq!(document_creation_information.data_license, "CC0-1.0");
738 assert_eq!(
739 document_creation_information.spdx_document_namespace,
740 "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301"
741 );
742 assert_eq!(
743 document_creation_information.document_name,
744 "SPDX-Tools-v2.0"
745 );
746 assert_eq!(
747 document_creation_information.spdx_identifier,
748 "SPDXRef-DOCUMENT"
749 );
750 assert_eq!(
751 document_creation_information.document_comment,
752 Some(
753 "This document was created using SPDX 2.0 using licenses from the web site."
754 .to_string()
755 )
756 );
757 assert_eq!(
758 document_creation_information.external_document_references,
759 vec![ExternalDocumentReference::new(
760 "spdx-tool-1.2".to_string(),
761 "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301"
762 .to_string(),
763 Checksum::new(Algorithm::SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759")
764 )]
765 );
766 assert!(document_creation_information
767 .creation_info
768 .creators
769 .contains(&"Tool: LicenseFind-1.0".to_string()));
770 assert!(document_creation_information
771 .creation_info
772 .creators
773 .contains(&"Organization: ExampleCodeInspect ()".to_string()));
774 assert!(document_creation_information
775 .creation_info
776 .creators
777 .contains(&"Person: Jane Doe ()".to_string()));
778 assert_eq!(
779 document_creation_information.creation_info.created,
780 Utc.with_ymd_and_hms(2010, 1, 29, 18, 30, 22).unwrap()
781 );
782 assert_eq!(
783 document_creation_information.creation_info.creator_comment,
784 Some(
785 "This package has been shipped in source and binary form.
786The binaries were created with gcc 4.5.1 and expect to link to
787compatible system run time libraries."
788 .to_string()
789 )
790 );
791 assert_eq!(
792 document_creation_information
793 .creation_info
794 .license_list_version,
795 Some("3.9".to_string())
796 );
797 }
798
799 #[test]
800 fn package_info_is_retrieved() {
801 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
802 let spdx = spdx_from_tag_value(&file).unwrap();
803 let packages = spdx.package_information;
804 assert_eq!(packages.len(), 4);
805
806 let glibc = packages.iter().find(|p| p.package_name == "glibc").unwrap();
807 assert_eq!(glibc.package_spdx_identifier, "SPDXRef-Package");
808 assert_eq!(glibc.package_version, Some("2.11.1".to_string()));
809 assert_eq!(
810 glibc.package_file_name,
811 Some("glibc-2.11.1.tar.gz".to_string())
812 );
813 assert_eq!(
814 glibc.package_supplier,
815 Some("Person: Jane Doe (jane.doe@example.com)".to_string())
816 );
817 assert_eq!(
818 glibc.package_originator,
819 Some("Organization: ExampleCodeInspect (contact@example.com)".to_string())
820 );
821 assert_eq!(
822 glibc.package_download_location,
823 "http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz".to_string()
824 );
825 assert_eq!(
826 glibc.package_verification_code.as_ref().unwrap().value,
827 "d6a770ba38583ed4bb4525bd96e50461655d2758".to_string()
828 );
829 assert_eq!(
830 glibc.package_verification_code.as_ref().unwrap().excludes,
831 vec!["./package.spdx"]
832 );
833 assert_eq!(
834 glibc.package_checksum,
835 vec![
836 Checksum::new(Algorithm::MD5, "624c1abb3664f4b35547e7c73864ad24"),
837 Checksum::new(Algorithm::SHA1, "85ed0817af83a24ad8da68c2b5094de69833983c"),
838 Checksum::new(
839 Algorithm::SHA256,
840 "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
841 ),
842 ]
843 );
844 assert_eq!(
845 glibc.package_home_page,
846 Some("http://ftp.gnu.org/gnu/glibc".to_string())
847 );
848 assert_eq!(
849 glibc.source_information,
850 Some("uses glibc-2_11-branch from git://sourceware.org/git/glibc.git.".to_string())
851 );
852 assert_eq!(
853 glibc.concluded_license.as_ref().unwrap().identifiers(),
854 HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-3".to_string()])
855 );
856 assert_eq!(
857 glibc.all_licenses_information_from_files,
858 vec!["GPL-2.0-only", "LicenseRef-2", "LicenseRef-1"]
859 );
860 assert_eq!(
861 glibc.declared_license.as_ref().unwrap().identifiers(),
862 HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-3".to_string()])
863 );
864 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()));
865 assert_eq!(
866 glibc.copyright_text.as_ref().unwrap().clone(),
867 "Copyright 2008-2010 John Smith"
868 );
869 assert_eq!(
870 glibc.package_summary_description,
871 Some("GNU C library.".to_string())
872 );
873 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()));
874 assert_eq!(
875 glibc.package_attribution_text,
876 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()]
877 );
878 assert_eq!(
879 glibc.external_reference,
880 vec![
881 ExternalPackageReference::new(
882 ExternalPackageReferenceCategory::Security,
883 "cpe23Type".to_string(),
884 "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*".to_string(),
885 None
886 ),
887 ExternalPackageReference::new(
888 ExternalPackageReferenceCategory::Other,
889 "LocationRef-acmeforge".to_string(),
890 "acmecorp/acmenator/4.1.3-alpha".to_string(),
891 Some("This is the external ref for Acme".to_string())
892 ),
893 ]
894 );
895 let jena = packages.iter().find(|p| p.package_name == "Jena").unwrap();
896 assert_eq!(jena.package_spdx_identifier, "SPDXRef-fromDoap-0");
897 assert_eq!(
898 jena.external_reference,
899 vec![ExternalPackageReference::new(
900 ExternalPackageReferenceCategory::PackageManager,
901 "purl".to_string(),
902 "pkg:maven/org.apache.jena/apache-jena@3.12.0".to_string(),
903 None
904 ),]
905 );
906 }
907
908 #[test]
909 fn file_info_is_retrieved() {
910 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
911 let spdx = spdx_from_tag_value(&file).unwrap();
912 let files = spdx.file_information;
913 assert_eq!(files.len(), 4);
914
915 let fooc = files
916 .iter()
917 .find(|p| p.file_name == "./package/foo.c")
918 .unwrap();
919 assert_eq!(fooc.file_spdx_identifier, "SPDXRef-File");
920 assert_eq!(fooc.file_comment, Some("The concluded license was taken from the package level that the file was included in.
921This information was found in the COPYING.txt file in the xyz directory.".to_string()));
922 assert_eq!(fooc.file_type, vec![FileType::Source]);
923 assert_eq!(
924 fooc.file_checksum,
925 vec![
926 Checksum::new(Algorithm::SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"),
927 Checksum::new(Algorithm::MD5, "624c1abb3664f4b35547e7c73864ad24")
928 ]
929 );
930 assert_eq!(
931 fooc.concluded_license.as_ref().unwrap().identifiers(),
932 HashSet::from_iter(["LGPL-2.0-only".to_string(), "LicenseRef-2".to_string(),])
933 );
934 assert_eq!(
935 fooc.license_information_in_file,
936 vec![
937 SimpleExpression::parse("GPL-2.0-only").unwrap(),
938 SimpleExpression::parse("LicenseRef-2").unwrap()
939 ]
940 );
941 assert_eq!(fooc.comments_on_license, Some("The concluded license was taken from the package level that the file was included in.".to_string()));
942 assert_eq!(
943 fooc.copyright_text.as_ref().unwrap().clone(),
944 "Copyright 2008-2010 John Smith".to_string()
945 );
946 assert_eq!(
947 fooc.file_notice,
948 Some("Copyright (c) 2001 Aaron Lehmann aaroni@vitelus.com
949
950Permission 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:
951The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
952
953THE 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())
954 );
955 assert_eq!(
956 fooc.file_contributor,
957 vec![
958 "The Regents of the University of California".to_string(),
959 "Modified by Paul Mundt lethal@linux-sh.org".to_string(),
960 "IBM Corporation".to_string(),
961 ]
962 );
963 let doap = files
964 .iter()
965 .find(|p| p.file_name == "./src/org/spdx/parser/DOAPProject.java")
966 .unwrap();
967
968 assert_eq!(doap.file_spdx_identifier, "SPDXRef-DoapSource");
969 }
970
971 #[test]
972 fn snippet_info_is_retrieved() {
973 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
974 let spdx = spdx_from_tag_value(&file).unwrap();
975 let snippets = spdx.snippet_information;
976 assert_eq!(snippets.len(), 1);
977
978 let snippet = snippets[0].clone();
979
980 assert_eq!(snippet.snippet_spdx_identifier, "SPDXRef-Snippet");
981 assert_eq!(
982 snippet.snippet_from_file_spdx_identifier,
983 "SPDXRef-DoapSource"
984 );
985 assert_eq!(snippet.ranges.len(), 2);
986 assert!(snippet
987 .ranges
988 .iter()
989 .any(|snip| snip.start_pointer == Pointer::new_byte(None, 310)));
990 assert!(snippet
991 .ranges
992 .iter()
993 .any(|snip| snip.end_pointer == Pointer::new_byte(None, 420)));
994 assert!(snippet
995 .ranges
996 .iter()
997 .any(|snip| snip.start_pointer == Pointer::new_line(None, 5)));
998 assert!(snippet
999 .ranges
1000 .iter()
1001 .any(|snip| snip.end_pointer == Pointer::new_line(None, 23)));
1002 assert_eq!(
1003 snippet.snippet_concluded_license.unwrap(),
1004 SpdxExpression::parse("GPL-2.0-only").unwrap()
1005 );
1006 assert_eq!(snippet.license_information_in_snippet, vec!["GPL-2.0-only"]);
1007 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()));
1008 assert_eq!(
1009 snippet.snippet_copyright_text.as_ref().unwrap().clone(),
1010 "Copyright 2008-2010 John Smith"
1011 );
1012 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()));
1013 assert_eq!(snippet.snippet_name, Some("from linux kernel".to_string()));
1014 }
1015
1016 #[test]
1017 fn relationships_are_retrieved() {
1018 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
1019 let spdx = spdx_from_tag_value(&file).unwrap();
1020 let relationships = spdx.relationships;
1021 assert_eq!(relationships.len(), 11);
1022
1023 assert!(relationships.contains(&Relationship::new(
1024 "SPDXRef-DOCUMENT",
1025 "SPDXRef-Package",
1026 crate::models::RelationshipType::Contains,
1027 None
1028 )));
1029 assert!(relationships.contains(&Relationship::new(
1030 "SPDXRef-CommonsLangSrc",
1031 "NOASSERTION",
1032 crate::models::RelationshipType::GeneratedFrom,
1033 None
1034 )));
1035
1036 // Implied relationship by the file following the package in tag-value.
1037 assert!(relationships.contains(&Relationship::new(
1038 "SPDXRef-Package",
1039 "SPDXRef-DoapSource",
1040 crate::models::RelationshipType::Contains,
1041 None
1042 )));
1043 }
1044
1045 #[test]
1046 fn annotations_are_retrieved() {
1047 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
1048 let spdx = spdx_from_tag_value(&file).unwrap();
1049 let annotations = spdx.annotations;
1050 assert_eq!(annotations.len(), 5);
1051
1052 assert_eq!(
1053 annotations[2],
1054 Annotation::new(
1055 "Person: Suzanne Reviewer".to_string(),
1056 Utc.with_ymd_and_hms(2011, 3, 13, 0, 0, 0).unwrap(),
1057 AnnotationType::Review,
1058 Some("SPDXRef-DOCUMENT".to_string()),
1059 "Another example reviewer.".to_string()
1060 )
1061 );
1062 }
1063
1064 #[test]
1065 fn license_info_is_retrieved() {
1066 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
1067 let spdx = spdx_from_tag_value(&file).unwrap();
1068 let license_info = spdx.other_licensing_information_detected;
1069 assert_eq!(license_info.len(), 5);
1070 assert_eq!(license_info[1].license_identifier, "LicenseRef-2");
1071 assert_eq!(
1072 license_info[3].license_name,
1073 "Beer-Ware License (Version 42)"
1074 );
1075 assert_eq!(
1076 license_info[3].license_cross_reference,
1077 vec!["http://people.freebsd.org/~phk/"]
1078 );
1079 assert_eq!(
1080 license_info[3].license_comment,
1081 Some("The beerware license has a couple of other standard variants.".to_string())
1082 );
1083 assert!(license_info[3]
1084 .extracted_text
1085 .starts_with(r#""THE BEER-WARE"#));
1086 assert!(license_info[3]
1087 .extracted_text
1088 .ends_with("Poul-Henning Kamp"));
1089 }
1090
1091 #[test]
1092 fn tag_value_is_parsed() {
1093 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
1094 let spdx = spdx_from_tag_value(&file).unwrap();
1095
1096 assert_eq!(spdx.package_information.len(), 4);
1097 assert_eq!(spdx.file_information.len(), 4);
1098 assert_eq!(spdx.snippet_information.len(), 1);
1099 assert_eq!(spdx.relationships.len(), 11);
1100 assert_eq!(spdx.annotations.len(), 5);
1101 assert_eq!(spdx.other_licensing_information_detected.len(), 5);
1102 }
1103}
1104