diff --git a/lib/puppet/network/http/compression.rb b/lib/puppet/network/http/compression.rb index 6e8314b72..839fe40b2 100644 --- a/lib/puppet/network/http/compression.rb +++ b/lib/puppet/network/http/compression.rb @@ -1,114 +1,118 @@ require 'puppet/network/http' module Puppet::Network::HTTP::Compression # this module function allows to use the right underlying # methods depending on zlib presence def module return(Puppet.features.zlib? ? Active : None) end module_function :module module Active require 'zlib' require 'stringio' # return an uncompressed body if the response has been # compressed def uncompress_body(response) case response['content-encoding'] when 'gzip' return Zlib::GzipReader.new(StringIO.new(response.body)).read when 'deflate' return Zlib::Inflate.new.inflate(response.body) when nil, 'identity' return response.body else raise Net::HTTPError.new("Unknown content encoding - #{response['content-encoding']}", response) end end def uncompress(response) raise Net::HTTPError.new("No block passed") unless block_given? case response['content-encoding'] when 'gzip','deflate' uncompressor = ZlibAdapter.new when nil, 'identity' uncompressor = IdentityAdapter.new else raise Net::HTTPError.new("Unknown content encoding - #{response['content-encoding']}", response) end yield uncompressor uncompressor.close end def add_accept_encoding(headers={}) - headers['accept-encoding'] = 'gzip; q=1.0, deflate; q=1.0; identity' if Puppet.settings[:http_compression] + if Puppet.settings[:http_compression] + headers['accept-encoding'] = 'gzip; q=1.0, deflate; q=1.0; identity' + else + headers['accept-encoding'] = 'identity' + end headers end # This adapters knows how to uncompress both 'zlib' stream (the deflate algorithm from Content-Encoding) # and GZip streams. class ZlibAdapter def initialize # Create an inflater that knows to parse GZip streams and zlib streams. # This uses a property of the C Zlib library, documented as follow: # windowBits can also be greater than 15 for optional gzip decoding. Add # 32 to windowBits to enable zlib and gzip decoding with automatic header # detection, or add 16 to decode only the gzip format (the zlib format will # return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is # a crc32 instead of an adler32. @uncompressor = Zlib::Inflate.new(15 + 32) @first = true end def uncompress(chunk) out = @uncompressor.inflate(chunk) @first = false return out rescue Zlib::DataError # it can happen that we receive a raw deflate stream # which might make our inflate throw a data error. # in this case, we try with a verbatim (no header) # deflater. @uncompressor = Zlib::Inflate.new if @first then @first = false retry end raise end def close @uncompressor.finish @uncompressor.close end end end module None def uncompress_body(response) response.body end def add_accept_encoding(headers) headers end def uncompress(response) yield IdentityAdapter.new end end class IdentityAdapter def uncompress(chunk) chunk end def close end end end diff --git a/spec/unit/network/http/compression_spec.rb b/spec/unit/network/http/compression_spec.rb index 9dbf4228c..093ae9633 100755 --- a/spec/unit/network/http/compression_spec.rb +++ b/spec/unit/network/http/compression_spec.rb @@ -1,196 +1,196 @@ #! /usr/bin/env ruby require 'spec_helper' describe "http compression" do describe "when zlib is not available" do before(:each) do Puppet.features.stubs(:zlib?).returns false require 'puppet/network/http/compression' class HttpUncompressor include Puppet::Network::HTTP::Compression::None end @uncompressor = HttpUncompressor.new end it "should have a module function that returns the None underlying module" do Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::None end it "should not add any Accept-Encoding header" do @uncompressor.add_accept_encoding({}).should == {} end it "should not tamper the body" do response = stub 'response', :body => "data" @uncompressor.uncompress_body(response).should == "data" end it "should yield an identity uncompressor" do response = stub 'response' @uncompressor.uncompress(response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end end describe "when zlib is available", :if => Puppet.features.zlib? do before(:each) do Puppet.features.stubs(:zlib?).returns true require 'puppet/network/http/compression' class HttpUncompressor include Puppet::Network::HTTP::Compression::Active end @uncompressor = HttpUncompressor.new end it "should have a module function that returns the Active underlying module" do Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::Active end it "should add an Accept-Encoding header when http compression is available" do Puppet[:http_compression] = true headers = @uncompressor.add_accept_encoding({}) headers.should have_key('accept-encoding') headers['accept-encoding'].should =~ /gzip/ headers['accept-encoding'].should =~ /deflate/ headers['accept-encoding'].should =~ /identity/ end it "should not add Accept-Encoding header if http compression is not available" do Puppet[:http_compression] = false - @uncompressor.add_accept_encoding({}).should == {} + @uncompressor.add_accept_encoding({}).should == {'accept-encoding' => 'identity'} end describe "when uncompressing response body" do before do @response = stub 'response' @response.stubs(:[]).with('content-encoding') @response.stubs(:body).returns("mydata") end it "should return untransformed response body with no content-encoding" do @uncompressor.uncompress_body(@response).should == "mydata" end it "should return untransformed response body with 'identity' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('identity') @uncompressor.uncompress_body(@response).should == "mydata" end it "should use a Zlib inflater with 'deflate' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('deflate') inflater = stub 'inflater' Zlib::Inflate.expects(:new).returns(inflater) inflater.expects(:inflate).with("mydata").returns "uncompresseddata" @uncompressor.uncompress_body(@response).should == "uncompresseddata" end it "should use a GzipReader with 'gzip' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('gzip') io = stub 'io' StringIO.expects(:new).with("mydata").returns io reader = stub 'gzip reader' Zlib::GzipReader.expects(:new).with(io).returns(reader) reader.expects(:read).returns "uncompresseddata" @uncompressor.uncompress_body(@response).should == "uncompresseddata" end end describe "when uncompressing by chunk" do before do @response = stub 'response' @response.stubs(:[]).with('content-encoding') @inflater = stub_everything 'inflater' Zlib::Inflate.stubs(:new).returns(@inflater) end it "should yield an identity uncompressor with no content-encoding" do @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end it "should yield an identity uncompressor with 'identity' content-encoding" do @response.stubs(:[]).with('content-encoding').returns 'identity' @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end %w{gzip deflate}.each do |c| it "should yield a Zlib uncompressor with '#{c}' content-encoding" do @response.stubs(:[]).with('content-encoding').returns c @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::Active::ZlibAdapter) } end end it "should close the underlying adapter" do adapter = stub_everything 'adapter' Puppet::Network::HTTP::Compression::IdentityAdapter.expects(:new).returns(adapter) adapter.expects(:close) @uncompressor.uncompress(@response) { |u| } end end describe "zlib adapter" do before do @inflater = stub_everything 'inflater' Zlib::Inflate.stubs(:new).returns(@inflater) @adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new end it "should initialize the underlying inflater with gzip/zlib header parsing" do Zlib::Inflate.expects(:new).with(15+32) Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new end it "should inflate the given chunk" do @inflater.expects(:inflate).with("chunk") @adapter.uncompress("chunk") end it "should return the inflated chunk" do @inflater.stubs(:inflate).with("chunk").returns("uncompressed") @adapter.uncompress("chunk").should == "uncompressed" end it "should try a 'regular' inflater on Zlib::DataError" do @inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream")) inflater = stub_everything 'inflater2' inflater.expects(:inflate).with("chunk").returns("uncompressed") Zlib::Inflate.expects(:new).with.returns(inflater) @adapter.uncompress("chunk") end it "should raise the error the second time" do @inflater.stubs(:inflate).raises(Zlib::DataError.new("not a zlib stream")) Zlib::Inflate.expects(:new).with.returns(@inflater) lambda { @adapter.uncompress("chunk") }.should raise_error end it "should finish the stream on close" do @inflater.expects(:finish) @adapter.close end it "should close the stream on close" do @inflater.expects(:close) @adapter.close end end end end