PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Composer/Util/SpdxLicense.php

https://gitlab.com/sea-light/composer
PHP | 233 lines | 147 code | 28 blank | 58 comment | 25 complexity | 3cc17a898ee65c224be692007d870ef9 MD5 | raw file
  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 SpdxLicense
  20. {
  21. /**
  22. * @var array
  23. */
  24. private $licenses;
  25. public function __construct()
  26. {
  27. $this->loadLicenses();
  28. }
  29. private function loadLicenses()
  30. {
  31. if (is_array($this->licenses)) {
  32. return $this->licenses;
  33. }
  34. $jsonFile = new JsonFile(__DIR__ . '/../../../res/spdx-licenses.json');
  35. $this->licenses = $jsonFile->read();
  36. return $this->licenses;
  37. }
  38. /**
  39. * Returns license metadata by license identifier.
  40. *
  41. * @param string $identifier
  42. *
  43. * @return array|null
  44. */
  45. public function getLicenseByIdentifier($identifier)
  46. {
  47. if (!isset($this->licenses[$identifier])) {
  48. return;
  49. }
  50. $license = $this->licenses[$identifier];
  51. // add URL for the license text (it's not included in the json)
  52. $license[2] = 'http://spdx.org/licenses/' . $identifier . '#licenseText';
  53. return $license;
  54. }
  55. /**
  56. * Returns the short identifier of a license by full name.
  57. *
  58. * @param string $identifier
  59. *
  60. * @return string
  61. */
  62. public function getIdentifierByName($name)
  63. {
  64. foreach ($this->licenses as $identifier => $licenseData) {
  65. if ($licenseData[0] === $name) { // key 0 = fullname
  66. return $identifier;
  67. }
  68. }
  69. }
  70. /**
  71. * Returns the OSI Approved status for a license by identifier.
  72. *
  73. * @return bool
  74. */
  75. public function isOsiApprovedByIdentifier($identifier)
  76. {
  77. return $this->licenses[$identifier][1]; // key 1 = osi approved
  78. }
  79. /**
  80. * Check, if the identifier for a license is valid.
  81. *
  82. * @param string $identifier
  83. *
  84. * @return bool
  85. */
  86. private function isValidLicenseIdentifier($identifier)
  87. {
  88. $identifiers = array_keys($this->licenses);
  89. return in_array($identifier, $identifiers);
  90. }
  91. /**
  92. * @param array|string $license
  93. *
  94. * @return bool
  95. * @throws \InvalidArgumentException
  96. */
  97. public function validate($license)
  98. {
  99. if (is_array($license)) {
  100. $count = count($license);
  101. if ($count !== count(array_filter($license, 'is_string'))) {
  102. throw new \InvalidArgumentException('Array of strings expected.');
  103. }
  104. $license = $count > 1 ? '('.implode(' or ', $license).')' : (string) reset($license);
  105. }
  106. if (!is_string($license)) {
  107. throw new \InvalidArgumentException(sprintf(
  108. 'Array or String expected, %s given.', gettype($license)
  109. ));
  110. }
  111. return $this->isValidLicenseString($license);
  112. }
  113. /**
  114. * @param string $license
  115. *
  116. * @return bool
  117. * @throws \RuntimeException
  118. */
  119. private function isValidLicenseString($license)
  120. {
  121. $tokens = array(
  122. 'po' => '\(',
  123. 'pc' => '\)',
  124. 'op' => '(?:or|and)',
  125. 'lix' => '(?:NONE|NOASSERTION)',
  126. 'lir' => 'LicenseRef-\d+',
  127. 'lic' => '[-+_.a-zA-Z0-9]{3,}',
  128. 'ws' => '\s+',
  129. '_' => '.',
  130. );
  131. $next = function () use ($license, $tokens) {
  132. static $offset = 0;
  133. if ($offset >= strlen($license)) {
  134. return null;
  135. }
  136. foreach ($tokens as $name => $token) {
  137. if (false === $r = preg_match('{' . $token . '}', $license, $matches, PREG_OFFSET_CAPTURE, $offset)) {
  138. throw new \RuntimeException('Pattern for token %s failed (regex error).', $name);
  139. }
  140. if ($r === 0) {
  141. continue;
  142. }
  143. if ($matches[0][1] !== $offset) {
  144. continue;
  145. }
  146. $offset += strlen($matches[0][0]);
  147. return array($name, $matches[0][0]);
  148. }
  149. throw new \RuntimeException('At least the last pattern needs to match, but it did not (dot-match-all is missing?).');
  150. };
  151. $open = 0;
  152. $require = 1;
  153. $lastop = null;
  154. while (list($token, $string) = $next()) {
  155. switch ($token) {
  156. case 'po':
  157. if ($open || !$require) {
  158. return false;
  159. }
  160. $open = 1;
  161. break;
  162. case 'pc':
  163. if ($open !== 1 || $require || !$lastop) {
  164. return false;
  165. }
  166. $open = 2;
  167. break;
  168. case 'op':
  169. if ($require || !$open) {
  170. return false;
  171. }
  172. $lastop || $lastop = $string;
  173. if ($lastop !== $string) {
  174. return false;
  175. }
  176. $require = 1;
  177. break;
  178. case 'lix':
  179. if ($open) {
  180. return false;
  181. }
  182. goto lir;
  183. case 'lic':
  184. if (!$this->isValidLicenseIdentifier($string)) {
  185. return false;
  186. }
  187. // Fall-through intended
  188. case 'lir':
  189. lir:
  190. if (!$require) {
  191. return false;
  192. }
  193. $require = 0;
  194. break;
  195. case 'ws':
  196. break;
  197. case '_':
  198. return false;
  199. default:
  200. throw new \RuntimeException(sprintf('Unparsed token: %s.', print_r($token, true)));
  201. }
  202. }
  203. return !($open % 2 || $require);
  204. }
  205. }