FOSSology  4.5.1
Open Source License Compliance by Open Source Software
SpdxThreeImportSource.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2015-2017,2023-2024 Siemens AG
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
7 
8 namespace Fossology\ReportImport;
9 
10 use EasyRdf\Graph;
11 use EasyRdf\Literal;
12 use EasyRdf\RdfNamespace;
13 use EasyRdf\Resource;
17 
18 require_once 'ReportImportData.php';
19 require_once 'ReportImportDataItem.php';
20 require_once 'ImportSource.php';
21 
23 {
24  const TERMS = 'https://spdx.org/rdf/3.0.0/terms#';
25  const SPDX_URL = 'http://spdx.org/licenses/';
26  const SPDX_FILE = 'spdx:File';
27 
29  private $filename;
31  private $uri;
33  private $graph;
35  private $spdxDoc;
36 
37  function __construct($filename, $uri = null)
38  {
39  $this->filename = $filename;
40  $this->uri = $uri;
41  }
42 
46  public function parse()
47  {
48  RdfNamespace::set('spdx', self::TERMS);
49  $this->graph = $this->loadGraph($this->filename, $this->uri);
50  $this->spdxDoc = $this->getSpdxDoc();
51  return $this->graph !== null && $this->spdxDoc !== null;
52  }
56  public function getVersion(){
57  return null;
58  }
59 
60  private function loadGraph($filename, $uri = null)
61  {
63  $graph = new Graph();
64  if (StringOperation::stringEndsWith($filename, ".rdf") || StringOperation::stringEndsWith($filename, ".rdf.xml")) {
65  $graph->parseFile($filename, 'rdfxml', $uri);
66  } elseif (StringOperation::stringEndsWith($filename, ".ttl")) {
67  $graph->parseFile($filename, 'turtle', $uri);
68  } else {
69  $graph->parseFile($filename, 'guess', $uri);
70  }
71  return $graph;
72  }
73 
74 
75  private function getSpdxDoc()
76  {
77  $docs = $this->graph->allOfType("spdx:SpdxDocument");
78  if (count($docs) == 1) {
79  return $docs[0];
80  } else {
81  error_log("ERROR: Expected exactly one SPDX document, found " . count($docs));
82  return null;
83  }
84  }
85 
89  public function getAllFiles()
90  {
91  $files = $this->graph->allOfType("spdx:File");
92  $fileIds = array();
93  foreach ($files as $file) {
94  $fileIds[$file->getUri()] = trim($file->getLiteral("spdx:name")->getValue());
95  }
96  return $fileIds;
97  }
98 
103  public function getHashesMap($fileId)
104  {
105  $fileNode = $this->graph->resource($fileId, self::SPDX_FILE);
106  if ($fileNode->getLiteral("spdx:name") == null) {
107  return [];
108  }
109  $hashes = [];
110  $algoKeyPrefix = self::TERMS . 'checksumAlgorithm_';
111  /* @var $checksums Resource[] */
112  $checksums = $fileNode->getResource("spdx:verifiedUsing");
113  $algorithm = $checksums->allResources("spdx:algorithm");
114  $value = $checksums->allLiterals("spdx:hashValue");
115  for ($i=0;$i<count($algorithm);$i++){
116  if (StringOperation::stringStartsWith($algorithm[$i]->getUri(), $algoKeyPrefix)) {
117  $algo = substr($algorithm[$i]->getUri(), strlen($algoKeyPrefix));
118  } else {
119  $algo = $algorithm[$i]->getUri();
120  }
121  $hashes[$algo] = trim($value[$i]);
122  }
123  return $hashes;
124  }
125 
130  public function getDataForFile($fileid): ReportImportData
131  {
132  return new ReportImportData($this->getLicenseInfoInFileForFile($fileid),
133  $this->getConcludedLicenseInfoForFile($fileid),
134  $this->getCopyrightTextsForFile($fileid));
135  }
136 
141  public function getLicenseInfoInFileForFile($propertyId)
142  {
143  return $this->getLicenseInfoForFile($propertyId, 'Annotation');
144  }
145 
151  private function getLicenseInfoForFile($fileId, $kind)
152  {
153  $licenses = $this->graph->allOfType("spdx:$kind");
154  $output = [];
155  foreach ($licenses as $license) {
156  $spdxIds = $license->allResources('spdx:spdxId');
157  foreach ($spdxIds as $spdxId)
158  if ($spdxId == $fileId) {
159  if (!$this->isNotNoassertion($license->getUri())) {
160  continue;
161  }
162  $innerOutput = $this->parseLicense($license);
163  foreach ($innerOutput as $innerItem) {
164  $output[] = $innerItem;
165  }
166  }
167  }
168  return $output;
169  }
170 
171  private function isNotNoassertion($str)
172  {
173  return !(strtolower($str) === self::TERMS . "NoAssertionLicense" ||
174  strtolower($str) === "http://spdx.org/licenses/NoAssertionLicense");
175  }
176 
186  private function parseLicense($license)
187  {
188  if (is_string($license)) {
189  return $this->parseLicenseId($license);
190  } elseif ($license->isA('spdx:expandedlicensing_CustomLicense') ||
191  $license->isA('spdx:expandedlicensing_ListedLicense') ||
192  $license->isA('spdx:Annotation')){
193  return $this->handleLicenseInfo($license);
194  } elseif ($license->isA('spdx:expandedlicensing_DisjunctiveLicenseSet') ||
195  $license->isA('spdx:expandedlicensing_ConjunctiveLicenseSet')) {
196  return $this->handleLicenseSet($license);
197  }
198  if ($license instanceof Resource || $license instanceof Graph) {
199  return $this->parseLicenseId($license->getUri());
200  } else {
201  error_log("ERROR: can not handle license=[" . $license . "] of class=[" .
202  get_class($license) . "]");
203  return [];
204  }
205  }
206 
207  private function parseLicenseId($licenseId)
208  {
209  if (!is_string($licenseId)) {
210  error_log("ERROR: Id not a string: " . $licenseId);
211  return [];
212  }
213  if (!$this->isNotNoassertion($licenseId)) {
214  return [];
215  }
216 
217  if (StringOperation::stringStartsWith($licenseId, self::SPDX_URL)) {
218  $spdxId = urldecode(substr($licenseId, strlen(self::SPDX_URL)));
219  $item = new ReportImportDataItem($spdxId);
220  return [$item];
221  } else {
222  error_log("ERROR: can not handle license with ID=" . $licenseId);
223  return [];
224  }
225  }
226 
234  private function handleLicenseInfo($license)
235  {
236  if ($license->isA('spdx:expandedlicensing_CustomLicense')) {
237  $licenseIdLiteral = $license->getLiteral("spdx:spdxId");
238  } else {
239  $licenseIdLiteral = $license->getUri();
240  }
241  $licenseNameLiteral = $license->getLiteral("spdx:name");
242  $licenseTextLiteral = $license->getLiteral("spdx:simplelicensing_licenseText");
243  if ($licenseIdLiteral != null && $licenseNameLiteral != null &&
244  $licenseTextLiteral != null) {
245  $seeAlsoLiteral = $license->getLiteral("spdx:expandedlicensing_seeAlso");
246  $rawLicenseId = $licenseIdLiteral->getValue();
247  $licenseId = $this->stripLicenseRefPrefix($rawLicenseId);
248 
249  if ($license->isA('spdx:expandedlicensing_CustomLicense') &&
250  (strlen($licenseId) > 33 &&
251  substr($licenseId, -33, 1) === "-" &&
252  ctype_alnum(substr($licenseId, -32))
253  )) {
254  $licenseId = substr($licenseId, 0, -33);
255  $item = new ReportImportDataItem($licenseId);
256  $item->setCustomText($licenseTextLiteral->getValue());
257  } else {
258  $item = new ReportImportDataItem($licenseId);
259  $item->setLicenseCandidate($licenseNameLiteral->getValue(),
260  $licenseTextLiteral->getValue(),
261  strpos($rawLicenseId, LicenseRef::SPDXREF_PREFIX),
262  ($seeAlsoLiteral != null) ? $seeAlsoLiteral->getValue() : ""
263  );
264  }
265  return [$item];
266  }
267  return [];
268  }
269 
270  private function stripLicenseRefPrefix($licenseId)
271  {
272  if (StringOperation::stringStartsWith($licenseId, LicenseRef::SPDXREF_PREFIX)) {
273  if (StringOperation::stringStartsWith($licenseId, LicenseRef::SPDXREF_PREFIX_FOSSOLOGY)) {
274  return urldecode(substr($licenseId, strlen(LicenseRef::SPDXREF_PREFIX_FOSSOLOGY)));
275  }
276  return urldecode(substr($licenseId, strlen(LicenseRef::SPDXREF_PREFIX)));
277  } else {
278  return urldecode($licenseId);
279  }
280  }
281 
282  private function handleLicenseSet($license)
283  {
284  $output = [];
285  $subLicenses = $license->allResources("spdx:expandedlicensing_member");
286  if (sizeof($subLicenses) > 1 && $license->isA('spdx:expandedlicensing_DisjunctiveLicenseSet')) {
287  $output[] = new ReportImportDataItem("Dual-license");
288  }
289  foreach ($subLicenses as $subLicense) {
290  $innerOutput = $this->parseLicense($subLicense);
291  $output = array_merge($output, $innerOutput);
292  }
293  return $output;
294  }
295 
300  public function getConcludedLicenseInfoForFile($propertyId)
301  {
302  return $this->getLicenseInfoForFile($propertyId, 'simplelicensing_AnyLicenseInfo');
303  }
304 
309  private function getCopyrightTextsForFile($fileId): array
310  {
311  $fileNode = $this->graph->resource($fileId, self::SPDX_FILE);
312  /* @var $licenses Literal[] */
313  $copyrights = $fileNode->allLiterals("spdx:software_copyrightText");
314  if (count($copyrights) == 1 && $copyrights[0] instanceof Literal) {
315  # There should be only 1 copyright element containing 1 copyright per line
316  $copyrights = explode("\n", trim($copyrights[0]->getValue()));
317  return array_map('trim', $copyrights);
318  }
319  # There is only 1 copyright literal or noAssertion resource
320  return [];
321  }
322 }
static stringEndsWith($haystack, $needle)
static stringStartsWith($haystack, $needle)
char * trim(char *ptext)
Trimming whitespace.
Definition: fossconfig.c:690