Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions google-cloud-storage/lib/google/cloud/storage/file/verifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,55 @@ def self.verify_md5 gcloud_file, local_file
def self.verify_crc32c gcloud_file, local_file
gcloud_file.crc32c == crc32c_for(local_file)
end
# Calculates MD5 digest using either file path or open stream.
def self.md5_for(local_file)
_digest_for(local_file, ::Digest::MD5)
end

def self.md5_for local_file
if local_file.respond_to? :to_path
::File.open Pathname(local_file).to_path, "rb" do |f|
::Digest::MD5.file(f).base64digest
end
else # StringIO
(local_file = ::File.open Pathname(local_file)) unless local_file.respond_to? :rewind
local_file.rewind
md5 = ::Digest::MD5.base64digest local_file.read
local_file.rewind
md5
end
# Calculates CRC32c digest using either file path or open stream.
def self.crc32c_for(local_file)
_digest_for(local_file, ::Digest::CRC32c)
end

def self.crc32c_for local_file
if local_file.respond_to? :to_path
private

# @private
# Computes a base64-encoded digest for a local file or IO stream.
#
# This method handles two types of inputs for `local_file`:
# 1. A file path (String or Pathname): It efficiently streams the file
# to compute the digest without loading the entire file into memory.
# 2. An IO-like stream (e.g., File, StringIO): It reads the stream's
# content to compute the digest. The stream is rewound before and after
# reading to ensure its position is not permanently changed.
#
# @param local_file [String, Pathname, IO] The local file path or IO
# stream for which to compute the digest.
# @param digest_class [Class] The digest class to use for the
# calculation (e.g., `Digest::MD5`). It must respond to `.file` and
# `.base64digest`.
#
# @return [String] The base64-encoded digest of the file's content.
#
def self._digest_for(local_file, digest_class)
if local_file.respond_to?(:to_path)
# Case 1: Input is a file path (or Pathname). Use the safe block form.
::File.open Pathname(local_file).to_path, "rb" do |f|
::Digest::CRC32c.file(f).base64digest
digest_class.file(f).base64digest
end
else
# Case 2: Input is an open stream (like File or StringIO).
file_to_close = nil
file_to_close = local_file = ::File.open(Pathname(local_file).to_path, "rb") unless local_file.respond_to?(:rewind)
begin
local_file.rewind
digest = digest_class.base64digest local_file.read
local_file.rewind
digest
ensure
# Only close the stream if we explicitly opened it
file_to_close.close if file_to_close.respond_to?(:close) && !file_to_close.closed?
end
else # StringIO
(local_file = ::File.open Pathname(local_file)) unless local_file.respond_to? :rewind
local_file.rewind
crc32c = ::Digest::CRC32c.base64digest local_file.read
local_file.rewind
crc32c
end
end
end
Expand Down