summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Wanninger <ajwann@ajwann.codes>2017-08-19 14:54:23 -0400
committerAdam Wanninger <ajwann@ajwann.codes>2017-08-27 10:55:37 -0400
commit31b8f312ac09cdbd94ccc9607221fa8a95ad24b4 (patch)
tree2a8d69a673ae91c085ec7077af76b27d6e8e797a
parent938ceb37cae42c8e45576918c63422b74b2f1790 (diff)
downloadbundler-31b8f312ac09cdbd94ccc9607221fa8a95ad24b4.tar.gz
ensure $HOME and Dir.tmpdir are writable
-rw-r--r--lib/bundler/compact_index_client/updater.rb96
-rw-r--r--spec/bundler/compact_index_client/updater_spec.rb32
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