# -*- encoding: binary -*- require "digest/md5" module HTTP_Spew::ContentMD5 class MismatchError < HTTP_Spew::Error end class LengthError < HTTP_Spew::Error end def self.input(env) if trailer = env["HTTP_TRAILER"] have_md5 = trailer.split(/\s*,\s*/).grep(/\AContent-MD5\z/i)[0] return env["rack.input"] if have_md5 && env["HTTP_CONTENT_MD5"] end if trailer unless have_md5 trailer << (trailer.empty? ? "Content-MD5" : ",Content-MD5") end else env["HTTP_TRAILER"] = "Content-MD5" end env["HTTP_TRANSFER_ENCODING"] = "chunked" rd, wr = HTTP_Spew::ChunkyPipe.new md5 = env.delete("HTTP_CONTENT_MD5") len = env.delete("CONTENT_LENGTH") start_write_driver(env["rack.input"], rd, wr, md5, len) rd end def self.start_write_driver(input, rd, wr, expect_md5, expect_len) Thread.new do begin digest, buf, bytes = Digest::MD5.new, "", 0 while input.read(0x4000, buf) n = buf.size bytes += n wr.kgio_write("#{n.to_s(16)}\r\n") digest.update(buf) wr.kgio_write(buf << "\r\n") end if expect_len && expect_len.to_i != bytes raise LengthError, "expect=#{expect_len} != got=#{bytes}" end digest = [ digest.digest ].pack("m").strip! if expect_md5 && expect_md5.strip != digest raise MismatchError, "expect=#{expect_md5} != got=#{digest}" end wr.write "0\r\nContent-MD5: #{digest}\r\n\r\n" rescue => e rd.error = e ensure wr.close end end end end