summaryrefslogtreecommitdiff
path: root/lib/chef/http/decompressor.rb
blob: 280c8911649cc58a1f461cbfef6846600f05435a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Copyright:: Copyright 2013-2016, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require "zlib"
require "chef/http/http_request"

class Chef
  class HTTP
    # Middleware-esque class for handling compression in HTTP responses.
    class Decompressor
      class NoopInflater
        def inflate(chunk)
          chunk
        end
        alias :handle_chunk :inflate
      end

      class GzipInflater < Zlib::Inflate
        def initialize
          super(Zlib::MAX_WBITS + 16)
        end
        alias :handle_chunk :inflate
      end

      class DeflateInflater < Zlib::Inflate
        def initialize
          super
        end
        alias :handle_chunk :inflate
      end

      CONTENT_ENCODING  = "content-encoding".freeze
      GZIP              = "gzip".freeze
      DEFLATE           = "deflate".freeze
      IDENTITY          = "identity".freeze

      def initialize(opts = {})
        @disable_gzip = false
        handle_options(opts)
      end

      def handle_request(method, url, headers = {}, data = false)
        headers[HTTPRequest::ACCEPT_ENCODING] = HTTPRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled?
        [method, url, headers, data]
      end

      def handle_response(http_response, rest_request, return_value)
        # temporary hack, skip processing if return_value is false
        # needed to keep conditional get stuff working correctly.
        return [http_response, rest_request, return_value] if return_value == false
        response_body = decompress_body(http_response)
        http_response.body.replace(response_body) if http_response.body.respond_to?(:replace)
        [http_response, rest_request, return_value]
      end

      def handle_stream_complete(http_response, rest_request, return_value)
        [http_response, rest_request, return_value]
      end

      def decompress_body(response)
        if gzip_disabled? || response.body.nil?
          response.body
        else
          case response[CONTENT_ENCODING]
          when GZIP
            Chef::Log.debug "Decompressing gzip response"
            Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
          when DEFLATE
            Chef::Log.debug "Decompressing deflate response"
            Zlib::Inflate.inflate(response.body)
          else
            response.body
          end
        end
      end

      # This isn't used when this class is used as middleware; it returns an
      # object you can use to unzip/inflate a streaming response.
      def stream_response_handler(response)
        if gzip_disabled?
          Chef::Log.debug "disable_gzip is set. \
            Not using #{response[CONTENT_ENCODING]} \
            and initializing noop stream deflator."
          NoopInflater.new
        else
          case response[CONTENT_ENCODING]
          when GZIP
            Chef::Log.debug "Initializing gzip stream deflator"
            GzipInflater.new
          when DEFLATE
            Chef::Log.debug "Initializing deflate stream deflator"
            DeflateInflater.new
          else
            Chef::Log.debug "content_encoding = '#{response[CONTENT_ENCODING]}' \
              initializing noop stream deflator."
            NoopInflater.new
          end
        end
      end

      # gzip is disabled using the disable_gzip => true option in the
      # constructor. When gzip is disabled, no 'Accept-Encoding' header will be
      # set, and the response will not be decompressed, no matter what the
      # Content-Encoding header of the response is. The intended use case for
      # this is to work around situations where you request +file.tar.gz+, but
      # the server responds with a content type of tar and a content encoding of
      # gzip, tricking the client into decompressing the response so you end up
      # with a tar archive (no gzip) named file.tar.gz
      def gzip_disabled?
        @disable_gzip
      end

      private

      def handle_options(opts)
        opts.each do |name, value|
          case name.to_s
          when "disable_gzip"
            @disable_gzip = value
          end
        end
      end
    end
  end
end