diff options
author | Adam Wanninger <ajwann@ajwann.codes> | 2017-08-19 14:54:23 -0400 |
---|---|---|
committer | Adam Wanninger <ajwann@ajwann.codes> | 2017-08-27 10:55:37 -0400 |
commit | 31b8f312ac09cdbd94ccc9607221fa8a95ad24b4 (patch) | |
tree | 2a8d69a673ae91c085ec7077af76b27d6e8e797a | |
parent | 938ceb37cae42c8e45576918c63422b74b2f1790 (diff) | |
download | bundler-31b8f312ac09cdbd94ccc9607221fa8a95ad24b4.tar.gz |
ensure $HOME and Dir.tmpdir are writable
-rw-r--r-- | lib/bundler/compact_index_client/updater.rb | 96 | ||||
-rw-r--r-- | spec/bundler/compact_index_client/updater_spec.rb | 32 |
2 files changed, 87 insertions, 41 deletions
diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb index 6a66fbc3fe..f69b81f116 100644 --- a/lib/bundler/compact_index_client/updater.rb +++ b/lib/bundler/compact_index_client/updater.rb @@ -28,55 +28,59 @@ module Bundler def update(local_path, remote_path, retrying = nil) headers = {} - Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| - local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) - - # first try to fetch any new bytes on the existing file - if retrying.nil? && local_path.file? - FileUtils.cp local_path, local_temp_path - headers["If-None-Match"] = etag_for(local_temp_path) - headers["Range"] = - if local_temp_path.size.nonzero? - # Subtract a byte to ensure the range won't be empty. - # Avoids 416 (Range Not Satisfiable) responses. - "bytes=#{local_temp_path.size - 1}-" - else - "bytes=#{local_temp_path.size}-" - end - else - # Fastly ignores Range when Accept-Encoding: gzip is set - headers["Accept-Encoding"] = "gzip" - end + Bundler::SharedHelpers.filesystem_access(Dir.tmpdir, :write) do + validate_permissions_on_home + + Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| + local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) + + # first try to fetch any new bytes on the existing file + if retrying.nil? && local_path.file? + FileUtils.cp local_path, local_temp_path + headers["If-None-Match"] = etag_for(local_temp_path) + headers["Range"] = + if local_temp_path.size.nonzero? + # Subtract a byte to ensure the range won't be empty. + # Avoids 416 (Range Not Satisfiable) responses. + "bytes=#{local_temp_path.size - 1}-" + else + "bytes=#{local_temp_path.size}-" + end + else + # Fastly ignores Range when Accept-Encoding: gzip is set + headers["Accept-Encoding"] = "gzip" + end - response = @fetcher.call(remote_path, headers) - return nil if response.is_a?(Net::HTTPNotModified) + response = @fetcher.call(remote_path, headers) + return nil if response.is_a?(Net::HTTPNotModified) - content = response.body - if response["Content-Encoding"] == "gzip" - content = Zlib::GzipReader.new(StringIO.new(content)).read - end + content = response.body + if response["Content-Encoding"] == "gzip" + content = Zlib::GzipReader.new(StringIO.new(content)).read + end - SharedHelpers.filesystem_access(local_temp_path) do - if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? - local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } - else - local_temp_path.open("w") {|f| f << content } + SharedHelpers.filesystem_access(local_temp_path) do + if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero? + local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) } + else + local_temp_path.open("w") {|f| f << content } + end end - end - response_etag = (response["ETag"] || "").gsub(%r{\AW/}, "") - if etag_for(local_temp_path) == response_etag - SharedHelpers.filesystem_access(local_path) do - FileUtils.mv(local_temp_path, local_path) + response_etag = (response["ETag"] || "").gsub(%r{\AW/}, "") + if etag_for(local_temp_path) == response_etag + SharedHelpers.filesystem_access(local_path) do + FileUtils.mv(local_temp_path, local_path) + end + return nil end - return nil - end - if retrying - raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path)) - end + if retrying + raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path)) + end - update(local_path, remote_path, :retrying) + update(local_path, remote_path, :retrying) + end end end @@ -102,6 +106,16 @@ module Bundler Digest::MD5.hexdigest(IO.read(path)) end end + + private + + def validate_permissions_on_home + return if File.stat(ENV["HOME"]).writable? + raise Bundler::PermissionError, + "Bundler does not have write access to $HOME. Bundler must " \ + "have write access to $HOME to function properly. " \ + "$HOME is currently #{ENV["HOME"]}" + end end end end diff --git a/spec/bundler/compact_index_client/updater_spec.rb b/spec/bundler/compact_index_client/updater_spec.rb index 3c4f212b60..0bd913ee7f 100644 --- a/spec/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/compact_index_client/updater_spec.rb @@ -27,4 +27,36 @@ RSpec.describe Bundler::CompactIndexClient::Updater do end.to raise_error(Bundler::CompactIndexClient::Updater::MisMatchedChecksumError) end end + + context "when bundler doesn't have permissions on Dir.tmpdir" do + let(:response) { double(:response, :body => "") } + let(:local_path) { Pathname("/tmp/localpath") } + let(:remote_path) { double(:remote_path) } + + it "Errno::EACCES is raised" do + allow(Dir).to receive(:tmpdir) { "/tmp" } + allow(Dir).to receive(:mktmpdir) { raise Errno::EACCES } + + expect do + updater.update(local_path, remote_path) + end.to raise_error(Bundler::PermissionError) + end + end + + context "when bundler doesn't have permissions on $HOME" do + let(:response) { double(:response, :body => "") } + let(:local_path) { Pathname("/tmp/localpath") } + let(:remote_path) { double(:remote_path) } + let(:home_file_stat) { File::Stat.new("/") } + + it "Bundler::PermissionError is raised" do + allow(Dir).to receive(:tmpdir) { "/tmp" } + allow(File).to receive(:stat).with(anything) { home_file_stat } + allow(home_file_stat).to receive(:writable?) { false } + + expect do + updater.update(local_path, remote_path) + end.to raise_error(Bundler::PermissionError) + end + end end |