# -*- encoding: binary -*- require 'test_helper' require 'digest' class TestUpload < MiniTest::Unit::TestCase def setup @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' @port = 8080 @bs = 4096 @count = 256 @server = nil @sha1 = Digest::SHA1.new # we want random binary data to test 1.9 encoding-aware IO craziness @random = File.open('/dev/urandom','rb') end def teardown @server.stop @random.close sleep 0.1 end def test_put start_server sock = TCPSocket.new(@addr, @port) sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n") @count.times do |i| buf = @random.sysread(@bs) @sha1.update(buf) sock.syswrite(buf) end read = sock.read.split(/\r\n/) assert_equal "HTTP/1.0 200 OK", read[0] resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, '')) assert_equal @sha1.hexdigest, resp[:sha1] sock.close end def test_put_content_md5 start_server md5 = Digest::MD5.new sock = TCPSocket.new(@addr, @port) sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \ "X-Expect-Size: #{length}\r\n" \ "Trailer: Content-MD5\r\n\r\n") @count.times do |i| buf = @random.sysread(@bs) @sha1.update(buf) md5.update(buf) sock.syswrite("#{'%x' % buf.size}\r\n") sock.syswrite(buf << "\r\n") end sock.syswrite("0\r\n") content_md5 = [ md5.digest! ].pack('m').strip.freeze sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n") read = sock.read.split(/\r\n/) assert_equal "HTTP/1.0 200 OK", read[0] resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, '')) assert_equal length, resp[:size] assert_equal @sha1.hexdigest, resp[:sha1] #Vert.x doesn't handle trailing headers #assert_equal content_md5, resp[:content_md5] end def test_put_trickle_small start_server @count, @bs = 2, 128 assert_equal 256, length sock = TCPSocket.new(@addr, @port) hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n" @count.times do buf = @random.sysread(@bs) @sha1.update(buf) hdr << buf sock.syswrite(hdr) hdr = '' sleep 0.6 end read = sock.read.split(/\r\n/) assert_equal "HTTP/1.0 200 OK", read[0] resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, '')) assert_equal @sha1.hexdigest, resp[:sha1] sock.close end def test_put_keepalive_truncates_small_overwrite start_server sock = TCPSocket.new(@addr, @port) to_upload = length + 1 sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n") @count.times do buf = @random.sysread(@bs) @sha1.update(buf) sock.syswrite(buf) end sock.syswrite('12345') # write 4 bytes more than we expected @sha1.update('1') buf = sock.readpartial(4096) while buf !~ /\r\n\r\n/ buf << sock.readpartial(4096) end read = buf.split(/\r\n/) assert_equal "HTTP/1.0 200 OK", read[0] resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, '')) #assert_equal to_upload, resp[:size] assert_equal @sha1.hexdigest, resp[:sha1] sock.close end def test_put_excessive_overwrite_closed config = Jubilee::Configuration.new(rackup: File.expand_path("../../apps/overwrite_check.ru", __FILE__)) @server = Jubilee::Server.new(config.options) q = Queue.new @server.start { q << 1 } q.pop sock = TCPSocket.new(@addr, @port) # buf = ' ' * @bs # Something is wrong with the vertx http compression buf = 'a' * @bs sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n") @count.times { sock.syswrite(buf) } assert_raises(Errno::ECONNRESET, Errno::EPIPE) do 16384.times { sock.syswrite(buf) } end assert_raises(Errno::ECONNRESET) do sock.gets end sock.close end def test_uncomfortable_with_onenine_encodings # POSIX doesn't require all of these to be present on a system which('curl') or return which('sha1sum') or return which('dd') or return start_server tmp = Tempfile.new('dd_dest') assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}", "bs=#{@bs}", "count=#{@count}"), "dd #@random to #{tmp}") sha1_re = %r!\b([a-f0-9]{40})\b! sha1_out = `sha1sum #{tmp.path}` assert $?.success?, 'sha1sum ran OK' assert_match(sha1_re, sha1_out) sha1 = sha1_re.match(sha1_out)[1] resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/` assert $?.success?, 'curl ran OK' assert_match(%r!\b#{sha1}\b!, resp) assert_match(/sysread_read_byte_match/, resp) # small StringIO path assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}", "bs=1024", "count=1"), "dd #@random to #{tmp}") sha1_re = %r!\b([a-f0-9]{40})\b! sha1_out = `sha1sum #{tmp.path}` assert $?.success?, 'sha1sum ran OK' assert_match(sha1_re, sha1_out) sha1 = sha1_re.match(sha1_out)[1] resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/` assert $?.success?, 'curl ran OK' assert_match(%r!\b#{sha1}\b!, resp) assert_match(/sysread_read_byte_match/, resp) end def test_curl_chunked_small # POSIX doesn't require all of these to be present on a system which('curl') or return which('sha1sum') or return which('dd') or return start_server tmp = Tempfile.new('dd_dest') # small StringIO path assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}", "bs=1024", "count=1"), "dd #@random to #{tmp}") sha1_re = %r!\b([a-f0-9]{40})\b! sha1_out = `sha1sum #{tmp.path}` assert $?.success?, 'sha1sum ran OK' assert_match(sha1_re, sha1_out) sha1 = sha1_re.match(sha1_out)[1] resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \ -isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}` assert $?.success?, 'curl ran OK' assert_match(%r!\b#{sha1}\b!, resp) assert_match(/sysread_read_byte_match/, resp) assert_match(/expect_size_match/, resp) end private def length @bs * @count end def start_server config = Jubilee::Configuration.new(rackup: File.expand_path("../../apps/sha1.ru", __FILE__), instances: 1) @server = Jubilee::Server.new(config.options) q = Queue.new @server.start{ q << 1 } q.pop sleep 0.1 end end