diff --git a/.rubocop.yml b/.rubocop.yml index 3e47678bc..fbce75af8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,9 +27,9 @@ BlockLength: ClassLength: Enabled: false CyclomaticComplexity: - Max: 10 + Max: 15 PerceivedComplexity: - Max: 10 + Max: 15 LineLength: Max: 200 MethodLength: diff --git a/CHANGELOG.md b/CHANGELOG.md index 365cc970e..10fc952c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +# [6.14.2] / 2021-10-27 + +### Added +* Zlib License - [0f004b52](https://github.com/pivotal/LicenseFinder/commit/0f004b528d436b4d53db8bd373ede0594c07d9e8) - blooper05 + +# [6.14.1] / 2021-06-25 + +First two commit were supposed to show up in v6.14.0, but GPG bug prevented a correct build. Therefore, a follow up patch build was made to include the GPG fix. + +### Changed +* Upgrade Docker image to use Ubuntu Bionic [#178471230] [1c12588c](https://github.com/pivotal/LicenseFinder/commit/1c12588cceecb8b7350d090c85b519b24bcc6682) +* Update the default timezone to GMT [#178471230] - [9fcab84](https://github.com/pivotal/LicenseFinder/commit/9fcab84605cda81e7f276d3c567d14409e371333) +* Use local copy of Swift puglic GPG keys [#178674224] - [4db4b3e](https://github.com/pivotal/LicenseFinder/commit/4db4b3e5980ca52019549d74da574a2342a7846e) + +### Added +* Added --npm_options option to customize npm behavior. [b8457a62](https://github.com/pivotal/LicenseFinder/commit/b8457a62e7b531294934364d1e5f72cd78a7686a) - Alexander-Malott + +### Security +* Fix issue where commands could be injected running on Cocoapods projects. [b0a61a2d](https://github.com/pivotal/LicenseFinder/commit/b0a61a2d833921c714cc39cdda8ba80af3f33d04) + + Thanks to Joern SchneeweiszStaff Security Engineer, Security Research | GitLab for raising the issue + + +# [6.13.0] / 2021-04-27 + +### Fixed +* Ignore packages with nil modules - [4eca0ec1](https://github.com/pivotal/LicenseFinder/commit/4eca0ec15dc6266afa48b74b3742278351246eb8) + +# [6.12.2] / 2021-04-14 + +### Changed +* exit when go mod list command fails - [fcf1f707](https://github.com/pivotal/LicenseFinder/commit/fcf1f7076dee2ff730e3c8b608381aca22de0e92) - Jeff Jun + +# [6.12.1] / 2021-04-12 + # [6.12.0] / 2021-03-05 ### Added @@ -939,3 +974,8 @@ Bugfixes: [6.10.1]: https://github.com/pivotal/LicenseFinder/compare/v6.10.0...v6.10.1 [6.11.0]: https://github.com/pivotal/LicenseFinder/compare/v6.10.1...v6.11.0 [6.12.0]: https://github.com/pivotal/LicenseFinder/compare/v6.11.0...v6.12.0 +[6.12.1]: https://github.com/pivotal/LicenseFinder/compare/v6.12.0...v6.12.1 +[6.12.2]: https://github.com/pivotal/LicenseFinder/compare/v6.12.1...v6.12.2 +[6.13.0]: https://github.com/pivotal/LicenseFinder/compare/v6.12.2...v6.13.0 +[6.14.1]: https://github.com/pivotal/LicenseFinder/compare/v6.13.0...v6.14.1 +[6.14.2]: https://github.com/pivotal/LicenseFinder/compare/v6.14.1...v6.14.2 diff --git a/Dockerfile b/Dockerfile index 94e1824e2..6c613801a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM ubuntu:xenial +FROM ubuntu:bionic + +WORKDIR /tmp # Versioning ENV PIP_INSTALL_VERSION 19.0.2 @@ -18,7 +20,8 @@ RUN apt-get update && apt-get install -y \ sudo \ unzip \ wget \ - gnupg2 \ + gnupg2 \ + apt-utils \ software-properties-common \ bzr @@ -76,7 +79,6 @@ RUN mkdir -p /usr/local/share/sbt-launcher-packaging && \ rm -f "/tmp/sbt-${SBT_VERSION}.tgz" # install gradle -WORKDIR /tmp RUN curl -L -o gradle.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip && \ unzip -q gradle.zip && \ rm gradle.zip && \ @@ -102,6 +104,7 @@ RUN mkdir /gopath && \ go get -u github.com/rancher/trash && \ go clean -cache +WORKDIR /tmp # Fix the locale RUN apt-get install -y locales RUN locale-gen en_US.UTF-8 @@ -109,11 +112,16 @@ ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8 +# install Cargo +RUN curl https://sh.rustup.rs -sSf | bash -ls -- -y --profile minimal + #install rvm RUN apt-add-repository -y ppa:rael-gc/rvm && \ apt update && apt install -y rvm && \ /usr/share/rvm/bin/rvm install --default $RUBY_VERSION -ENV PATH=/usr/share/rvm/bin:$PATH + +# install bundler +RUN bash -lc "gem update --system && gem install bundler" #install mix RUN wget https://packages.erlang-solutions.com/erlang-solutions_${MIX_VERSION}_all.deb && \ @@ -123,41 +131,40 @@ RUN wget https://packages.erlang-solutions.com/erlang-solutions_${MIX_VERSION}_a sudo apt-get install -y esl-erlang && \ sudo apt-get install -y elixir -# install bundler -RUN bash -lc "gem update --system && gem install bundler" - # install conan RUN apt-get install -y python-dev && \ pip install --no-cache-dir --ignore-installed six --ignore-installed colorama \ --ignore-installed requests --ignore-installed chardet \ --ignore-installed urllib3 \ --upgrade setuptools && \ - pip install --no-cache-dir -Iv conan==1.11.2 + pip install --no-cache-dir -Iv conan==1.11.2 && \ + conan config install https://github.com/conan-io/conanclientcert.git -# install Cargo -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal # install NuGet (w. mono) # https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools#macoslinux RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF &&\ - echo "deb https://download.mono-project.com/repo/ubuntu stable-xenial main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list &&\ + echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list &&\ apt-get update &&\ apt-get install -y mono-complete &&\ curl -o "/usr/local/bin/nuget.exe" "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" &&\ curl -o "/usr/local/bin/nugetv3.5.0.exe" "https://dist.nuget.org/win-x86-commandline/v3.5.0/nuget.exe" # install dotnet core -WORKDIR /tmp -RUN wget -q https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb &&\ +RUN wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb &&\ sudo dpkg -i packages-microsoft-prod.deb &&\ rm packages-microsoft-prod.deb &&\ sudo apt-get update &&\ sudo apt-get install -y dotnet-runtime-2.1 dotnet-sdk-2.1 dotnet-sdk-2.2 dotnet-sdk-3.0 dotnet-sdk-3.1 # install Composer +# The ARG and ENV are for installing tzdata which is part of this installaion. +# https://serverfault.com/questions/949991/how-to-install-tzdata-on-a-ubuntu-docker-image +ENV TZ=GMT RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 4F4EA0AAE5267A6C &&\ - echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu xenial main" | sudo tee /etc/apt/sources.list.d/php.list &&\ + echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu bionic main" | sudo tee /etc/apt/sources.list.d/php.list &&\ apt-get update &&\ + export DEBIAN_FRONTEND=noninteractive &&\ apt-get install -y php7.4-cli &&\ EXPECTED_COMPOSER_INSTALLER_CHECKSUM="$(curl --silent https://composer.github.io/installer.sig)" &&\ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" &&\ @@ -170,7 +177,6 @@ RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 4F4EA0AAE5 # install miniconda # See https://docs.conda.io/en/latest/miniconda_hashes.html # for latest versions and SHAs. -WORKDIR /tmp RUN \ conda_installer=Miniconda3-py38_4.9.2-Linux-x86_64.sh &&\ ref='1314b90489f154602fd794accfc90446111514a5a72fe1f71ab83e07de9504a7' &&\ @@ -179,6 +185,61 @@ RUN \ ([ "$sha" = "${ref}" ] || (echo "Verification failed: ${sha} != ${ref}"; false)) &&\ (echo; echo "yes") | sh "${conda_installer}" +# install Swift Package Manager +# Based on https://github.com/apple/swift-docker/blob/main/5.3/ubuntu/18.04/Dockerfile +# The GPG download steps has been modified. Keys are now on LF repo and copied instaad of downloaded. +# Refer to https://swift.org/download/#using-downloads in the Linux section on how to download the keys +RUN apt-get -q install -y \ + libatomic1 \ + libcurl4 \ + libxml2 \ + libedit2 \ + libsqlite3-0 \ + libc6-dev \ + binutils \ + libgcc-5-dev \ + libstdc++-5-dev \ + zlib1g-dev \ + libpython2.7 \ + tzdata \ + git \ + pkg-config \ + && rm -r /var/lib/apt/lists/* + +# pub 4096R/ED3D1561 2019-03-22 [SC] [expires: 2023-03-23] +# Key fingerprint = A62A E125 BBBF BB96 A6E0 42EC 925C C1CC ED3D 1561 +# uid Swift 5.x Release Signing Key + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/lib/license_finder/license/text.rb b/lib/license_finder/license/text.rb index c1478c104..e96b4f817 100644 --- a/lib/license_finder/license/text.rb +++ b/lib/license_finder/license/text.rb @@ -5,6 +5,7 @@ class License module Text SPACES = /\s+/.freeze QUOTES = /['`"]{1,2}/.freeze + YEAR_PLACEHOLDERS = //.freeze PLACEHOLDERS = /<[^<>]+>/.freeze SPECIAL_SINGLE_QUOTES = /[‘’]/.freeze SPECIAL_DOUBLE_QUOTES = /[“”„«»]/.freeze @@ -32,6 +33,7 @@ def self.normalize_punctuation(text) def self.compile_to_regex(text) Regexp.new(Regexp.escape(normalize_punctuation(text)) + .gsub(YEAR_PLACEHOLDERS, '(\S*)') .gsub(PLACEHOLDERS, '(.*)') .gsub(',', '(,)?') .gsub('HOLDER', '(HOLDER|OWNER)') diff --git a/lib/license_finder/package.rb b/lib/license_finder/package.rb index d52261fc0..deb204777 100644 --- a/lib/license_finder/package.rb +++ b/lib/license_finder/package.rb @@ -38,7 +38,7 @@ def initialize(name, version = nil, options = {}) ## DESCRIPTION @name = name - @version = version + @version = version || '' @authors = options[:authors] || '' @summary = options[:summary] || '' @description = options[:description] || '' @@ -188,6 +188,7 @@ def log_activation(activation) require 'license_finder/packages/gradle_package' require 'license_finder/packages/cocoa_pods_package' require 'license_finder/packages/carthage_package' +require 'license_finder/packages/spm_package' require 'license_finder/packages/rebar_package' require 'license_finder/packages/erlangmk_package' require 'license_finder/packages/mix_package' diff --git a/lib/license_finder/package_manager.rb b/lib/license_finder/package_manager.rb index 8b3bc9920..276fbd424 100644 --- a/lib/license_finder/package_manager.rb +++ b/lib/license_finder/package_manager.rb @@ -165,6 +165,7 @@ def log_to_file(prep_cmd, contents) require 'license_finder/package_managers/mix' require 'license_finder/package_managers/cocoa_pods' require 'license_finder/package_managers/carthage' +require 'license_finder/package_managers/spm' require 'license_finder/package_managers/gradle' require 'license_finder/package_managers/rebar' require 'license_finder/package_managers/erlangmk' diff --git a/lib/license_finder/package_managers/cocoa_pods.rb b/lib/license_finder/package_managers/cocoa_pods.rb index 52baf4a42..516b00d65 100644 --- a/lib/license_finder/package_managers/cocoa_pods.rb +++ b/lib/license_finder/package_managers/cocoa_pods.rb @@ -53,7 +53,9 @@ def acknowledgements_path end def read_plist(pathname) - JSON.parse(`plutil -convert json -o - '#{pathname}'`) + transformed_pathname = pathname.gsub!(%r{[^0-9A-Za-z. \-'/]}, '') + transformed_pathname = pathname if transformed_pathname.nil? + JSON.parse(`plutil -convert json -o - '#{transformed_pathname}'`) end end end diff --git a/lib/license_finder/package_managers/go_modules.rb b/lib/license_finder/package_managers/go_modules.rb index 908e0b20f..68422e0c2 100644 --- a/lib/license_finder/package_managers/go_modules.rb +++ b/lib/license_finder/package_managers/go_modules.rb @@ -33,6 +33,8 @@ def packages_info # Explanations: # * Only list dependencies (packages not listed in the project directory) # (.DepOnly) + # * Ignore packages that have nil modules + # (.Module) # * Ignore standard library packages # (not .Standard) # * Replacement modules are respected @@ -40,7 +42,7 @@ def packages_info # * Module cache directory or (vendored) package directory # (or $mod.Dir .Dir) format_str = \ - '{{ if and (.DepOnly) (not .Standard) }}'\ + '{{ if and (.DepOnly) (.Module) (not .Standard) }}'\ '{{ $mod := (or .Module.Replace .Module) }}'\ '{{ $mod.Path }},{{ $mod.Version }},{{ or $mod.Dir .Dir }}'\ '{{ end }}' @@ -58,6 +60,7 @@ def packages_info go_list_cmd = "GO111MODULE=on go list -mod=readonly -deps -f '#{format_str}' ./..." info_output, stderr, status = Cmd.run(go_list_cmd) log_errors_with_cmd(go_list_cmd, "Getting the dependencies from go list failed \n\t#{stderr}") unless status.success? + raise "Command '#{go_list_cmd}' failed to execute" unless status.success? # Since many packages may belong to a single module, #uniq is used to deduplicate info_output.split("\n").uniq diff --git a/lib/license_finder/package_managers/npm.rb b/lib/license_finder/package_managers/npm.rb index 1d24a2d9c..5b5f86602 100644 --- a/lib/license_finder/package_managers/npm.rb +++ b/lib/license_finder/package_managers/npm.rb @@ -5,6 +5,11 @@ module LicenseFinder class NPM < PackageManager + def initialize(options = {}) + super + @npm_options = options[:npm_options] + end + def current_packages NpmPackage.packages_from_json(npm_json, detected_package_path) end @@ -35,6 +40,7 @@ def prepare def npm_json command = "#{package_management_command} list --json --long#{production_flag}" + command += " #{@npm_options}" unless @npm_options.nil? stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) } # we can try and continue if we got an exit status 1 - unmet peer dependency raise "Command '#{command}' failed to execute: #{stderr}" if !status.success? && status.exitstatus != 1 diff --git a/lib/license_finder/package_managers/spm.rb b/lib/license_finder/package_managers/spm.rb new file mode 100644 index 000000000..40407c741 --- /dev/null +++ b/lib/license_finder/package_managers/spm.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'json' + +module LicenseFinder + class Spm < PackageManager + class SpmError < RuntimeError; end + + def current_packages + unless File.exist?(workspace_state_path) + raise SpmError, 'No checked-out SPM packages found. + Please install your dependencies first.' + end + + workspace_state = JSON.parse(IO.read(workspace_state_path)) + workspace_state['object']['dependencies'].map do |dependency| + package_ref = dependency['packageRef'] + checkout_state = dependency['state']['checkoutState'] + + subpath = dependency['subpath'] + package_name = package_ref['name'] + package_version = checkout_state['version'] || checkout_state['revision'] + homepage = package_ref['path'] + + SpmPackage.new( + package_name, + package_version, + license_text(subpath), + logger: logger, + install_path: project_checkout(subpath), + homepage: homepage + ) + end + end + + def package_management_command + LicenseFinder::Platform.darwin? ? 'xcodebuild' : 'swift' + end + + def prepare_command + LicenseFinder::Platform.darwin? ? 'xcodebuild -resolvePackageDependencies' : 'swift package resolve' + end + + def possible_package_paths + [workspace_state_path] + end + + private + + def resolved_package + if File.exist?(resolved_path) + @resolved_file ||= IO.read(resolved_path) + else + raise SpmError, 'No Package.resolved found. + Please install your dependencies first and provide it via environment variable + SPM_PACKAGE_RESOLVED' + end + end + + def resolved_path + # Xcode projects have SPM packages info under project's derived data location + derived_data_folder = ENV['SPM_DERIVED_DATA'] + if derived_data_folder + pathname = Pathname.new(derived_data_folder) + pathname.absolute? ? pathname : project_path.join(derived_data_folder) + else + project_path.join('.build') + end + end + + def workspace_state_path + resolved_path.join('workspace-state.json') + end + + def license_text(subpath) + license_path = license_pattern(subpath).find { |f| File.exist?(f) } + license_path.nil? ? nil : IO.read(license_path) + end + + def project_checkout(subpath) + resolved_path.join('checkouts', subpath) + end + + def license_pattern(subpath) + checkout_path = project_checkout(subpath) + Dir.glob(checkout_path.join('LICENSE*'), File::FNM_CASEFOLD) + end + + def name_version_from_line(cartfile_line) + cartfile_line.split(' ')[1, 2].map { |f| f.split('/').last.delete('"').gsub('.git', '') } + end + end +end diff --git a/lib/license_finder/packages/spm_package.rb b/lib/license_finder/packages/spm_package.rb new file mode 100644 index 000000000..7e040e10e --- /dev/null +++ b/lib/license_finder/packages/spm_package.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module LicenseFinder + class SpmPackage < Package + def initialize(name, version, license_text, options = {}) + super(name, version, options) + @license = License.find_by_text(license_text.to_s) + end + + def licenses_from_spec + [@license].compact + end + + def package_manager + 'Spm' + end + end +end diff --git a/lib/license_finder/scanner.rb b/lib/license_finder/scanner.rb index ac072dc89..a428b210d 100644 --- a/lib/license_finder/scanner.rb +++ b/lib/license_finder/scanner.rb @@ -5,7 +5,7 @@ class Scanner PACKAGE_MANAGERS = [ GoModules, GoDep, GoWorkspace, Go15VendorExperiment, Glide, Gvt, Govendor, Trash, Dep, Bundler, NPM, Pip, Yarn, Bower, Maven, Gradle, CocoaPods, Rebar, Erlangmk, Nuget, Carthage, Mix, Conan, Sbt, Cargo, Dotnet, Composer, Pipenv, - Conda + Conda, Spm ].freeze class << self diff --git a/license_finder.gemspec b/license_finder.gemspec index b5ef67fc0..2b75b4f9a 100644 --- a/license_finder.gemspec +++ b/license_finder.gemspec @@ -50,7 +50,7 @@ Gem::Specification.new do |s| s.add_dependency 'with_env', '1.1.0' s.add_dependency 'xml-simple', '~> 1.1.5' - s.add_development_dependency 'addressable', '2.7.0' + s.add_development_dependency 'addressable', '2.8.0' s.add_development_dependency 'capybara', '~> 3.15.0' s.add_development_dependency 'cocoapods', '>= 1.0.0' if RUBY_PLATFORM =~ /darwin/ s.add_development_dependency 'fakefs', '~> 1.2.0' diff --git a/spec/fixtures/all_pms/.build/workspace-state.json b/spec/fixtures/all_pms/.build/workspace-state.json new file mode 100644 index 000000000..51d502516 --- /dev/null +++ b/spec/fixtures/all_pms/.build/workspace-state.json @@ -0,0 +1,58 @@ +{ + "object": { + "artifacts": [], + "dependencies": [{ + "basedOn": null, + "packageRef": { + "identity": "urlsessiondecodable", + "kind": "remote", + "name": "URLSessionDecodable", + "path": "https://github.com/ViacomInc/URLSessionDecodable" + }, + "state": { + "checkoutState": { + "branch": null, + "revision": "72c2776c737b580ffa4d0e7699c8429a1a6b5a36", + "version": "0.1.0" + }, + "name": "checkout" + }, + "subpath": "URLSessionDecodable" + }, { + "basedOn": null, + "packageRef": { + "identity": "nimble", + "kind": "remote", + "name": "Nimble", + "path": "https://github.com/Quick/Nimble" + }, + "state": { + "checkoutState": { + "branch": null, + "revision": "6956ffbde4ea6aab94fd2e823c5ede95072feef2", + "version": null + }, + "name": "checkout" + }, + "subpath": "Nimble" + }, { + "basedOn": null, + "packageRef": { + "identity": "swift-snapshot-testing", + "kind": "remote", + "name": "SnapshotTesting", + "path": "https://github.com/pointfreeco/swift-snapshot-testing" + }, + "state": { + "checkoutState": { + "branch": null, + "revision": "c466812aa2e22898f27557e2e780d3aad7a27203", + "version": "1.8.2" + }, + "name": "checkout" + }, + "subpath": "swift-snapshot-testing" + }] + }, + "version": 4 +} \ No newline at end of file diff --git a/spec/lib/license_finder/core_spec.rb b/spec/lib/license_finder/core_spec.rb index bdb61bb9a..74b94b773 100644 --- a/spec/lib/license_finder/core_spec.rb +++ b/spec/lib/license_finder/core_spec.rb @@ -40,6 +40,7 @@ module LicenseFinder gradle_include_groups: nil, maven_include_groups: nil, maven_options: nil, + npm_options: nil, pip_requirements_path: nil, python_version: nil, rebar_command: configuration.rebar_command, diff --git a/spec/lib/license_finder/license/definitions_spec.rb b/spec/lib/license_finder/license/definitions_spec.rb index ac980e755..becdbad53 100644 --- a/spec/lib/license_finder/license/definitions_spec.rb +++ b/spec/lib/license_finder/license/definitions_spec.rb @@ -223,3 +223,40 @@ expect(described_class.find_by_name('Zero-Clause BSD').url).to be end end + +describe LicenseFinder::License, 'Zlib' do + it 'should be recognized' do + expect(described_class.find_by_name('Zlib').url).to be + expect(described_class.find_by_name('zlib/libpng license').url).to be + expect(described_class.find_by_name('zlib License').url).to be + end + + it 'should match regardless of year or copyright holder names' do + license = <<-LICENSE +SOFTWARE NAME - Copyright (c) 1995-2017 - COPYRIGHT HOLDER NAME + +This software is provided 'as-is', without any express or +implied warranty. In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; + you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but + is not required. + +2. Altered source versions must be plainly marked as such, + and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any + source distribution. + LICENSE + + expect(described_class.find_by_name('Zlib')).to be_matches_text license + expect(described_class.find_by_name('Zlib')).not_to be_matches_text 'SOME OTHER LICENSE' + end +end diff --git a/spec/lib/license_finder/package_managers/go_modules_spec.rb b/spec/lib/license_finder/package_managers/go_modules_spec.rb index f1f16988d..92ea19e6c 100644 --- a/spec/lib/license_finder/package_managers/go_modules_spec.rb +++ b/spec/lib/license_finder/package_managers/go_modules_spec.rb @@ -10,7 +10,7 @@ module LicenseFinder let(:src_path) { '/workspace/code' } let(:mod_path) { "#{src_path}/go.mod" } let(:vendor_path) { "#{src_path}/vendor" } - let(:go_list_format) { '{{ if and (.DepOnly) (not .Standard) }}{{ $mod := (or .Module.Replace .Module) }}{{ $mod.Path }},{{ $mod.Version }},{{ or $mod.Dir .Dir }}{{ end }}' } + let(:go_list_format) { '{{ if and (.DepOnly) (.Module) (not .Standard) }}{{ $mod := (or .Module.Replace .Module) }}{{ $mod.Path }},{{ $mod.Version }},{{ or $mod.Dir .Dir }}{{ end }}' } let(:go_list_string) do "foo,,/workspace/code/\ngopkg.in/check.v1,v0.0.0-20161208181325-20d25e280405,"\ "/workspace/LicenseFinder/features/fixtures/go_modules/vendor/gopkg.in/check.v1\n"\ @@ -79,11 +79,11 @@ module LicenseFinder allow(SharedHelpers::Cmd).to receive(:run).with(go_list_cmd).and_return(['', 'some-error-message', failure_status]) end - it 'should print out the error from calling go list' do + it 'should print out the error from calling go list and raise' do expect(logger).to receive(:info).with(go_list_cmd, 'did not succeed.', color: :red) expect(logger).to receive(:info).with(go_list_cmd, "Getting the dependencies from go list failed \n\tsome-error-message", color: :red).ordered - subject.current_packages + expect { subject.current_packages }.to raise_error("Command '#{go_list_cmd}' failed to execute") end end end diff --git a/spec/lib/license_finder/package_managers/spm_spec.rb b/spec/lib/license_finder/package_managers/spm_spec.rb new file mode 100644 index 000000000..b74db3399 --- /dev/null +++ b/spec/lib/license_finder/package_managers/spm_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module LicenseFinder + describe Spm do + let(:project_path) { fixture_path('all_pms') } + let(:spm) { Spm.new(project_path: project_path) } + it_behaves_like 'a PackageManager' + + def stub_resolved(frameworks) + allow(IO).to receive(:read) + .with(project_path.join('.build', 'workspace-state.json')) + .and_return(frameworks) + end + + def stub_license_md(hash = {}) + hash.each_key do |key| + license_pattern = project_path.join('.build', 'checkouts', key, 'LICENSE*') + filename = project_path.join('.build', 'checkouts', key, 'LICENSE.md') + allow(IO).to receive(:read) + .with(filename) + .and_return(hash[key]) + + allow(File).to receive(:exist?) + .with(filename) + .and_return(true) + + allow(Dir).to receive(:glob) + .with(license_pattern, File::FNM_CASEFOLD) + .and_return([filename]) + end + end + + before do + allow(IO).to receive(:read).and_call_original + allow(File).to receive(:exist?).and_return(false) + allow(Dir).to receive(:glob).and_return([]) + end + + describe 'xcode packages' do + context 'when SPM_DERIVED_DATA is provided as a relative path' do + before do + allow(ENV).to receive(:[]).with('SPM_DERIVED_DATA').and_return('./.build/') + end + + it_behaves_like 'a PackageManager' + end + end + + describe 'current_packages' do + context 'when SPM already ran and workspace-state.json exists' do + before do + allow(File).to receive(:exist?).and_return(true) + end + + it 'lists all the current packages' do + expect(spm.current_packages.map { |p| [p.name, p.version] }).to eq [ + %w[URLSessionDecodable 0.1.0], + %w[Nimble 6956ffbde4ea6aab94fd2e823c5ede95072feef2], + %w[SnapshotTesting 1.8.2] + ] + end + + it 'passes the license text to the package' do + stub_license_md('URLSessionDecodable' => 'The MIT License') + + expect(spm.current_packages.first.licenses.map(&:name)).to eq ['MIT'] + end + + it 'handles packages with different id and name' do + stub_license_md('swift-snapshot-testing' => 'The MIT License') + + expect(spm.current_packages.last.licenses.map(&:name)).to eq ['MIT'] + end + + it 'handles no licenses' do + expect(spm.current_packages.first.licenses.map(&:name)).to eq ['unknown'] + end + end + + context 'when spm did not run yet' do + it 'raises an exception to explain the reason' do + expect do + spm.current_packages.first.licenses.map(&:name) + end.to raise_exception(Spm::SpmError) + end + end + end + end +end diff --git a/spec/lib/license_finder/package_spec.rb b/spec/lib/license_finder/package_spec.rb index 84a48c706..cf91a4217 100644 --- a/spec/lib/license_finder/package_spec.rb +++ b/spec/lib/license_finder/package_spec.rb @@ -36,7 +36,7 @@ module LicenseFinder it 'has defaults' do subject = described_class.new(nil, nil) expect(subject.name).to be_nil - expect(subject.version).to be_nil + expect(subject.version).to eq '' expect(subject.authors).to eq '' expect(subject.summary).to eq '' expect(subject.description).to eq '' diff --git a/spec/lib/license_finder/reports/merged_report_spec.rb b/spec/lib/license_finder/reports/merged_report_spec.rb index 2f2978dc1..d0c68d827 100644 --- a/spec/lib/license_finder/reports/merged_report_spec.rb +++ b/spec/lib/license_finder/reports/merged_report_spec.rb @@ -26,6 +26,17 @@ module LicenseFinder subject = described_class.new([dep], columns: %w[name licenses license_links]) expect(subject.to_s).to eq("gem_a,MIT,#{mit.url}\n") end + + it 'should not error if there are 2 packages with the same name and the version is nil' do + bar1 = Package.new('bar', nil, spec_licenses: ['MIT']) + bar2 = Package.new('bar', '2.0.0', spec_licenses: ['GPLv2']) + + merged_bar1 = MergedPackage.new(bar1, ['path/to/bar1']) + merged_bar2 = MergedPackage.new(bar2, ['path/to/bar2']) + + report = MergedReport.new([merged_bar1, merged_bar2]) + expect { report.to_s }.not_to raise_error + end end context 'when no groups are specified' do diff --git a/swift-all-keys.asc b/swift-all-keys.asc new file mode 100644 index 000000000..f1576741a --- /dev/null +++ b/swift-all-keys.asc @@ -0,0 +1,240 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2.0.14 (GNU/Linux) + +mQINBFZNNIwBEADNFSwCfUUyXZujWzQCqYLlvRwZkuMUcJQH4NRwC/an9KwilFrz +pf/+atZFSciMx5R9vOG2odWbwBYU7t8XEd/NYCgyTwMNxAFkuaN1E0b/3mOWwbT5 +91HziQNy0jDUlM28370pyv0rBTHvPbBQlo7BxJbwQXL3rs3KObAnhTG51aLztGds +OTxZDdpNY07dPGKJevP61l35yZI40tUmCKn/z4sLmX+DoUUplA35SMRnvQjd/pGF +9BTAFzLfsbN0caBixy++0ZIyK9ep1dezhqisUndE/rNk8KPNAM5zeFA1onzEEGFc +gk4iZ0N/S7dVb7N4Uhn8C/87E9yUkrzO0Kl+p2zumORiqIvkv9vtHMaKa6rBP/vI +iG02eiLmaWdDkg7afpJjnC6WrrJoBe/lbDAYrbD5eR4KGHdxMtAdh0sYZrZR8T4m +gI6alC8PjYY22wiNLeekATobDu5JVQna0525L57gpbLOhSjXk/69HHw/l7d/e6br +35rmMYGJd/9Q1w/4gMo8tX5m1WaoQxac4tZs8bBRKNP7YJkHmwfVG21h0KA91yFA +l1BMe9U+gomfqRoBCRXGDl1mHmyGqu59JjpYEP04Xu2dLCu8yt/HHYirMdsKIvHT +WKm6EvuQ4vFTQbPcdrMltL9yf8xG8EL0oDIbaSAnHI+sY+C30i0S557G3wARAQAB +tD9Td2lmdCBBdXRvbWF0aWMgU2lnbmluZyBLZXkgIzEgPHN3aWZ0LWluZnJhc3Ry +dWN0dXJlQHN3aWZ0Lm9yZz6JAj0EEwEKACcFAlZNNIwCGwMFCQPCZwAFCwkIBwMF +FQoJCAsFFgIDAQACHgECF4AACgkQ1EHJd0ErN63tFA/+N3ANSFFc2FcdimzisPOv +wBL8xSxQxUdsdELsht6WLh3buTunlIO36Ubo980hXyf6oxsfvMrDsn1eve28cpks +SXZxLpfQm96753mWICz1WJcoUv1EXpFKuqxb3OJXu6di11NQEtFNd9rM4x7MJQja +ICr5WXxzX0ObEUxY6xTjMlUyHkmR9ekEEPZUdrwpRFC847bV35vdCtMRztghecBH +tHMhTEbuYxpjBZ0hqIkLzDAiWenqca2P9YD2sgntTJFfZ3V/oi/REFxj5v5BmNQ2 +wCyppoWDyD+oWJyh5dwuOc+xlYboN5EtIkuhS7/ZuvQipZpeJD7w4pkPO7l2Twal +aHulJJV5f40gpIniullTC/I6P9HIp94wZ+UPm3/9M4AiO0oB0x4uk+buA7nCZ5Oi +BaZKwO8nMFQBEaNuqKz+xLeokum3Alcwx3drwi0TlBX+GafrFFHwhdwDGuQsWpGT +vNl6LEBNduPvs57382W13aiYT/p470M/RuTt6qpLyLVsWmGO5oh8mm/WWGDzza6Q +2NuUBcs2ZCECvRlEt//689elx7VDbJwE6Mw3V8bosGx8y4XcQYATFqxm7WflbBpo +5HR9AST2Noc6g4VPepZSgi0T4lRbIsyLml7kIvOQBre4i36br0VLIUairez1mqpm +sG3P11076R+g/BcL4hg9jXE= +=ONEK +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFZZS3oBEACbRmmzVwF/Zf/zGEZTGXWzeCjwmYcvCx/aqklIWRFC/PPRwZ53 +rNgeVpEHNu31duF3JCEX/7fL9rmjnW0C2mQCzkvKF+rZNO+jPL/0/fsjVfnSXx+g +m9yVyPmR0XvR0Kk5PqjR8XH/syNblnCo16fr7YK/JMrGM9E/d+UomomXKALbgebX +oO0c8eA2Vij4b2BAFr+L54MTzquKqyEru9Ty2PTcIBxcEYqko8i3Ttej6WVMKphW +J9QNaeVF9mfHuA7zpFlwtfFOrUnA2Qh6IwT+w7o941q4TpzYNVlcR3IV2P51CZrG +zQAGRvWWmg5yswhyTEJpt0i4eLoqeJrcNZRT8FQjmcXgtdPvSFkJPi39e5LrT23M +/W5tHvnldFDd4RYQmoO3XcIJzyEe6yXNrcaTT386m2e+cjA0SwZ7l6kHVhMikaS3 +FWccQ9VgWqLHZPd0f9oKdXbMMoIZs0D3F4hoz4oE6xkntEhw3Z3cVrKGpYuhJOKQ +yPoCaNEpnT9qaTU8BmG/ToUWXZDuhm3FOAOEOOtFFChfy/0+9HnS6Q2aogYuD++o +6XNSFCwONIRnoHyIRjCA8ce/GNsxpBp7bjUDGLoQJzmki4O/lyxMxS5k0Z4mjLS+ +xZo6OLt8LW5/w3YJUEU6PM88GhA1c3dm+U4mV3oW0QL0WG0b1tSxhLHf+wARAQAB +tD5Td2lmdCAyLjIgUmVsZWFzZSBTaWduaW5nIEtleSA8c3dpZnQtaW5mcmFzdHJ1 +Y3R1cmVAc3dpZnQub3JnPokCPQQTAQoAJwUCVllLegIbAwUJA8JnAAULCQgHAwUV +CgkICwUWAgMBAAIeAQIXgAAKCRCfWX9NIaVtX/L3D/9NJVFh9tBYgzwtbhgHTVxq +U3VKeKUIvvWxETtaYmKsL8BPBwVM2IHDPyiOGczxe7RbpNszoIY/EBKNsI7yOnon +qrMebQVpzV0wK2WwfHKnXYXXFeWADapwBGEcST7WNwX+la/oJOQtBZpMbBHDia4C +g7EENIdeWDRHxzl785PSlAOERvaKsPlFk3bolQl/Vhy5YZIe4Mt9KN5LZ2E+xz3Q +DDplOTi3I2df9Maf9UOxrS7zk2NvHw+5YeqNs7cqp++mjIRQ7/M+PGPx7PZMttBk +Y7sIn1m3uSZkK2nEWZ91b3pZdr3Sff2Ig1H+ConUqy9w0CqGSqCmY/6QP05J4kpv +ga1uNmKRJdv/pGHfTaoZ+dubm4+UzGpfzHdEhvmm4gC/+kU+nq5BuhvzO7iWKk0o +kZOKT642WSsp7TYKp96sMh2xeUbYl/eAHi8nQfGuYeo6n1NaUMtwb8jYusTuISuW +yWRSKaUanjjKGkl9LTpODWLkXHmqGJhp9l61AQ3fKDQgXuKuglWBfsZOhDRl+oYr +Q22LzpSd4ISG/gXN3X4ADeuWJpe8d+nvpc6KtuShgRT+KeqrE9BcXfDdEk56I0Xx ++eGb1v4f4nibyiCvw/mBDCRMIqOsc/jyqA9lydLMxv/Us7KNYqh/Dzx7obfRUE6L +2S6Hfx8PaYq16WXdxd/PBg== +=5r2z +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFdM2VsBEAC63xLZsLc7HBbJ1V7ak8dGNSDs2ibcaMKAuWU916UWjFX+KlZE +8ruOVTol1KvA0cac6CX+Fd5SMirpxAaEUbYMwVEMzxOG7NhBp0wAJiO3LWsVoL4z +G7wIpdPzjxP32qUABpF0Z8uo5zhQL6uaWtmKIhlBlzUmhbxYFzGUuCO8Y94hCH5A +9l5Oedin3D3/v2T60/B2rygmQ1fC372Peg6m0dR8PCp42Jm+VXjgF4yfGyP4MJPO +c1gq8AWNQ4Ex1qhQ9E/Mhbn88LvUQ+OO5u3iNw7AflBuvLaLy7VbD+pGpoGv3EdJ +LsDJg3dU4F9Ii2hLAD5Sushtjx6DwwFJvKFsm7QzG7OTQB0rC9Gtw/MpopafFI0e +Wo/6xmXnw9GQQ6uS8RoVri6Lfp4IpXdLZ3CMxBeJulmUpBdIFK+/SnyMfYW88tzx +DbEFpXa6SaksSXjAXuXhUu+h37YdIfWcbE0KUXH848RcRPnTUOBwsqE2UzLQjF3A +aPV7YIQxNNp0mUsRE+9R6yGhSog77jsu5XusswYEOfSU9p/bkQd5kJ5acTyHDU8A ++q/+O2T6aZqIFOhgXFiRj2TDGlqj4K+LUycpMWzPgn+KWKNnWgbJH1u3xXPnOZjT +0i1Vm2umWd7OxzThSvlPGlvRLwPKWhdeFL37M3Bg3og1P5TgP7j4HT6Q2QARAQAB +tD5Td2lmdCAzLnggUmVsZWFzZSBTaWduaW5nIEtleSA8c3dpZnQtaW5mcmFzdHJ1 +Y3R1cmVAc3dpZnQub3JnPokCPQQTAQoAJwUCV0zZWwIbAwUJA8JnAAULCQgHAwUV +CgkICwUWAgMBAAIeAQIXgAAKCRBjvBz+kdMGxjBhD/9RUFlNP0cG6RJPl51mPCtO +YMW/y5ssYyWi6u8q44Ccqit4EKjXUaeha4KSWQS9c+7q8bbfxbtl6AEsI+fxx3RA +mYMjXfPnpjxqJLvwXOzAmG8bZzycdKiC1XBINR3e/Jw4Zu/yj/GtWSG2+OogFfNH +n5Gg43B8D/fdqQkvlFXVuM0kMKPmAc+S1ikBvprpyjya47EKx5rWSf7pl/gm4jBE +dBrWdpaBicOQmuv+Vr59rj2/UCWdD/dZRtwB66qn/AIZIgEOLyW2s/Mu9VK5zoNs +oUZhRpe9t3eH59KbKvU+JdADMhBnUe3r4/HkSbHmPoML56g8RQV4T3LZa2sMt9Z3 +UC7WFr3VfPxXHpI9BhY8HJvaB4ML6PvYuXHP7TYkrSQ9FMoxjHcwEE7cga8BzAHu +MxCuGrPryHRyApdFOh6oTts8/bjn+sb0VVvnAjFsAjDcBF18s5aI58eGLzLBgRnZ +jyGvZ20h+I0JE3vDydi3rVEBm/fgkUwTtBuEG/mIwIea9QEcMkv5dq1aYheav1NZ +Amqr2BsSl8PufUdfGgMKjMIR6Sv40mhTCUFSL1TG8OsaOecK7XXEjcpaQjCg1lqj +hL04ayrZehJzuiP30htRdbrFraDr/WQOPW3VqE0DtjVfaH7AARKLp/HRDOd5hiUU +RBrjB1unJxVpnKg4t0El0g== +=ssEv +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFlAigEBEACbGsfWe5nCOFrYSpz6oPh9ihq0RZQlktSz7E3Ou0cwSNqOnGRO +s6z5pyPYlJgpvvaxsKcBS6R4DFq4/30Aut4Jj4T2qa0BXZqqrHrXyM8ucmpPpF0T +jcRJAQZg/Rj8AhelYtcNKml/j8b9n8p14K+u+Zp0SudaNxLdxIbr94eEEDd/ecH/ +caGspqPuJjBanQNTzV9ws8ft7NLCdWnSvh5aiZrsWIrFfFxQm8zh9kCim2e3QX+p +wufMDIKH/uEaOlinVCmFJNXGqpeyRjrile+r6Bd8fJiFLFtUeIiCdyxD4nrV+iLx +ZAc6boVeSXdawPpX/eyXeH7NhpEofFz02W1EAoNFKgzdoEi6CBaA9P9Jrk/wRZuH +vR+Mzbuu5aXDwDCjhtFu2Mm3bmN5pt3xuYCYoCgOp1wEld+szMfSjQNNJfUkFwzU +eaHuaofXHQxuQ/uIEh8cr8/GEYtqGOU82T1raj3Sv1GcUfj5RO9k2rI7hWro8Hzc +oXjLTDfABSZJrsuDLrzMUDOSKb+R3rf7uDYf/bTWkpLwSvrJRQaMXkQm70WWBFF0 +A75HxFBi8/9f35azYlFfCi0cEHAAHm0wdxU/D9b6TV/9zDedbgFl+/8xMBQiOX1r +WfQrXA7zONhZpc+to5tyzrV57+Q4buKQUibyYut/7Gl1FbVaD4LvAUpbqwARAQAB +tD5Td2lmdCA0LnggUmVsZWFzZSBTaWduaW5nIEtleSA8c3dpZnQtaW5mcmFzdHJ1 +Y3R1cmVAc3dpZnQub3JnPokCPQQTAQoAJwUCWUCKAQIbAwUJA8JnAAULCQgHAwUV +CgkICwUWAgMBAAIeAQIXgAAKCRDvVDDwceGyNQE4D/4jyvkwjMzp3KSZKTMvcalT +JPmc4rkpqc1b3VJCTpG/QRK9faJAbUVBz+iJBRIA7OjsjW9nS+oEkschjs7mjdiA +U3XxtJMsZSJiO6TRdstJg0i2jT1yhQ7zuf8xeFEpx0Ekya05mYHiz3LdS2TZJcZZ +D713M1GFsb+bDRGk18nVvlUIDB+9jIoqLgXf3muzHUmF0VF1HHfjB5zHfd9h6P+n +DG5e/qA1MA6jaoGcHK31qoqvrLvoDMYlo6Nirq5Gsjf8rNTwij2GM1tNP8BIn2Y2 +5rgQ+UbZ3dUE+2KgUg+cqNtZPe0szHOInUew/Kq4ogRWgSWw/NbR+W5J5Ro9pCFJ +pJaLBhCDQ35CIfcp5Mn2qSr0UW+7U4wfsoI4Oui5/dRx4iXFLD2LRlQuLTe0vRDK +N6PcSeybtCyxeXXopWZvdNvO9oMoJ6vrorZciF+tcG9qaqaaf/Y1h7pfwiYkLvwp +4RC/FFURZyjeyk8gfxaE1NfcnXM3rByW8br/gtL1VR/s3igKXk/rVeywlMeXyeXP +CxS1uflC2dxGfzFiNFJoQlS+0XYV4nYNTVALe4/css3LLFtLt9rBKe3U7JmXl52k +zyUq5nMsltYp3kg3dRYHejwzyrlVGt0Twos/Zqf8NztD9LPiVsZP/f84AobFsmkN +ex1agHRY0JDH1IrwmfVwrw== +=nP+X +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFoBAhsBEACtaQbqQhwMedruhTrierPCHRy6ZmO4HuRdrd9LnOBSraQnHFb9 +kc2IfuHwB6YytZg6VFy4iaFx6vOkZQk7bFiAMB5RwIdPm7OmHGAYWkKeDOCB3lJB +gJ0YdP7bMOGJL21RRZfUmkTMaT4zP3J0NeUL/mt4ezdJQyyWxdYigN2AgglAxZve +27K+IjlQVvuAe5MWqPn4pTktjWxuDkIN2BvdWXz5Ycbewul7Hi9dZzaLIVDeTq2F +MUjR9FBJoNpJKjPTAeOyXj2jvHR753yAeRG/79sfeId9568XNxTi/2OXqdkvwg/q +huGbx0fgkqnkMw/0nvqDVsbnHXMdE0WNVluemt6I9hJvoHfOEBYYAZi4+spoVCJW +xwwgVt8tNTgbynejzr09d1RgVJH/OKcKCCOTtbbjUdtMt3MMbaBaN10AKKFWWBbR +Tom8J6wwM8LsKkFj0F5uzkG928C/vx5hFv3ZltlFUu9j8u4WU2kF1D1nzhw4E9+S +epq4jrWMa5Dvr9UhnkJCW0g/0vPnynbOcJaXyWQtqxdgAT9LxMBeMSd26O9UklaH +INivaw8l2SpdauJrYon0uhK8RTTp2rSt+SLHqgxsbmGmr5jTm1Z37EzffgoIFDOB +a+3zHaLJvkeZQXzbLAqzfHYSV9lUe7In+F9tqZ5l7702UqJmIWoGb6IGqwARAQAB +tD9Td2lmdCBBdXRvbWF0aWMgU2lnbmluZyBLZXkgIzIgPHN3aWZ0LWluZnJhc3Ry +dWN0dXJlQHN3aWZ0Lm9yZz6JAj0EEwEKACcFAloBAhsCGwMFCQPCZwAFCwkIBwMF +FQoJCAsFFgIDAQACHgECF4AACgkQdjjx+ysrCMSihxAApw7qKGfiN8a4bFVwKRdY +7BLYu9HLetKHUJPerypKEKfUshU0X8Ns3lBxjSqNY2TvLyun1kbtUsxy+qi/X+HM +nb93ONYOAeny1DFxUdiehhVttTQQB3RXdmhHGytoxzHX62N/uo9tYW9xQ+gaK+QC +Rt6nZN0Prm6X+4vpQGD7H7O9cN8pw4YuoJRDwn6bFPl7WgqrYWX8/jvbXH1NjbJi +j6kXvDSzwtPQNsj7vTC5K4TBMgfBFw5kndu+1sEHAfPIbwK8w/EGr32eSRT6l3xk +TYFquL0S5NGCi9/pIqT90knsDqXyNer4xmwQ5FJPUTpdUbFz0o/DQvIFrbJ465Q4 +feibfhpxVqLCpX8goosAS5nd0jRGicSYmoxdtUBUp4gn3ee3yI6JbA0TN8haczNC +e0FfQD9s9O+m6ft2/CzIx7YFeiIRhfcjfmGJ0+eAdpEsDqC5Hyc60hwHhPg222Ah +ASkhwDD9NejskxvdMUPALk2C41ZlR1dYqfXwUJJDPwNY210QQgBZ0+GG72ErYssh +4sG5U9XRv4IZhFlgBVmMlfbSi71BawOy+IN7ii49m9AY7p6U+m7yoeZV2H8YJbTk +KhfnS3rZqsSU+ZYJYkGOxnQetkKUDGaVKdwJE49ll5R1/4om0h8aOOU8VyzrXPc9 +cZMQ1BcfMNkeu81HaxE0Lpc= +=l0K5 +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFyUJhwBEADDBN6jVDScsgzfQDxSQga0Og4O3N/nIquwi/DGOgsAsTMWcVA1 +gmRJb792vWa9CmnHILl3ap8zObDSbcXg90nx+eCzWJjG+Ud5c7xuam96NR8ntKmU +8+BbH/dp8zc9WJB37TiWcaLsddrn58zfm7Ml6P9M48WAeRJX8nxVBTw1SjXJurW0 +Ab8LOgfb60I09Skq72Ud7HaYJOG03iNTf6qLlF977OQsHCb21BnKSAmJqapUS96/ +VOngz7TBzYz4rntfetb8hg28WtUl1s5BQzWaFunkP03b8mPh3PL1SZxutwViVWBu +hf9kJtx/MLb79fnuJEOus1FvDqJdpd83H+XmXMIDYWgcBIBVrLT5HDtRerjF178H +okb1F+gboGIqhnx25xTPIYctSHPgJRScZp4WKrqQLKswAmSL4YJXnkXSff05l4gE +WXQpEMLBZa46qmu8lj8HfoZSbP9lfvEtZ6A+Q3sfh3gjYPv7e6n37x22tSgvyzCb +jHU1pA2rv10AHK7EIeEQElN+zCyAbmKuhPBiCyxDFg5Dx3xNkYwg9szBJ0KleVD4 ++Y3PZJ4N+u+SSATYnHGtmzvkhiNtqJCCwuaqY+jjVObzzqvLLtGjtjxNUWi2X4He +q+r2fubjCOW14UnQ06qfr3mVUSmuLSKs8BD8qTGuqlgcenGsY0bk0qUPcwARAQAB +tD5Td2lmdCA1LnggUmVsZWFzZSBTaWduaW5nIEtleSA8c3dpZnQtaW5mcmFzdHJ1 +Y3R1cmVAc3dpZnQub3JnPokCPQQTAQoAJwUCXJQmHAIbAwUJA8JnAAULCQgHAwUV +CgkICwUWAgMBAAIeAQIXgAAKCRCSXMHM7T0VYSm3EACUtuxMfTmGexA2xBvGhKQ+ +bi3kRMBj+9BoSkVV11gDvumurldXRoKsIIxEdrD4xjagSNiVd7zj3L9wANu6+YgP +HgBq8sUMXwsyXdL16jNC28qVqKF4jZxwmI9t6nVpQOSxGfBxdXmIhTxDDXFo4JMT +uKx67BaP1GzZ+BKvxm09xChIJ5xh7x8/bsZ7HlXqc++ARn0Lh9d5CISUKE1PPnMN +KyRUNt4X/qj7zJHDoAihufPKodvvXPsVAIpfCJZm7XB5WvP63IvQaFW/cX5wineR +FZY/BKS+tsJt45jJ9c/Ofzqrr//stCz4a44fYvpqaxIqTLr4CpNH3WBIM0RBQqJj +35jJvdrNIuueby7GN3afRcvZIQBkbMkwZrW1iT8eah3EQDiXszbi7GkbXuOxQOn/ +n8KJJvTJU7wAyH4WLvvWLFUm0oyHjIW4K9vSUM+x74WRuKxVikM6FAJ7uXsYtZ3Z +lgL2hNlbMwpB+wxvA9isT3dSyzAtZmXpomOLuQTETtm4tSpt8XnF3eCsAKHSWqrA +/zknqizKeBTPprqibixfO3dwUzFhUu3tkhxznIN3eEUrDMPxNrxObqJf1I4ySUYe +vc6fqBG86SJ2yjN3IMJm5Y/z58QQUmSOweL9xQSr1hdcCxBtmLSjAlVzJMkpQ/TW +xXPfHp4mZnRMLsGyO3eYrA== +=l2j3 +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBF3DedcBEADmr11PU7BBVSEjSx3z2bqrktMURD2AnrXJgL+sAeZfMaZpDZCU +pmLKtI32Yp5X3Mv3mlj2Mz3VdtRh7ni83E5cqTLXs4Yjsujgu6nWS5qWqmSYw0Ax +RszPPJISf2MAbZyBQJpaoDK9t2eIh3yG6J9XhIMu/ZpDND8afNYGNRhXKetv6+l7 +MHTYd5rlU94eIXcxLA/yV340BVTMoaiIfAwUF2uFubDYfro4Ckj+xzjGIvn3ueu0 +u+Yfmc40YIuCsA+zgf24Tie/rIwifEzWxFCka2jNPU+q8XIrbPmTKGvxnpwjBKIz +B5HtB+yyV5mhnaVg1kkNZH/rZxJQm/FdFS9IXmOmV5CFn+R8grTxsYP/A4jDHnmz +wx/gJuUvt/ADt3cuU7sGLtT1cvH1UlS/fso2bDDHBhuEa/9hjD0uIYpLuR4eTUQs +WNU9a2WFxprNfM7nbUIWqfTr6tdnQORPnDi8lSGePlJNWQHzg36WyxPzq9EG0S5r +35Gb5NXeok6CN9x0EUiRt/Fl0POQRJ2Y6TWmOzaZ+8B8HExhA02d86Wnzfss8kAB +eLSHE6aOguwujep9LFrTxUeMZTIkk/kxc0ibHuq0r1jwRWnCx9TwXCFeAW1gs7eo +xybF/HVuDqSLaRIgpIl2KFxvXRY6g37ggtcAxV5DlH6oK90ja+72Wre8+wARAQAB +tD9Td2lmdCBBdXRvbWF0aWMgU2lnbmluZyBLZXkgIzMgPHN3aWZ0LWluZnJhc3Ry +dWN0dXJlQHN3aWZ0Lm9yZz6JAj0EEwEKACcFAl3DedcCGwMFCQPCZwAFCwkIBwMF +FQoJCAsFFgIDAQACHgECF4AACgkQ+vaYnhvBb+oGHQ//ViB09Rk7W4BxyuvwDZx8 +w7JR/WfCFDYAz210EILu2o3tqHWpfShynA2V/oOFT+VHykr2TK7wT2n/o9axfIq6 +rF8W+X2VnXzwzN3a04iVtakcSeCuH4KIiT7vIjfOtmEwqJlpAIe6LF0NXb5wV9/I +mxc7xLKbTCBSjrNqQsCOZ6VbLqwQuOjI0afRjnUsI07u/8bo/8WeZS1roKy0ntVI +wyw05P5cV64pjHwNXH5CN8nB/G4pjCRwehI+4hAm0SOUfIDyLdQ4IThAJY+FVzIQ +dXSKMyVNQMe/PuwLB3CBNMPOtxsEwOdgrLwClm2jOOS/bjrBI5jerUgCSBDPerEP +3rb6hjehuvhYudYZ+awmWG2NRxdWqUBO3twudVci4keQjjaBM+XiaWpEn9jD3XkI +ZZC++bXx0o6ugDGFNTNwpFZ9hCC0x8djDrJDz1OdSYA4Mn4bB1n/8FI3+wSk8AP2 +YzfjXbdXQMe44FUWPJgcCJ8EEsF9eKtw5019288ECIlfaHIzw+tgeg7iTShhB7wi +8T6PFJvRp8L4hssDfzMS9ZG93InUGL+6eyasIVXJtPDmCCP4LHMN3XYH0TgqBriD +A6MXJzpykPpl51JkXNRtCWtUVBJpDN3L6Ue4Ouxqs0amMBJCNXM/6t+aJfrgwqiq +bDzlXsGoKE1i/038Ag41vE0= +=kzvP +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +mQINBFyUJhwBEADDBN6jVDScsgzfQDxSQga0Og4O3N/nIquwi/DGOgsAsTMWcVA1 +gmRJb792vWa9CmnHILl3ap8zObDSbcXg90nx+eCzWJjG+Ud5c7xuam96NR8ntKmU +8+BbH/dp8zc9WJB37TiWcaLsddrn58zfm7Ml6P9M48WAeRJX8nxVBTw1SjXJurW0 +Ab8LOgfb60I09Skq72Ud7HaYJOG03iNTf6qLlF977OQsHCb21BnKSAmJqapUS96/ +VOngz7TBzYz4rntfetb8hg28WtUl1s5BQzWaFunkP03b8mPh3PL1SZxutwViVWBu +hf9kJtx/MLb79fnuJEOus1FvDqJdpd83H+XmXMIDYWgcBIBVrLT5HDtRerjF178H +okb1F+gboGIqhnx25xTPIYctSHPgJRScZp4WKrqQLKswAmSL4YJXnkXSff05l4gE +WXQpEMLBZa46qmu8lj8HfoZSbP9lfvEtZ6A+Q3sfh3gjYPv7e6n37x22tSgvyzCb +jHU1pA2rv10AHK7EIeEQElN+zCyAbmKuhPBiCyxDFg5Dx3xNkYwg9szBJ0KleVD4 ++Y3PZJ4N+u+SSATYnHGtmzvkhiNtqJCCwuaqY+jjVObzzqvLLtGjtjxNUWi2X4He +q+r2fubjCOW14UnQ06qfr3mVUSmuLSKs8BD8qTGuqlgcenGsY0bk0qUPcwARAQAB +tD5Td2lmdCA1LnggUmVsZWFzZSBTaWduaW5nIEtleSA8c3dpZnQtaW5mcmFzdHJ1 +Y3R1cmVAc3dpZnQub3JnPokCPQQTAQoAJwIbAwULCQgHAwUVCgkICwUWAgMBAAIe +AQIXgAUCYFlw2wUJB4exvwAKCRCSXMHM7T0VYcDjD/9gX5EXlPJU1QgFw4PwRUDj +9GxJ7DQ8ocgZT3mtt03gpRj7yTOL0iBDsJ4GtDiPcq6xCm7WEmNYBs/cJWVGwBC2 +jeQCUCkxCXR310V3/RRFMhKZZPBq9A1UqqooO0WYkpR6JFP2a5cf2kounuyBoVGX +wV7Cn4ZqX/fLOt156EiWUtOxC5L/nJqn6Ea57arwVUwZ3Q2tcKthxFrmMD9H13oW +Y4coWiQhVx0xCMbc4cPh7TeRIdUu7D2JJPg/ypqhE2fIPPYrCAJSWp9A/m5oaLM+ +6/ABV+JYw3sgmnCSMxX+EV5iDsRamWMJwO4Yep8qMBD61jID5zn0N+VSLdEVfX+5 +O4tdHMvC/YenJ0SFlWmZ7VGD99497IlsTMBFuOzBwXsfwdbSmG5KB6uKormLB6KB +INvASgi7HbcYgM6vh82mvpswp5UmrT8f2trnSaxHUz5gtUiYFKdBrLgbLBhMlUTZ +giRHJqm7DnYJVUAOOj26uu06gcXu77MzEiBT3PGVNPMgfYhc5I6hnx4nrXDYT5IT +bP9Gt1BpsaG8J2jZL2OyOdkd7FkAuWzJVmozDIhMcsf5AjxrZaSYKc2BwgevdIB/ +nvrD/IHRzS34EDnqv2J2LsQqWI3PM7JC5fW9L69NaSWdWpIoHl4gPcm55wbF+tHP +SnioHPOvgHUi1wc55I6QNA== +=z7kI +-----END PGP PUBLIC KEY BLOCK-----