rack 1.0.1 → 2.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Rack::CommonLogger forwards every request to the given +app+, and
5
+ # logs a line in the
6
+ # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
7
+ # to the configured logger.
8
+ class CommonLogger
9
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
10
+ #
11
+ # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
12
+ #
13
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
14
+ #
15
+ # The actual format is slightly different than the above due to the
16
+ # separation of SCRIPT_NAME and PATH_INFO, and because the elapsed
17
+ # time in seconds is included at the end.
18
+ FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %d %s %0.4f }
19
+
20
+ # +logger+ can be any object that supports the +write+ or +<<+ methods,
21
+ # which includes the standard library Logger. These methods are called
22
+ # with a single string argument, the log message.
23
+ # If +logger+ is nil, CommonLogger will fall back <tt>env['rack.errors']</tt>.
24
+ def initialize(app, logger = nil)
25
+ @app = app
26
+ @logger = logger
27
+ end
28
+
29
+ # Log all requests in common_log format after a response has been
30
+ # returned. Note that if the app raises an exception, the request
31
+ # will not be logged, so if exception handling middleware are used,
32
+ # they should be loaded after this middleware. Additionally, because
33
+ # the logging happens after the request body has been fully sent, any
34
+ # exceptions raised during the sending of the response body will
35
+ # cause the request not to be logged.
36
+ def call(env)
37
+ began_at = Utils.clock_time
38
+ status, headers, body = @app.call(env)
39
+ headers = Utils::HeaderHash[headers]
40
+ body = BodyProxy.new(body) { log(env, status, headers, began_at) }
41
+ [status, headers, body]
42
+ end
43
+
44
+ private
45
+
46
+ # Log the request to the configured logger.
47
+ def log(env, status, header, began_at)
48
+ length = extract_content_length(header)
49
+
50
+ msg = FORMAT % [
51
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
52
+ env["REMOTE_USER"] || "-",
53
+ Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
54
+ env[REQUEST_METHOD],
55
+ env[SCRIPT_NAME],
56
+ env[PATH_INFO],
57
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
58
+ env[SERVER_PROTOCOL],
59
+ status.to_s[0..3],
60
+ length,
61
+ Utils.clock_time - began_at ]
62
+
63
+ msg.gsub!(/[^[:print:]]/) { |c| sprintf("\\x%x", c.ord) }
64
+ msg[-1] = "\n"
65
+
66
+ logger = @logger || env[RACK_ERRORS]
67
+
68
+ # Standard library logger doesn't support write but it supports << which actually
69
+ # calls to write on the log device without formatting
70
+ if logger.respond_to?(:write)
71
+ logger.write(msg)
72
+ else
73
+ logger << msg
74
+ end
75
+ end
76
+
77
+ # Attempt to determine the content length for the response to
78
+ # include it in the logged data.
79
+ def extract_content_length(headers)
80
+ value = headers[CONTENT_LENGTH]
81
+ !value || value.to_s == '0' ? '-' : value
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+
5
+ # Middleware that enables conditional GET using If-None-Match and
6
+ # If-Modified-Since. The application should set either or both of the
7
+ # Last-Modified or Etag response headers according to RFC 2616. When
8
+ # either of the conditions is met, the response body is set to be zero
9
+ # length and the response status is set to 304 Not Modified.
10
+ #
11
+ # Applications that defer response body generation until the body's each
12
+ # message is received will avoid response body generation completely when
13
+ # a conditional GET matches.
14
+ #
15
+ # Adapted from Michael Klishin's Merb implementation:
16
+ # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb
17
+ class ConditionalGet
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ # Return empty 304 response if the response has not been
23
+ # modified since the last request.
24
+ def call(env)
25
+ case env[REQUEST_METHOD]
26
+ when "GET", "HEAD"
27
+ status, headers, body = @app.call(env)
28
+ headers = Utils::HeaderHash[headers]
29
+ if status == 200 && fresh?(env, headers)
30
+ status = 304
31
+ headers.delete(CONTENT_TYPE)
32
+ headers.delete(CONTENT_LENGTH)
33
+ original_body = body
34
+ body = Rack::BodyProxy.new([]) do
35
+ original_body.close if original_body.respond_to?(:close)
36
+ end
37
+ end
38
+ [status, headers, body]
39
+ else
40
+ @app.call(env)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Return whether the response has not been modified since the
47
+ # last request.
48
+ def fresh?(env, headers)
49
+ # If-None-Match has priority over If-Modified-Since per RFC 7232
50
+ if none_match = env['HTTP_IF_NONE_MATCH']
51
+ etag_matches?(none_match, headers)
52
+ elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since))
53
+ modified_since?(modified_since, headers)
54
+ end
55
+ end
56
+
57
+ # Whether the ETag response header matches the If-None-Match request header.
58
+ # If so, the request has not been modified.
59
+ def etag_matches?(none_match, headers)
60
+ headers['ETag'] == none_match
61
+ end
62
+
63
+ # Whether the Last-Modified response header matches the If-Modified-Since
64
+ # request header. If so, the request has not been modified.
65
+ def modified_since?(modified_since, headers)
66
+ last_modified = to_rfc2822(headers['Last-Modified']) and
67
+ modified_since >= last_modified
68
+ end
69
+
70
+ # Return a Time object for the given string (which should be in RFC2822
71
+ # format), or nil if the string cannot be parsed.
72
+ def to_rfc2822(since)
73
+ # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A
74
+ # anything shorter is invalid, this avoids exceptions for common cases
75
+ # most common being the empty string
76
+ if since && since.length >= 16
77
+ # NOTE: there is no trivial way to write this in a non exception way
78
+ # _rfc2822 returns a hash but is not that usable
79
+ Time.rfc2822(since) rescue nil
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Rack::Config modifies the environment using the block given during
5
+ # initialization.
6
+ #
7
+ # Example:
8
+ # use Rack::Config do |env|
9
+ # env['my-key'] = 'some-value'
10
+ # end
11
+ class Config
12
+ def initialize(app, &block)
13
+ @app = app
14
+ @block = block
15
+ end
16
+
17
+ def call(env)
18
+ @block.call(env)
19
+ @app.call(env)
20
+ end
21
+ end
22
+ end
@@ -1,7 +1,11 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
- # Sets the Content-Length header on responses with fixed-length bodies.
4
+
5
+ # Sets the Content-Length header on responses that do not specify
6
+ # a Content-Length or Transfer-Encoding header. Note that this
7
+ # does not fix responses that have an invalid Content-Length
8
+ # header specified.
5
9
  class ContentLength
6
10
  include Rack::Utils
7
11
 
@@ -11,16 +15,21 @@ module Rack
11
15
 
12
16
  def call(env)
13
17
  status, headers, body = @app.call(env)
14
- headers = HeaderHash.new(headers)
18
+ headers = HeaderHash[headers]
19
+
20
+ if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
21
+ !headers[CONTENT_LENGTH] &&
22
+ !headers[TRANSFER_ENCODING]
23
+
24
+ obody = body
25
+ body, length = [], 0
26
+ obody.each { |part| body << part; length += part.bytesize }
15
27
 
16
- if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
17
- !headers['Content-Length'] &&
18
- !headers['Transfer-Encoding'] &&
19
- (body.respond_to?(:to_ary) || body.respond_to?(:to_str))
28
+ body = BodyProxy.new(body) do
29
+ obody.close if obody.respond_to?(:close)
30
+ end
20
31
 
21
- body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
22
- length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
23
- headers['Content-Length'] = length.to_s
32
+ headers[CONTENT_LENGTH] = length.to_s
24
33
  end
25
34
 
26
35
  [status, headers, body]
@@ -1,4 +1,4 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
4
 
@@ -7,17 +7,24 @@ module Rack
7
7
  # Builder Usage:
8
8
  # use Rack::ContentType, "text/plain"
9
9
  #
10
- # When no content type argument is provided, "text/html" is assumed.
10
+ # When no content type argument is provided, "text/html" is the
11
+ # default.
11
12
  class ContentType
13
+ include Rack::Utils
14
+
12
15
  def initialize(app, content_type = "text/html")
13
16
  @app, @content_type = app, content_type
14
17
  end
15
18
 
16
19
  def call(env)
17
20
  status, headers, body = @app.call(env)
18
- headers = Utils::HeaderHash.new(headers)
19
- headers['Content-Type'] ||= @content_type
20
- [status, headers.to_hash, body]
21
+ headers = Utils::HeaderHash[headers]
22
+
23
+ unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
24
+ headers[CONTENT_TYPE] ||= @content_type
25
+ end
26
+
27
+ [status, headers, body]
21
28
  end
22
29
  end
23
30
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regexp has `match?` since Ruby 2.4
4
+ # so to support Ruby < 2.4 we need to define this method
5
+
6
+ module Rack
7
+ module RegexpExtensions
8
+ refine Regexp do
9
+ def match?(string, pos = 0)
10
+ !!match(string, pos)
11
+ end
12
+ end unless //.respond_to?(:match?)
13
+ end
14
+ end
data/lib/rack/deflater.rb CHANGED
@@ -1,32 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "zlib"
2
- require "stringio"
3
4
  require "time" # for Time.httpdate
4
- require 'rack/utils'
5
5
 
6
6
  module Rack
7
+ # This middleware enables content encoding of http responses,
8
+ # usually for purposes of compression.
9
+ #
10
+ # Currently supported encodings:
11
+ #
12
+ # * gzip
13
+ # * identity (no transformation)
14
+ #
15
+ # This middleware automatically detects when encoding is supported
16
+ # and allowed. For example no encoding is made when a cache
17
+ # directive of 'no-transform' is present, when the response status
18
+ # code is one that doesn't allow an entity body, or when the body
19
+ # is empty.
20
+ #
21
+ # Note that despite the name, Deflater does not support the +deflate+
22
+ # encoding.
7
23
  class Deflater
8
- def initialize(app)
24
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
25
+
26
+ # Creates Rack::Deflater middleware. Options:
27
+ #
28
+ # :if :: a lambda enabling / disabling deflation based on returned boolean value
29
+ # (e.g <tt>use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }</tt>).
30
+ # However, be aware that calling `body.each` inside the block will break cases where `body.each` is not idempotent,
31
+ # such as when it is an +IO+ instance.
32
+ # :include :: a list of content types that should be compressed. By default, all content types are compressed.
33
+ # :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces
34
+ # latency for time-sensitive streaming applications, but hurts compression and throughput.
35
+ # Defaults to +true+.
36
+ def initialize(app, options = {})
9
37
  @app = app
38
+ @condition = options[:if]
39
+ @compressible_types = options[:include]
40
+ @sync = options.fetch(:sync, true)
10
41
  end
11
42
 
12
43
  def call(env)
13
44
  status, headers, body = @app.call(env)
14
- headers = Utils::HeaderHash.new(headers)
45
+ headers = Utils::HeaderHash[headers]
15
46
 
16
- # Skip compressing empty entity body responses and responses with
17
- # no-transform set.
18
- if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
19
- headers['Cache-Control'].to_s =~ /\bno-transform\b/
47
+ unless should_deflate?(env, status, headers, body)
20
48
  return [status, headers, body]
21
49
  end
22
50
 
23
51
  request = Request.new(env)
24
52
 
25
- encoding = Utils.select_best_encoding(%w(gzip deflate identity),
53
+ encoding = Utils.select_best_encoding(%w(gzip identity),
26
54
  request.accept_encoding)
27
55
 
28
56
  # Set the Vary HTTP header.
29
- vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
57
+ vary = headers["Vary"].to_s.split(",").map(&:strip)
30
58
  unless vary.include?("*") || vary.include?("Accept-Encoding")
31
59
  headers["Vary"] = vary.push("Accept-Encoding").join(",")
32
60
  end
@@ -34,63 +62,83 @@ module Rack
34
62
  case encoding
35
63
  when "gzip"
36
64
  headers['Content-Encoding'] = "gzip"
37
- headers.delete('Content-Length')
38
- mtime = headers.key?("Last-Modified") ?
39
- Time.httpdate(headers["Last-Modified"]) : Time.now
40
- [status, headers, GzipStream.new(body, mtime)]
41
- when "deflate"
42
- headers['Content-Encoding'] = "deflate"
43
- headers.delete('Content-Length')
44
- [status, headers, DeflateStream.new(body)]
65
+ headers.delete(CONTENT_LENGTH)
66
+ mtime = headers["Last-Modified"]
67
+ mtime = Time.httpdate(mtime).to_i if mtime
68
+ [status, headers, GzipStream.new(body, mtime, @sync)]
45
69
  when "identity"
46
70
  [status, headers, body]
47
71
  when nil
48
72
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
49
- [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
73
+ bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
74
+ [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp]
50
75
  end
51
76
  end
52
77
 
78
+ # Body class used for gzip encoded responses.
53
79
  class GzipStream
54
- def initialize(body, mtime)
80
+ # Initialize the gzip stream. Arguments:
81
+ # body :: Response body to compress with gzip
82
+ # mtime :: The modification time of the body, used to set the
83
+ # modification time in the gzip header.
84
+ # sync :: Whether to flush each gzip chunk as soon as it is ready.
85
+ def initialize(body, mtime, sync)
55
86
  @body = body
56
87
  @mtime = mtime
88
+ @sync = sync
57
89
  end
58
90
 
91
+ # Yield gzip compressed strings to the given block.
59
92
  def each(&block)
60
93
  @writer = block
61
- gzip =::Zlib::GzipWriter.new(self)
62
- gzip.mtime = @mtime
63
- @body.each { |part| gzip.write(part) }
64
- @body.close if @body.respond_to?(:close)
94
+ gzip = ::Zlib::GzipWriter.new(self)
95
+ gzip.mtime = @mtime if @mtime
96
+ @body.each { |part|
97
+ # Skip empty strings, as they would result in no output,
98
+ # and flushing empty parts would raise Zlib::BufError.
99
+ next if part.empty?
100
+
101
+ gzip.write(part)
102
+ gzip.flush if @sync
103
+ }
104
+ ensure
65
105
  gzip.close
66
- @writer = nil
67
106
  end
68
107
 
108
+ # Call the block passed to #each with the the gzipped data.
69
109
  def write(data)
70
110
  @writer.call(data)
71
111
  end
112
+
113
+ # Close the original body if possible.
114
+ def close
115
+ @body.close if @body.respond_to?(:close)
116
+ end
72
117
  end
73
118
 
74
- class DeflateStream
75
- DEFLATE_ARGS = [
76
- Zlib::DEFAULT_COMPRESSION,
77
- # drop the zlib header which causes both Safari and IE to choke
78
- -Zlib::MAX_WBITS,
79
- Zlib::DEF_MEM_LEVEL,
80
- Zlib::DEFAULT_STRATEGY
81
- ]
119
+ private
82
120
 
83
- def initialize(body)
84
- @body = body
121
+ # Whether the body should be compressed.
122
+ def should_deflate?(env, status, headers, body)
123
+ # Skip compressing empty entity body responses and responses with
124
+ # no-transform set.
125
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
126
+ /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
127
+ headers['Content-Encoding']&.!~(/\bidentity\b/)
128
+ return false
85
129
  end
86
130
 
87
- def each
88
- deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
89
- @body.each { |part| yield deflater.deflate(part) }
90
- @body.close if @body.respond_to?(:close)
91
- yield deflater.finish
92
- nil
93
- end
131
+ # Skip if @compressible_types are given and does not include request's content type
132
+ return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
133
+
134
+ # Skip if @condition lambda is given and evaluates to false
135
+ return false if @condition && !@condition.call(env, status, headers, body)
136
+
137
+ # No point in compressing empty body, also handles usage with
138
+ # Rack::Sendfile.
139
+ return false if headers[CONTENT_LENGTH] == '0'
140
+
141
+ true
94
142
  end
95
143
  end
96
144
  end