| 
									
										
										
										
											2020-10-10 14:16:11 +02:00
										 |  |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-10 15:23:03 +02:00
										 |  |  |  | require "utils/curl" | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  | require "utils/github" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 09:16:05 +02:00
										 |  |  |  | # Helper module for updating SPDX license data. | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # @api private | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  | module SPDX | 
					
						
							|  |  |  |  |   module_function | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |   DATA_PATH = (HOMEBREW_DATA_PATH/"spdx").freeze | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  |   API_URL = "https://api.github.com/repos/spdx/license-list-data/releases/latest" | 
					
						
							| 
									
										
										
										
											2020-10-22 10:01:40 -04:00
										 |  |  |  |   ALLOWED_LICENSE_SYMBOLS = [ | 
					
						
							|  |  |  |  |     :public_domain, | 
					
						
							|  |  |  |  |     :cannot_represent, | 
					
						
							|  |  |  |  |   ].freeze | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |   def license_data | 
					
						
							|  |  |  |  |     @license_data ||= JSON.parse (DATA_PATH/"spdx_licenses.json").read | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def exception_data | 
					
						
							|  |  |  |  |     @exception_data ||= JSON.parse (DATA_PATH/"spdx_exceptions.json").read | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-10 11:32:05 -07:00
										 |  |  |  |   def latest_tag | 
					
						
							| 
									
										
										
										
											2021-02-17 23:22:26 +05:30
										 |  |  |  |     @latest_tag ||= GitHub::API.open_rest(API_URL)["tag_name"] | 
					
						
							| 
									
										
										
										
											2020-08-10 11:32:05 -07:00
										 |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |   def download_latest_license_data!(to: DATA_PATH) | 
					
						
							|  |  |  |  |     data_url = "https://raw.githubusercontent.com/spdx/license-list-data/#{latest_tag}/json/" | 
					
						
							| 
									
										
										
										
											2023-09-04 22:17:57 -04:00
										 |  |  |  |     Utils::Curl.curl_download("#{data_url}licenses.json", to: to/"spdx_licenses.json") | 
					
						
							|  |  |  |  |     Utils::Curl.curl_download("#{data_url}exceptions.json", to: to/"spdx_exceptions.json") | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def parse_license_expression(license_expression) | 
					
						
							| 
									
										
										
										
											2020-12-28 13:34:07 +00:00
										 |  |  |  |     licenses = T.let([], T::Array[T.any(String, Symbol)]) | 
					
						
							|  |  |  |  |     exceptions = T.let([], T::Array[String]) | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     case license_expression | 
					
						
							|  |  |  |  |     when String, Symbol | 
					
						
							|  |  |  |  |       licenses.push license_expression | 
					
						
							| 
									
										
										
										
											2020-08-19 10:25:02 -04:00
										 |  |  |  |     when Hash, Array | 
					
						
							|  |  |  |  |       if license_expression.is_a? Hash | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |  |         license_expression = license_expression.filter_map do |key, value| | 
					
						
							| 
									
										
										
										
											2020-08-19 10:25:02 -04:00
										 |  |  |  |           if key.is_a? String | 
					
						
							|  |  |  |  |             licenses.push key | 
					
						
							|  |  |  |  |             exceptions.push value[:with] | 
					
						
							|  |  |  |  |             next | 
					
						
							|  |  |  |  |           end | 
					
						
							|  |  |  |  |           value | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-08-19 10:25:02 -04:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |       license_expression.each do |license| | 
					
						
							|  |  |  |  |         sub_license, sub_exception = parse_license_expression license | 
					
						
							|  |  |  |  |         licenses += sub_license | 
					
						
							|  |  |  |  |         exceptions += sub_exception | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     [licenses, exceptions] | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def valid_license?(license) | 
					
						
							| 
									
										
										
										
											2020-10-22 10:01:40 -04:00
										 |  |  |  |     return ALLOWED_LICENSE_SYMBOLS.include? license if license.is_a? Symbol | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     license = license.delete_suffix "+" | 
					
						
							|  |  |  |  |     license_data["licenses"].any? { |spdx_license| spdx_license["licenseId"] == license } | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def deprecated_license?(license) | 
					
						
							| 
									
										
										
										
											2020-10-22 10:01:40 -04:00
										 |  |  |  |     return false if ALLOWED_LICENSE_SYMBOLS.include? license | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |     return false unless valid_license?(license) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-23 16:28:24 -05:00
										 |  |  |  |     license = license.delete_suffix "+" | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |     license_data["licenses"].none? do |spdx_license| | 
					
						
							|  |  |  |  |       spdx_license["licenseId"] == license && !spdx_license["isDeprecatedLicenseId"] | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def valid_license_exception?(exception) | 
					
						
							|  |  |  |  |     exception_data["exceptions"].any? do |spdx_exception| | 
					
						
							|  |  |  |  |       spdx_exception["licenseExceptionId"] == exception && !spdx_exception["isDeprecatedLicenseId"] | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def license_expression_to_string(license_expression, bracket: false, hash_type: nil) | 
					
						
							|  |  |  |  |     case license_expression | 
					
						
							|  |  |  |  |     when String | 
					
						
							|  |  |  |  |       license_expression | 
					
						
							| 
									
										
										
										
											2020-10-22 10:01:40 -04:00
										 |  |  |  |     when Symbol | 
					
						
							| 
									
										
										
										
											2023-02-24 09:09:30 -08:00
										 |  |  |  |       license_expression.to_s.tr("_", " ").gsub(/\b(?<!\w['’`()])[a-z]/, &:capitalize) | 
					
						
							| 
									
										
										
										
											2020-08-20 10:26:37 -04:00
										 |  |  |  |     when Hash | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |       expressions = [] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       if license_expression.keys.length == 1
 | 
					
						
							|  |  |  |  |         hash_type = license_expression.keys.first | 
					
						
							|  |  |  |  |         if hash_type.is_a? String | 
					
						
							|  |  |  |  |           expressions.push "#{hash_type} with #{license_expression[hash_type][:with]}" | 
					
						
							|  |  |  |  |         else | 
					
						
							|  |  |  |  |           expressions += license_expression[hash_type].map do |license| | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |  |             license_expression_to_string license, bracket: true, hash_type: | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |           end | 
					
						
							|  |  |  |  |         end | 
					
						
							|  |  |  |  |       else | 
					
						
							|  |  |  |  |         bracket = false | 
					
						
							|  |  |  |  |         license_expression.each do |expression| | 
					
						
							| 
									
										
										
										
											2021-02-16 09:25:34 +00:00
										 |  |  |  |           expressions.push license_expression_to_string([expression].to_h, bracket: true) | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |         end | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       operator = if hash_type == :any_of | 
					
						
							|  |  |  |  |         " or " | 
					
						
							|  |  |  |  |       else | 
					
						
							|  |  |  |  |         " and " | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       if bracket | 
					
						
							|  |  |  |  |         "(#{expressions.join operator})" | 
					
						
							|  |  |  |  |       else | 
					
						
							|  |  |  |  |         expressions.join operator | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-06 09:57:25 +00:00
										 |  |  |  |   def string_to_license_expression(string) | 
					
						
							|  |  |  |  |     return if string.blank? | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     result = string | 
					
						
							|  |  |  |  |     result_type = nil | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     and_parts = string.split(/ and (?![^(]*\))/) | 
					
						
							|  |  |  |  |     if and_parts.length > 1
 | 
					
						
							|  |  |  |  |       result = and_parts | 
					
						
							|  |  |  |  |       result_type = :all_of | 
					
						
							|  |  |  |  |     else | 
					
						
							|  |  |  |  |       or_parts = string.split(/ or (?![^(]*\))/) | 
					
						
							|  |  |  |  |       if or_parts.length > 1
 | 
					
						
							|  |  |  |  |         result = or_parts | 
					
						
							|  |  |  |  |         result_type = :any_of | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if result_type | 
					
						
							|  |  |  |  |       result.map! do |part| | 
					
						
							|  |  |  |  |         part = part[1..-2] if part[0] == "(" && part[-1] == ")" | 
					
						
							|  |  |  |  |         string_to_license_expression(part) | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |       { result_type => result } | 
					
						
							|  |  |  |  |     else | 
					
						
							|  |  |  |  |       with_parts = string.split(" with ", 2) | 
					
						
							|  |  |  |  |       if with_parts.length > 1
 | 
					
						
							|  |  |  |  |         { with_parts.first => { with: with_parts.second } } | 
					
						
							|  |  |  |  |       else | 
					
						
							|  |  |  |  |         result | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |   def license_version_info(license) | 
					
						
							| 
									
										
										
										
											2020-10-22 10:01:40 -04:00
										 |  |  |  |     return [license] if ALLOWED_LICENSE_SYMBOLS.include? license | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     match = license.match(/-(?<version>[0-9.]+)(?:-.*?)??(?<or_later>\+|-only|-or-later)?$/) | 
					
						
							|  |  |  |  |     return [license] if match.blank? | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     license_name = license.split(match[0]).first | 
					
						
							|  |  |  |  |     or_later = match["or_later"].present? && %w[+ -or-later].include?(match["or_later"]) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     # [name, version, later versions allowed?] | 
					
						
							|  |  |  |  |     # e.g. GPL-2.0-or-later --> ["GPL", "2.0", true] | 
					
						
							|  |  |  |  |     [license_name, match["version"], or_later] | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def licenses_forbid_installation?(license_expression, forbidden_licenses) | 
					
						
							|  |  |  |  |     case license_expression | 
					
						
							|  |  |  |  |     when String, Symbol | 
					
						
							|  |  |  |  |       forbidden_licenses_include? license_expression.to_s, forbidden_licenses | 
					
						
							| 
									
										
										
										
											2020-08-20 10:26:37 -04:00
										 |  |  |  |     when Hash | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |       key = license_expression.keys.first | 
					
						
							|  |  |  |  |       case key | 
					
						
							|  |  |  |  |       when :any_of | 
					
						
							|  |  |  |  |         license_expression[key].all? { |license| licenses_forbid_installation? license, forbidden_licenses } | 
					
						
							|  |  |  |  |       when :all_of | 
					
						
							|  |  |  |  |         license_expression[key].any? { |license| licenses_forbid_installation? license, forbidden_licenses } | 
					
						
							|  |  |  |  |       else | 
					
						
							|  |  |  |  |         forbidden_licenses_include? key, forbidden_licenses | 
					
						
							|  |  |  |  |       end | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |   end | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   def forbidden_licenses_include?(license, forbidden_licenses) | 
					
						
							|  |  |  |  |     return true if forbidden_licenses.key? license | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     name, version, = license_version_info license | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 02:52:30 +00:00
										 |  |  |  |     forbidden_licenses.each_value do |license_info| | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  |       forbidden_name, forbidden_version, forbidden_or_later = *license_info | 
					
						
							| 
									
										
										
										
											2023-04-18 15:06:50 -07:00
										 |  |  |  |       next if forbidden_name != name | 
					
						
							| 
									
										
										
										
											2020-08-18 10:56:54 -04:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       return true if forbidden_or_later && forbidden_version <= version | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       return true if forbidden_version == version | 
					
						
							|  |  |  |  |     end | 
					
						
							|  |  |  |  |     false | 
					
						
							| 
									
										
										
										
											2020-08-04 10:07:57 -07:00
										 |  |  |  |   end | 
					
						
							|  |  |  |  | end |