SpdxLicenseIdentifier.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Util;
  12. use Composer\Json\JsonFile;
  13. /**
  14. * Supports composer array and SPDX tag notation for disjunctive/conjunctive
  15. * licenses.
  16. *
  17. * @author Tom Klingenberg <tklingenberg@lastflood.net>
  18. */
  19. class SpdxLicenseIdentifier
  20. {
  21. /**
  22. * @var array
  23. */
  24. private $identifiers;
  25. public function __construct()
  26. {
  27. $this->initIdentifiers();
  28. }
  29. /**
  30. * @param array|string $license
  31. *
  32. * @return bool
  33. * @throws \InvalidArgumentException
  34. */
  35. public function validate($license)
  36. {
  37. if (is_array($license)) {
  38. $count = count($license);
  39. if ($count !== count(array_filter($license, 'is_string'))) {
  40. throw new \InvalidArgumentException('Array of strings expected.');
  41. }
  42. $license = $count > 1 ? '('.implode(' or ', $license).')' : (string) reset($license);
  43. }
  44. if (!is_string($license)) {
  45. throw new \InvalidArgumentException(sprintf(
  46. 'Array or String expected, %s given.', gettype($license)
  47. ));
  48. }
  49. return $this->isValidLicenseString($license);
  50. }
  51. /**
  52. * Loads SPDX identifiers
  53. */
  54. private function initIdentifiers()
  55. {
  56. $jsonFile = new JsonFile(__DIR__ . '/../../../res/spdx-identifier.json');
  57. $this->identifiers = $jsonFile->read();
  58. }
  59. /**
  60. * @param string $identifier
  61. *
  62. * @return bool
  63. */
  64. private function isValidLicenseIdentifier($identifier)
  65. {
  66. return in_array($identifier, $this->identifiers);
  67. }
  68. /**
  69. * @param string $license
  70. *
  71. * @return bool
  72. * @throws \RuntimeException
  73. */
  74. private function isValidLicenseString($license)
  75. {
  76. $tokens = array(
  77. 'po' => '\(',
  78. 'pc' => '\)',
  79. 'op' => '(?:or|and)',
  80. 'lix' => '(?:NONE|NOASSERTION)',
  81. 'lir' => 'LicenseRef-\d+',
  82. 'lic' => '[-+_.a-zA-Z0-9]{3,}',
  83. 'ws' => '\s+',
  84. '_' => '.',
  85. );
  86. $next = function () use ($license, $tokens) {
  87. static $offset = 0;
  88. if ($offset >= strlen($license)) {
  89. return null;
  90. }
  91. foreach ($tokens as $name => $token) {
  92. if (false === $r = preg_match('{' . $token . '}', $license, $matches, PREG_OFFSET_CAPTURE, $offset)) {
  93. throw new \RuntimeException('Pattern for token %s failed (regex error).', $name);
  94. }
  95. if ($r === 0) {
  96. continue;
  97. }
  98. if ($matches[0][1] !== $offset) {
  99. continue;
  100. }
  101. $offset += strlen($matches[0][0]);
  102. return array($name, $matches[0][0]);
  103. }
  104. throw new \RuntimeException('At least the last pattern needs to match, but it did not (dot-match-all is missing?).');
  105. };
  106. $open = 0;
  107. $require = 1;
  108. $lastop = null;
  109. while (list($token, $string) = $next()) {
  110. switch ($token) {
  111. case 'po':
  112. if ($open || !$require) {
  113. return false;
  114. }
  115. $open = 1;
  116. break;
  117. case 'pc':
  118. if ($open !== 1 || $require || !$lastop) {
  119. return false;
  120. }
  121. $open = 2;
  122. break;
  123. case 'op':
  124. if ($require || !$open) {
  125. return false;
  126. }
  127. $lastop || $lastop = $string;
  128. if ($lastop !== $string) {
  129. return false;
  130. }
  131. $require = 1;
  132. break;
  133. case 'lix':
  134. if ($open) {
  135. return false;
  136. }
  137. goto lir;
  138. case 'lic':
  139. if (!$this->isValidLicenseIdentifier($string)) {
  140. return false;
  141. }
  142. // Fall-through intended
  143. case 'lir':
  144. lir:
  145. if (!$require) {
  146. return false;
  147. }
  148. $require = 0;
  149. break;
  150. case 'ws':
  151. break;
  152. case '_':
  153. return false;
  154. default:
  155. throw new \RuntimeException(sprintf('Unparsed token: %s.', print_r($token, true)));
  156. }
  157. }
  158. return !($open % 2 || $require);
  159. }
  160. }