rack 2.0.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/{HISTORY.md → CHANGELOG.md} +220 -155
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +77 -117
  5. data/Rakefile +25 -18
  6. data/SPEC +3 -4
  7. data/bin/rackup +1 -0
  8. data/example/lobster.ru +2 -0
  9. data/example/protectedlobster.rb +3 -1
  10. data/example/protectedlobster.ru +2 -0
  11. data/lib/rack/auth/abstract/handler.rb +3 -1
  12. data/lib/rack/auth/abstract/request.rb +2 -0
  13. data/lib/rack/auth/basic.rb +4 -1
  14. data/lib/rack/auth/digest/md5.rb +9 -7
  15. data/lib/rack/auth/digest/nonce.rb +6 -3
  16. data/lib/rack/auth/digest/params.rb +4 -2
  17. data/lib/rack/auth/digest/request.rb +2 -0
  18. data/lib/rack/body_proxy.rb +3 -6
  19. data/lib/rack/builder.rb +38 -15
  20. data/lib/rack/cascade.rb +6 -5
  21. data/lib/rack/chunked.rb +29 -6
  22. data/lib/rack/common_logger.rb +9 -8
  23. data/lib/rack/conditional_get.rb +3 -1
  24. data/lib/rack/config.rb +2 -0
  25. data/lib/rack/content_length.rb +3 -1
  26. data/lib/rack/content_type.rb +3 -1
  27. data/lib/rack/core_ext/regexp.rb +14 -0
  28. data/lib/rack/deflater.rb +28 -17
  29. data/lib/rack/directory.rb +17 -14
  30. data/lib/rack/etag.rb +3 -1
  31. data/lib/rack/events.rb +5 -3
  32. data/lib/rack/file.rb +5 -173
  33. data/lib/rack/files.rb +178 -0
  34. data/lib/rack/handler/cgi.rb +3 -1
  35. data/lib/rack/handler/fastcgi.rb +4 -2
  36. data/lib/rack/handler/lsws.rb +3 -1
  37. data/lib/rack/handler/scgi.rb +9 -6
  38. data/lib/rack/handler/thin.rb +3 -1
  39. data/lib/rack/handler/webrick.rb +4 -2
  40. data/lib/rack/handler.rb +7 -2
  41. data/lib/rack/head.rb +2 -0
  42. data/lib/rack/lint.rb +14 -11
  43. data/lib/rack/lobster.rb +7 -5
  44. data/lib/rack/lock.rb +2 -0
  45. data/lib/rack/logger.rb +2 -0
  46. data/lib/rack/media_type.rb +10 -5
  47. data/lib/rack/method_override.rb +4 -2
  48. data/lib/rack/mime.rb +9 -1
  49. data/lib/rack/mock.rb +74 -15
  50. data/lib/rack/multipart/generator.rb +6 -7
  51. data/lib/rack/multipart/parser.rb +55 -52
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +5 -3
  54. data/lib/rack/null_logger.rb +2 -0
  55. data/lib/rack/query_parser.rb +51 -25
  56. data/lib/rack/recursive.rb +7 -5
  57. data/lib/rack/reloader.rb +10 -4
  58. data/lib/rack/request.rb +80 -27
  59. data/lib/rack/response.rb +71 -31
  60. data/lib/rack/rewindable_input.rb +4 -2
  61. data/lib/rack/runtime.rb +4 -2
  62. data/lib/rack/sendfile.rb +15 -8
  63. data/lib/rack/server.rb +88 -16
  64. data/lib/rack/session/abstract/id.rb +104 -21
  65. data/lib/rack/session/cookie.rb +21 -11
  66. data/lib/rack/session/memcache.rb +4 -87
  67. data/lib/rack/session/pool.rb +17 -8
  68. data/lib/rack/show_exceptions.rb +15 -9
  69. data/lib/rack/show_status.rb +4 -2
  70. data/lib/rack/static.rb +15 -10
  71. data/lib/rack/tempfile_reaper.rb +2 -0
  72. data/lib/rack/urlmap.rb +11 -2
  73. data/lib/rack/utils.rb +55 -70
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +30 -171
  77. data/test/builder/an_underscore_app.rb +0 -5
  78. data/test/builder/anything.rb +0 -5
  79. data/test/builder/comment.ru +0 -4
  80. data/test/builder/end.ru +0 -5
  81. data/test/builder/line.ru +0 -1
  82. data/test/builder/options.ru +0 -2
  83. data/test/cgi/assets/folder/test.js +0 -1
  84. data/test/cgi/assets/fonts/font.eot +0 -1
  85. data/test/cgi/assets/images/image.png +0 -1
  86. data/test/cgi/assets/index.html +0 -1
  87. data/test/cgi/assets/javascripts/app.js +0 -1
  88. data/test/cgi/assets/stylesheets/app.css +0 -1
  89. data/test/cgi/lighttpd.conf +0 -26
  90. data/test/cgi/rackup_stub.rb +0 -6
  91. data/test/cgi/sample_rackup.ru +0 -5
  92. data/test/cgi/test +0 -9
  93. data/test/cgi/test+directory/test+file +0 -1
  94. data/test/cgi/test.fcgi +0 -9
  95. data/test/cgi/test.gz +0 -0
  96. data/test/cgi/test.ru +0 -5
  97. data/test/gemloader.rb +0 -10
  98. data/test/helper.rb +0 -34
  99. data/test/multipart/bad_robots +0 -259
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/content_type_and_no_filename +0 -6
  102. data/test/multipart/empty +0 -10
  103. data/test/multipart/fail_16384_nofile +0 -814
  104. data/test/multipart/file1.txt +0 -1
  105. data/test/multipart/filename_and_modification_param +0 -7
  106. data/test/multipart/filename_and_no_name +0 -6
  107. data/test/multipart/filename_with_encoded_words +0 -7
  108. data/test/multipart/filename_with_escaped_quotes +0 -6
  109. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  110. data/test/multipart/filename_with_null_byte +0 -7
  111. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_single_quote +0 -7
  113. data/test/multipart/filename_with_unescaped_percentages +0 -6
  114. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  116. data/test/multipart/filename_with_unescaped_quotes +0 -6
  117. data/test/multipart/ie +0 -6
  118. data/test/multipart/invalid_character +0 -6
  119. data/test/multipart/mixed_files +0 -21
  120. data/test/multipart/nested +0 -10
  121. data/test/multipart/none +0 -9
  122. data/test/multipart/quoted +0 -15
  123. data/test/multipart/rack-logo.png +0 -0
  124. data/test/multipart/semicolon +0 -6
  125. data/test/multipart/text +0 -15
  126. data/test/multipart/three_files_three_fields +0 -31
  127. data/test/multipart/unity3d_wwwform +0 -11
  128. data/test/multipart/webkit +0 -32
  129. data/test/rackup/config.ru +0 -31
  130. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  131. data/test/spec_auth_basic.rb +0 -89
  132. data/test/spec_auth_digest.rb +0 -260
  133. data/test/spec_body_proxy.rb +0 -85
  134. data/test/spec_builder.rb +0 -233
  135. data/test/spec_cascade.rb +0 -63
  136. data/test/spec_cgi.rb +0 -84
  137. data/test/spec_chunked.rb +0 -103
  138. data/test/spec_common_logger.rb +0 -95
  139. data/test/spec_conditional_get.rb +0 -103
  140. data/test/spec_config.rb +0 -23
  141. data/test/spec_content_length.rb +0 -86
  142. data/test/spec_content_type.rb +0 -46
  143. data/test/spec_deflater.rb +0 -375
  144. data/test/spec_directory.rb +0 -148
  145. data/test/spec_etag.rb +0 -108
  146. data/test/spec_events.rb +0 -133
  147. data/test/spec_fastcgi.rb +0 -85
  148. data/test/spec_file.rb +0 -264
  149. data/test/spec_handler.rb +0 -57
  150. data/test/spec_head.rb +0 -46
  151. data/test/spec_lint.rb +0 -515
  152. data/test/spec_lobster.rb +0 -59
  153. data/test/spec_lock.rb +0 -204
  154. data/test/spec_logger.rb +0 -24
  155. data/test/spec_media_type.rb +0 -42
  156. data/test/spec_method_override.rb +0 -110
  157. data/test/spec_mime.rb +0 -51
  158. data/test/spec_mock.rb +0 -359
  159. data/test/spec_multipart.rb +0 -722
  160. data/test/spec_null_logger.rb +0 -21
  161. data/test/spec_recursive.rb +0 -75
  162. data/test/spec_request.rb +0 -1398
  163. data/test/spec_response.rb +0 -510
  164. data/test/spec_rewindable_input.rb +0 -128
  165. data/test/spec_runtime.rb +0 -50
  166. data/test/spec_sendfile.rb +0 -125
  167. data/test/spec_server.rb +0 -193
  168. data/test/spec_session_abstract_id.rb +0 -31
  169. data/test/spec_session_abstract_session_hash.rb +0 -45
  170. data/test/spec_session_cookie.rb +0 -442
  171. data/test/spec_session_memcache.rb +0 -320
  172. data/test/spec_session_pool.rb +0 -210
  173. data/test/spec_show_exceptions.rb +0 -93
  174. data/test/spec_show_status.rb +0 -104
  175. data/test/spec_static.rb +0 -184
  176. data/test/spec_tempfile_reaper.rb +0 -64
  177. data/test/spec_thin.rb +0 -96
  178. data/test/spec_urlmap.rb +0 -237
  179. data/test/spec_utils.rb +0 -742
  180. data/test/spec_version.rb +0 -11
  181. data/test/spec_webrick.rb +0 -206
  182. data/test/static/another/index.html +0 -1
  183. data/test/static/foo.html +0 -1
  184. data/test/static/index.html +0 -1
  185. data/test/testrequest.rb +0 -78
  186. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  187. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/builder.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Builder implements a small DSL to iteratively construct Rack
3
5
  # applications.
@@ -29,29 +31,43 @@ module Rack
29
31
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
30
32
 
31
33
  class Builder
34
+
35
+ # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
36
+ UTF_8_BOM = '\xef\xbb\xbf'
37
+
32
38
  def self.parse_file(config, opts = Server::Options.new)
33
- options = {}
34
- if config =~ /\.ru$/
35
- cfgfile = ::File.read(config)
36
- if cfgfile[/^#\\(.*)/] && opts
37
- options = opts.parse! $1.split(/\s+/)
38
- end
39
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
40
- app = new_from_string cfgfile, config
39
+ if config.end_with?('.ru')
40
+ return self.load_file(config, opts)
41
41
  else
42
42
  require config
43
43
  app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
44
+ return app, {}
45
+ end
46
+ end
47
+
48
+ def self.load_file(path, opts = Server::Options.new)
49
+ options = {}
50
+
51
+ cfgfile = ::File.read(path)
52
+ cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
53
+
54
+ if cfgfile[/^#\\(.*)/] && opts
55
+ options = opts.parse! $1.split(/\s+/)
44
56
  end
57
+
58
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
59
+ app = new_from_string cfgfile, path
60
+
45
61
  return app, options
46
62
  end
47
63
 
48
- def self.new_from_string(builder_script, file="(rackup)")
64
+ def self.new_from_string(builder_script, file = "(rackup)")
49
65
  eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
50
66
  TOPLEVEL_BINDING, file, 0
51
67
  end
52
68
 
53
69
  def initialize(default_app = nil, &block)
54
- @use, @map, @run, @warmup = [], nil, default_app, nil
70
+ @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
55
71
  instance_eval(&block) if block_given?
56
72
  end
57
73
 
@@ -81,7 +97,7 @@ module Rack
81
97
  def use(middleware, *args, &block)
82
98
  if @map
83
99
  mapping, @map = @map, nil
84
- @use << proc { |app| generate_map app, mapping }
100
+ @use << proc { |app| generate_map(app, mapping) }
85
101
  end
86
102
  @use << proc { |app| middleware.new(app, *args, &block) }
87
103
  end
@@ -113,7 +129,7 @@ module Rack
113
129
  #
114
130
  # use SomeMiddleware
115
131
  # run MyApp
116
- def warmup(prc=nil, &block)
132
+ def warmup(prc = nil, &block)
117
133
  @warmup = prc || block
118
134
  end
119
135
 
@@ -141,10 +157,17 @@ module Rack
141
157
  @map[path] = block
142
158
  end
143
159
 
160
+ # Freeze the app (set using run) and all middleware instances when building the application
161
+ # in to_app.
162
+ def freeze_app
163
+ @freeze_app = true
164
+ end
165
+
144
166
  def to_app
145
167
  app = @map ? generate_map(@run, @map) : @run
146
168
  fail "missing run or map statement" unless app
147
- app = @use.reverse.inject(app) { |a,e| e[a] }
169
+ app.freeze if @freeze_app
170
+ app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
148
171
  @warmup.call(app) if @warmup
149
172
  app
150
173
  end
@@ -156,8 +179,8 @@ module Rack
156
179
  private
157
180
 
158
181
  def generate_map(default_app, mapping)
159
- mapped = default_app ? {'/' => default_app} : {}
160
- mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
182
+ mapped = default_app ? { '/' => default_app } : {}
183
+ mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
161
184
  URLMap.new(mapped)
162
185
  end
163
186
  end
data/lib/rack/cascade.rb CHANGED
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Cascade tries a request on several apps, and returns the
3
5
  # first response that is not 404 or 405 (or in a list of configurable
4
6
  # status codes).
5
7
 
6
8
  class Cascade
7
- NotFound = [404, {CONTENT_TYPE => "text/plain"}, []]
9
+ NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
8
10
 
9
11
  attr_reader :apps
10
12
 
11
- def initialize(apps, catch=[404, 405])
12
- @apps = []; @has_app = {}
13
+ def initialize(apps, catch = [404, 405])
14
+ @apps = []
13
15
  apps.each { |app| add app }
14
16
 
15
17
  @catch = {}
@@ -39,12 +41,11 @@ module Rack
39
41
  end
40
42
 
41
43
  def add(app)
42
- @has_app[app] = true
43
44
  @apps << app
44
45
  end
45
46
 
46
47
  def include?(app)
47
- @has_app.include? app
48
+ @apps.include?(app)
48
49
  end
49
50
 
50
51
  alias_method :<<, :add
data/lib/rack/chunked.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -10,7 +12,7 @@ module Rack
10
12
  # A body wrapper that emits chunked responses
11
13
  class Body
12
14
  TERM = "\r\n"
13
- TAIL = "0#{TERM}#{TERM}"
15
+ TAIL = "0#{TERM}"
14
16
 
15
17
  include Rack::Utils
16
18
 
@@ -18,7 +20,7 @@ module Rack
18
20
  @body = body
19
21
  end
20
22
 
21
- def each
23
+ def each(&block)
22
24
  term = TERM
23
25
  @body.each do |chunk|
24
26
  size = chunk.bytesize
@@ -28,11 +30,28 @@ module Rack
28
30
  yield [size.to_s(16), term, chunk, term].join
29
31
  end
30
32
  yield TAIL
33
+ insert_trailers(&block)
34
+ yield TERM
31
35
  end
32
36
 
33
37
  def close
34
38
  @body.close if @body.respond_to?(:close)
35
39
  end
40
+
41
+ private
42
+
43
+ def insert_trailers(&block)
44
+ end
45
+ end
46
+
47
+ class TrailerBody < Body
48
+ private
49
+
50
+ def insert_trailers(&block)
51
+ @body.trailers.each_pair do |k, v|
52
+ yield "#{k}: #{v}\r\n"
53
+ end
54
+ end
36
55
  end
37
56
 
38
57
  def initialize(app)
@@ -43,7 +62,7 @@ module Rack
43
62
  # a version (nor response headers)
44
63
  def chunkable_version?(ver)
45
64
  case ver
46
- when "HTTP/1.0", nil, "HTTP/0.9"
65
+ when 'HTTP/1.0', nil, 'HTTP/0.9'
47
66
  false
48
67
  else
49
68
  true
@@ -54,15 +73,19 @@ module Rack
54
73
  status, headers, body = @app.call(env)
55
74
  headers = HeaderHash.new(headers)
56
75
 
57
- if ! chunkable_version?(env[HTTP_VERSION]) ||
58
- STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
76
+ if ! chunkable_version?(env[SERVER_PROTOCOL]) ||
77
+ STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
59
78
  headers[CONTENT_LENGTH] ||
60
79
  headers[TRANSFER_ENCODING]
61
80
  [status, headers, body]
62
81
  else
63
82
  headers.delete(CONTENT_LENGTH)
64
83
  headers[TRANSFER_ENCODING] = 'chunked'
65
- [status, headers, Body.new(body)]
84
+ if headers['Trailer']
85
+ [status, headers, TrailerBody.new(body)]
86
+ else
87
+ [status, headers, Body.new(body)]
88
+ end
66
89
  end
67
90
  end
68
91
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/body_proxy'
2
4
 
3
5
  module Rack
@@ -23,13 +25,13 @@ module Rack
23
25
  # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
24
26
  FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
25
27
 
26
- def initialize(app, logger=nil)
28
+ def initialize(app, logger = nil)
27
29
  @app = app
28
30
  @logger = logger
29
31
  end
30
32
 
31
33
  def call(env)
32
- began_at = Time.now
34
+ began_at = Utils.clock_time
33
35
  status, header, body = @app.call(env)
34
36
  header = Utils::HeaderHash.new(header)
35
37
  body = BodyProxy.new(body) { log(env, status, header, began_at) }
@@ -39,20 +41,19 @@ module Rack
39
41
  private
40
42
 
41
43
  def log(env, status, header, began_at)
42
- now = Time.now
43
44
  length = extract_content_length(header)
44
45
 
45
46
  msg = FORMAT % [
46
47
  env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
47
48
  env["REMOTE_USER"] || "-",
48
- now.strftime("%d/%b/%Y:%H:%M:%S %z"),
49
+ Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
49
50
  env[REQUEST_METHOD],
50
51
  env[PATH_INFO],
51
52
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
52
- env[HTTP_VERSION],
53
+ env[SERVER_PROTOCOL],
53
54
  status.to_s[0..3],
54
55
  length,
55
- now - began_at ]
56
+ Utils.clock_time - began_at ]
56
57
 
57
58
  logger = @logger || env[RACK_ERRORS]
58
59
  # Standard library logger doesn't support write but it supports << which actually
@@ -65,8 +66,8 @@ module Rack
65
66
  end
66
67
 
67
68
  def extract_content_length(headers)
68
- value = headers[CONTENT_LENGTH] or return '-'
69
- value.to_s == '0' ? '-' : value
69
+ value = headers[CONTENT_LENGTH]
70
+ !value || value.to_s == '0' ? '-' : value
70
71
  end
71
72
  end
72
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -68,7 +70,7 @@ module Rack
68
70
  # anything shorter is invalid, this avoids exceptions for common cases
69
71
  # most common being the empty string
70
72
  if since && since.length >= 16
71
- # NOTE: there is no trivial way to write this in a non execption way
73
+ # NOTE: there is no trivial way to write this in a non exception way
72
74
  # _rfc2822 returns a hash but is not that usable
73
75
  Time.rfc2822(since) rescue nil
74
76
  else
data/lib/rack/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::Config modifies the environment using the block given during
3
5
  # initialization.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
  require 'rack/body_proxy'
3
5
 
@@ -15,7 +17,7 @@ module Rack
15
17
  status, headers, body = @app.call(env)
16
18
  headers = HeaderHash.new(headers)
17
19
 
18
- if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
20
+ if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
19
21
  !headers[CONTENT_LENGTH] &&
20
22
  !headers[TRANSFER_ENCODING] &&
21
23
  body.respond_to?(:to_ary)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -19,7 +21,7 @@ module Rack
19
21
  status, headers, body = @app.call(env)
20
22
  headers = Utils::HeaderHash.new(headers)
21
23
 
22
- unless STATUS_WITH_NO_ENTITY_BODY.include?(status)
24
+ unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
23
25
  headers[CONTENT_TYPE] ||= @content_type
24
26
  end
25
27
 
@@ -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,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "zlib"
2
4
  require "time" # for Time.httpdate
3
5
  require 'rack/utils'
4
6
 
7
+ require_relative 'core_ext/regexp'
8
+
5
9
  module Rack
6
10
  # This middleware enables compression of http responses.
7
11
  #
@@ -15,19 +19,26 @@ module Rack
15
19
  # directive of 'no-transform' is present, or when the response status
16
20
  # code is one that doesn't allow an entity body.
17
21
  class Deflater
22
+ using ::Rack::RegexpExtensions
23
+
18
24
  ##
19
25
  # Creates Rack::Deflater middleware.
20
26
  #
21
27
  # [app] rack app instance
22
28
  # [options] hash of deflater options, i.e.
23
29
  # 'if' - a lambda enabling / disabling deflation based on returned boolean value
24
- # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.map(&:bytesize).reduce(0, :+) > 512 }
30
+ # e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }
25
31
  # 'include' - a list of content types that should be compressed
32
+ # 'sync' - determines if the stream is going to be flushed after every chunk.
33
+ # Flushing after every chunk reduces latency for
34
+ # time-sensitive streaming applications, but hurts
35
+ # compression and throughput. Defaults to `true'.
26
36
  def initialize(app, options = {})
27
37
  @app = app
28
38
 
29
39
  @condition = options[:if]
30
40
  @compressible_types = options[:include]
41
+ @sync = options[:sync] == false ? false : true
31
42
  end
32
43
 
33
44
  def call(env)
@@ -52,33 +63,34 @@ module Rack
52
63
  case encoding
53
64
  when "gzip"
54
65
  headers['Content-Encoding'] = "gzip"
55
- headers.delete(CONTENT_LENGTH)
56
- mtime = headers.key?("Last-Modified") ?
57
- Time.httpdate(headers["Last-Modified"]) : Time.now
58
- [status, headers, GzipStream.new(body, mtime)]
66
+ headers.delete('Content-Length')
67
+ mtime = headers["Last-Modified"]
68
+ mtime = Time.httpdate(mtime).to_i if mtime
69
+ [status, headers, GzipStream.new(body, mtime, @sync)]
59
70
  when "identity"
60
71
  [status, headers, body]
61
72
  when nil
62
73
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
63
74
  bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
64
- [406, {CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s}, bp]
75
+ [406, { 'Content-Type' => "text/plain", 'Content-Length' => message.length.to_s }, bp]
65
76
  end
66
77
  end
67
78
 
68
79
  class GzipStream
69
- def initialize(body, mtime)
80
+ def initialize(body, mtime, sync)
81
+ @sync = sync
70
82
  @body = body
71
83
  @mtime = mtime
72
- @closed = false
73
84
  end
74
85
 
75
86
  def each(&block)
76
87
  @writer = block
77
- gzip =::Zlib::GzipWriter.new(self)
78
- gzip.mtime = @mtime
88
+ gzip = ::Zlib::GzipWriter.new(self)
89
+ gzip.mtime = @mtime if @mtime
79
90
  @body.each { |part|
80
- gzip.write(part)
81
- gzip.flush
91
+ len = gzip.write(part)
92
+ # Flushing empty parts would raise Zlib::BufError.
93
+ gzip.flush if @sync && len > 0
82
94
  }
83
95
  ensure
84
96
  gzip.close
@@ -90,9 +102,8 @@ module Rack
90
102
  end
91
103
 
92
104
  def close
93
- return if @closed
94
- @closed = true
95
105
  @body.close if @body.respond_to?(:close)
106
+ @body = nil
96
107
  end
97
108
  end
98
109
 
@@ -101,14 +112,14 @@ module Rack
101
112
  def should_deflate?(env, status, headers, body)
102
113
  # Skip compressing empty entity body responses and responses with
103
114
  # no-transform set.
104
- if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
105
- headers[CACHE_CONTROL].to_s =~ /\bno-transform\b/ ||
115
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
116
+ /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
106
117
  (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
107
118
  return false
108
119
  end
109
120
 
110
121
  # Skip if @compressible_types are given and does not include request's content type
111
- return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/]))
122
+ return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
112
123
 
113
124
  # Skip if @condition lambda is given and evaluates to false
114
125
  return false if @condition && !@condition.call(env, status, headers, body)
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'rack/utils'
3
5
  require 'rack/mime'
6
+ require 'rack/files'
4
7
 
5
8
  module Rack
6
9
  # Rack::Directory serves entries below the +root+ given, according to the
@@ -8,7 +11,7 @@ module Rack
8
11
  # will be presented in an html based index. If a file is found, the env will
9
12
  # be passed to the specified +app+.
10
13
  #
11
- # If +app+ is not specified, a Rack::File of the same +root+ will be used.
14
+ # If +app+ is not specified, a Rack::Files of the same +root+ will be used.
12
15
 
13
16
  class Directory
14
17
  DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
@@ -41,9 +44,9 @@ table { width:100%%; }
41
44
 
42
45
  class DirectoryBody < Struct.new(:root, :path, :files)
43
46
  def each
44
- show_path = Rack::Utils.escape_html(path.sub(/^#{root}/,''))
45
- listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) }*"\n"
46
- page = DIR_PAGE % [ show_path, show_path , listings ]
47
+ show_path = Rack::Utils.escape_html(path.sub(/^#{root}/, ''))
48
+ listings = files.map{|f| DIR_FILE % DIR_FILE_escape(*f) } * "\n"
49
+ page = DIR_PAGE % [ show_path, show_path, listings ]
47
50
  page.each_line{|l| yield l }
48
51
  end
49
52
 
@@ -56,9 +59,9 @@ table { width:100%%; }
56
59
 
57
60
  attr_reader :root, :path
58
61
 
59
- def initialize(root, app=nil)
62
+ def initialize(root, app = nil)
60
63
  @root = ::File.expand_path(root)
61
- @app = app || Rack::File.new(@root)
64
+ @app = app || Rack::Files.new(@root)
62
65
  @head = Rack::Head.new(lambda { |env| get env })
63
66
  end
64
67
 
@@ -86,9 +89,9 @@ table { width:100%%; }
86
89
 
87
90
  body = "Bad Request\n"
88
91
  size = body.bytesize
89
- return [400, {CONTENT_TYPE => "text/plain",
92
+ return [400, { CONTENT_TYPE => "text/plain",
90
93
  CONTENT_LENGTH => size.to_s,
91
- "X-Cascade" => "pass"}, [body]]
94
+ "X-Cascade" => "pass" }, [body]]
92
95
  end
93
96
 
94
97
  def check_forbidden(path_info)
@@ -96,13 +99,13 @@ table { width:100%%; }
96
99
 
97
100
  body = "Forbidden\n"
98
101
  size = body.bytesize
99
- return [403, {CONTENT_TYPE => "text/plain",
102
+ return [403, { CONTENT_TYPE => "text/plain",
100
103
  CONTENT_LENGTH => size.to_s,
101
- "X-Cascade" => "pass"}, [body]]
104
+ "X-Cascade" => "pass" }, [body]]
102
105
  end
103
106
 
104
107
  def list_directory(path_info, path, script_name)
105
- files = [['../','Parent Directory','','','']]
108
+ files = [['../', 'Parent Directory', '', '', '']]
106
109
  glob = ::File.join(path, '*')
107
110
 
108
111
  url_head = (script_name.split('/') + path_info.split('/')).map do |part|
@@ -126,7 +129,7 @@ table { width:100%%; }
126
129
  files << [ url, basename, size, type, mtime ]
127
130
  end
128
131
 
129
- return [ 200, { CONTENT_TYPE =>'text/html; charset=utf-8'}, DirectoryBody.new(@root, path, files) ]
132
+ return [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, DirectoryBody.new(@root, path, files) ]
130
133
  end
131
134
 
132
135
  def stat(node)
@@ -154,9 +157,9 @@ table { width:100%%; }
154
157
  def entity_not_found(path_info)
155
158
  body = "Entity not found: #{path_info}\n"
156
159
  size = body.bytesize
157
- return [404, {CONTENT_TYPE => "text/plain",
160
+ return [404, { CONTENT_TYPE => "text/plain",
158
161
  CONTENT_LENGTH => size.to_s,
159
- "X-Cascade" => "pass"}, [body]]
162
+ "X-Cascade" => "pass" }, [body]]
160
163
  end
161
164
 
162
165
  # Stolen from Ramaze
data/lib/rack/etag.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
  require 'digest/sha2'
3
5
 
@@ -13,7 +15,7 @@ module Rack
13
15
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
14
16
  class ETag
15
17
  ETAG_STRING = Rack::ETAG
16
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
18
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
17
19
 
18
20
  def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
19
21
  @app = app
data/lib/rack/events.rb CHANGED
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/response'
2
4
  require 'rack/body_proxy'
3
5
 
4
6
  module Rack
5
7
  ### This middleware provides hooks to certain places in the request /
6
- #response lifecycle. This is so that middleware that don't need to filter
7
- #the response data can safely leave it alone and not have to send messages
8
- #down the traditional "rack stack".
8
+ # response lifecycle. This is so that middleware that don't need to filter
9
+ # the response data can safely leave it alone and not have to send messages
10
+ # down the traditional "rack stack".
9
11
  #
10
12
  # The events are:
11
13
  #