From 0c1d2e9a3bf140129d37d349c2334d42511504b0 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Thu, 14 Nov 2019 12:15:20 -0800 Subject: [PATCH 01/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c73846e..dce17fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.3.0 (2019-11-14) ##### Enhancements From f4aa8bd2ad2dbdde4ebc4d653dbf968f445a45e7 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Wed, 15 Apr 2020 17:16:50 -0700 Subject: [PATCH 02/55] Remove os: osx from travis config to allow tests to run on linux --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b06d82c..7d35265 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ dist: trusty -os: - - osx - language: ruby branches: From 989478342c483c427c9bfe791736df22a446805a Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Wed, 15 Apr 2020 17:51:17 -0700 Subject: [PATCH 03/55] remove references to homebrew, install bzr with apt-install --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d35265..9cbd433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,10 +21,9 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi + - sudo apt-get install bzr install: - - brew update - - brew install bzr - bundle install - git config --global user.name 'CI' - git config --global user.email 'CI@example.com' From aa1dcaaa28d1f16b930be46cda0df31e38ff052a Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Wed, 6 May 2020 21:44:17 -0700 Subject: [PATCH 04/55] revert linux changes. try installing openssl and setting openssl directory before install --- .travis.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9cbd433..460f5ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ -dist: trusty +os: + - osx language: ruby @@ -21,9 +22,15 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - - sudo apt-get install bzr + - rvm pkg install openssl + - rvm remove 2.0.0 + - rvm remove 2.3.4 + - rvm install 2.0.0 --with-openssl-dir=$HOME/.rvm/usr --binary --fuzzy + - rvm install 2.3.4 --with-openssl-dir=$HOME/.rvm/usr --binary --fuzzy install: + - brew update + - brew install bzr - bundle install - git config --global user.name 'CI' - git config --global user.email 'CI@example.com' From 97c430f8e23e6dbd7f5dd77a268e0f6338785081 Mon Sep 17 00:00:00 2001 From: Oleksii Khomchenko Date: Thu, 14 May 2020 15:44:36 -0700 Subject: [PATCH 05/55] add test that shows that gzip content might exist under bzip2 Real life example: https://github.com/CocoaPods/Specs/blob/master/Specs/d/d/a/BLink2/1.29/BLink2.podspec.json#L15 $ file tar-blink.tar.bz2 tar-blink.tar.bz2: gzip compressed data, last modified: Fri May 8 00:02:03 2020, from Unix --- .../remote_file/lib-actually-gzipped.tar.bz2 | Bin 0 -> 409 bytes spec/remote_file_spec.rb | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 spec/fixtures/remote_file/lib-actually-gzipped.tar.bz2 diff --git a/spec/fixtures/remote_file/lib-actually-gzipped.tar.bz2 b/spec/fixtures/remote_file/lib-actually-gzipped.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..11ab702bcfaff96ee5857049e505a45e027ecfe6 GIT binary patch literal 409 zcmV;K0cQRmiwFQkz`b4o1MSwmPQx$|0N_Y$c?2e}Ea};?9cRdZU?>tol_&#(CD0VC zrm09$6p5wpz-#ePT%don>QF&IrS~bSqjMU&Io+k5xN0YHFUeb}y>prY0D)4>2|g@F zm=Y2g7k=P#$+hqqa4l8fu&SZ`d@(M{ILECFMrkrAtlf0o@kRIcbTo`@#;%GaFIMM2 ze+rwAJAxbbV0#fap$6p#U|fuh>xdFYN=%D#ePH*c9VQqvp$vVz($flr5mJeIHeQ}J zLVJONz;HZVOy}iE`|SMM>m|<-*O+)AU@2^~Q6!WOG=N{PJq>56%?6S0{`K?u@387y zO|LPl{|TPo93H*a^gb+dqA%qY@2qA Date: Thu, 14 May 2020 15:44:41 -0700 Subject: [PATCH 06/55] rely on tar compression auto-detection instead of specifying -j, -z Most known implementations will auto detect compression for plain files: * GNU tar: https://www.gnu.org/software/tar/manual/html_node/gzip.html#SEC135 > The only case when you have to specify a decompression option while reading the archive is when reading from a pipe or from a tape drive that does not support random access * BSD tar (libarchive): https://www.freebsd.org/cgi/man.cgi?tar(1) > implementation recognizes XZ compression automatically > implementation recognizes bzip2 compression automatically * Schily tar: http://cdrtools.sourceforge.net/private/man/star/star.1.html > will auto detect compression --- lib/cocoapods-downloader/remote_file.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/cocoapods-downloader/remote_file.rb b/lib/cocoapods-downloader/remote_file.rb index 2721cc3..3388342 100644 --- a/lib/cocoapods-downloader/remote_file.rb +++ b/lib/cocoapods-downloader/remote_file.rb @@ -93,13 +93,7 @@ def extract_with_type(full_filename, type = :zip) case type when :zip unzip! unpack_from, '-d', unpack_to - when :tgz - tar! 'xfz', unpack_from, '-C', unpack_to - when :tar - tar! 'xf', unpack_from, '-C', unpack_to - when :tbz - tar! 'xfj', unpack_from, '-C', unpack_to - when :txz + when :tar, :tgz, :tbz, :txz tar! 'xf', unpack_from, '-C', unpack_to when :dmg extract_dmg(unpack_from, unpack_to) From 431003c95d1595a9df677837cdc019e403758130 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 11:21:13 -0700 Subject: [PATCH 07/55] run ruby 2.0.0 and 2.3.4 on older macos image --- .travis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 460f5ef..6dec2bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,21 +12,21 @@ addons: code_climate: repo_token: 4d2c1cec2a5ba5fd0cd09aa76d1bcb52854e12ace21660dbf65a36a59ba7a973 -rvm: - - 2.0.0 - - 2.3.4 - - 2.5.0 - - 2.6.2 +jobs: + include: + - rvm: 2.0.0 + osx_image: xcode7.3 + - rvm: 2.3.4 + osx_image: xcode7.3 + - rvm: 2.5.0 + osx_image: xcode11.5 + - rvm: 2.6.2 + osx_image: xcode11.5 before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - - rvm pkg install openssl - - rvm remove 2.0.0 - - rvm remove 2.3.4 - - rvm install 2.0.0 --with-openssl-dir=$HOME/.rvm/usr --binary --fuzzy - - rvm install 2.3.4 --with-openssl-dir=$HOME/.rvm/usr --binary --fuzzy install: - brew update From de83a48401fe69ec2323d94b945391a3bf7bab13 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 11:33:32 -0700 Subject: [PATCH 08/55] try macos sierra --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6dec2bd..f914494 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,9 @@ addons: jobs: include: - rvm: 2.0.0 - osx_image: xcode7.3 + osx_image: xcode9 - rvm: 2.3.4 - osx_image: xcode7.3 + osx_image: xcode9 - rvm: 2.5.0 osx_image: xcode11.5 - rvm: 2.6.2 From 7289391d76bfa68ba1ffbe4fdb8f457288750078 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 12:04:40 -0700 Subject: [PATCH 09/55] add xcode command line tools for SVN command --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f914494..448a91a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi + - sudo xcode-select --install install: - brew update From 3bedecf2c2e31f29844ad2d8962580f3b7ad8bde Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 12:24:42 -0700 Subject: [PATCH 10/55] try brew svn --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 448a91a..102770d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,11 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - - sudo xcode-select --install install: - brew update - brew install bzr + - brew install svn - bundle install - git config --global user.name 'CI' - git config --global user.email 'CI@example.com' From 79c51a352988b644812618ff5d43c7a345c5761a Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 12:54:47 -0700 Subject: [PATCH 11/55] complete install of xcode tools --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 102770d..3350bb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,12 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi + - sudo rm -rf /Library/Developer/CommandLineTools + - sudo xcode-select --install install: - brew update - brew install bzr - - brew install svn - bundle install - git config --global user.name 'CI' - git config --global user.email 'CI@example.com' From 194f3342bb63a4722b7cf2e41bfc9aa25510eb81 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 13:47:15 -0700 Subject: [PATCH 12/55] run xcode-select install on catalina agents only --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3350bb0..0454fe1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,10 @@ jobs: include: - rvm: 2.0.0 osx_image: xcode9 + env: SKIP_XCODE_SELECT=1 - rvm: 2.3.4 osx_image: xcode9 + env: SKIP_XCODE_SELECT=1 - rvm: 2.5.0 osx_image: xcode11.5 - rvm: 2.6.2 @@ -27,8 +29,7 @@ before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - - sudo rm -rf /Library/Developer/CommandLineTools - - sudo xcode-select --install + - "[ -z $SKIP_XCODE_SELECT ] && sudo rm -rf /Library/Developer/CommandLineTools && sudo xcode-select --install" install: - brew update From 6cde3c5c382dc1ed05b52df59af9cd7fbb33c3fe Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Thu, 4 Jun 2020 14:05:49 -0700 Subject: [PATCH 13/55] conditionally install svn through homebrew --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0454fe1..fee04d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,24 +16,24 @@ jobs: include: - rvm: 2.0.0 osx_image: xcode9 - env: SKIP_XCODE_SELECT=1 - rvm: 2.3.4 osx_image: xcode9 - env: SKIP_XCODE_SELECT=1 - rvm: 2.5.0 osx_image: xcode11.5 + env: INSTALL_SVN=1 - rvm: 2.6.2 osx_image: xcode11.5 + env: INSTALL_SVN=1 before_install: # There is a bug in travis. When using system ruby, bundler is not # installed and causes the default install action to fail. - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - - "[ -z $SKIP_XCODE_SELECT ] && sudo rm -rf /Library/Developer/CommandLineTools && sudo xcode-select --install" install: - brew update - brew install bzr + - if [ "$INSTALL_SVN" = "1" ]; then brew install svn; fi - bundle install - git config --global user.name 'CI' - git config --global user.email 'CI@example.com' From 8179b8ff68a400bd497dd5e21aa0b45d1674240e Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Mon, 6 Apr 2020 21:51:05 -0700 Subject: [PATCH 14/55] [Http] add default User-Agent string to cURL requests, unless one is provided by :headers --- CHANGELOG.md | 4 +- lib/cocoapods-downloader/base.rb | 12 ++++++ lib/cocoapods-downloader/http.rb | 12 ++++++ spec/base_spec.rb | 14 ++++++ spec/http_spec.rb | 74 ++++++++++++++++++++++++++++++++ spec/remote_file_spec.rb | 1 + 6 files changed, 116 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dce17fb..5bb7afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ##### Enhancements -* None. +* Add User-Agent to `cURL` requests when downloading source via the `:http` download strategy, unless one was provided by the `:headers` option. + [Sean Reinhardt](https://github.com/seanreinhardtapps) + [#9619](https://github.com/CocoaPods/CocoaPods/issues/9619) ##### Bug Fixes diff --git a/lib/cocoapods-downloader/base.rb b/lib/cocoapods-downloader/base.rb index 13d4ad5..8e2d38a 100644 --- a/lib/cocoapods-downloader/base.rb +++ b/lib/cocoapods-downloader/base.rb @@ -121,6 +121,18 @@ def checkout_options raise 'Abstract method' end + # Returns a User-Agent string that itentifies http network requests as + # originating from CocoaPods. + # Contains version numbers from the CocoaPods Gem and the cocoapods-downloader Gem. + # + # @param [module] base_module The Base CocoaPods Module to retrieve the version number from. + # @return [String] the User-Agent string. + # + def self.user_agent_string(base_module = Pod) + pods_version = base_module.const_defined?('VERSION') ? "CocoaPods/#{base_module::VERSION} " : '' + "#{pods_version}cocoapods-downloader/#{Pod::Downloader::VERSION}" + end + #-----------------------------------------------------------------------# # Defines two methods for an executable, based on its name. The bang diff --git a/lib/cocoapods-downloader/http.rb b/lib/cocoapods-downloader/http.rb index 4ba7f0e..90e13dd 100644 --- a/lib/cocoapods-downloader/http.rb +++ b/lib/cocoapods-downloader/http.rb @@ -3,12 +3,16 @@ module Pod module Downloader class Http < RemoteFile + USER_AGENT_HEADER = 'User-Agent'.freeze + private executable :curl def download_file(full_filename) parameters = ['-f', '-L', '-o', full_filename, url, '--create-dirs', '--netrc-optional', '--retry', '2'] + parameters << user_agent_argument if headers.nil? || + headers.none? { |header| header.casecmp(USER_AGENT_HEADER).zero? } headers.each do |h| parameters << '-H' @@ -17,6 +21,14 @@ def download_file(full_filename) curl! parameters end + + # Returns a cURL command flag to add the CocoaPods User-Agent. + # + # @return [String] cURL command -A flag and User-Agent. + # + def user_agent_argument + "-A '#{Http.user_agent_string}'" + end end end end diff --git a/spec/base_spec.rb b/spec/base_spec.rb index 929146f..b7870b6 100644 --- a/spec/base_spec.rb +++ b/spec/base_spec.rb @@ -14,6 +14,20 @@ module Downloader new_options = Base.preprocess_options(options) new_options.should == options end + + it 'defines a user agent with the cocoapods-downloader version' do + module TestModuleNoVersion + end + Base.user_agent_string(TestModuleNoVersion).should == "cocoapods-downloader/#{Pod::Downloader::VERSION}" + end + + it 'defines a user agent containing CocoaPods downloader versions when available' do + module TestModuleWithVersion + VERSION = 'a.b.c'.freeze + end + Base.user_agent_string(TestModuleWithVersion).should == + "CocoaPods/#{TestModuleWithVersion::VERSION} cocoapods-downloader/#{Pod::Downloader::VERSION}" + end end end end diff --git a/spec/http_spec.rb b/spec/http_spec.rb index c785a68..533beb8 100644 --- a/spec/http_spec.rb +++ b/spec/http_spec.rb @@ -3,9 +3,11 @@ module Pod module Downloader describe 'HTTP' do + mock_user_agent = 'mock_user_agent'.freeze before do tmp_folder.rmtree if tmp_folder.exist? @fixtures_url = 'file://' + fixture('http').to_s + Http.stubs(:user_agent_string).returns(mock_user_agent) end it 'download file and unzip it' do @@ -75,6 +77,78 @@ module Downloader downloader.download end end + + it 'passes User-Agent to cURL' do + options = { :http => "#{@fixtures_url}/lib.zip" } + downloader = Downloader.for_target(tmp_folder, options) + downloader.expects(:curl!).with( + includes("-A \'#{mock_user_agent}\'"), + ) + should.raise DownloaderError do + downloader.download + end + end + + it 'passes default User-Agent to cURL with other request headers' do + options = { :http => "#{@fixtures_url}/lib.zip", + :headers => ['Accept: application/json'], + } + downloader = Downloader.for_target(tmp_folder, options) + downloader.expects(:curl!).with( + includes("-A \'#{mock_user_agent}\'"), + ) + should.raise DownloaderError do + downloader.download + end + end + + it 'prefers User-Agent provided in headers over default User-Agent' do + options = { + :http => "#{@fixtures_url}/lib.zip", + :headers => ['Accept: application/json', 'User-Agent: custom_user_agent'], + } + downloader = Downloader.for_target(tmp_folder, options) + downloader.expects(:curl!).with( + all_of( + includes('-H'), + includes('Accept: application/json'), + includes('-H'), + includes('User-Agent: custom_user_agent'), + ), + Not( + includes("-A \'#{mock_user_agent}\'"), + ), + ) + should.raise DownloaderError do + downloader.download + end + end + + it 'prefers case insensitive User-Agent provided in headers' do + options = { + :http => "#{@fixtures_url}/lib.zip", + :headers => ['Accept: application/json', 'user-agent: custom_user_agent'], + } + downloader = Downloader.for_target(tmp_folder, options) + downloader.expects(:curl!).with( + all_of( + includes('-H'), + includes('Accept: application/json'), + includes('-H'), + includes('user-agent: custom_user_agent'), + ), + Not( + includes("-A \'#{mock_user_agent}\'"), + ), + ) + should.raise DownloaderError do + downloader.download + end + end + + it 'supplies User-Agent argument for cURL' do + Http.new('', '', {}).instance_eval { user_agent_argument }.should.match /-A '#{mock_user_agent}'/ + end end end end diff --git a/spec/remote_file_spec.rb b/spec/remote_file_spec.rb index eca4eab..bfc9447 100644 --- a/spec/remote_file_spec.rb +++ b/spec/remote_file_spec.rb @@ -14,6 +14,7 @@ def download_file(full_filename) before do tmp_folder.rmtree if tmp_folder.exist? @fixtures_url = 'file://' + fixture('remote_file').to_s + RemoteFile.stubs(:user_agent_string).returns('mock_user_agent') end it 'download file and unzip it' do From 867a9c659562bdf52c30240070848516aa94d355 Mon Sep 17 00:00:00 2001 From: Sean Reinhardt Date: Sun, 10 May 2020 14:09:07 -0700 Subject: [PATCH 15/55] [Git] update regex match for branch commit --- CHANGELOG.md | 4 ++- lib/cocoapods-downloader/git.rb | 25 ++++++++++++++++-- spec/git_spec.rb | 47 +++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dce17fb..57ec7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ ##### Bug Fixes -* None. +* Improves handling of multiple results from `git ls-remote` by using branch name in commit regex match. + [Sean Reinhardt](https://github.com/seanreinhardtapps) + [#88](https://github.com/CocoaPods/cocoapods-downloader/issues/88) ## 1.3.0 (2019-11-14) diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 497b0a3..307e6d0 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -27,16 +27,37 @@ def self.preprocess_options(options) options[:git], options[:branch]] output = Git.execute_command('git', command) - match = /^([a-z0-9]*)\t.*/.match(output) + match = commit_from_ls_remote output, options[:branch] return options if match.nil? - options[:commit] = match[1] + options[:commit] = match options.delete(:branch) options end + # Matches a commit from the branches reported by git ls-remote. + # + # @note When there is a branch and tag with the same name, it will match + # the branch, since `refs/heads` is sorted before `refs/tags`. + # + # @param [String] output + # The output from git ls-remote. + # + # @param [String] branch_name + # The desired branch to match a commit to. + # + # @return [String] commit hash string, or nil if no match found + # + def self.commit_from_ls_remote(output, branch_name) + return nil if branch_name.nil? + match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(branch_name)}}.match(output) + match[1] unless match.nil? + end + + private_class_method :commit_from_ls_remote + private # @!group Base class hooks diff --git a/spec/git_spec.rb b/spec/git_spec.rb index 8bab70f..d518f59 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -217,6 +217,53 @@ def ensure_only_one_ref(folder) end end + describe ':commit_from_ls_remote' do + it 'finds commit for a branch' do + test_commit = 'abcde' + test_branch = 'stable' + test_output = "#{test_commit}\trefs/heads/#{test_branch}" + result = Git.send(:commit_from_ls_remote, test_output, test_branch) + result.should == test_commit + end + it 'returns nil for no match' do + test_commit = 'abcde' + test_branch = 'stable' + test_output = "#{test_commit}\trefs/heads/other_branch" + result = Git.send(:commit_from_ls_remote, test_output, test_branch) + result.should.nil? + end + it 'can match a commit for a tag' do + test_commit = '12345' + test_branch = 'stable' + test_output = "#{test_commit}\trefs/tags/#{test_branch}" + result = Git.send(:commit_from_ls_remote, test_output, test_branch) + result.should == test_commit + end + it 'finds correct commit from multiple ls-remote branches' do + test_commit1 = 'abcde' + test_branch1 = 'stable' + test_commit2 = '12345' + test_branch2 = 'release/stable' + test_output = "#{test_commit1}\trefs/heads/#{test_branch1}\n#{test_commit2}\trefs/heads/#{test_branch2}" + result1 = Git.send(:commit_from_ls_remote, test_output, test_branch1) + result1.should == test_commit1 + result2 = Git.send(:commit_from_ls_remote, test_output, test_branch2) + result2.should == test_commit2 + end + it 'returns a branch over a tag' do + test_commit1 = 'abcde' + test_branch = 'stable' + test_commit2 = '12345' + test_output = "#{test_commit1}\trefs/heads/#{test_branch}\n#{test_commit2}\trefs/tags/#{test_branch}" + result = Git.send(:commit_from_ls_remote, test_output, test_branch) + result.should == test_commit1 + end + it 'handles nil inputs' do + Git.send(:commit_from_ls_remote, nil, 'test_branch').should.nil? + Git.send(:commit_from_ls_remote, "123\trefs/heads/abc", nil).should.nil? + end + end + describe '::preprocess_options' do it 'skips non-branch requests' do options = { :git => fixture_url('git-repo'), :commit => 'aaaa' } From a11bffd447145716bde03b33c6bb7729bad11827 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 7 Jul 2020 09:41:38 -0700 Subject: [PATCH 16/55] Add Ruby 2.7.0 CI support and bump Ruby minimum version --- .travis.yml | 5 +++-- Gemfile.lock | 2 +- cocoapods-downloader.gemspec | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fee04d0..b640cd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,6 @@ addons: jobs: include: - - rvm: 2.0.0 - osx_image: xcode9 - rvm: 2.3.4 osx_image: xcode9 - rvm: 2.5.0 @@ -24,6 +22,9 @@ jobs: - rvm: 2.6.2 osx_image: xcode11.5 env: INSTALL_SVN=1 + - rvm: 2.7.0 + osx_image: xcode11.5 + env: INSTALL_SVN=1 before_install: # There is a bug in travis. When using system ruby, bundler is not diff --git a/Gemfile.lock b/Gemfile.lock index 5cc1a17..c29e872 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,7 +14,7 @@ GEM crack (0.4.2) safe_yaml (~> 1.0.0) docile (1.1.5) - ffi (1.9.6) + ffi (1.13.1) kicker (3.0.0) listen (~> 1.3.0) notify (~> 0.5.2) diff --git a/cocoapods-downloader.gemspec b/cocoapods-downloader.gemspec index 4476203..908639d 100644 --- a/cocoapods-downloader.gemspec +++ b/cocoapods-downloader.gemspec @@ -18,6 +18,6 @@ Gem::Specification.new do |s| ## Make sure you can build the gem on older versions of RubyGems too: s.rubygems_version = "1.6.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.required_ruby_version = '>= 2.0.0' + s.required_ruby_version = '>= 2.3.3' s.specification_version = 3 if s.respond_to? :specification_version end From e3f116e5f9bbd3a4cdef05c315ef9fcf870ee718 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 17 Jul 2020 13:57:55 -0700 Subject: [PATCH 17/55] Release 1.4.0 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 481bf66..31158b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.4.0 (2020-07-17) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index c29e872..88148b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.3.0) + cocoapods-downloader (1.4.0) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index bf1769a..aad37bc 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.3.0'.freeze + VERSION = '1.4.0'.freeze end end From e2c4ac9f7536c3f31594b26ba2c4c09639f3dca8 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 17 Jul 2020 13:58:17 -0700 Subject: [PATCH 18/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31158b4..786e589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.4.0 (2020-07-17) ##### Enhancements From 751a34ca621e29889e53ead891488ee4ff03a558 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2020 21:01:32 +0000 Subject: [PATCH 19/55] Bump rake from 10.3.2 to 13.0.1 Bumps [rake](https://github.com/ruby/rake) from 10.3.2 to 13.0.1. - [Release notes](https://github.com/ruby/rake/releases) - [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc) - [Commits](https://github.com/ruby/rake/compare/v10.3.2...v13.0.1) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 88148b3..21703ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,7 +36,7 @@ GEM prettybacon (0.0.2) bacon (~> 1.2) rainbow (2.1.0) - rake (10.3.2) + rake (13.0.1) rb-fsevent (0.9.4) rb-inotify (0.9.5) ffi (>= 0.5.0) From b511dd16bb3049bdad48e82d0299eefc7e731322 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 20:18:44 +0000 Subject: [PATCH 20/55] Bump addressable from 2.3.6 to 2.8.0 Bumps [addressable](https://github.com/sporkmonger/addressable) from 2.3.6 to 2.8.0. - [Release notes](https://github.com/sporkmonger/addressable/releases) - [Changelog](https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md) - [Commits](https://github.com/sporkmonger/addressable/compare/addressable-2.3.6...addressable-2.8.0) --- updated-dependencies: - dependency-name: addressable dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 21703ae..9dfeb6a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,8 @@ PATH GEM remote: https://rubygems.org/ specs: - addressable (2.3.6) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) ast (2.2.0) bacon (1.2.0) codeclimate-test-reporter (0.4.1) @@ -35,6 +36,7 @@ GEM powerpack (0.1.1) prettybacon (0.0.2) bacon (~> 1.2) + public_suffix (4.0.6) rainbow (2.1.0) rake (13.0.1) rb-fsevent (0.9.4) From 1ba3f9117918cc1c94891ee98d115f6dfbfdddbf Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 3 Aug 2021 13:08:45 +0300 Subject: [PATCH 21/55] Switch to GH actions --- .github/workflows/Specs.yml | 45 +++++++++++++++++++++++++++++++++++++ .travis.yml | 40 --------------------------------- 2 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/Specs.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/Specs.yml b/.github/workflows/Specs.yml new file mode 100644 index 0000000..a2068df --- /dev/null +++ b/.github/workflows/Specs.yml @@ -0,0 +1,45 @@ +name: Spec + +jobs: + specs: + strategy: + fail-fast: false + matrix: + os: [macos-10.15] + ruby: [2.3, 2.5, 2.6, 2.7] + + name: ${{ matrix.os }} / Ruby ${{ matrix.ruby }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout git + uses: actions/checkout@v1 + + - name: Install dependencies + run: | + brew install bzr + brew install hg + brew install svn + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + if: ${{ matrix.ruby != 'system' }} + with: + ruby-version: ${{ matrix.ruby }} + + - name: Run bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 --without debugging documentation + + - name: Run Specs + Rubocop + run: bundle exec rake spec + +on: + push: + branches: + - "master" + - "*-stable" + pull_request: + branches: + - master + - "*-stable" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b640cd5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,40 +0,0 @@ -os: - - osx - -language: ruby - -branches: - only: - - master - - /.+-stable$/ - -addons: - code_climate: - repo_token: 4d2c1cec2a5ba5fd0cd09aa76d1bcb52854e12ace21660dbf65a36a59ba7a973 - -jobs: - include: - - rvm: 2.3.4 - osx_image: xcode9 - - rvm: 2.5.0 - osx_image: xcode11.5 - env: INSTALL_SVN=1 - - rvm: 2.6.2 - osx_image: xcode11.5 - env: INSTALL_SVN=1 - - rvm: 2.7.0 - osx_image: xcode11.5 - env: INSTALL_SVN=1 - -before_install: - # There is a bug in travis. When using system ruby, bundler is not - # installed and causes the default install action to fail. - - if [ "$TRAVIS_RUBY_VERSION" = "system" ]; then sudo gem install "bundler:~> 1.15.0"; else gem install "bundler:~> 1.15.0"; fi - -install: - - brew update - - brew install bzr - - if [ "$INSTALL_SVN" = "1" ]; then brew install svn; fi - - bundle install - - git config --global user.name 'CI' - - git config --global user.email 'CI@example.com' From f276018aed446f0a612e09c06e6b061911218530 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sat, 26 Dec 2020 12:34:19 -0800 Subject: [PATCH 22/55] Run specs on ruby 3 --- .github/workflows/Specs.yml | 2 +- Gemfile | 1 + Gemfile.lock | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Specs.yml b/.github/workflows/Specs.yml index a2068df..9e64d20 100644 --- a/.github/workflows/Specs.yml +++ b/.github/workflows/Specs.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false matrix: os: [macos-10.15] - ruby: [2.3, 2.5, 2.6, 2.7] + ruby: [2.3, 2.5, 2.6, 2.7, 3.0] name: ${{ matrix.os }} / Ruby ${{ matrix.ruby }} runs-on: ${{ matrix.os }} diff --git a/Gemfile b/Gemfile index fe75175..3ffcbcc 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ group :development do gem 'mocha-on-bacon' gem 'prettybacon' gem 'rake' + gem 'rexml', '~> 3.2.4' gem 'vcr' gem 'webmock', '< 1.9' diff --git a/Gemfile.lock b/Gemfile.lock index 9dfeb6a..0a47fa2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,6 +44,7 @@ GEM ffi (>= 0.5.0) rb-kqueue (0.2.3) ffi (>= 0.5.0) + rexml (3.2.4) rubocop (0.38.0) parser (>= 2.3.0.6, < 3.0) powerpack (~> 0.1) @@ -57,7 +58,7 @@ GEM multi_json (~> 1.0) simplecov-html (~> 0.8.0) simplecov-html (0.8.0) - unicode-display_width (1.0.2) + unicode-display_width (1.7.0) vcr (2.9.3) webmock (1.8.11) addressable (>= 2.2.7) @@ -75,6 +76,7 @@ DEPENDENCIES mocha-on-bacon prettybacon rake + rexml (~> 3.2.4) rubocop simplecov vcr From 8c0f6f8c78249d671595161723dcca2291ba9d1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 10:38:35 +0000 Subject: [PATCH 23/55] Bump rexml from 3.2.4 to 3.2.5 Bumps [rexml](https://github.com/ruby/rexml) from 3.2.4 to 3.2.5. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.2.4...v3.2.5) --- updated-dependencies: - dependency-name: rexml dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 3ffcbcc..13ac913 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ group :development do gem 'mocha-on-bacon' gem 'prettybacon' gem 'rake' - gem 'rexml', '~> 3.2.4' + gem 'rexml', '~> 3.2.5' gem 'vcr' gem 'webmock', '< 1.9' diff --git a/Gemfile.lock b/Gemfile.lock index 0a47fa2..24d955e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,7 +44,7 @@ GEM ffi (>= 0.5.0) rb-kqueue (0.2.3) ffi (>= 0.5.0) - rexml (3.2.4) + rexml (3.2.5) rubocop (0.38.0) parser (>= 2.3.0.6, < 3.0) powerpack (~> 0.1) @@ -76,7 +76,7 @@ DEPENDENCIES mocha-on-bacon prettybacon rake - rexml (~> 3.2.4) + rexml (~> 3.2.5) rubocop simplecov vcr From 0de094b25988f66e4b982d0d543bb7eb2f6f0f49 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 3 Aug 2021 13:49:50 +0300 Subject: [PATCH 24/55] Force encode branch name to properly clone it. --- CHANGELOG.md | 5 ++++- lib/cocoapods-downloader/git.rb | 3 ++- spec/fixtures/git-repo.tar.gz | Bin 19793 -> 25327 bytes spec/git_spec.rb | 7 +++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 786e589..78cc0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ ##### Bug Fixes -* None. +* Force encode branch name to properly clone it. + [xdkhan](https://github.com/xdkhan) + [Dimitris Koutsogiorgas](https://github.com/dnkoutso) + [#116](https://github.com/CocoaPods/cocoapods-downloader/pull/116) ## 1.4.0 (2020-07-17) diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 307e6d0..f7914e6 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -52,7 +52,8 @@ def self.preprocess_options(options) # def self.commit_from_ls_remote(output, branch_name) return nil if branch_name.nil? - match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(branch_name)}}.match(output) + encoded_branch_name = branch_name.force_encoding(Encoding::ASCII_8BIT) + match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(encoded_branch_name)}}.match(output) match[1] unless match.nil? end diff --git a/spec/fixtures/git-repo.tar.gz b/spec/fixtures/git-repo.tar.gz index 86b101326d8e72cd8d3a1b37f81b5d582495f0a4..e379423c5900df8dc17bb410edfb26e1ed6b4789 100644 GIT binary patch literal 25327 zcmc$_RaBf$5cY`$cXtTEg1ZD6g1cL=L4!+hA0$Z7V1eKS4ekyD!QI`1CBfZ>d6)n9 zt(@Jx*^Aw?_g&p}y8BdD*RLMBSabxFHy9QO$j3ow8H@^E=d?(@xaP5}Z+cQck~vR* zKF!T-EZvyfI8l1%S?czlp3A#z(|JSnt}Ni-mUIIn9D@NP?tMla4*6I}UGHx+WaedN zmCt$RvI)P@Wa|-Lhr#d4%LDv>osOTLOc-fHH7c_H{w?>v(e5(1DR-ExE|oCyXpkwz zWmIB2xcdBTybUPFi~=N5)_UKF<&v)4Llxb*z{7Y+u($nhKSzR>#H9urE4()vo{Klb zmpB~1`^$oULC#+DkezS2{?S`<(*3ilA`ONaxcli%u%DxzgTP-?Gl1}ruK>k;|0{DP z!b;K*+E;p}nLs@UGEf|5GOohci4Nl~zr;^kq)c*gqdNq;HP4jzU>V|H$*b*e{P@&< z*?!rHGzdjo@o)Mj_Y(tRahYOxme60zhJFKCYMy8iYspC#^8X7hYD@&mGslJ=LFm(} zDVYSEVAnuPa*oD0pp+HeunnjwSAKm9pdwbmpq@*&Sa_x=Z+bJEXXfCCS@66f((eG6 zqyH9ypRd(b^cFL%?4wpiSyn`+!@fu6M~4XQOdvDy3axPUk+!W4VIWL8VXeeVFdq;#p6nBg_ z9Hm&x9BG5=8e{Ob*iN(cfBMcl%&S(ZKc5Q*Oop!FOyH?-QMe5D?mtRZz&jrG=F6TO zkI}08ffQ?M083kv>|) z3*bPjv8)8_oK025)#phG{uxMJZb2y+@-RK!63K95a>w^@;Og>x2(#L{s`ojrm+a_m zI~|zgCa1(rCycy3UX8qw_{geI)b9wsxF0J|l!VVs0kIc3cwMl^oEraIGKE{SdRwy2 z^B|8XN4r0#Uysf5{DXz?6OK6~l-XUXlxZ|cm!>GIcN_$luIEZRwslP=ip&;kwB9(b z4E@7Nd|Z+0)9wdfA2lj}6m;!1Qvv+{TnB??X)oly7ZgNwGd?99eN5bfe`N7Bj2Q=D4t}nCrbD^@4H#LY+|~p5C&E_%Bgs8C`+zSTLhq)01(2GYDQ*pea;0K@vaTs!90&7uz+ZT6Az9){D>L6Ebj$*1!Uu7L5O5~qjfqXj7W zx2XT&|N9sJBP;{qyHfdNfMe#T&zbg~$od=d8c%ubjYE(a>LCbnC_xzYM;H^9cTjN5 zJZqJdR0dDX$Lg{_^P1=8`GJz$>9HTXE6KioS1u@0WQrISRc@~u7?>Nr!~Zh|9I7B! zxLEDqf|Fi$g=d-!I9gRwL#hb_v;*N+=K0)kw85Y>=gKg^kLduAGM9sqg+D2;8HxgW zN(oC4RHTX>K-oju{{cu|?S%+R0xxrn1JM%qPYjhkz#(B>jFA67NO#b)aJo*~f3fyi z{-N%5tB*9~2pVbp>O|4txh<|n&yl44hmik0#Jyk+7*qK#c9DyURrJ}NO|i%mzOinQ z`NKPC9u~(UeunHJ8RzFfZJ|VGLaI2B^G5IA(<=3Z)JT=lD_fi!xnz|0gxpLksSKZJ zlm%Q9ig!v4X*k0Bo0e_Ic|$S(T^Z$!I|et#=LbvQj-!!Jbi~JC82*~7bRY7X)hNux z(nE;-hm&VVimlUs`y6>PPB8J>sOq3iaB48#<%hDRF^c1Kc6;K+)sYL1aio#&+KiBM zS%dZ`vV6#S`EY{0Lh{X)d&7M{<$LS4Cbr@wXxSI5TD}s{6 zMKJYPl-zaIlXr(O1P?-WVJqX<0dbA1;X&9%3)BBvmF33et1PL8|EN1WNLjkM9Wj1q zoYR~Tar|^_P9<&iW2c|z!n6kW*-qqkfzcU7;)gXyfEyi!B+uJjpy6N{rSB@UpGU^) zfI4EFD{+V?1uXi>awBR1ehitoxeyFrRrouq5Mp)<9&K3zI#Xj$0I7WLC)YOG2Evgt zYDNqR%x}sWos(d>51QIFJLrWR!K%BNMEtjz7SoigB=J=;@6|G`n)gZT$-ITXaV?$C^|-4k!I^uPXtEjm?z1w;NUVwY*V zeTbD`7>1y8-(2$ZA)A3r^~01_lWMGfFI&2P6EnpTbRyl>ip@I#Jx)9~ALay?)rHp)GMt^ojd;{5GEG1Dh>`UD43#{A zMm^DcO@JeA&TIf1(XBc_MKAW^Ve2KBcQ2(kBAOHMd@FmQ-}*$>>$x$O=k^J_8h*dz|ddL zvn-akNmg(0p%jFkYE_MP-_JMnwwZ){LihLl?4CNkL8}*rWEozoUT3yxoD#R|t6jA~ zHu6h64t&geH&x(+Gu4-S8AgP-8p3VBi|s|q<8S_2N|CJPBNgND=x;3|m%&B0kg7Ix zy;|qtaV@&O_m&(zaax}e7LN!}UL)j3E}p-|b2}&N!1dy&r5`H{F4P_(5Rf$yka582 zvV6Ll#-$$@Mquo3uy+y+UAT%Y{oaRrL3-DvpfLTUPbh`N!aLs1pA#1sQ>e`Mex=zR zzGjh13#lvb*w(bl#q8aW>f3h1;x=NSLbSkSc$FNoj3m}2U$rT_TIAI^|I3<*=v$6r zt;FGo)}l0F3?l=&(`sr?wj#FcdPWcAo9nL#Gv7Z`eQWbe*l{Xch>@I{gT&FzY@zwT z%xU*cEApjymwLB&Sr38{--${NmZh7;b?$Nhx>Lg--PfXJyl z#KB+l5Bjv~#d@WOd$}Z?iW}vg6ig}Z^ppLqY|$5P?$4APG~eT;=HL17SDb1w>N6k5 zZ|C=-WMPQGs0X1v>}3mxg2){xT%4ndMQ8c`W<`R;=W0E)SV*$;<#u+s*lQbXxJp_<3qgqpzSJ zJ{FE@jD8JKM130~WQzDTHS9-7ShHotM=qH(lKt~<%pcFo$%V$%A~g-f>!isELdm@w zOWowrmx-q>4)}J-KNOlp$N|&pURq^X)9#*E)mSylEHulkxdzXoMd#M%sFB=(X2mN{ zB|LS^$Q3+ITS_qwgL&ldgOJ!N9Rzi^c6N??FM_n&D~qd;57obNbc4ik-z;5YC$XHb zVf`d0xAx)^OaoDRyjJ zex-=K4DEs@l@JL6K1}ODQP7VG`)!Ok9jb-I459ugFP>lYn$1Lk$lM=)ddpNeBjE7Z zp$&1ZN=IW0IX9edqU6T7ZCQFR7MR#xN8-pAh+3`&#;o4pV{vE*)>sx!peoHLXwtz{ zSUaaflMq)859byY$qsgEF_)Jemcm5;A&3gG$LUl^^51SVtj3|^_K$Yg_{EM(eWBH@ zvw=xjh#*)a47^EQtYtCV79!Y^@7=Zz;4@ScnwNbVb>hJPfR4=VJnlG2ZNtA*sj_TI zFZWhmzrTY`acn;aN{NFd4(_*Y`0gRheib+BsOZ}E%cThrg>Z+l3dET6X3SaQc{Jj>qV9}&NKbtnI+ zI(KIJ6PZ-!&^|`;h_xF7RBd*8laH@nw6LUj9~~L12IW3f{cBwHq6+eHiM3QBqXEh|AHb1Ch98B9U8%19|4Txy( zW!nt@gxegEvyG{`>@cd%NPsSO&ou2~!@F=GS~qJ-O!ce6j_!RuX`hzvb%;m!PgyC? zM16relNrwx!ED~WBb4!sP|E_Ly5DNX=#n`!yu$i@RufohDj1`f8ae_AgKuW1MOrbh zj3!(ovnACh3oca=&giQNcgU+nct!V0N|Z+XagFG%lwJ*CsW}bevM^hihx56-%Q<~u z`J9_Jj0xhsZ`tMVj=&RO{Q`NnvVK$SwAR3NCY$>^53wl%Vy$wRwK5DQy7lQK57Mw@ zb#eC7=}c!DGx%4okVj>EHP}}=uHp-no$*c*C zxSrV(x31Eg$4HV&;7i0sng+t%D4_T53KXj?`SNc-xlRW{xl}zE(rWhYMf^UyaNE7l z5bB+MCEzje1)l&>#!*C$>(h#2@RU|j0P8gr-r#U^Rt$(1TRnp(#y-IhrSi96+|i{@ zSn0g2Ow*n%T4Yw@WY|KZ+<9mRtrNwMli6IX>)#ieer0nt|Cs;2K(gmqHP0HMohXEn z9nzyzW>4!&{kM@GlZ*Z9_e!P2k2L4#(R>~W93{GP*}N0_wD)m)=qsnoUU4aY8#?`s zoa}LmJjAx_^2ggI!7zsgT0QQ!esip4F$H90W{X8(vRCCUY2(r#-llQ5kuKC1q4F<_ zf3^4e{k1vMby?`>?d=+iB+2_hEgZXY52Wp+&hIGfC^m)!N#{&qJ|C(1IJs5L8#-9? z@EPbC1-V#)H;Qu8m8Pxc=`Y7cF}qt9Jc6P}=ym(9v5}~4q9&5xd1Zi#X}p{psJf}b zZ+O0mC|1m1@J$G0g@X4!vmr@xO6XU#G6YS{V)NEtbb^EmhTBi;6tydLFSovc=XxTd z=%>RiMIL#5mawJ8(TziO=^36B-5Vq5mE3Oylo;No>>-FAQWJ-$oNsUE+lS!_pg{1Y z$Q4+d|Lma94Q}O1y}Czo2-698%jY6v9-U?m6hI7wL*}=??2dC`ReN<Pd$}?UydkWNxH?KJIBhE4NAY`!azKh&!><~}UV!=LL^LX55xYs% zGDA)CQ=NHO_JP50);XeI+F%6geH^qnG_+N@k%A0I9{J0Vs#v|%QNe_mx2o%adZ`*; zb7E1AL@k)|&Cp2L``JO1WUUx345k*p+#|K533LDQb;S$u5q&=kEn#D{FzNSNLSLfz zi32Z)Q1I@}Zo+#&(`4C^%BVKvg#(PAk5EzwrM;gMNHgsBuz#Y=@n-tX-y)wlOf8RC zuO8vA#uAyWpZ^wat{zSn@B8pAU;U46H0RWk^Y*)WUE`+D95ugG&$4VXJ${epo)3`q zTUuT!Tlv_5hN-KxzO7<#7S>8nI#> zao%}KV=N)>042GMm#O~RH~!ZRwt4|h`Hr07Bo#Y~OZ8OsZS&nilxRq#pNbOgp8KhNSUh8!3aSaTx3 zAu6DIE&E`bxK%Nx<|`l)t?rtv*=*a9d&^n@KgH5%c@+3ILG5n>Sy$w#l0{VO`PPe`1jgO`5`YpvYjA)h2ZSJ+kv zm{z4cWBSqr+baNW@;*8>EM7p~yMO?q-@r0JhhDk=h^LESwgAjhgy>a13k1}MDN5Gc zHF`4A1=&|IJS@D2JVO5!8RUlrJZp$UUh#VFc8|7$qW@4 zmgaDmeb@8*HR;UX>M?h}Ad_I@j8Z52(!vTX21@J$-0=ML1tL>BMq|=1N}M>tQ@v;A zf~Ij+UP5Ywy%8)!?>8iPEY9kj!#8F`-ZDx}_150WOZP2)xaDk|z+ACl!&fP6F=ZjL zPt}V;^=s?CDjEsypkr37p}|o7$8o8K5LEDMYPe4gyK-p5EsWCnwXx-dr+w|0!~o zF7nxaQ^qTztkCVx@tam|Q$rnv-ytc?$&z!$&;EmZGBwM{o@~$M$cU0V1@|J~r*Zi> zzllTd5WX9i{a{aa?i9rC^CrVW5iN{gtVP&@WD1X}%;A8!@;SLWZ_LVeJJoUUv|H*J z<8)z;x^j>--!Kz~EM<$6B?-aK0fGYyl{ zI)b7uDvKpVqr~?xWVG)Mk7S84s-iD?b*BM{n7nevvA;Aa=_5@mJ9j2C(M;Mm)$jQ! zeFTw~!_%7%F>%d}nV7d9o8oXdXe3ifCeQ390zxsZ6j`Zvdsgk})$H@A$#)gDwri;- zuzI&;vSbolKcs)9UH5wz$2Tu6JV~oA{F*e|QcL^u<5CKKLvQpj+CIs(?+1?WsBdCU z0^=B-$PSJ8hYQ#-eG9!J>Rw+NooHV&Khr&w8p1d%B@p2-feHW2z5vj>LHMH>G# z9U^WlF<$7B61Mrr+Cm!STyazMPGaO@v@NWhX0WxHLv`OCZUPSDmBP0{N79 z8KjElu^jUG$X)z$=fPTE=dW8R@s^$7A$13STXWJWQr#8n@>Az#fju^INcSnk>~H9q zGyRPSAiDGk-cU)p`;>=@^!%=<=SCE@@sj4u*p#c91f?w+nuYnPkrCos|?yO$$k^Tfg(vLD5^9*#d z4LpHRk;WciTG7uy088aOU_8O3$-V>;WEnaH;h|VQLtSOKpNGWPI3xydKUX%#=RSh} zL<4%FlP_^JuGXd_X6jp1P_p{fo2s2~bAL(__e>=PUDB88EZU|`-`!Yk1EH_?((*9* z8V#+J!f3RkVB@wgnzmPvI6qNb-=glC-!W{Qcl>xq%<rw_bVnYU+AYMVqUp8Qx8{mZmG!fRmkg124ax>D)$cNwxCo4hN&{LizJkQ@S z^!bb%em09bEn~_B*U3Bt_L$m24k5(5Pf%3kA)(G&xh!Ds&0bu=U%(HJX7((MHh2St z|KZ&y1BqOnN7ifs55k)d*Kd4sZhxZ@29{FENHb zw>3a64QvB1%DFqzH-iqy%7v}r&oaWt?;5W<)uukUQm&_4yn;hNOsni`CmmoHn@=a> zzN^agANf%nXvSYjxE$(UJzX4c_Q<`TS_0>P0_(b8z^0_Omm_0A4_{P#l>Y5pj8AH8 z2LA1zSc5{K1==@=P-E9|OLCM-bNG-Zr(lioH~F#oR8CP>5}Y{>w)$k@%gEfZzsV-a z9gQFaB6MXxMQcGLt{_Zy~YE?O&bn19-|B zK6QpGag-PQWUy7IK}Q)hO7ZhXUNlnhv;H$^L>9`FUvsaK_WD@IT8WFW7O|Z25Wq%h zKhHZsH69bv#|I8${U{#tbaw9{^tHr5;^=ols^DVUBd9h%@MasL6@Lf8M$yGUYS-T6 zNL6d9BpKyfz$y-$WGidSHW9$&)7#0@<}wrM^7)m#3+j0KhrjjUo9tXE^#Ww8qmQ8H zFCZ)&K~MO~zuIJ4@49VVyUp%9=7Oi*7@6Hq&Dk;CDdR}WBcO}u+dQzq=+A}t{1?K7 zhg3FPj+uK_7AYI?g1H>(3;G`+XP=pF z0KSuDxzyst;4=wVpciNA8SXXp&^xwSSNlt>k<4&tMf3^pbN794vh%edyyXv6JNgQW zhRozZSz<}y7k!HriP+t(KmJyZJ$){=c+Ue_G|sq0G7sokS7V53WPf-62`0hIa@oV9Q8!me96!j~i{N|33cV{st?nWXVH8xB> z1Ktvx;+;w*H-oi0USPwgcu+uLmQl3&Mf>8p*;sD+5pMoM30v70X|WqMA? z6dOvMbc*BZ&Kj4MV0#vp2YOIid=$8^(4CvU|P%V+=00c^f^iaZk-+3G&mQ(=TamK?X16{6D}jh5LR$ zrSnH}lC?0t0vEpHlV0ioG9gt0&*;qD_V>Ca?C{0u>hl1ba|g7}$F}`>r&|WG%5*w! zJ5;i$_XKo;eBzq9bR&P-;@kX$s58z@sei?%U;i#xr?CY!@E6PoOhw1Xgdwzk`f8V% zIrY}PH}!^EZ>-Udq|#kU&qig8Y}BC_<*+UPE#ceL${!`hY!O=|#f&5>#r0_ho-*1V z^E@8)W*o*$ADC$PE93rmq=3u!f)pIVn6BcEE9Z*T{*{wM2=wIh)4^G8zA5TGc+_A% zm^TlERyAnR6Q@~Y72zoNeV*eO66F`M_<{dcdU`F$YdBxmZZf06j52ZL%C6KY~sG;9yJRMGVL>^jb3|KrPuKMC3(}ljC|IqWt z;{7^w@Nd}I<%BVd;e7w8%`VHwhBehL(bKZT9eQ3;^T9F^g`*qvu%GAMYMk^K9Rz7- zeRpMvce=|2{o&zljyPSvaIu&}c*M-alJKWW>K0J+xm`HSamD(L*Y9X)-k%vOou{OK z@F_`2(jste-X2=M+FE{<2DjAn=`B#-eLduRId9?C8bQXH6d%I#D#@~xf6bRHeyMCB zOEIf)(zUridB*DO1J-fs6FO3skj+KyK|B@F{nVw}c^@GkXF94061H)E+*^ESJ*4*o zS4D3+9Z85;58mC2p$@EbOR)?Mk>AY5KKz}$X9(O%HP^~*%}+DH5dW#$Z~r%oFwUG_ zD9-S2Mb@)ZtX6V*$fy85NkWn@CtiQfR82ce9EfQX` zCfWi*UDXm(^F ze&K?mE@~7Wb~cMpy)jITf?p{v`)EpV&r)LEGka^)p4IEH^cSIwMyhAIM}^iO>5}4E zgvz*6eUdh!#=htGkJl_9B6pBJ_7l-I`S8P+sGZo=!4w6L_F3Kmmz8svZlj%OYIME+GTfO@nSt@P9%Epoo^1xd`02`(hfWRm#7=#HsZW zsf<+)%0607 z=ruorUS2`e!m>Yzhp0MVLjKTJ*;%&z_{;1lVl}*kk->{RAnj~9kkyPI_)g5yr806T zlZ-8$FqI^?#Ev>%p{U@K8_7Xa<`%YSP*?67I-S}L`y{!u3j8;(y{czJuL)m}fGcBlR(mUvc&ZHL0QCC>Wk0o%rMK|9xopEE=PYQGzc{|o6jwf7LH zD=yrT5Ro!)#HmFyZ`X?Mh~!5T6_cY%T?0-cBoZl&e z$plg@z=tlfqW+d*{c4{iZ>7DJ>tSd& zp*hAFxq?DMhQ&OLzjLwB>`D2*0fC*@yZ;{nfi(?+A^S3G%}8D&Rq0lBp0}^9m(J`$ zzspa4Rpy~7{I1{(ny$W$%TX=q!Y$|$*%`p$4z@aq;TYl1w!!%KSv_L;GH?U6T1Kr& zPi6R$ZT!0XLy0KM)B?jS*!mG5D*6N1fcc9A4eBI;z`xq*N}?>I`J^ybz|WCG*9GcpO8(Cqa{PIycokZ^4~KuP zwi!{5=ueDYlE)RyZ$GuA%hx1LsU_s*i6>>ed%M;u3tMWY*xqxJ!h1Gwr+I)VOLhbH zJTC=atnY^VxcM#t!A9ON0hX__@o$rgha8EkS^5U$565EYf4OfOzIO8n^5)f*-5PQu z84qU-X`9J;+~k-L%tdF3B0}gQ0S#|C@TvdATdpAq#3@en391mOy zn@jK0Hfu_TdUU?es_I`A8TR~7@r+#?{1Zc&mLT_LS-q72@m0DIt1 zg=9W8xc&R0W@+(z8J(9(FK1q2;^Na|A9z60Lj{#mj5-?RsjA3f(iFUJ9Y`Xi+(P}LM((Myq@9tmepedQl3LC z8QADcA*IpF{y0KXDRW$gh_}53SnTtQ)27>5)K4jn_)YH|)gmo@8p^%>$H^4tr<33e z`Yvxqo}UfF!id)qm@XU@R(n$c>o_cBGom4JUo@Db&GOIP3tnLyP=P;%F}(Kt z>{_^2O4eh`^nDwRyywt_kG0jf!7eCCWASRiu31b4TT;p0{LPo@{7F9l?O@YL6}Q#! zVO89_Ee6W(j4pea>G(r$*o(zevRvmcsYE^!&d@tzAGva>$}@TY`=xqlk4@B@HK8~3 zQ5c0OnepTjt<&>Ltfr_iD_{ncVN|j(DUU44qcZCvVw6HZYfqfUoGG$~NltCySsS{@@)Ow2AK_@`H&xdhsSDK=o_Z8b+ z2`OoiGs{p;w)H_H3ctyIQ@{3*_cGi(NB^byJjGL@2g1-!@NT+NcbcAkJX&JClgN^9 zKnzWnmNxW)cY{yrmyFwf*JDACdA!*h$@n;dqaIVg8G-N7)s5DpC^^&#Jj61QxB5EP zjl~PPwZauPPUe5l3o6}$6&yFQbjf9;cgWEe<&o``pZ0plL64|W8+K;ssC28;kHZt~ zpRczk=PXhSNZUW%y0v4}Z4<{PGt5JCKWE^`%Sy7=-dY@G17LK>2ocZ@xT@H3U$#gzT#r-LuPeG>sUNO|X$KK&h zw6c5S#$`R(x1FuUo`7kF00NDK2*g`c#;PtNn!<7AJ?Ej*BqDXtD9+p zf~0ufxC0vv*Vo@vRR`HtWDH9}v!?Xkw66m4mc#_&EfA*P0Xm;7=zC1|1)XJ#;4oXg z5t3h5c~`zbp~=_O|A4d<6!-00hmY#)HnzXn79%kp@>?xE@aJh`XV3z_F7&9y+yCLyz`Ml)e>N?1&_;iHikWVq$B5 zT{2%H3ToLpi`QqD(@itN&tdSDGx~A1s7_)O@imydtQAM9@*l0umlpD&ShbYi_lUox zOG)xK4xgpiYl|-rdwe)2N2Y`P`z$8MAvQ(1oE=Uu#XR~oVnsN zrS#U#e;wOa<2|jo6DV&P-UL8@Im~cZAU;;5ZoXP=^f3N$cA&xg{o>Es{xCI@OMiZK zi;w476c-$X)v{C{|59Qe@4{Y!#Et`KDr8%L7`kB$;OK|p3x*wD&~-+?$e&^e|06Eq zz|sNXGVKL4HQn9$4$5}Ulkn2z9yA1$iU*Uhm|u=uY>&${yTT!Uwe6c6;%^Pj{-?p? zi+;i6=(z^7o9^gEyyN1IC@}sasy!f)icPe z0`C4h5FXN{d&^Va+J1hXO1_GFfnoI7m#(PyKp;4Xs_Pn5)3^oXTHQg1;K0wQ?q?vh zs*ghg&rRcl=Lgr^Y80azI-iA)e|f!;`d6D~Q~-~N<|~i|) zXbz^#eox;|AO9%4@tWofK|S(W{r;iN3*~t@t*%Sr^?qmKMy(`oQt|WEkeD>=a5skn zU)M>kX=7+`ih3wV0%naP{%y8tc_hD5?^^HQyhkA4raR5YrsqA$^@M~emUGIfk#R`_ zu@JiIE``4b^!a+g!>7XFX8Njqr2Ki{kuDEN#}bu`dC?dkZajd6(58T4SdEEhvB~(0 zPQwSDHE=Tnh@&9-l>okg&p4oixA9Oj7kUMX0}SC&<%72n`so~Cb^C#P5c0uh7ituJ z^}@e49yHRtenY+j#lIi&cmWLw&qHW=O#@J){33Qi_dEpUF4lV!)-pWXUV-G45?(g% zWvTSVF(5tW961(<<$XC@m2rq(MuBZqRx1D$(Ai);2{`D z63~g>g@*Pd!_HjM^uW#t67&1F0PBP08XTmTy!#A64z;4%3~V^g_P(V!gq&j4%;b4r z^r@@lG#@2_{r-kJH{YeN7J^f_I!{LL0?z>$tRN5A^-*8H--T4DK!PR`=RC;L=x*m% zf{cUVl~CHT$GrP{72wnr{5b0adW_F&|JNbBE^(8$Hz>R}>A(NYpbMB>@d&=ou?5w6 zHngij_NWp!E+&kjH?D~RTOE+gt1hzOt05Mb&gXCOL!jpbN9e`pv+g}B=%fJL4G_yM z1hMk#)o=OF%_j=2Up$q%LbPtsYMySL6}AFSgrVorOW>ver>+sNnsBBe0IqKQx8j9$ za=)$Gg(i=Y?LL9gkp9DfA0ISMeQqhH786VuAlrUZ%Yi@?m21FV4G~`3zCt1QeS-qP z;n)@hdFnkgx!R;kPTcym@zi}GJoRS@s#Rl2YgSMJNx;@D;@eU zTMGGmy)i=NxD|8-dcOP)Ts>(#AH;BjTc2|huV!tP!E9R>GY_`UJ8@Z)j~6*D06g4P zWJvOIJ?iwGXPf9%$W2L45Y@Lh;885OwB|o~P4zS-@K*HIE@1d_qeI|eFgztu(H48- zKI(7_ZZP80xhtm;xCnyvxq{r6p1C&Rt!S{JvqFV>&8&8KI7o$W;EzQif!-%23m0DQ+<%B*Fj@Ng80AVhL?`) z&FVwQb7@Up_tP}R>F%cI;a6er8UCfLcXz>tFC5$Mn~hGx<*Rb``s|iVQbCVMqgft? z>9NQnBPKuRYcSj1>J2o%9}fJ7Ha%E!8$D`try2E<-7oj?E-7!o0)K|E0)*fPj?kyP zpD%1`vwx}^20^zdWF;3cerai1Y|@WQ>{Pg`gxLqaC75e<`e6*i+K8$L0R-&HY@uoI zPn8dzr3l4~7f>iN_me|mDNu7@yaez3z%Fo)5yNMs>$@-5S9#B*cG0H9&-ZKPR|d}Yzr{&lha?Om8dP>dq1$~ zQ|1x)zAv`L+Vy@39PHeebx5lv%0?x|mvWf>(EJ7DH?zOAmLC5pf4$}uHtKp7+ynLl`3@U! zxBY!$xduD=!kcpv&0HBaVl75s+;6~a88O=F2n>(i41dPhGrikbP-!JcEbF5RLxCr>!0u!6ZWGuJ&!k^Ne0xGO|~ z^L9c0>;n1>1G38pA3^vc%{%)o>#*rWMLbb($9@`}-#NHbe&hOwZO+AZrl^4T^$UQ7 zE8HAuwEi~s>H0fc+I5F&^ZxOVW8Q7FZJs5S~{sE+6w5$3Ql=o_Z#{k1_Z)pe^%VhpBa|HVK@;!Fqq@5E68(h z&HE%^3ynw^^6v}yR=pzlQ~?A_xK0|%zV-15y2y$$`*fF8iV4YkIAmFGwLO?gr)$4@ zOO|xQJkv$_Z5_~A^zbt(YAn-YBa7AE{84VSc;!%6nZ}q>9#cYk-M#up6kg%{1R)l` zkz9Ov7e(MyWGBnrL5KUhy^K9~XTn7lc>ewVyJUSoBbe?WnKo6LsrF{P^I;!`*#|7u z*-1%s#?s?ET@I8*MZtzy_u!ayVB!|SCi)E^IeJMSN&v`l&fv*ey(`pnx2k5%Z{L=d zM*yhn$2p2O#V-BN!@QKVFvLD+xlFBwhwj3d`}^W=P9z@Rv{G-hF^;9c&e!u0aRAf~ zfJaEME*=3X`f13!+$ZT%<2$N%SAL}%kgiWL7H+kB58O88X@VQGDx&B2SPtF!WMgg8 z>Z9p#d}%gq=yVfJA71i-0dD~O}SgBx8Zkc*+KhycDucR3#b@HJVNhYZiKdx<5UDmDTlLzWwN*kARP7 zH^5)}?ni*O?h*8O-eA_ac|YXl{xVS_sa?=as(qcI5LQWUdW^dnKX!3ts@_io^-vR` zMGip2UIHMv22BDq*c@qq&y3tsu-%^3S=SZz-J3I*>wfc3;HgGfjfSrM$Og}E-`|S5 z&*)e3A@GgU)S#?V20j1V^$(_>?ZrmsSh_|jmp@x*bGuKBHG=jFT26ge6Du~+ppEg&D+AMd`T zy9PXaY+Yz%Z3=s=>A-{UK=7&Nxsq;JZGq23Hj}Aa!0u^H;Mpl!;Dv(5GmHxQkdtn9 zUV#I^zs2rijOd`LYy#TaG3_7Uw}Pit1}t&It~-;t)4a<6$d3pRn?})ht-rh}&w$Aj z94y!k`0&)G0+5YC<3tzXVA%Ct_h}^PY1=CAe<|lIgW~$uJsmV?@Zc^X1b1&B!Ciur z5Fj{#;1(JuKnU(0G-%@zT!U+HcMT8%G|=69I&=Q#)SNo^p1F5wYQD_=w5wLts@iMq zRcr5eJ-;XO7TU4D>e?TkUx&EU3}{+oAB2QMcHpmlm32uuw8y^aYm4Ce{21OB9Gk@{ zYWunuWo^`i12-dH@q-yG?*T812=|ennl7+T%0a+JeO>tCcvSu@;3{7r;G(@+@^(A7 zWvg4lYu$H{{+0XwA^q)SN%X`I&!WSZ)6lJ#v17?Uy;m-dKiy@n>HANY@0ZYDPMhRH z(~tM6Z+{=8B5uiZLE!t64g;Ritou;%SKe^N)4EqlE5D8++AbAA%?C!#u&!85@CBu_ zvgutb{Y0TdEU@8+fcAnk?5l^@yn8JprQk`ql9y(6B+D!7ANC5oY^wc~-~H@-4NEGw zu22MRRz&jz{PLYSSi?gOX({<`7UzD2&R4CZ_lCl1(Zd<}smWi8j&-=7r2c4_0zU>6 z&%k=FKCC&&XnU3#3^)Y)oh=6hY+t=@Kg3@%X|L{F1im?5V1PuQ5d)f%Ha~5s%?@|h z;|?Q7-hJp&TnED&UVDTiT+HsC9yusU5saOBoaA52R9dU{ul(b6 z#`5Ibn}shrO+T#x&n{uP-Y|NstWSvfcTcmR+aUNs!=NyLAj-Bl@`_{RMDZUDcnqz`j2(Tl{Ww2Eh#XAGd*{yKlK4 zy1`=~eCLzt;{qVti$@syY)v4Tg7aB#E)Zh!3LfKT%_BYX?dYkddi#0jJ4yecrrf9N zK#yYkbwT)(c0iySNF~UYxler(76n%$O- zBBUV_V7>=Jt~#@*CXfp#;~qQ!iibl#AM<4UPnYm?>>@X>rIzg=n@SN^zARU)tG`St zww`~Jn^eA`ZtiKSK#YvRfdo+i^yg=h!OD-RX05aj@@P^XNPhxJaZ-BL3^4Mz!DIjI zXq^4vUJp*ccTP(OZ-HI^nlS`9ZVOUyzyO(Wk@98W`^3Mo>@jarZ(3dUBUWdk$t*&R z>oSHWFMr;2iiZ)A?;t@>}FiFrCqKq&>()*JAWef&|X9k#M!d88k3Ko zG7T^gn??)Y)dJdZgN=SD0{Q!=1G{Of&!@a}a3z@KfbtC90FN+Jwg7oT^s1NCQrn;+ zOIRw-pHzOJwqH~i06UTVp$0Pf0TaL4dVkk}s(RRYYWUL_Bj{Np;L6tl_{bnxz3!6N z)cel1FJT5nf7S-_`4aEKZRU>%(Q@38eS(eN-2hQKZ3qd%g=10UOt(j8Df4J0)=4u275Zn&M|VBIKZ-C3N|Jf@gV5tOAaH_C}AxDu8 zi}Mu7{JlWcFy#Q_5u67X%JB_Cb7PU1UKcXnXR?`l-@T6M1U+fm8yAO$t)8t2gmgMl zP_+WR^w5?jJbbBb_48AM^Mm;Fn+=|}!?&^bvz;rVZZkJ@S0gixD|b_=roi-axQ25v z{>fqkKJZ~QPOY$SHx~*ws9tS4M!o;Ne(HlLsd^8r+vS#@wOdVadvDr|KboJMtYlbh zm%f5Ic7^^!6GABe?=>MB${IxAsp=Vy^dD^)_dD;P^V&}*P zq}_S+1?bqy=;cvafd_kl)NP)^=cUH@_6{>Xp&%zDs&J=p0m%X*=X2cY8~2K*?R-^( zZ9yvhj2}V1gKR-Zw#=z|{rn>hXk0w?^ZeOyzO!^N1a4WV0&Y#)05=91T6zV|t;w*c z)4t=&8!8uLo3n9=ri|t^VaPZ)-zMT>dzQbM=pf?vh6Xswu#|G66)6Oe{Vtl#vP&ms({J$9S zni4JFP&W!F3P#vAWbDsLnv~Dv{cq$DhO|!qB!{?V;+TtPq9YqA?ylv~OLjam;7<%h zo+kLCT3O&9kW%&E2+pujEjmqz&8mG%Nyg;2Y!2_-j3$yEZC$XNi^7+3_6$78P$vT{h?|*5l$Ui6Z(?oOY_2t}*c9uyR@0d7cO9J^QOh#XNR{sFb6E&J z%l%MJph-^LfxefE@nIkX3k&qpF;`xj)1%o5)r*ho6W3{%FJSVwCK^k}HEY1dmDz>_ z^#sO^X(SfHC(~?2`PBU9%b$a&K7SBWrk0ImryQYMQ4A+g8-@ZEV#3|$ZESp_T7O}i ze4{%C8d-)nZ#;bNU%P#PE#_Tox8D5=y7`wILh?U!L(B_Os7)?7gv&bE8VCkYdzr>i-mJNLBZ!$CMuMv-1T(MtjTr6L1o~&BNVPf2&rH@-|mxiGp zuas{^CbX*a6jsH~`qhvlxjT9eUwKTscXY_N#xdEdLA8u4VWLDdOOyfJIw`5(7UPnw zN(7y@rKJ7`$VvE!r(UeJ{v2&E6>mt`pCAptSlXc#(?p;bUuJP`pa5g0zHBksqe+)9 ziwe0$x>?Ult8SmXf%`3jjmDeyfe@g&@22w+y74KX-_&@p=yvJB|4xliJ^|+`3>+zm z?az?v$VzVaa_l`b8;CT{>VW%8ODE2E9N}fTiFge3ZdD~G-dVQRuUe7#f8m^%&?BwQ zsjh+i_TK%Jef=8!hh`=&UnYHeV*l`*vgiyjU@wrA!RmY=Gz{0DQ^uLj;Xy8W1)Jd; za8xo?vaTH9g|U7 z#6`5!9zyJQ)uzva?kg828S3-hvhe(IR0RS~bgaXzImZz6@=Hnkp0PAz42TeUeiX;m zUUPwTcu1dNAZ5W8P31f@mfQmw{K`#Tx{VDUPue3xbJ-L{xI)oqX4{X=w&Al>zWvwx z`Lr@=PXgKgt9A&T|HTdwQelMN#72u5Vd6bSyoiOROZYSnz3=hDp^*I*+caC{w!Uy; zvYMeMQ+Hz_o1zh}GJC>D28f-mrz;a*+j~n?z^DV8+;=k{MBuFZT7Z|k1)ZW%hEkVd zsSG_BC&VYot1=k1-}E7PFfZ*M<6s9$QSi!_?^i-6*18C*VUnUx-e@^`jvj{^k3Te< z2(K@k=-CI4x?Cqdm0!eK>6l-7_=v0JgGp@dzVR$Us1{!6HjAcY63fIrZ*=$9IGr`NYp@b%~((abgh;eObn0W7QIN zUrLg|OEN2|^~S=bNm%|!GQDNql{@7Sij&=?MPf108^1M7+@CpF-td+o!lNjOxiBK4 zk8c$HaBM;Y)lq1zxdlGQ3mv^Q{7rDG_n(8FJ^L>++MRJo9QYwPy7$?IrPi=WqS+^T zJ!SgOuW0GU>1fuU6pa`!iM#wnAKJR_-R5;wpCI%>?aXEfsI-_Lth>*4RLOfnZw`8+ z7bH53Q<-o98EzCG6i6f~XxrNS(6*-?c2(`LIvf;<{fgjw9~9}-x&4no&v{96*2Q)k z&*dt!Pf;{Mo8gwhj1JPG@dw@#%E6dBXu+W;LQralrA{l-BCC>iY&?4V&i^CS(}g}l zE9UEqV6_BQ+K|RriGwa^TTkJEHwUaQ38&Hw^&~P*n?YdwmdHs1P18JUsWhOko>96Y zga|XJHhY8Gn@_cY+Sm!rHc(bV)N|^woE5|_Gwg=#Trc$gab4Y`UGV}DM_!yUqM1G2 z*{TRbbDuIFGHNGbr7xbLcN?!QqEsI$VA@({Ynm{drA72|m|K>c;qH~9?JQLAYE<5h zE`&te3NVQhKD+POb!1TxZG6m}{7G!8T&XsRnp zAzK%0Ejkn@^GHkkS=0Bd*D^5$jryBtzldnS*dT@C&6-6QC?8+cWnbO0S0tMC1A~+3 z(-4ttZ+@@I{9>63-A|+|)cq~K3eN}49$e6+K2c&z?2d#9D?j%e`Tl+HQ$|V(GahFf zF{$k%<+&;_MIi6h<#9}gQ}`B1ch8I#%;-&loKRLG&K8Xa&ao5k}mD=P*ALF>X@W>lt-B{`wh~#Q<2$KWz z%(Z1g(wh<}3{na>m~FqEksV={dlL+ZEj;!Ne#ZNs)1K%<#(0X_Q)uJ(#l$F6QpQt# zuJSYLb1nmM)g-)@m{DO%eNiG#j8J#az7<3KSRo!gZdUeZlIl~URIy6Ww>LOO9fUe5 zUGGH2B8CM}+CrLDLM@xO`gZ=pJ#9aXJ|Uyw5v-x)$w~_i!XIuvbZ_0yCG^#2dIsy; zSpPf$eJnkh$AUPta+^=Bb(&~xBk_p>W6nNC#B*gkTQh^z7uGKyPY0Y7@N`?JF;(&)E!00|5@}e;}|TF-OTikjh>RX^k~dHdc-z_p?iU=wo5Gf z*0|3s7az?SN)N4?9luyKpH$Eb6&eWGG{_2n!_nj9!JAO)*;s2lg<8L?Kq}y$VA%njp8oAb#3bNg7r!&~r8|%WXpm?j41n@S>___FU!6 z9aG;K0wlOw<+%@jCIcm8aC2jh_f>eoj*IB)C4&lbgZ)CR8Wb zb!cFl%C;Pu|G@fmmy9Xv(x+(5f}lRjV%o);tI0iDiQ*=Uk{LG2WyR@vx&@w?_-(6h z20KgCA$IxH(1c~liL2u8*1S?L-xYf2Se3UyN;50bWb^abGy8feaXO&b!4Lhb?_@XV z4{$Y}vxe}0nzA77@j%+17{3K5;*JWshU*0%QjAI3-v@a%*qm7LpL$@O_G9zB^S+=q zF|bn3+2mB!dg9K}m$H8>>_Tf_R%tAkV(_N;W{iJK?7g)nbPLy_hSwSGEd7-hlK$Qj z?`z#Leq8$S3nhQIjf$n6r+nHU7W4#yO-d8)&MuQ5S~ycf?;U0C0}wdMyp11A&mR4| z8v@?>h4@bqph6x2>Q;7rIDoIVj>j4vJ9IE!$QQz!P)LUiQ1a)3pH9R!O*u=0=N$to zwq|!On~rdOV<_ug?ZDFbbovDYmlHFCUCLNNTwX1ZtEA_08YM6{hg#P^rRsBT79_hkOE=*ebz4%km^`ij`9h_E zC9syvJtcWym_@!pma20@YUA_h+wY8N_#sjH-a5l^MAic z00s5sP^t*LzzhJHlJ`L`&F_9Ijwt9i=Uons1+mo2ptV0^I|;bXY0if#>-klYC`X}`1^T_ zkm-RKcIL0!6z~lT8ypdBt{)osGbswtVQjDI#QSWPp}1bv0Vl=~f+q^$VhVwaV*7F0 zY|i^Rs>O^<(G1VO@1RGr$W`qlAXmcWHxvh!@QAZkvE*Y=zyJQ7X4{{_+Vn;4*^Ipy zZ<@MPjWVYi4K<^6L0MvJiI}to7+P+Ov&A3LYc8tWMIS_eDl1?onJYc_D=}j27&Ml) zr`Hc)v`i8KgZp57%sE&1Lyia4N8m%eE{?f?3@Ia77J4H=WATL8j(Yug*payp9CY}1 zHALh6Yclz#-;pI-RHRF!eTz!bf7(TG@cpr5lTIc{pU)83gqt=|w+mU=C8w zmhZsM;(x|@U`;+5ad%@*wR?J)$F-ZIi11%d$DVI*}URKdlSu3`PduO5; zHDVFhFx8LS@yR+lLt`TLxA-&yk+yL{;a5$M*$RjyJl+&v3Wi5+jO~?K)=>u&D<^o~ zqP=Rp%{G{x8f!nGSL2$ow<$oOPh+(cp{Z0RJQR!?CKk44nqoX{S5aKgR;tH6;4fN! z?PN!t)YKWiDX-02kJpY9&QrTX(4iB`m%pv>#EFEd9gSW#rdwP5*AGjg8}?rTu1sm# z(WF)+4nyOr4he}nqRMPfP*XHDG!jOUDg)K%Mq?^!^zIr{oDKPO#$2$+J?x*UH zR3TJgd|TQgI&H&G345p;Jm?aoJ&HNMYJL+EUSP@=M0e`0b3~FL%dN~X-TaoM{3DhK za7g$?cx)NxT2di^uwv%OZtm(|5l~5N_x~pdsPDl4e+Ve#1fJ{Qj$J>y4cqruM3BG3 z`M8f{(~x3_KK6_jV14b!px(>y_uMRDnV%6v)6yE|*xl_}H(57Q^mv49y^)MT4S3cr z(#OO%uRM9GOdPa*xZXngNFA)-0zV#u`z9ONYNQK!8;tS8TZk zkt`Op=aC0#=N*AEzjQ|BPE7|-Cs>;u)yb~A5qRWQ1&*@w*E-e98$%dJZ zP#I?1sq10Ms>`2VLTZB8&u^1uc)eqy;z9#A#i$71CA61WNWaJB8vEz~)YYR;?!e}~ z+bs5gZ8$+=R~C@YgOV-{JeWo@v0RX0KrKZ^Z5`0X>${|PLb*mNbbuR#flypYXpHe> z`Y>-DI3w}`;Ox15Z4zfkvn&+;X?v4!(^FB=*q%Ib=i8OL)|*iQK9NLkz1+9-m8n9$ ztObj!tm&$senOt@e$N@IS?Wo?*61O}IuxiA95G3ecuVKtzV|U_FEZgzk#|dzO`6yd$b;7_|`7aP`QR@I)NyN8O)bNr&$CP zbRP_YJ-a@E>BM_MYk&j6D73`R5x{(>iy#|Lhv%{&5KS1?Fx86ZFy_%tTHfu~)yr9j zB)lIGDA*AAGO7R~#u=GEX(mEk!FkevOacMKTbnJg(sUYF!aN9!@{3R@fgLrdgpBAZ zA;U|}DeYPYKFOh{l0}gBR<<{21FHbd3=l*?oC_=lLmYOyypn&gB-Kfhp*`fNBdDMd z%SqFkO~^W_x2F*9l7l&O9TO{tr^9vmZb2`$;m+3#CrgX3_yJWWq5O0I<2736>M`Q= zCGz5NM2h~IxUb@|BDhIXB>p!Wd_W43^Gkx<_hFOsscI)+3a<^3KVAGmaMoN6u>Agc zW=ddMqVmv~t#wu1k`*_$;u>>}2s5Q)$01D%C(zmOuDrLMY@3#_eq zP&!z*Z(&w=EI0Vx*|ld&=xd8c`6s?;NF30huSTXAZU-Z}rdmzlZN}g>O=&ha3~WMfiFFzShr)CG_j0ite_ne{xmwNZm|+SGeu{ zyqWR5L;Y>8^Ei7sT)5szqZbOh>W%-d`)#Rl_}i=H)BrqA->&u&>)pft3Fk;FPKr+6 zj2&{Ez5P9HzK`w-~nvsGrL?bBL-CiL>Eu( z=v9zBQsc^@gu6qp96YwKJb|+ohnNV1GW-eU%NKDZEb(NNFE^EqF*!dmZ)3th&yfc+ zxK;ZA=w9y~9HHkoPX8_XXUFd=+sngzrL(B#Ri7@uDrV_Zq9HHK$L9puok95zW>Fvf E55MRUApigX literal 19793 zcmd?QWmg zagpu#c$s$%bi`{}7MyYh1)5r$D0feOowbY@^ow577Z>`!&_3Mhd6F+! zru>@Qh@K-QL5~NM{@T{l#o#6(mC{cI9@ndmYR-f^_7!8L{V7X3JC3@~yU&s9cSmYM z%`pm9PBr@@LG;*}g)eUPcEK1*dtfyB!XJ8828pst%)#ktBqiZ&3NOkqXAzlasBd<0 zJ;a^Q9ybM6a?5hesbe8v1|Fhm5MEx~3u2B4WCQ+9n%oDq13IjxDaeT-^d&T{lQ5l7 zCw0FR!WJb7mLlbs+q}`U`f$w9@5;nWCDwNcS*fMUK6$-%pT3@aRDIN}#H&oX@Uh#Y z7MuNjf4-Qc36LS z%eebCR^!>Fv<#ApqYkdVDGdj;p`AjCaU%h8tZ`|;Iv$+Q18rRir~^!VdUTQ2PPI;<3fi=-b&! zlgrqIqo@FPb^HX(%%KE=4rmc0=&+`u%m8)Z@%RsZo7Ss}x6%{d&< z2Gm272sf0^LQUUz&(`R1cFQA%94BNk3XR6>sS__(Xd zd9dLLnD!bJ38(X3)TubIwofdD*D}qz+7}BP-lJpW;mdki1kM@;_CtdU<07k0?(f=YiGqh4Ay*q4onCkMhZFDOPrClK;H~A+A!3(TKyDk=gjO}kQkUx6 z>&rZFJ*=zevDPeO3$$h7^w64C&EiD_Gt+#Dpu-!N1Cm7RjrYJGZuF9&fWA2w%z1^c z$9tyd@^bf}$Pz9X4U{oonsM-uBTTma?B`PzFgT=UCIxp6mb{iS?T9Yn^6IY`X%@x@+A& zMcq)^yeW3%*uGvbGMZAd5ZDe>ztI~EMdIh?-*Eqa`wzfVLV(4lB9Wynyf=E+IEeom zwCXA%b>0<#sh{p^8#2IHA2#&!i4kc<&&`Q$^pfDt|2?3yCRX?CCaqZ(X&Jms!dN?-eSe7X|uv~e|4=8U1o@@35#5zHj>*e1(MQKY6>Z7V=!8()J|DGS0wm_GJWW#|WVBt;SoZngW{1Nju7u>*_@6zD9NG$UV`(O3D(gTcRS zYyBT{rMAzVBwnupjh1({u)gG)He`Xyb1}ags;gSj8rSXUJl8I_{grjsnQP>p=cC~2 zyKi$-_n8i6U&p!|+*bH&iS!VD+tq1eeln%7Uf4DSGT^dNu;KH2%hl1x_N+|M|9-8u z<7%ig03J&WhL+B4hyd!IkCwwo+lS1PeWwl^zm1k;jfa(#+z?h?_e9{_Dab2VLgY6@ zYycp?eu`z!!gaf$apzE4<{Am;$>jNc5_~-`=GOE!N3UnU-MPzpyZvZ)o?zo)l{Mfd zYk{^}{Nkz=8VA0Jp4?~yZ=ajq%#8g65T%`_$mXlvGB7OmaK;n71U^ol1h(Isx-FEX z^_-m7`b9(UYI`2ML1Y?U7JD0}0ejq!W$S)+uy!zfrZ%qTU_b-ne5Z1!19F`knCx`B zo^kg56%FdVn;nhsx(#ad!CTi@haPYTYzJJ|3+5rZK{WuZThOW&Hc=1$s&4kLGyG*b zU57rvAKha2ic$w(E=lL>z0Qi}POuYYX2Eh3+YaZ4~{Lzy}O}lB(66VlRYE1gX=yLQ{=5DiV?%;fA{#>N#x3>g*03yYHS6S-J$$4=`j__%N~D~k0ED}AlIxv68)O# zGXaUh$o4@=#`SN2*YOz%*$04u`SEEbnz%88vDAH^F(dh9g#eKqq#{+8(3{L3xs|-x zM}c3@A`u!VK=wIM5I+8KEz{Yq_U*xIz}v$Hk@jN{sF$QewNFmU>(lFoy-s#_HPRa3OEm8IWWrnMLvQ@rw;O0uf2i?yld$wB>GbZr|#YgSw zT8!2UwJD`IitAw(x0D7eoGGj9WxP_Qt3IQQnBO~DjTaats)-iD$bwCkbZUcMRo+|G znd12Lviu&bA8j&X)e>+jw(;9t9u8??<03}V}leY@Sm%R7Q`;b4b zWKU~8O}S|I^@UOftVaShsgy3C^pmO#o?0A3xlqJ&PMfLXFhN@b1yR-kT9%BF85#9_ z&>JV_YuuyuaxHt0yaNGjF&95b9IkZjg@jn^k+4s!fF{brFqtIWUv{CkeGa?DVEW|m zWj)TU&}_d^NUMn;CRs}a?mI~`r^*BQ$g8wZmTw0Ji~1bu1{SPjv<`K>m29;tI4o>S z)NLeF=Bg?vp$@OzB8ndcotN63w^J`Ar@S^!7#eKUD${5S_qqh3W^B1X&OrSBAMCA^ zL>vPioNwFomlO`@fkF``WHJeR-Uyhwj`}K|B>+v4WF+~W+Vp|uMb@-&@ft3LyK`aO ze5R2fi%89%Y7jOdUoSjDX)RBVq-a{StS69VZ;Ar%FZtB(FO=JghtUzTAvFg~orUTI z_fk{r>t#k{Ub3Ruk+{vgHIBCCneVQ8H!g;-X#_fiKF{L`>HD~$3{G+W)7Kt8CT@}z z@8(mD5jn5K6+!80Q6*;h{MN7KBp8I%Xs}I;k~|RBZuw|ega}bQ4e3zO)T!QRzK%KF zer)-&>OQaTO3@`4ZS}^gck)!+nWyvHNms$iGSYaAM}EZnQ;i4@`X4nE+}CbF8q>ay z1&4g)pm%D|=~!m7Lo5C*3=J*rlDuaKG#sgBIWoSGul8x)Nmo3`XvC;tTYwz$z4~s0 zX%LU*yYhk(TSOiDas#9GmZw558&7cvUqN)r(g3fM0M>Yup$#1=D%sn)5 zN^vrWYtks~lPmDn4C@$Ucqr}riCoDB3-U*GjU{N$r@q5|XNxDKI=nEROqlq`w}(kQ z<$q6pYBCBr^%+-DRCr)eJCM52&b%~UsqJf|-!<)ic@VQXjDPmVdLs8ZWpPx%cl8TO zc^lHI(=QC)ili9&clM(VHnSWp2=qMYI9(5nc+<)R4%?xTN_)=@FLF%!sgw1>9CoZ) zt-NnGDTLdl%kHt_m&+4}f@O7hP}7ANJGkC@D`|879nor6p31R~K z`JY~K4WRclz3x){PFv|h_ksoA@bhP#e9vL2Z4^q1)Q zHEQp5BJ$vJ?ZMgmns)W76qp(Ck3(g&?#X@45wh%@G$JDqh4+85_Y!|U?ej(619@G* zd$I#vq|_d@Z>B0nV!l(G$G0`?YxB)CDYT)Rykp#6>1Iv5eQx?>Ge@hxd!suF&W1Ir z$bb2baBgA5j1uyCI_6&EWAT5RqTL^W`Y{S=>#NE?Mrk~O`w)@wiau5pjgyJeMVSkH-`9%w*E3s7 zs}D=5RAF^1F80E-sXi#l+QNB~JnG`yV{5`LUt&Y=950ON3h$Fl2QI`oUZ76@#Wk{< zBJ7Ccr|LPLqWg7%W+YT-?PQ1lJAI(xUPNvM=hu!gF1{{jd*4-Tb(WM(a_u9^dmZ~P z2b&$w1nV=&HpK#337xnvh-4=&C~QEeGQHFLCx&jy>DTCFO9UM~*3#2EgCCNHDpZrj z#~E)dT$2Sl_G%i{@Cj)gPX4+bla5EscA=2w<3)_Vh92;|a<@m55D4B5zckSduV@L& zAdW+}I0-RhEe$h^uD;ZqVJ+m@ydI&G>)sY&m%AvaGn)-Ew&(pC_{?Ta7bh%QGar>0xXdy*73Z{fRZF zwWl0ao7{YbXXG$m`XIAa^yPE}{lj~YY=3jHeBUMKTEoy|&fgB7p3AKtwOLugF20k*2u)=-g@^rUSY)IN7(Vpb18-Hi*pm9`_IL-wc9sPqunL#x zgC|3WhOU0wXb&$2(+EVx3?w-5Xokq}X1tyim7ZDxuFDG^Rg?ht3qbaR9SER;Lv|3{ zmy%ULYhSgx@5G>0EPn9NuZ6nj+hDw%U(l%SB!V6M?w@+AioYR!^oR28D5Z~+;dU4p zpDlMKGdtDbrM;EB1Vdf?dD-Wb1FHP_ym%)*0}hsN&|*cK45d7tJnoQ~|#lrg8dqsu*4fX$@F%;3hl3sB))1E*InBmbn8%4Qx;5b?ku3+N%UO> zvj4hU=@gqOW5w|Tjv&`~kcq%*lI647rSY|OjpuaFza&e}qxOF$2_k-BL|>*?JD;g& z)HFnZd)@yxDI<_3zu!l3fT;reSy^_Hk?4;jlUAoEs;D@6GxB~pE;>ZmsjaIss&nXhS+RZ!;qLd3T}t^%6@xqd z;Tub#s4I1&zm2D;ZH;1;VI1UX7pyWd!7Gfv*gsz)@(DZRO!zXQExna-S*-H3mWCm& z_7)N7*@54v7F%?)Xg%KksMa~%ZROWo-q^(ZiamqibSx>FpdCm9g}xLQ}2#!7=8e*lYqR6%6ZWWiZ|+7rQeQVOC<+dl{tOSYF>l>oj zsSfTj5i<9uj1jsFnAGDrdZPqb2a&#u8WhZoaU}gTyo;Y3s{izzEEfc;PKaud>iJ6UbB^UMCjsE|({ zY@uoO2amkOW;KV!0_$I{Iqs6!xdF#X}j^n0ip-#iWl`&_H6Vo4!OPFbgWmb&Di z<3pq2wGx8fdiQxnX<}@#pqR+_!ypa;RfACbqAv$MX52->dxh_kloOk+MA08kzhTi( zMi2=qklb0tZkJb!f~;SL#x@}tNEe_W1}}ZqiQ2;Uclmc z-DfoF&d-DdR&@@vTt<>J`Kuuc{iMIGhss6sNixZ1wwQl!LM$8bbIUsPTs3XcSdXyS zWJ^WP=Cgtlai;aR50$gY_qOUV(;Yo3En;0GsK*RH*;P;e5O0fko)wDwOIm0x%Lexi zh14tGAwrUIRC+Wv+We*{rc3G8$ouEKn0YO3QVzUY#58Yb>eE-Y#(VPwSl>PLyJmk; zT(lgK8}Ks>as2SbUKnys^=xP>V{MDac7EjXIU3-ddT1CFLiCMl{MFDUd+zOSwQKD7 zAx5Lii|fpBdOC9@!d&ZeUk)i$P$?xIgA}qr)rBLDaNYgL_$*&Wg~5pM+8w_U)#4-O z3`6gF$iEk*syx;+Lw4oPOMDHP1*N6C*l0Lrn9ulvwC1_M6oEXK*ACE1Oa4XH;O}Kj zkB#AfUe49Cu*bLsR|G#$Kd(&RwA3zd2crb5Ef+`cCmBu!;>fq4uH#zbG$nD_ay=_x z`zrF0OHro2i3I@Wrw5QnX=N_q-tg^ZmXGKOXLuL5d5}gxrW6kNIm{#(+)GI=qkdU54)zn*c6(@ zP!?YyL6XamPcx_^B@~unI}~m_$5+PNoHCE;_fgun0IT9b-hz@sz-#D_6)z2dp*op6 zf)ViP-gC&d3y=HY;x+R92+Y9wce6>>ZtjQ<+*aBl7QEAx;km~MWPV+bp(?t2h5w6y z(-ZSw%+b>M%U&L`HJvh<>4!{5!`5e&$N9v@95H3SIM>3%?KI+nNM z|JwY4bVi_hO#FgC$~X1~H8ee4TpvWu5fRvVc1XA^%iNjY({ z+c@fq-MB%7(ix(_z;j$qF4+*SG_xCr{{l{?%e_QlO7hB%g;xqacejQAAT-NA^$$); z?`q}Ok{KTpnPIr|N=yE)ogmSlfXc(G2Qvfh|IsT8py22gRH2nzNGc8Z55a4x=mDwJ z)K&@(ao#V~#(s@V5p~(DzStb4gt#LyXB5|jjSw1A#v`V9wy;PV^KORbB?-la$oBBxHm$bT>Wc0V@b6Y@sjgpmr8s{IKtaDX7%?{3Ap`MjwZ!QLw`o zJ&oJsjg~fZ6|d+@7aJ*8e;TE3{2}{oF_FSj#e40bUNI=-H+urS8%)O8%gT>d{D%C~Z zwu8=9yM0{MhxyCnI6)zdaQjG5q;sGx(zPa_GQH(!J2lx>eR^MK`nAgKWV_P_{RL9n zjC&ZH$EY#v=6!(Z;cH$I~XM`qp%`kGb>lJ`G4f+1S9B9ti2OTX!bvSc)9wwWhC_6`>Z2gH$kiKL4?N>}n{y?VJi#~F2C6n>C z7fyAAq$16dc*~1iz2x=p8qw)^@q=Seq!LxEP*mx7W1ZT##&+-Z1Nixs40=J9)(ev)HtmhXI2& z`Z8ma3Sw01Q-#DAx|;6=1(jDbu;qis$1=!-(#a+2TE{SW`_ukDoXOpDlxX>(G5JlD z4Pbd52vnn*$ z|7O>VC7yfs!Q*EJt5o>U_g^4Ti)rpUlQhfso|ezQdkW}%${fJ9VN~Pgg7oG&1?35f!KD_GFuz$iEg_3vf``pzm7q zz!$_M6yTF7!=e&w_-AH17>*Z9DoEK>5`1Oah0YmPJN<&2BH~MY#A+TbuK3YN$auZ_ zc_lra>|K7zLNF*po$&i>v(9gv-bxoe?VmSCI4}pZjR`eb)~jdZQ(bvzH@SNTUf+HDwdM{1Cels;xIc1og|2lvvj7V4CU{RR@3Z-gczKiqCKYD zy-WD|H!t+w)2zG>#*Hrlh4QcT>MVWEaF^9Jd{Z(Ybt{h@Wnto5J5UTMS=jpOSo2Gu z&i!+Uo=|rY=XtOJMQ@}u71pvmmecQht4Wj%Se%WZA$g1u;SJ7BOqL_YYDfxJ_lOMV z_0Z_RKBqaJgyr9g&m(5Tk&6_DvQ9t9dH#9VTfK~7YnkDXvX)s~!CP7RrVuJ7H>9{S z5wX5*polj>flc@7Gsnfn$KVqVl9iz;{a$MK*QB*b``yc4_Rr&zpTA~KWB*J#b6)5= z3G+T^Ww@ce88N(>R;2g@!YgYg{1ldAfDPd!?^#W@&pW4dlKR}C(+J|p;`!s{Bs{b{ zf0TsTox0uG!}~{<4aeU8q7Yt}E8&CqQJ@{|`l*lLeH&9g@DRo z$|9KL{IkwJbtk5)W9r$B`7ZlUe}ls*z95SHM8Luzk0;Y0Ooone$_{AlT?g383t8S) zzIiF7%_jzMdILaWxGv0jvB#0h>tB|D(^9+Ra6?WPo4Xg#N*@^u&tp@O1Y3Q7{gjYI zzb#5cs5K#p@eFtK!tck{6j&e9toWw@+=u{LC|F_%ASFB*S^%F)(TTc$auKaE6;%|c zBW5Y;%Ira3ZhQcd_N!qf$OYzaP}c9sCiw(e@{!T=JM>_=WNEnkL_W z$<;9Au_?~k=-h9e+m8)H&N0HoCGLayI!LC92J4gmvXh02dq9OZzc=8(!Sg<)gX?Dk z4*4P`qw%}mThRB%d8VZlSy_O$KnIz2*gGk3Z~{0Kz^mYJ4THpF7(^fC=j{jwH?xUd z)5{2)r_tykI6Ud1M*zi!q#f|TKwVfPc|C_OUCs@Jqjeul@mi)~Ww?RpRYx%dPI6Y1OLR#@jQ|mX32)Bd1mPgdXyu>5lF| z*oN9K(J{+QRCKryY#^Fu?Eg(;GTw*r)cBU{@)l^_GW$U+@sJuHw_;|d(|$rwRC|8t z7@8z5IxJ}6I}F`nDDOUVy2W#AT~xdTunq}HF{F^?iVm-7IuGLX z&H7-25m0^!{6c%`K%&R`y2o1M*|%9BYEd5`wu-F=*hgVtj(n}6e3h4^Tf5 z9hqy7Q)|dbR3;pB|JKbFb8WidR$ooN>>FDj{`3!I45-{Z*|7l3*N^xDcOJOB*B~VZ zxXIGxkPp&RytnmV0S(fp!-Fjc4<2I4DPi}-lHsbB{U=i91u<;+ncH6=Kix#&uG1HQ zNP!KKUIL)!2^g_^eSh_$Q;lcm3Ka=19|Yn!R5GS)X)xs71MwQ^GstV{1na{h z(TDhDFlX)Sr@X3Uy*Z_TY6tz1VV|b97C&DF+v1x+R1AYD{*v;;LU!gW;67A%H?RBg zE8WJ6mO%+Jb3liomPji2tr9|kgPtn&(@u0H16m|WmFDahmBJ2&<0xf3`#!dhGWBZ0 ziNG-Hcn^|^WIG-Rfr3&2Jq(7tL9k2j$qjiLKiZ-=f>bJmE&-{W1Vm%lltDOtCcxij zjvsPk|Cdx1K5+i|18Iw-`whvQdP)d~Am^i&z`8*40nldt9Jcbd8kqY}G+a8Y{h0uS z9|Ok7=0W7}v&I=S6Lx^R4u~pce)@~!Qb5WTAPJ~EP5OV`J~cmlTM8H;G@eqs!Apl` zFOuPARATi9+_lrJOudvalUUbqqn<2Wblulz#vZJOrG-F9NUIHEQxVdu$Omk#60_-oh@)|{8XryyY{SZW%7jf>KIj_tFd=?e_GkGOIn z%3%g|s-rwt4caG_gs5hhf@X-;Oy@b^BW-`+(|0`=YC0+|Aa`D2F7KenlQtE5tnKnS zMT&HPSu??EO^0;tXr1@$dFO$)JeIi6d*E?yx=?DsbJEMI0OwFFJI_6 zZAiF3rpAarY){tsKbSl!oIm^>+o(U!Ywh`$W_^CWA#qx*ft(!yybl1yInmi@QI0q$Mcy}XJPKpaR)oC%ahLTK2=hqbT=(LplpDw56LJQ7#L>fs2 zKQiq~St1e8f6Ab@n)>fOC`4D~`3tiQ`{zpmt zfXj(9*rmnyc7ohb;O^%Da0C-I^@XLe^oSrECUHS4FvEwN%pK~fkTDP^Ddwy3Q1?4b7_c{ncm+wRXAM?B@2}iOuSn0glTehP%&0xxLSU zq2a6A_T#)B*M+K{&+MKiEptkU9fMjh&m)V=@#PBA=Ii4KhNuHL{S9#2eWU<>^xxMI zT)Qv0T+2iH9o?>&x|eynKTtv1?wq#phG`HGI^;aCa@R5}!BdEoxivHOLQK@}=XReL zq~%KK`b5z#@Yo{qO!8B0BDH6tQte+wG-Iv~z{as)suP$@jW6N9Igbz7FaQ#Tj3n1G!ZI#RqO zB^VIc2`*R){<7FW&>>k$$@$S>{)@?c$5D|e0Qv|J^hiFD252gmKsB+1*V^|xaS{JD z4?^Uy5iNWzjY~-lVX=*KE>^RzQ)aR$b`8n}S^;Rw0&s~z2oidu_cTCH-|z_$uI5uP zqJyW1r|H!Z+$dIqG#57hzQi^y_nK+D%hTUn>vC2n zzUSdlK^zudE8#ZDTKhBG9|^0s^c@dZZ?h;-Na+1&K7q)St0ec}L1KJ`MpynU57Qm5 z@|I#xmwOXch)GwyI}XTQ14)&8|506gtpLRTc-I!P?WqS}2nU&UFnCN(N{GAv8=C+h zZEV1fu1)LqFLJkWi5KEbT+h&K{lnmsyLZ{wn`61i&CZ&7krkj7%zS;ELi_C*>UcY_ z@%y;aF4M>KEoG&Jop#G<;ZM5;V@>|P=_olZonJMugLJ@Q5s*jZ{m2GL2&oM{)=vMu zj}vi$IzZ8EBlEi9I>&i8s@F1k_m)R?p725dI_Z1n?Ap=^Xyid2`!!6G1N!F2BoG6w z5M{q}(C;jeL;`H<>G-))&G6=DXc$J!iAWf3>;ZSZu+LMtQDqWu~-VqHy62;PV@dxuoLYg2fg^ zJR2!p)eJ)ntp;==<7Xe(DW|Vy0fn7B2=L+e{`Wy39wYM#JmmO9R@mmb3@+(l0%+VcZCKfft` zO>SL@hhab3@|pB1iGw7z%etb0v8to^Xe=2ZUfBS+cI6mGpMalOD5&ZhwsF8_hIq89 zgDn8#+qH&vaAJMRac`u8=WwvcoWd|1BF2PFBL+aUy$H~;3ouzKekbaSWp(-l0Wc{?jdW#YDoQI-u>Ik?5o02=S`8$Gc2WiB2o z@~EqN0a%!$>j5{k)*Xc??{2$39Ci=?H^JqKwOMtrV5Xgr6%}5S-&23=uvi}fL1G-(y6T97U zM*Q2|@Y?`7AODnr>W9SEPDvG1hGaX44I=N-+X7Prs9 zZfz|&mf#tezxCK2d|2ZS>EGyfY|RZ!*#^LZin#ChYGdAlZc@sqA5}ZHaUHk+=$Sf} zfUhOM7X{AUb@0q&Q_qF?nulRfz(K`%2Xs-?tZlyso-<*+TVFH@y2vJuKVS2ExF-d* zor@R6pBkM?00=^;7n*ICPl@gRC81j`5IzPpl;cHN*i|}xvR7xr_GYeSk9re)^ETCq zLjz$pgA9j4l2Wq)YQlVYt;_@X3Q8UXhg|+J>)D^4oBS|=IQ@gg<$KF%yMaL5CH$`V zEgHU#7^uwyf<2O*ABHEDr~)4A=O%=y%-(jryBj~gGefQsb9cdh`%a2+dj5eR&mOBJ zcrJfVOZ{EVH0j)1BqR{uu59G4F?9hdq+tv3$jxjS#Bko%tDA?7w|e(q+SUH`bG;tqd@s^SZvjrt>=J1pAzfI$s6cHl62qY-@1yo|$?4Ds`MRIaKnY z9?0H@NaxpT#(q>Q)1Kv%s;}T6Cy<+u@{y`5=QwQWP(213rg{Lg->lFltbqw|&aL+t zOLep*3{wisqn)!H0YiVL3~>iB;ZW4ltNQGnAa_G7>Ets8uAby?ea?QLImyNQH(UZcl=iyM_mqiHGWS9?Lx=J!YP zP6H~gqN^RY)3ImTB|ArCP}hKzOyLz_?JD0X+TXHhYAiGrYsQ30?gn=bvnq|REX(6w zuKQQEs&?bRX{CN_`phPu4#-j}6F>x($lr|${hO(mj|T?;dn}xg3zZ3> z!yfr`Mv65W670<@+T5t5p-a2_*POO`X_Dnk;^lu^tAxqVEQTumfuz3N zK>4p`VtSHcLTIq;FEQrE-G9k^{btziNnrU^=G%IT@lUj3QfM)fiR`HXzrT{yCgYBo?tf|lUJ}sTZuFTBuMBLeJ^_x$B9j5W z*Wh?Y0ftAAR|xC->l)ZWgv>c*T{H` zjuKGK%ONGej}fTvefIxy&%_#-Y!tkK)+ApHHA&W*@LuqcTpw}xW7BK^D`J3*`Hdd` zy9yu%gYpr$Q}9HR|JDewDtV_u|6D&1*f1$HPn8fEg}mmQ1YXFB0a%%;6Hin8yt?D8 z5;}SR%wX+3?D4IH@9D}NJ|)=mn&A+cN7D(OBg}RG1tm0b-P|hLNC2<7q)?f`e{AQ7 zZrrA3<=zQGRv=q(^;Ka*3&xMZMrSEGD>dG+ZAbf5&fPm(RJ?Oo^K<}$ChqY$VqWWc zn!}8!8>stm;5=Y1MQ|Ou?VNpIC0LD|fAH*Bp%c9YjV)M#JZJcyaJ(G9-y1bI)MAMi zs1I~nE!My49COGjn3oGbIh0!4C9$!WexiKnVu<*@w2r;{7pbrg{Mj`N;r_4g!mS&4 z)U5U!OJQPNJpkyl5UmQgCa}b(_d4yT=OWW;Xie>Y5bMhyR3gEVS%Kf5AA=rV9fJzX zUqd1Ffm6&0HDf^3Sqe#3fzw>ivSf zcbUws(h3o$?Ym5unDHsO2OzBjWn`K@ItNE7^6h_rqoWM?7ad5h4p#b*1UKWD5=W4m zp?7_S?aK*2>8W^6et^gQ%tQ!bEu5$ERHwp`iNuS*3B`DPQsG{oKhsj-zq!3;y3o zZPcgmHv>8J)<=&pbj8eoK;4LU1e|=r-;G>p49D~52%RSy;=ZYeiL3G^mW5|ag0)wb z_ef{mK9tQKN&Izu;q~u6fQB^Gbj#n%Y0mA2(lH<9DxNd&{xPD)%&m&(OJng;(Y7UV zxl8%?R!nD6Cwz`CM5LbrIaam*#=hL;mK)Lyi&O|%pbxKA3M-wic1@Zn{b^*qrX*y9wvb6n-6#ilsV5|^F-sRW&tna#c{MdJNRo<@|Fax2GjD3y7z z0)KMJ-#lW|?3iiW?tx|bmw;Dz9O3Yy$PJX27*|%R$$TOt@KQcd_#%TmU9gyd^= zX|!@&M0^t>CbhRVUYY3SOY42?|G$)|Jym}qYOM*qEQ$VysQt4F~xiCT*P zKT%tU@^DwKloR57#)23)=s4z<{{=&$p)9BSf-#b^=@9LL?w9OxwVlg5s&7bkO$hw>8UL}RU z;_9)1_9>()tu zH1Rc8a|$h;^sk2L6h=o1I6-(gmqknJbvcz&j)`Eu7{$HyRNy zzELSH_*hhqS1C!lREL*p_yZS%>Yr6R+X(wgAN51(d?j{ERNtQNo!YPk(=V6DpHsmE z$o%JS_7^z4Oc{;rw=cv7XmMU5;o4sXJOf&G!x&D!WgEtYm(cdU)|p^^5u-IZ=IW_V zZN#+YPbIbx&el`Bz#~S8CmZPA)UQi<6H;UOC+uA0M&aFKFIn8@3?s@A-n}}G9`(Q9 zq&bDto`vYBIMw-C9@``=?Ho(=s-{p z=k$B^UshcsL*JxmOde5C6HLSgd|$rC7YVfaLZOi^UtfN%s?CEl@L{afCxc<}mtHaDwO<^DFs<{G}Hv;GKIMn*h!C`E9Ip>CvDYVBrwE#0O^c`%z7kL2q9g z*GL$H<<9+hsw3+a;4R`8M%1Bfq8;b-y-uV8^Bnfx$GkF;7@c-A!(~n6;J(1U+4f7r z(yhR=Lvc;i0_!Mp2tx@+%SiafSA5+?7W~Q}PP5u-v>aCXmbX#TVi2`1K6!Ed>99J_|%_zAvb+f7bMB?p=#{xGLi|Pcaq#>}G;0 z3l22yh5I?+Jv)W?RSXun2e&PBBH*?Ls_Vc@!X_A)YW1jTUsZK}g#NQJj`Xz!+&QSL z&D6!XAt<{b4H`wI^dLyVw8lo>>kd5dROh2O{-joPYmM9>(bp;MOK;svzBaK;ufWu{ z2{Li^a#Xd>cL~>hTdv;QD4Q}^DJR7wL*>}>$;fNTxMe4`qhy_gT3A!nJPA&8s|~^K z2&3AqBz&H3x`+7^E~aUw)e&z))@T;-vZE96@g7Bg5;wGK6>F=gd4#@(mB-o1KFPqL zZT$GXxx2^YK;jivxO5Y$8P}%+GK~VT2*?evn@2I=W{d z=GR1X`oWk#|In-F7_pzD^{k>FZT%2B(7F^cj)y@%5~zXEF7v5AR#Pz#D=-sazrTBv zf?E^&sh0gquBX^ z4&fiWkLOD+CgCO*pAI=XvtS*O0#_VO^Q+Q^+T7n`qL9X+->PGt#kU_g==>$xIhJl4 z*)7_rL#Uo{ZpL09evnjM6?ADv3F7dvr=%O-Ux;S$m{SQDWZ-3Pf2sPQl(+luCDJP5 z!&jqXAq>tQx*Lmy`e+t67Y+@jzR(l5H;?yv>YVtAfy7fzXe;yfGeu6{8=5P-;VqcT zk5qoB&n-G2CDrb;OP2Rn;hKfE3N|#V_OaaWQ`bU-43>sIKf}$8srRPdnWcnk&|cRw zNK^-#1`EBr_|{(^a^Rh~p3tYkaDe~7b=@CKLUt7arZ~2xiZw%**eUO<_S(DkQLDT01J@4y z`ufjeJbuOQ%!>9`s)ufu*pcg+Yz3D=S$mqHRVaB+!`d{bGisfk$7gKG)bdA{T6k3HskRgMlIK*KgXlP{dsb zA90Fg2NR{+WH71JTAGE^O+;rWsm|8^7@egp-rG1DEx+d*TtUsH5KF9J$h%RvfFyXN zzX~zvU~5ZRi*_RoIN$9Xh|8f8jE!$J*ulP$=E0Bp>Ah7r8`C-S`b2V-Se2oZ07<|H zH4};Y=u_GgIV6J0R02!IuF#$>zIIP@n!$-^;{@wENL0`j|6FC5<7-H~2~#NJ5l{FP z#vZNd_=8~W?%0}d*qBfLBYQ*YmvDN>+F3r@_BG~AUvD^7Ub<#Mi%O6Lju%PkD+dYh z;3)i$;TSz)eQ3%Q6uAAZn^a!!+@LE{%PixlWR8KB6GpUL7y@zk3V)M71%> zvDYu6W*lxv)i*qTZ%xQmIKWaMUH?M;t~4VUS6*`{_Yw`m4P^u+3X84!pJLaPBV$iU zOkdfl@l@vctnBb_caFVpLhmoi{wS&T)KRliy}YGx{mYX#Z*D}AWjJvT4-0hhvhfre z&)~o68R!M)T6gGfnuo4ox`ny;MV0w`R~a+8`Sg9E9_(UyKZ2z{9b)_Hh*m|jH@;#> zS}_64rbQjS9U_<`k|ugE^>Ji=K#XSKi)8wbK6mW8yL_Qgc4F0~CKt*hBZ1hTHsuv| z!u^u{X+bDzn?IVz=rHM3;c&z5BYlF~Nb{}35#)(QqpZhICEV?7^Gb7x@Q^nw_Y|b| zXLNz)$pVM}+Kb8f%!3mh16%v`pH&x+WkgG|;bzDEz?;_O;KYy-C5YH6ym5)*<5RR^ zG(d|`7Ana9AQ_y|nnd|yFH1+D;Iv0iMnHc+^xcG=RcSg3v920~pbR^m=KUwqgp4hL z$w+bWXmc4aQW~bO^X>ytm`t0q=8Qz@9SSNw)ft$3On>V?#VkaIXE6U@x%JW_57yY1 zosBiQ)4vNEA;5Z1<&`P5UoOT-Ll9i`RLq9389B79B>ZlR{Ta*Uf3(0Mjj$<|pH6xVZgD8&2PI-!GF z1i?<1z(0h*=1o@S>vmkpk~bDi7F{1lIc0NI#1Kb<@fhpJsirw~SKj2e%H1S;XkEW5 z2=cM5WjV>W;k;sXQD2yAQ;2Nsa*FwM``zOOSq7%NAESD8?94(wY}>4F_eIojfp zJ(7srX~G3x;`YKg=MEmPyuTM8*N+oE_iEYBl==qh==x2F8FP4uMpB-I;H+T~C6&C9 zS?L&A#_pH3f0E!M@qg|}K=HBMOa zPkk2cZjf1BOI}01ku@!a_*NqQ&97}JS0=;3N%z27f;VrTGKmySNBRaRdvn5a(p^xE z+czovS_o+^J)1yA=kxo`!Jd(whPzYT(Ol72RiXps!1b8q(GM)@oqftlH3fz+Cs+nw4T-l5y?8NQvYN!{kP&k_1|T8-!a#<Pqb`PNSf%-BO=~T>`sq?*MY(9crQeilA6kLH zMhp@};R=3jK1&b@EJ)jwf7PN*SG+>~q8b znmqF%rAqjqBB)2YBi-p_P?Nm5Okzdk&9x$r!r)PF}CG zMJmGQgEm8VLg0jVG?P7vEu{keto*zM-q~haxhJ9al!c9uO;+36k?_w~Ua#}tB;ko&w8wG5!SYxJzYY3kobi=IhKsORVlo0*i}gKJyu z9=l%DK#V3_W|yS33k*+RpufKN+X3Chd)j>TSLmE43%|`9o$tBWdqA!?rsJ&qfV?Vd zZd*|m#ehPnwSBv{s4qyiVmX4IG{w^FdV;+H3w?b+MsoQBO=^qk_YzbJ%}R+WofI>m z_2=d(*>uR8Jf}k6(L z2AUX`+!9J*3DraZMr@CYPe?2nMbrGHfdt48hbj88pR)hfhWlQqB0$G<{ht7hSHCGr zo*M*WeL9P`+m`>eBn+JR@zww5Njr~+4ghp5PfCS23ENDoGDDgB#b=kOjh@|+z#`JE$_$`VS4 zYGh(VZPSz12Q+_0;Y~$3J4U%bBz12IrTXI{I}cI2)FEj=DJ`gh_9JKdZT-|)b-2z^ z+ubSv44VL!4>0&6l$MR{48ZrjxSzZJ!zA07;e_0OJ8Uko15#Zqr(=#08IJt}arK>P diff --git a/spec/git_spec.rb b/spec/git_spec.rb index d518f59..bafea00 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -26,6 +26,13 @@ def fixture_url(name) tmp_folder('README').read.strip.should == 'topic_branch' end + it 'checks out a specific branch with forced encoding' do + options = { :git => fixture('git-repo'), :branch => '中文分支' } + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + tmp_folder('README').read.strip.should == '中文分支' + end + it 'checks out a specific tag' do options = { :git => fixture('git-repo'), :tag => 'v1.0' } downloader = Downloader.for_target(tmp_folder, options) From 65745d92c1e6ec1965dbd385800bb35a18aad53f Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 15:51:46 +0300 Subject: [PATCH 25/55] Fix build badge with GH actions --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index dc964cf..929e1a8 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -[![Build Status](https://img.shields.io/travis/CocoaPods/cocoapods-downloader/master.svg?style=flat)](https://travis-ci.org/CocoaPods/cocoapods-downloader) +[![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg) [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) From 09cd0a13b5045f7b073fd713b1c3cd6ad7ead319 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 15:52:33 +0300 Subject: [PATCH 26/55] Fix typo --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 929e1a8..1c81e72 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -[![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg) +[![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg)] [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) From 080bbb065068bcb720d0cffa0c8d51c0857a6205 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 15:52:53 +0300 Subject: [PATCH 27/55] Again fix typo --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 1c81e72..92405c5 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -[![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg)] +![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg) [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) From eb82dd35523b75dcd1eb1c67e0f0f318095faa1e Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 15:54:41 +0300 Subject: [PATCH 28/55] actually use same as CocoaPods repo --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 92405c5..0d3a20a 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -![Build Status](https://github.com/CocoaPods/cocoapods-downloader/actions/workflows/Specs.yml/badge.svg) +[![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods/Specs)](https://github.com/CocoaPods/CocoaPods/actions) [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) From f1a7fa375397e799fd75a764fb5a81682c15e80d Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 15:55:28 +0300 Subject: [PATCH 29/55] One last time --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 0d3a20a..be77269 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -[![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods/Specs)](https://github.com/CocoaPods/CocoaPods/actions) +[![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods/Specs)](https://github.com/CocoaPods/cocoapods-downloader/actions) [![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) [![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) From c2ee972817a8de12118809d5d9daf8cef2a0217f Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 18 Aug 2021 16:03:51 +0300 Subject: [PATCH 30/55] fix maintainability and coverage links --- README.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index be77269..764a1bb 100644 --- a/README.markdown +++ b/README.markdown @@ -3,8 +3,9 @@ A small library for downloading files from remotes in a folder. [![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods/Specs)](https://github.com/CocoaPods/cocoapods-downloader/actions) -[![Coverage](https://img.shields.io/codeclimate/coverage/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) -[![Code Climate](https://img.shields.io/codeclimate/github/CocoaPods/cocoapods-downloader.svg?style=flat)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader) +[![Gem Version](https://img.shields.io/gem/v/cocoapods-downloader)](https://rubygems.org/gems/cocoapods-downloader) +[![Maintainability](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/test_coverage)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/test_coverage) ## Install From 19786ab6275c8c0e167c6493778d3e3280cdcef7 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 27 Aug 2021 16:56:06 +0300 Subject: [PATCH 31/55] Fix build badge src --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 764a1bb..974b335 100644 --- a/README.markdown +++ b/README.markdown @@ -2,7 +2,7 @@ A small library for downloading files from remotes in a folder. -[![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods/Specs)](https://github.com/CocoaPods/cocoapods-downloader/actions) +[![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods-Downloader/Spec)](https://github.com/CocoaPods/cocoapods-downloader/actions) [![Gem Version](https://img.shields.io/gem/v/cocoapods-downloader)](https://rubygems.org/gems/cocoapods-downloader) [![Maintainability](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/test_coverage)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/test_coverage) From 39cef0b86dc7dd891a7c28b47517aff70addc189 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 31 Aug 2021 08:48:44 -0700 Subject: [PATCH 32/55] Release 1.5.0 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78cc0af..99a7d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.5.0 (2021-08-31) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 24d955e..d9748bb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.4.0) + cocoapods-downloader (1.5.0) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index aad37bc..0352972 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.4.0'.freeze + VERSION = '1.5.0'.freeze end end From 979ce9fb4b5c983e161e7fdb926ad0e163cc637e Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 31 Aug 2021 08:48:47 -0700 Subject: [PATCH 33/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a7d8b..b35d946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.5.0 (2021-08-31) ##### Enhancements From f890ef67b86bd468b8ea707b593eb22d2bf96d28 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 1 Sep 2021 12:06:11 -0700 Subject: [PATCH 34/55] Update code climate badges --- README.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 974b335..b6484ba 100644 --- a/README.markdown +++ b/README.markdown @@ -4,8 +4,7 @@ A small library for downloading files from remotes in a folder. [![Build Status](https://img.shields.io/github/workflow/status/CocoaPods/CocoaPods-Downloader/Spec)](https://github.com/CocoaPods/cocoapods-downloader/actions) [![Gem Version](https://img.shields.io/gem/v/cocoapods-downloader)](https://rubygems.org/gems/cocoapods-downloader) -[![Maintainability](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/test_coverage)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/test_coverage) +[![Maintainability](https://api.codeclimate.com/v1/badges/2253ffb0c2c98e4d1c71/maintainability)](https://codeclimate.com/github/CocoaPods/cocoapods-downloader/maintainability) ## Install From 11ef6ab8d52c03edf9de9e1976de3202d29ffcdc Mon Sep 17 00:00:00 2001 From: Yen-Chia Lin Date: Tue, 7 Sep 2021 14:19:34 +0200 Subject: [PATCH 35/55] Fix "can't modify frozen string" errors when pods are integrated using the `branch` option (#10920) --- CHANGELOG.md | 5 +++-- lib/cocoapods-downloader/git.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b35d946..932b614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ ##### Bug Fixes -* None. - +* Fix "can't modify frozen string" errors when pods are integrated using the `branch` option + [buju77](https://github.com/Buju77) + [#10920](https://github.com/CocoaPods/CocoaPods/issues/10920) ## 1.5.0 (2021-08-31) diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index f7914e6..58feb6a 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -52,7 +52,7 @@ def self.preprocess_options(options) # def self.commit_from_ls_remote(output, branch_name) return nil if branch_name.nil? - encoded_branch_name = branch_name.force_encoding(Encoding::ASCII_8BIT) + encoded_branch_name = branch_name.dup.force_encoding(Encoding::ASCII_8BIT) match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(encoded_branch_name)}}.match(output) match[1] unless match.nil? end From be5360ed2d40073cb74af2e3e8a7352ce03610e3 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 7 Sep 2021 09:20:25 -0700 Subject: [PATCH 36/55] Fix CHANGELOG formatting --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 932b614..1656964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ [buju77](https://github.com/Buju77) [#10920](https://github.com/CocoaPods/CocoaPods/issues/10920) + ## 1.5.0 (2021-08-31) ##### Enhancements From 863c0d1744b4b0d1f4ebbcf5cc9161a802677f31 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 7 Sep 2021 09:22:42 -0700 Subject: [PATCH 37/55] Release 1.5.1 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1656964..5f5a3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.5.1 (2021-09-07) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index d9748bb..3223f67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.5.0) + cocoapods-downloader (1.5.1) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index 0352972..964c31b 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.5.0'.freeze + VERSION = '1.5.1'.freeze end end From a08be3e88333a73d3b19b1fc5407bb9754e97024 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 7 Sep 2021 09:22:56 -0700 Subject: [PATCH 38/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5a3a0..e60d01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.5.1 (2021-09-07) ##### Enhancements From ad2e1e2a3cbcf0b39a2c8de028fdfc8f9da1960c Mon Sep 17 00:00:00 2001 From: Seth Friedman Date: Tue, 22 Feb 2022 14:19:34 -0500 Subject: [PATCH 39/55] Add .idea to .gitignore to ignore JetBrains IDE files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3908cc9..3563db6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ coverage/ spec/fixtures/git-repo spec/fixtures/git-submodule-repo spec/fixtures/mercurial-repo + +# IDEs +.idea/ From 35340f4b238bf395c9b21a56866d1c415f4468ce Mon Sep 17 00:00:00 2001 From: orta Date: Tue, 22 Mar 2022 14:32:28 +0000 Subject: [PATCH 40/55] Adds a check for command injections in the input for hg and git --- README.markdown | 4 ++++ lib/cocoapods-downloader/git.rb | 9 ++++++++- lib/cocoapods-downloader/mercurial.rb | 13 +++++++++++++ spec/git_spec.rb | 20 ++++++++++++++++++++ spec/mercurial_spec.rb | 8 ++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index b6484ba..5d93a5a 100644 --- a/README.markdown +++ b/README.markdown @@ -72,6 +72,10 @@ All CocoaPods development happens on GitHub, there is a repository for [CocoaPod Follow [@CocoaPods](http://twitter.com/CocoaPods) to get up to date information about what's going on in the CocoaPods world. +## Development + +You need to have `svn`, `bzr`, `hg` and `git` installed to run the specs. There are some specs which require `hdiutil` which will only run on macOS. + ## License This gem and CocoaPods are available under the MIT license. diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 58feb6a..f9804a2 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -21,6 +21,7 @@ def checkout_options end def self.preprocess_options(options) + validate_input options return options unless options[:branch] command = ['ls-remote', @@ -57,7 +58,13 @@ def self.commit_from_ls_remote(output, branch_name) match[1] unless match.nil? end - private_class_method :commit_from_ls_remote + def self.validate_input(options) + input = [options[:git], options[:branch], options[:commit], options[:tag]] + invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } + raise DownloaderError, "Provided unsafe input for git #{options}." if invalid + end + + private_class_method :commit_from_ls_remote, :validate_input private diff --git a/lib/cocoapods-downloader/mercurial.rb b/lib/cocoapods-downloader/mercurial.rb index 6dc6c49..bb94054 100644 --- a/lib/cocoapods-downloader/mercurial.rb +++ b/lib/cocoapods-downloader/mercurial.rb @@ -18,6 +18,19 @@ def checkout_options end end + def self.preprocess_options(options) + validate_input options + options + end + + def self.validate_input(options) + input = [options[:hg], options[:revision], options[:branch], options[:tag]].map(&:to_s) + invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } + raise DownloaderError, "Provided unsafe input for hg #{options}." if invalid + end + + private_class_method :validate_input + private executable :hg diff --git a/spec/git_spec.rb b/spec/git_spec.rb index bafea00..6b3da9e 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -290,6 +290,26 @@ def ensure_only_one_ref(folder) new_options[:branch].should == 'aaaa' end end + + describe ':bad input' do + it 'bails when you provide a bad input' do + options = { :git => '--upload-pack=touch ./HELLO1;', :branch => 'foo' } + e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end + + it 'bails when you provide a bad input after valid input' do + options = { :git => 'github.com --upload-pack=touch ./HELLO1;', :branch => 'foo' } + e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end + + it 'bails with other fields' do + options = { :branch => '--upload-pack=touch ./HELLO1;', :git => 'foo' } + e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end + end end end end diff --git a/spec/mercurial_spec.rb b/spec/mercurial_spec.rb index f1bf942..61f6983 100644 --- a/spec/mercurial_spec.rb +++ b/spec/mercurial_spec.rb @@ -106,5 +106,13 @@ module Downloader new_options.should == options end end + + describe ':bad input' do + it 'bails when you provide a bad input' do + options = { :hg => '--config=alias.clone=!touch ./HELLO2;' } + e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end + end end end From de9f331a938802ba16029ca71f34682ae1593b0c Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 11:29:26 -0700 Subject: [PATCH 41/55] Use Ruby 3.0.0 in CI --- .github/workflows/Specs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Specs.yml b/.github/workflows/Specs.yml index 9e64d20..bf02332 100644 --- a/.github/workflows/Specs.yml +++ b/.github/workflows/Specs.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false matrix: os: [macos-10.15] - ruby: [2.3, 2.5, 2.6, 2.7, 3.0] + ruby: [2.3, 2.5, 2.6, 2.7, 3.0.0] name: ${{ matrix.os }} / Ruby ${{ matrix.ruby }} runs-on: ${{ matrix.os }} From 2a1302f1cde5adab3caa5df511fdfb0a01a480ea Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 13:36:47 -0700 Subject: [PATCH 42/55] Add CHANGELOG entry for #124 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e60d01d..c4087b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ ##### Bug Fixes -* None. - +* Adds a check for command injections in the input for hg and git. + [orta](https://github.com/orta) + [#124](https://github.com/CocoaPods/cocoapods-downloader/pull/124) ## 1.5.1 (2021-09-07) From 26a9482d1f03a8cd58fb624e94bce29fc6f074f8 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 13:39:08 -0700 Subject: [PATCH 43/55] Release 1.6.0 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4087b7..06f5daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.6.0 (2022-03-22) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 3223f67..55ebc28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.5.1) + cocoapods-downloader (1.6.0) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index 964c31b..01e8af5 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.5.1'.freeze + VERSION = '1.6.0'.freeze end end From e53d02304426c3f7b0f725371c8a5594cecf9be4 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 13:40:32 -0700 Subject: [PATCH 44/55] Release 1.6.0 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4087b7..06f5daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.6.0 (2022-03-22) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 3223f67..55ebc28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.5.1) + cocoapods-downloader (1.6.0) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index 964c31b..01e8af5 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.5.1'.freeze + VERSION = '1.6.0'.freeze end end From d5ceec992e94c9effbf666fb5b851bf6f3acf0ff Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 13:40:33 -0700 Subject: [PATCH 45/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f5daa..83c3d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.6.0 (2022-03-22) ##### Enhancements From dc919d92db73b2cf6c010f0151ddf40bb8203363 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Tue, 22 Mar 2022 13:42:33 -0700 Subject: [PATCH 46/55] Fix CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc32a8c..83c3d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.6.0 (2022-03-22) +## Master ##### Enhancements From 247c5efc77282d1ed35c7f57c7ee5f846e2fdf97 Mon Sep 17 00:00:00 2001 From: orta Date: Wed, 23 Mar 2022 07:45:17 +0000 Subject: [PATCH 47/55] Adds a to_s map during validation --- lib/cocoapods-downloader/git.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index f9804a2..1312a88 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -59,7 +59,7 @@ def self.commit_from_ls_remote(output, branch_name) end def self.validate_input(options) - input = [options[:git], options[:branch], options[:commit], options[:tag]] + input = [options[:git], options[:branch], options[:commit], options[:tag]].map(&:to_s) invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } raise DownloaderError, "Provided unsafe input for git #{options}." if invalid end From 3a7c54bf346ff549f5e8d85a4db9bd77a06c5f6f Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 23 Mar 2022 18:19:49 -0500 Subject: [PATCH 48/55] Release 1.6.1 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c3d3f..3678c43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.6.1 (2022-03-23) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 55ebc28..768c465 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.6.0) + cocoapods-downloader (1.6.1) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index 01e8af5..c531274 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.6.0'.freeze + VERSION = '1.6.1'.freeze end end From 96679f2614b71cdddb24145fb38d55613b20accd Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Wed, 23 Mar 2022 18:19:50 -0500 Subject: [PATCH 49/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3678c43..09bee36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.6.1 (2022-03-23) ##### Enhancements From 99fec61600a8651a6f8942a0a9e972709cf77927 Mon Sep 17 00:00:00 2001 From: orta Date: Mon, 28 Mar 2022 17:03:55 +0100 Subject: [PATCH 50/55] Switches where we check for invalid input, to move it inside the download function --- lib/cocoapods-downloader/base.rb | 9 +++++++++ lib/cocoapods-downloader/git.rb | 15 +++++++-------- lib/cocoapods-downloader/mercurial.rb | 19 ++++++------------- spec/git_spec.rb | 6 +++--- spec/mercurial_spec.rb | 8 +++++++- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/cocoapods-downloader/base.rb b/lib/cocoapods-downloader/base.rb index 8e2d38a..c7f54b1 100644 --- a/lib/cocoapods-downloader/base.rb +++ b/lib/cocoapods-downloader/base.rb @@ -77,6 +77,7 @@ def name # @return [void] # def download + validate_input ui_action("#{name} download") do target_path.mkpath download! @@ -121,6 +122,14 @@ def checkout_options raise 'Abstract method' end + # Provides a before-download check for safety of the options in the + # concrete downloader. + # + # @return [void] + # + def validate_input + end + # Returns a User-Agent string that itentifies http network requests as # originating from CocoaPods. # Contains version numbers from the CocoaPods Gem and the cocoapods-downloader Gem. diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 1312a88..4128ed2 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -21,7 +21,6 @@ def checkout_options end def self.preprocess_options(options) - validate_input options return options unless options[:branch] command = ['ls-remote', @@ -58,13 +57,7 @@ def self.commit_from_ls_remote(output, branch_name) match[1] unless match.nil? end - def self.validate_input(options) - input = [options[:git], options[:branch], options[:commit], options[:tag]].map(&:to_s) - invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } - raise DownloaderError, "Provided unsafe input for git #{options}." if invalid - end - - private_class_method :commit_from_ls_remote, :validate_input + private_class_method :commit_from_ls_remote private @@ -160,6 +153,12 @@ def checkout_commit def target_git(*args) git!(['-C', target_path] + args) end + + def validate_input + input = [url, options[:branch], options[:commit], options[:tag]].map(&:to_s) + invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } + raise DownloaderError, "Provided unsafe input for git #{options}." if invalid + end end end end diff --git a/lib/cocoapods-downloader/mercurial.rb b/lib/cocoapods-downloader/mercurial.rb index bb94054..5582383 100644 --- a/lib/cocoapods-downloader/mercurial.rb +++ b/lib/cocoapods-downloader/mercurial.rb @@ -18,19 +18,6 @@ def checkout_options end end - def self.preprocess_options(options) - validate_input options - options - end - - def self.validate_input(options) - input = [options[:hg], options[:revision], options[:branch], options[:tag]].map(&:to_s) - invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } - raise DownloaderError, "Provided unsafe input for hg #{options}." if invalid - end - - private_class_method :validate_input - private executable :hg @@ -62,6 +49,12 @@ def download_tag! def download_branch! hg! 'clone', url, '--updaterev', options[:branch], @target_path end + + def validate_input + input = [url, options[:revision], options[:branch], options[:tag]].map(&:to_s) + invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } + raise DownloaderError, "Provided unsafe input for hg #{options}." if invalid + end end end end diff --git a/spec/git_spec.rb b/spec/git_spec.rb index 6b3da9e..f646d0c 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -294,19 +294,19 @@ def ensure_only_one_ref(folder) describe ':bad input' do it 'bails when you provide a bad input' do options = { :git => '--upload-pack=touch ./HELLO1;', :branch => 'foo' } - e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e = lambda { Downloader.for_target(tmp_folder, options).download }.should.raise DownloaderError e.message.should.match /Provided unsafe input/ end it 'bails when you provide a bad input after valid input' do options = { :git => 'github.com --upload-pack=touch ./HELLO1;', :branch => 'foo' } - e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e = lambda { Downloader.for_target(tmp_folder, options).download }.should.raise DownloaderError e.message.should.match /Provided unsafe input/ end it 'bails with other fields' do options = { :branch => '--upload-pack=touch ./HELLO1;', :git => 'foo' } - e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e = lambda { Downloader.for_target(tmp_folder, options).download }.should.raise DownloaderError e.message.should.match /Provided unsafe input/ end end diff --git a/spec/mercurial_spec.rb b/spec/mercurial_spec.rb index 61f6983..82a188c 100644 --- a/spec/mercurial_spec.rb +++ b/spec/mercurial_spec.rb @@ -110,7 +110,13 @@ module Downloader describe ':bad input' do it 'bails when you provide a bad input' do options = { :hg => '--config=alias.clone=!touch ./HELLO2;' } - e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e = lambda { Downloader.for_target(tmp_folder, options).download }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end + + it 'bails when you provide a bad input2' do + options = { :hg => 'foo/bar', :revision => '--config=alias.clone=!touch ./HELLO3;' } + e = lambda { Downloader.for_target(tmp_folder, options).download }.should.raise DownloaderError e.message.should.match /Provided unsafe input/ end end From 591167a841c64ca1ae9a8b411c1d1bdffc3bf285 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Mon, 28 Mar 2022 09:28:23 -0700 Subject: [PATCH 51/55] Release 1.6.2 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09bee36..73d0a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.6.2 (2022-03-28) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 768c465..7c2d1ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.6.1) + cocoapods-downloader (1.6.2) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index c531274..bc250e6 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.6.1'.freeze + VERSION = '1.6.2'.freeze end end From 3adfe1f4139f5aedae002fac283028415942544f Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Mon, 28 Mar 2022 09:28:24 -0700 Subject: [PATCH 52/55] [CHANGELOG] Add empty Master section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73d0a59..61d83c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Master + +##### Enhancements + +* None. + +##### Bug Fixes + +* None. + + ## 1.6.2 (2022-03-28) ##### Enhancements From d27c98343ee618349241b7a5c7643102a6efcbb3 Mon Sep 17 00:00:00 2001 From: orta Date: Thu, 31 Mar 2022 18:27:34 +0100 Subject: [PATCH 53/55] Ensure that the git pre-processor doesn't accidentally bail also --- lib/cocoapods-downloader/git.rb | 6 ++++++ spec/git_spec.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 4128ed2..a247644 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -23,9 +23,15 @@ def checkout_options def self.preprocess_options(options) return options unless options[:branch] + input = [options[:git], options[:commit]].map(&:to_s) + invalid = input.compact.any? { |value| value.start_with?('--') || value.include?(' --') } + raise DownloaderError, "Provided unsafe input for git #{options}." if invalid + command = ['ls-remote', + '--', options[:git], options[:branch]] + output = Git.execute_command('git', command) match = commit_from_ls_remote output, options[:branch] diff --git a/spec/git_spec.rb b/spec/git_spec.rb index f646d0c..d93ded5 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -289,6 +289,12 @@ def ensure_only_one_ref(folder) new_options = Downloader.preprocess_options(options) new_options[:branch].should == 'aaaa' end + + it 'throws when proving an invalid input' do + options = { :git => '--upload-pack=touch ./HELLO1;', :branch => 'foo' } + e = lambda { Downloader.preprocess_options(options) }.should.raise DownloaderError + e.message.should.match /Provided unsafe input/ + end end describe ':bad input' do From f75bcccaa9a26acf6517f37e7376f671c8351fd7 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 1 Apr 2022 07:57:43 -0700 Subject: [PATCH 54/55] Disable Bazaar tests due to macOS 12.3 not including python2 --- spec/bazaar_spec.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/spec/bazaar_spec.rb b/spec/bazaar_spec.rb index 03363bc..dfde5db 100644 --- a/spec/bazaar_spec.rb +++ b/spec/bazaar_spec.rb @@ -7,28 +7,30 @@ module Downloader tmp_folder.rmtree if tmp_folder.exist? end - it 'checks out a specific revision' do + # Many tests disabled here due to macOS 12.3 no longer including python2. + + xit 'checks out a specific revision' do options = { :bzr => fixture('bazaar-repo'), :revision => '1' } downloader = Downloader.for_target(tmp_folder, options) downloader.download tmp_folder('README').read.strip.should == 'First Commit' end - it 'checks out a specific tag as a revision' do + xit 'checks out a specific tag as a revision' do options = { :bzr => fixture('bazaar-repo'), :revision => 'my_tag' } downloader = Downloader.for_target(tmp_folder, options) downloader.download tmp_folder('README').read.strip.should == 'Second Commit' end - it 'checks out a specific tag as a tag' do + xit 'checks out a specific tag as a tag' do options = { :bzr => fixture('bazaar-repo'), :tag => 'my_other_tag' } downloader = Downloader.for_target(tmp_folder, options) downloader.download tmp_folder('README').read.strip.should == 'Third Commit' end - it 'checks out the head revision' do + xit 'checks out the head revision' do options = { :bzr => fixture('bazaar-repo') } downloader = Downloader.for_target(tmp_folder, options) downloader.download @@ -42,14 +44,14 @@ module Downloader end describe 'when the directory name has quotes or spaces' do - it 'checks out the head revision' do + xit 'checks out the head revision' do options = { :bzr => fixture('bazaar-repo') } downloader = Downloader.for_target(tmp_folder_with_quotes, options) downloader.download tmp_folder_with_quotes('README').read.strip.should == 'Fourth Commit' end - it 'checks out a specific revision into a directory with quotes' do + xit 'checks out a specific revision into a directory with quotes' do options = { :bzr => fixture('bazaar-repo'), :revision => '1' } downloader = Downloader.for_target(tmp_folder_with_quotes, options) downloader.download @@ -57,7 +59,7 @@ module Downloader end end - it 'returns the checked out revision' do + xit 'returns the checked out revision' do options = { :bzr => fixture('bazaar-repo') } downloader = Downloader.for_target(tmp_folder, options) downloader.download From c03e2ed652536050ac9d82472b5137d5e6651552 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 1 Apr 2022 07:59:21 -0700 Subject: [PATCH 55/55] Release 1.6.3 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- lib/cocoapods-downloader/gem_version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d83c0..a84449f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Master +## 1.6.3 (2022-04-01) ##### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 7c2d1ed..8e77261 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cocoapods-downloader (1.6.2) + cocoapods-downloader (1.6.3) GEM remote: https://rubygems.org/ diff --git a/lib/cocoapods-downloader/gem_version.rb b/lib/cocoapods-downloader/gem_version.rb index bc250e6..1381e16 100644 --- a/lib/cocoapods-downloader/gem_version.rb +++ b/lib/cocoapods-downloader/gem_version.rb @@ -3,6 +3,6 @@ module Downloader # @return [String] Downloader’s version, following # [semver](http://semver.org). # - VERSION = '1.6.2'.freeze + VERSION = '1.6.3'.freeze end end