license_finder 6.3.0 → 6.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/CHANGELOG.md +60 -0
  4. data/Dockerfile +13 -11
  5. data/README.md +28 -6
  6. data/Rakefile +1 -1
  7. data/VERSION +1 -1
  8. data/ci/pipelines/release.yml.erb +14 -4
  9. data/ci/tasks/rubocop.yml +1 -1
  10. data/lib/license_finder/cli.rb +1 -0
  11. data/lib/license_finder/cli/base.rb +1 -0
  12. data/lib/license_finder/cli/inherited_decisions.rb +50 -0
  13. data/lib/license_finder/cli/main.rb +3 -1
  14. data/lib/license_finder/configuration.rb +5 -1
  15. data/lib/license_finder/decision_applier.rb +8 -4
  16. data/lib/license_finder/decisions.rb +99 -20
  17. data/lib/license_finder/license.rb +37 -0
  18. data/lib/license_finder/license/definitions.rb +26 -3
  19. data/lib/license_finder/license/templates/0BSD.txt +10 -0
  20. data/lib/license_finder/license/templates/SimplifiedBSD.txt +0 -4
  21. data/lib/license_finder/license/text.rb +24 -2
  22. data/lib/license_finder/logger.rb +2 -0
  23. data/lib/license_finder/package.rb +2 -1
  24. data/lib/license_finder/package_manager.rb +6 -2
  25. data/lib/license_finder/package_managers/bundler.rb +1 -1
  26. data/lib/license_finder/package_managers/dotnet.rb +2 -1
  27. data/lib/license_finder/package_managers/go_15vendorexperiment.rb +1 -1
  28. data/lib/license_finder/package_managers/go_modules.rb +35 -12
  29. data/lib/license_finder/package_managers/mix.rb +1 -1
  30. data/lib/license_finder/package_managers/nuget.rb +51 -4
  31. data/lib/license_finder/package_managers/pipenv.rb +1 -1
  32. data/lib/license_finder/package_managers/rebar.rb +29 -8
  33. data/lib/license_finder/package_managers/yarn.rb +16 -2
  34. data/lib/license_finder/package_utils/license_files.rb +2 -2
  35. data/lib/license_finder/packages/bower_package.rb +7 -0
  36. data/lib/license_finder/packages/bundler_package.rb +4 -0
  37. data/lib/license_finder/packages/cargo_package.rb +4 -0
  38. data/lib/license_finder/packages/cocoa_pods_package.rb +4 -0
  39. data/lib/license_finder/packages/composer_package.rb +4 -0
  40. data/lib/license_finder/packages/conan_package.rb +4 -0
  41. data/lib/license_finder/packages/go_package.rb +4 -0
  42. data/lib/license_finder/packages/gradle_package.rb +4 -0
  43. data/lib/license_finder/packages/maven_package.rb +4 -0
  44. data/lib/license_finder/packages/merged_package.rb +1 -1
  45. data/lib/license_finder/packages/mix_package.rb +4 -0
  46. data/lib/license_finder/packages/npm_package.rb +4 -0
  47. data/lib/license_finder/packages/nuget_package.rb +4 -0
  48. data/lib/license_finder/packages/pip_package.rb +13 -2
  49. data/lib/license_finder/packages/rebar_package.rb +4 -0
  50. data/lib/license_finder/packages/yarn_package.rb +4 -0
  51. data/lib/license_finder/reports/csv_report.rb +7 -3
  52. data/lib/license_finder/reports/json_report.rb +2 -0
  53. metadata +5 -3
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open-uri'
4
+ require 'license_finder/license'
5
+
3
6
  module LicenseFinder
4
7
  class Decisions
5
8
  ######
6
9
  # READ
7
10
  ######
8
11
 
9
- attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name
12
+ attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name, :inherited_decisions
10
13
 
11
14
  def licenses_of(name)
12
15
  @licenses[name]
@@ -37,6 +40,9 @@ module LicenseFinder
37
40
  end
38
41
 
39
42
  def permitted?(lic)
43
+ return lic.sub_licenses.any? { |sub_lic| @permitted.include?(sub_lic) } if lic.is_a?(OrLicense)
44
+ return lic.sub_licenses.all? { |sub_lic| @permitted.include?(sub_lic) } if lic.is_a?(AndLicense)
45
+
40
46
  @permitted.include?(lic)
41
47
  end
42
48
 
@@ -72,40 +78,41 @@ module LicenseFinder
72
78
  @restricted = Set.new
73
79
  @ignored = Set.new
74
80
  @ignored_groups = Set.new
81
+ @inherited_decisions = Set.new
75
82
  end
76
83
 
77
84
  def add_package(name, version, txn = {})
78
- @decisions << [:add_package, name, version, txn]
85
+ add_decision [:add_package, name, version, txn]
79
86
  @packages << ManualPackage.new(name, version)
80
87
  self
81
88
  end
82
89
 
83
90
  def remove_package(name, txn = {})
84
- @decisions << [:remove_package, name, txn]
91
+ add_decision [:remove_package, name, txn]
85
92
  @packages.delete(ManualPackage.new(name))
86
93
  self
87
94
  end
88
95
 
89
96
  def license(name, lic, txn = {})
90
- @decisions << [:license, name, lic, txn]
97
+ add_decision [:license, name, lic, txn]
91
98
  @licenses[name] << License.find_by_name(lic)
92
99
  self
93
100
  end
94
101
 
95
102
  def unlicense(name, lic, txn = {})
96
- @decisions << [:unlicense, name, lic, txn]
103
+ add_decision [:unlicense, name, lic, txn]
97
104
  @licenses[name].delete(License.find_by_name(lic))
98
105
  self
99
106
  end
100
107
 
101
108
  def homepage(name, homepage, txn = {})
102
- @decisions << [:homepage, name, homepage, txn]
109
+ add_decision [:homepage, name, homepage, txn]
103
110
  @homepages[name] = homepage
104
111
  self
105
112
  end
106
113
 
107
114
  def approve(name, txn = {})
108
- @decisions << [:approve, name, txn]
115
+ add_decision [:approve, name, txn]
109
116
 
110
117
  versions = []
111
118
  versions = @approvals[name][:safe_versions] if @approvals.key?(name)
@@ -115,71 +122,144 @@ module LicenseFinder
115
122
  end
116
123
 
117
124
  def unapprove(name, txn = {})
118
- @decisions << [:unapprove, name, txn]
125
+ add_decision [:unapprove, name, txn]
119
126
  @approvals.delete(name)
120
127
  self
121
128
  end
122
129
 
123
130
  def permit(lic, txn = {})
124
- @decisions << [:permit, lic, txn]
131
+ add_decision [:permit, lic, txn]
125
132
  @permitted << License.find_by_name(lic)
126
133
  self
127
134
  end
128
135
 
129
136
  def unpermit(lic, txn = {})
130
- @decisions << [:unpermit, lic, txn]
137
+ add_decision [:unpermit, lic, txn]
131
138
  @permitted.delete(License.find_by_name(lic))
132
139
  self
133
140
  end
134
141
 
135
142
  def restrict(lic, txn = {})
136
- @decisions << [:restrict, lic, txn]
143
+ add_decision [:restrict, lic, txn]
137
144
  @restricted << License.find_by_name(lic)
138
145
  self
139
146
  end
140
147
 
141
148
  def unrestrict(lic, txn = {})
142
- @decisions << [:unrestrict, lic, txn]
149
+ add_decision [:unrestrict, lic, txn]
143
150
  @restricted.delete(License.find_by_name(lic))
144
151
  self
145
152
  end
146
153
 
147
154
  def ignore(name, txn = {})
148
- @decisions << [:ignore, name, txn]
155
+ add_decision [:ignore, name, txn]
149
156
  @ignored << name
150
157
  self
151
158
  end
152
159
 
153
160
  def heed(name, txn = {})
154
- @decisions << [:heed, name, txn]
161
+ add_decision [:heed, name, txn]
155
162
  @ignored.delete(name)
156
163
  self
157
164
  end
158
165
 
159
166
  def ignore_group(name, txn = {})
160
- @decisions << [:ignore_group, name, txn]
167
+ add_decision [:ignore_group, name, txn]
161
168
  @ignored_groups << name
162
169
  self
163
170
  end
164
171
 
165
172
  def heed_group(name, txn = {})
166
- @decisions << [:heed_group, name, txn]
173
+ add_decision [:heed_group, name, txn]
167
174
  @ignored_groups.delete(name)
168
175
  self
169
176
  end
170
177
 
171
178
  def name_project(name, txn = {})
172
- @decisions << [:name_project, name, txn]
179
+ add_decision [:name_project, name, txn]
173
180
  @project_name = name
174
181
  self
175
182
  end
176
183
 
177
184
  def unname_project(txn = {})
178
- @decisions << [:unname_project, txn]
185
+ add_decision [:unname_project, txn]
179
186
  @project_name = nil
180
187
  self
181
188
  end
182
189
 
190
+ def inherit_from(filepath_info)
191
+ decisions =
192
+ if filepath_info.is_a?(Hash)
193
+ resolve_inheritance(filepath_info)
194
+ elsif filepath_info =~ %r{^https?://}
195
+ open_uri(filepath_info).read
196
+ else
197
+ Pathname(filepath_info).read
198
+ end
199
+
200
+ add_decision [:inherit_from, filepath_info]
201
+ @inherited_decisions << filepath_info
202
+ restore_inheritance(decisions)
203
+ end
204
+
205
+ def resolve_inheritance(filepath_info)
206
+ if (gem_name = filepath_info['gem'])
207
+ Pathname(gem_config_path(gem_name, filepath_info['path'])).read
208
+ else
209
+ open_uri(filepath_info['url'], filepath_info['authorization']).read
210
+ end
211
+ end
212
+
213
+ def gem_config_path(gem_name, relative_config_path)
214
+ spec = Gem::Specification.find_by_name(gem_name)
215
+ File.join(spec.gem_dir, relative_config_path)
216
+ rescue Gem::LoadError => e
217
+ raise Gem::LoadError,
218
+ "Unable to find gem #{gem_name}; is the gem installed? #{e}"
219
+ end
220
+
221
+ def remove_inheritance(filepath)
222
+ @decisions -= [[:inherit_from, filepath]]
223
+ @inherited_decisions.delete(filepath)
224
+ self
225
+ end
226
+
227
+ def add_decision(decision)
228
+ @decisions << decision unless @inherited
229
+ end
230
+
231
+ def restore_inheritance(decisions)
232
+ @inherited = true
233
+ self.class.restore(decisions, self)
234
+ @inherited = false
235
+ self
236
+ end
237
+
238
+ def open_uri(uri, auth = nil)
239
+ header = {}
240
+ auth_header = resolve_authorization(auth)
241
+ header['Authorization'] = auth_header if auth_header
242
+
243
+ # ruby < 2.5.0 URI.open is private
244
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
245
+ # rubocop:disable Security/Open
246
+ open(uri, header)
247
+ # rubocop:enable Security/Open
248
+ else
249
+ URI.open(uri, header)
250
+ end
251
+ end
252
+
253
+ def resolve_authorization(auth)
254
+ return unless auth
255
+
256
+ token_env = auth.match(/\$(\S.*)/)
257
+ return auth unless token_env
258
+
259
+ token = ENV[token_env[1]]
260
+ auth.sub(token_env[0], token)
261
+ end
262
+
183
263
  #########
184
264
  # PERSIST
185
265
  #########
@@ -192,8 +272,7 @@ module LicenseFinder
192
272
  write!(persist, file)
193
273
  end
194
274
 
195
- def self.restore(persisted)
196
- result = new
275
+ def self.restore(persisted, result = new)
197
276
  return result unless persisted
198
277
 
199
278
  actions = YAML.load(persisted)
@@ -19,6 +19,9 @@ module LicenseFinder
19
19
 
20
20
  def find_by_name(name)
21
21
  name ||= 'unknown'
22
+ return OrLicense.new(name) if name.include?(OrLicense.operator)
23
+ return AndLicense.new(name) if name.include?(AndLicense.operator)
24
+
22
25
  all.detect { |l| l.matches_name? l.stripped_name(name) } || Definitions.build_unrecognized(name)
23
26
  end
24
27
 
@@ -61,6 +64,10 @@ module LicenseFinder
61
64
  name.hash
62
65
  end
63
66
 
67
+ def unrecognized_matcher?
68
+ matcher.is_a?(NoneMatcher)
69
+ end
70
+
64
71
  private
65
72
 
66
73
  attr_reader :short_name, :pretty_name, :other_names
@@ -70,4 +77,34 @@ module LicenseFinder
70
77
  ([short_name, pretty_name] + other_names).uniq
71
78
  end
72
79
  end
80
+ class AndLicense < License
81
+ def self.operator
82
+ ' AND '
83
+ end
84
+
85
+ def initialize(name, operator = AndLicense.operator)
86
+ @short_name = name
87
+ @pretty_name = name
88
+ @url = nil
89
+ @matcher = NoneMatcher.new
90
+ # removes heading and trailing parentesis and splits
91
+ name = name[1..-2] if name.start_with?('(')
92
+ names = name.split(operator)
93
+ @sub_licenses = names.map do |sub_name|
94
+ License.find_by_name(sub_name)
95
+ end
96
+ end
97
+
98
+ attr_reader :sub_licenses
99
+ end
100
+
101
+ class OrLicense < AndLicense
102
+ def self.operator
103
+ ' OR '
104
+ end
105
+
106
+ def initialize(name)
107
+ super(name, OrLicense.operator)
108
+ end
109
+ end
73
110
  end
@@ -25,7 +25,8 @@ module LicenseFinder
25
25
  python,
26
26
  ruby,
27
27
  simplifiedbsd,
28
- wtfpl
28
+ wtfpl,
29
+ zerobsd
29
30
  ]
30
31
  end
31
32
 
@@ -294,13 +295,35 @@ module LicenseFinder
294
295
  def wtfpl
295
296
  License.new(
296
297
  short_name: 'WTFPL',
297
- pretty_name: 'Do What The Fuck You Want To Public License',
298
+ pretty_name: 'WTFPL',
298
299
  other_names: [
299
- 'WTFPL V2'
300
+ 'WTFPL V2',
301
+ 'Do What The Fuck You Want To Public License'
300
302
  ],
301
303
  url: 'http://www.wtfpl.net/'
302
304
  )
303
305
  end
306
+
307
+ def zerobsd
308
+ matcher = AnyMatcher.new(
309
+ Matcher.from_template(Template.named('0BSD'))
310
+ )
311
+
312
+ License.new(
313
+ short_name: '0BSD',
314
+ pretty_name: 'BSD Zero Clause License',
315
+ other_names: [
316
+ '0-Clause BSD',
317
+ 'Zero-Clause BSD',
318
+ 'BSD-0-Clause',
319
+ 'BSD-Zero-Clause',
320
+ 'BSD 0-Clause',
321
+ 'BSD Zero-Clause'
322
+ ],
323
+ url: 'https://opensource.org/licenses/0BSD',
324
+ matcher: matcher
325
+ )
326
+ end
304
327
  end
305
328
  end
306
329
  end
@@ -0,0 +1,10 @@
1
+ Permission to use, copy, modify, and/or distribute this software for any
2
+ purpose with or without fee is hereby granted.
3
+
4
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
5
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
6
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
7
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
8
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
9
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
10
+ PERFORMANCE OF THIS SOFTWARE.
@@ -17,7 +17,3 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
17
17
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
18
18
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
19
19
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20
-
21
- The views and conclusions contained in the software and documentation are those
22
- of the authors and should not be interpreted as representing official policies,
23
- either expressed or implied, of the FreeBSD Project.
@@ -6,15 +6,37 @@ module LicenseFinder
6
6
  SPACES = /\s+/.freeze
7
7
  QUOTES = /['`"]{1,2}/.freeze
8
8
  PLACEHOLDERS = /<[^<>]+>/.freeze
9
+ SPECIAL_SINGLE_QUOTES = /[‘’]/.freeze
10
+ SPECIAL_DOUBLE_QUOTES = /[“”„«»]/.freeze
11
+ ALPHABET_ORDERED_LIST = /\\\([a-z]\\\)\\\s/.freeze
12
+ ALPHABET_ORDERED_LIST_OPTIONAL = '(\([a-z]\)\s)?'
13
+ LIST_BULLETS = /(\d{1,2}\\\.|\\\*)\\\s/.freeze
14
+ LIST_BULLETS_OPTIONAL = '(\d{1,2}.|\*)?\s*'
15
+ NEWLINE_CHARACTER = /\n+/.freeze
16
+ QUOTE_COMMENT_CHARACTER = /^\s*\>+/.freeze
17
+ ESCAPED_QUOTES = /\\\"/.freeze
9
18
 
10
19
  def self.normalize_punctuation(text)
11
- text.gsub(SPACES, ' ')
20
+ text.dup.force_encoding('UTF-8')
21
+ .gsub(SPECIAL_DOUBLE_QUOTES, '"')
22
+ .gsub(SPECIAL_SINGLE_QUOTES, "'")
23
+ .gsub(QUOTE_COMMENT_CHARACTER, '')
24
+ .gsub(SPACES, ' ')
25
+ .gsub(NEWLINE_CHARACTER, ' ')
26
+ .gsub(ESCAPED_QUOTES, '"')
12
27
  .gsub(QUOTES, '"')
13
28
  .strip
29
+ rescue ArgumentError => _e
30
+ text
14
31
  end
15
32
 
16
33
  def self.compile_to_regex(text)
17
- Regexp.new(Regexp.escape(text).gsub(PLACEHOLDERS, '(.*)'))
34
+ Regexp.new(Regexp.escape(normalize_punctuation(text))
35
+ .gsub(PLACEHOLDERS, '(.*)')
36
+ .gsub(',', '(,)?')
37
+ .gsub('HOLDER', '(HOLDER|OWNER)')
38
+ .gsub(ALPHABET_ORDERED_LIST, ALPHABET_ORDERED_LIST_OPTIONAL)
39
+ .gsub(LIST_BULLETS, LIST_BULLETS_OPTIONAL))
18
40
  end
19
41
  end
20
42
  end
@@ -36,6 +36,8 @@ module LicenseFinder
36
36
  "\e[31m#{string}\e[0m"
37
37
  when :green
38
38
  "\e[32m#{string}\e[0m"
39
+ when :magenta
40
+ "\e[35m#{string}\e[0m"
39
41
  else
40
42
  string
41
43
  end
@@ -43,6 +43,7 @@ module LicenseFinder
43
43
  @summary = options[:summary] || ''
44
44
  @description = options[:description] || ''
45
45
  @homepage = options[:homepage] || ''
46
+ @package_url = options[:package_url].to_s
46
47
  @children = options[:children] || []
47
48
  @parents = Set.new # will be figured out later by package manager
48
49
  @groups = options[:groups] || []
@@ -61,7 +62,7 @@ module LicenseFinder
61
62
 
62
63
  ## DESCRIPTION
63
64
 
64
- attr_accessor :homepage
65
+ attr_accessor :homepage, :package_url
65
66
 
66
67
  attr_reader :name, :version, :authors,
67
68
  :summary, :description,
@@ -119,8 +119,12 @@ module LicenseFinder
119
119
  attr_reader :logger, :project_path
120
120
 
121
121
  def log_errors(stderr)
122
- logger.info prepare_command, 'did not succeed.', color: :red
123
- logger.info prepare_command, stderr, color: :red
122
+ log_errors_with_cmd(prepare_command, stderr)
123
+ end
124
+
125
+ def log_errors_with_cmd(prep_cmd, stderr)
126
+ logger.info prep_cmd, 'did not succeed.', color: :red
127
+ logger.info prep_cmd, stderr, color: :red
124
128
  log_to_file stderr
125
129
  end
126
130
 
@@ -27,7 +27,7 @@ module LicenseFinder
27
27
  def prepare_command
28
28
  ignored_groups_argument = !ignored_groups.empty? ? "--without #{ignored_groups.to_a.join(' ')}" : ''
29
29
 
30
- gem_path = SecureRandom.uuid
30
+ gem_path = "lf-bundler-gems-#{SecureRandom.uuid}"
31
31
  logger.info self.class, "Running bundle install for #{Dir.pwd} with path #{gem_path}", color: :blue
32
32
 
33
33
  "bundle install #{ignored_groups_argument} --path #{gem_path}".strip