lib/protocol/http/body/digestable.rb
# frozen_string_literal: true # Released under the MIT License. # Copyright, 2020-2024, by Samuel Williams. require_relative "wrapper" require "digest/sha2" module Protocol module HTTP module Body # Invokes a callback once the body has finished reading. class Digestable < Wrapper # Wrap a message body with a callback. If the body is empty, the callback is not invoked, as there is no data to digest. # # @parameter message [Request | Response] the message body. # @parameter digest [Digest] the digest to use. # @parameter block [Proc] the callback to invoke when the body is closed. def self.wrap(message, digest = Digest::SHA256.new, &block) if body = message&.body and !body.empty? message.body = self.new(message.body, digest, block) end end # Initialize the digestable body with a callback. # # @parameter body [Readable] the body to wrap. # @parameter digest [Digest] the digest to use. # @parameter callback [Block] The callback is invoked when the digest is complete. def initialize(body, digest = Digest::SHA256.new, callback = nil) super(body) @digest = digest @callback = callback end # @attribute [Digest] digest the digest object. attr :digest # Generate an appropriate ETag for the digest, assuming it is complete. If you call this method before the body is fully read, the ETag will be incorrect. # # @parameter weak [Boolean] If true, the ETag is marked as weak. # @returns [String] the ETag. def etag(weak: false) if weak "W/\"#{digest.hexdigest}\"" else "\"#{digest.hexdigest}\"" end end # Read the body and update the digest. When the body is fully read, the callback is invoked with `self` as the argument. # # @returns [String | Nil] the next chunk of data, or nil if the body is fully read. def read if chunk = super @digest.update(chunk) return chunk else @callback&.call(self) return nil end end end end end end