rack 2.0.9.4 → 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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/{HISTORY.md → CHANGELOG.md} +214 -164
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +79 -133
  5. data/Rakefile +25 -18
  6. data/SPEC +9 -9
  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 -11
  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 +15 -12
  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 -62
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +6 -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 +79 -26
  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 +40 -22
  65. data/lib/rack/session/cookie.rb +10 -9
  66. data/lib/rack/session/memcache.rb +4 -93
  67. data/lib/rack/session/pool.rb +4 -2
  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 +64 -93
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +33 -175
  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 -107
  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 -520
  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 -721
  160. data/test/spec_null_logger.rb +0 -21
  161. data/test/spec_recursive.rb +0 -75
  162. data/test/spec_request.rb +0 -1423
  163. data/test/spec_response.rb +0 -528
  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 -357
  172. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  173. data/test/spec_session_pool.rb +0 -247
  174. data/test/spec_show_exceptions.rb +0 -93
  175. data/test/spec_show_status.rb +0 -104
  176. data/test/spec_static.rb +0 -184
  177. data/test/spec_tempfile_reaper.rb +0 -64
  178. data/test/spec_thin.rb +0 -96
  179. data/test/spec_urlmap.rb +0 -237
  180. data/test/spec_utils.rb +0 -742
  181. data/test/spec_version.rb +0 -11
  182. data/test/spec_webrick.rb +0 -206
  183. data/test/static/another/index.html +0 -1
  184. data/test/static/foo.html +0 -1
  185. data/test/static/index.html +0 -1
  186. data/test/testrequest.rb +0 -78
  187. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  188. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core_ext/regexp'
4
+
1
5
  module Rack
2
6
  class QueryParser
7
+ using ::Rack::RegexpExtensions
8
+
3
9
  DEFAULT_SEP = /[&;] */n
4
10
  COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
5
11
 
@@ -36,7 +42,7 @@ module Rack
36
42
 
37
43
  (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
38
44
  next if p.empty?
39
- k, v = p.split('='.freeze, 2).map!(&unescaper)
45
+ k, v = p.split('=', 2).map!(&unescaper)
40
46
 
41
47
  if cur = params[k]
42
48
  if cur.class == Array
@@ -49,7 +55,7 @@ module Rack
49
55
  end
50
56
  end
51
57
 
52
- return params.to_params_hash
58
+ return params.to_h
53
59
  end
54
60
 
55
61
  # parse_nested_query expands a query string into structural types. Supported
@@ -61,13 +67,13 @@ module Rack
61
67
  return {} if qs.nil? || qs.empty?
62
68
  params = make_params
63
69
 
64
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
65
- k, v = p.split('='.freeze, 2).map! { |s| unescape(s) }
70
+ qs.split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
71
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
66
72
 
67
73
  normalize_params(params, k, v, param_depth_limit)
68
74
  end
69
75
 
70
- return params.to_params_hash
76
+ return params.to_h
71
77
  rescue ArgumentError => e
72
78
  raise InvalidParameterError, e.message
73
79
  end
@@ -79,22 +85,22 @@ module Rack
79
85
  raise RangeError if depth <= 0
80
86
 
81
87
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
- k = $1 || ''.freeze
83
- after = $' || ''.freeze
88
+ k = $1 || ''
89
+ after = $' || ''
84
90
 
85
91
  if k.empty?
86
- if !v.nil? && name == "[]".freeze
92
+ if !v.nil? && name == "[]"
87
93
  return Array(v)
88
94
  else
89
95
  return
90
96
  end
91
97
  end
92
98
 
93
- if after == ''.freeze
99
+ if after == ''
94
100
  params[k] = v
95
- elsif after == "[".freeze
101
+ elsif after == "["
96
102
  params[name] = v
97
- elsif after == "[]".freeze
103
+ elsif after == "[]"
98
104
  params[k] ||= []
99
105
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
100
106
  params[k] << v
@@ -135,7 +141,7 @@ module Rack
135
141
  end
136
142
 
137
143
  def params_hash_has_key?(hash, key)
138
- return false if key =~ /\[\]/
144
+ return false if /\[\]/.match?(key)
139
145
 
140
146
  key.split(/[\[\]]+/).inject(hash) do |h, part|
141
147
  next h if part == ''
@@ -171,22 +177,42 @@ module Rack
171
177
  @params.key?(key)
172
178
  end
173
179
 
174
- def to_params_hash
175
- hash = @params
176
- hash.keys.each do |key|
177
- value = hash[key]
178
- if value.kind_of?(self.class)
179
- if value.object_id == self.object_id
180
- hash[key] = hash
181
- else
182
- hash[key] = value.to_params_hash
183
- end
184
- elsif value.kind_of?(Array)
185
- value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
180
+ # Recursively unwraps nested `Params` objects and constructs an object
181
+ # of the same shape, but using the objects' internal representations
182
+ # (Ruby hashes) in place of the objects. The result is a hash consisting
183
+ # purely of Ruby primitives.
184
+ #
185
+ # Mutation warning!
186
+ #
187
+ # 1. This method mutates the internal representation of the `Params`
188
+ # objects in order to save object allocations.
189
+ #
190
+ # 2. The value you get back is a reference to the internal hash
191
+ # representation, not a copy.
192
+ #
193
+ # 3. Because the `Params` object's internal representation is mutable
194
+ # through the `#[]=` method, it is not thread safe. The result of
195
+ # getting the hash representation while another thread is adding a
196
+ # key to it is non-deterministic.
197
+ #
198
+ def to_h
199
+ @params.each do |key, value|
200
+ case value
201
+ when self
202
+ # Handle circular references gracefully.
203
+ @params[key] = @params
204
+ when Params
205
+ @params[key] = value.to_h
206
+ when Array
207
+ value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
208
+ else
209
+ # Ignore anything that is not a `Params` object or
210
+ # a collection that can contain one.
186
211
  end
187
212
  end
188
- hash
213
+ @params
189
214
  end
215
+ alias_method :to_params_hash, :to_h
190
216
  end
191
217
  end
192
218
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
 
3
5
  module Rack
@@ -10,14 +12,14 @@ module Rack
10
12
  class ForwardRequest < Exception
11
13
  attr_reader :url, :env
12
14
 
13
- def initialize(url, env={})
15
+ def initialize(url, env = {})
14
16
  @url = URI(url)
15
17
  @env = env
16
18
 
17
- @env[PATH_INFO] = @url.path
18
- @env[QUERY_STRING] = @url.query if @url.query
19
- @env[HTTP_HOST] = @url.host if @url.host
20
- @env["HTTP_PORT"] = @url.port if @url.port
19
+ @env[PATH_INFO] = @url.path
20
+ @env[QUERY_STRING] = @url.query if @url.query
21
+ @env[HTTP_HOST] = @url.host if @url.host
22
+ @env["HTTP_PORT"] = @url.port if @url.port
21
23
  @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
22
24
 
23
25
  super "forwarding to #{url}"
data/lib/rack/reloader.rb CHANGED
@@ -1,9 +1,13 @@
1
- # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
2
- # Rack::Reloader is subject to the terms of an MIT-style license.
3
- # See COPYING or http://www.opensource.org/licenses/mit-license.php.
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
4
+ # Rack::Reloader is subject to the terms of an MIT-style license.
5
+ # See MIT-LICENSE or https://opensource.org/licenses/MIT.
4
6
 
5
7
  require 'pathname'
6
8
 
9
+ require_relative 'core_ext/regexp'
10
+
7
11
  module Rack
8
12
 
9
13
  # High performant source reloader
@@ -20,6 +24,8 @@ module Rack
20
24
  # It is performing a check/reload cycle at the start of every request, but
21
25
  # also respects a cool down time, during which nothing will be done.
22
26
  class Reloader
27
+ using ::Rack::RegexpExtensions
28
+
23
29
  def initialize(app, cooldown = 10, backend = Stat)
24
30
  @app = app
25
31
  @cooldown = cooldown
@@ -69,7 +75,7 @@ module Rack
69
75
  paths = ['./', *$LOAD_PATH].uniq
70
76
 
71
77
  files.map{|file|
72
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
78
+ next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
73
79
 
74
80
  found, stat = figure_path(file, paths)
75
81
  next unless found && stat && mtime = stat.mtime
data/lib/rack/request.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
  require 'rack/media_type'
3
5
 
6
+ require_relative 'core_ext/regexp'
7
+
4
8
  module Rack
5
9
  # Rack::Request provides a convenient interface to a Rack
6
10
  # environment. It is stateless, the environment +env+ passed to the
@@ -11,7 +15,18 @@ module Rack
11
15
  # req.params["data"]
12
16
 
13
17
  class Request
14
- SCHEME_WHITELIST = %w(https http).freeze
18
+ using ::Rack::RegexpExtensions
19
+
20
+ class << self
21
+ attr_accessor :ip_filter
22
+ end
23
+
24
+ self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
25
+ ALLOWED_SCHEMES = %w(https http).freeze
26
+ SCHEME_WHITELIST = ALLOWED_SCHEMES
27
+ if Object.respond_to?(:deprecate_constant)
28
+ deprecate_constant :SCHEME_WHITELIST
29
+ end
15
30
 
16
31
  def initialize(env)
17
32
  @params = nil
@@ -100,7 +115,7 @@ module Rack
100
115
 
101
116
  module Helpers
102
117
  # The set of form-data media-types. Requests that do not indicate
103
- # one of the media types presents in this list will not be eligible
118
+ # one of the media types present in this list will not be eligible
104
119
  # for form-data / param parsing.
105
120
  FORM_DATA_MEDIA_TYPES = [
106
121
  'application/x-www-form-urlencoded',
@@ -108,7 +123,7 @@ module Rack
108
123
  ]
109
124
 
110
125
  # The set of media-types. Requests that do not indicate
111
- # one of the media types presents in this list will not be eligible
126
+ # one of the media types present in this list will not be eligible
112
127
  # for param parsing like soap attachments or generic multiparts
113
128
  PARSEABLE_DATA_MEDIA_TYPES = [
114
129
  'multipart/related',
@@ -119,11 +134,11 @@ module Rack
119
134
  # to include the port in a generated URI.
120
135
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
121
136
 
122
- HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
123
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
124
- HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
125
- HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
126
- HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
137
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
138
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
139
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
140
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
141
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
127
142
 
128
143
  def body; get_header(RACK_INPUT) end
129
144
  def script_name; get_header(SCRIPT_NAME).to_s end
@@ -159,10 +174,10 @@ module Rack
159
174
  def delete?; request_method == DELETE end
160
175
 
161
176
  # Checks the HTTP request method (or verb) to see if it was of type GET
162
- def get?; request_method == GET end
177
+ def get?; request_method == GET end
163
178
 
164
179
  # Checks the HTTP request method (or verb) to see if it was of type HEAD
165
- def head?; request_method == HEAD end
180
+ def head?; request_method == HEAD end
166
181
 
167
182
  # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
168
183
  def options?; request_method == OPTIONS end
@@ -208,7 +223,7 @@ module Rack
208
223
  string = get_header HTTP_COOKIE
209
224
 
210
225
  return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
211
- hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
226
+ hash.replace Utils.parse_cookies_header string
212
227
  set_header(RACK_REQUEST_COOKIE_STRING, string)
213
228
  hash
214
229
  end
@@ -232,18 +247,23 @@ module Rack
232
247
 
233
248
  def host
234
249
  # Remove port number.
235
- host_with_port.to_s.sub(/:\d+\z/, '')
250
+ h = host_with_port
251
+ if colon_index = h.index(":")
252
+ h[0, colon_index]
253
+ else
254
+ h
255
+ end
236
256
  end
237
257
 
238
258
  def port
239
- if port = host_with_port.split(/:/)[1]
259
+ if port = extract_port(host_with_port)
240
260
  port.to_i
241
261
  elsif port = get_header(HTTP_X_FORWARDED_PORT)
242
262
  port.to_i
243
263
  elsif has_header?(HTTP_X_FORWARDED_HOST)
244
264
  DEFAULT_PORTS[scheme]
245
265
  elsif has_header?(HTTP_X_FORWARDED_PROTO)
246
- DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
266
+ DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
247
267
  else
248
268
  get_header(SERVER_PORT).to_i
249
269
  end
@@ -260,6 +280,7 @@ module Rack
260
280
  return remote_addrs.first if remote_addrs.any?
261
281
 
262
282
  forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
283
+ .map { |ip| strip_port(ip) }
263
284
 
264
285
  return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
265
286
  end
@@ -337,7 +358,7 @@ module Rack
337
358
 
338
359
  # Fix for Safari Ajax postings that always append \0
339
360
  # form_vars.sub!(/\0\z/, '') # performance replacement:
340
- form_vars.slice!(-1) if form_vars[-1] == ?\0
361
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
341
362
 
342
363
  set_header RACK_REQUEST_FORM_VARS, form_vars
343
364
  set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
@@ -386,12 +407,13 @@ module Rack
386
407
  #
387
408
  # <tt>env['rack.input']</tt> is not touched.
388
409
  def delete_param(k)
389
- [ self.POST.delete(k), self.GET.delete(k) ].compact.first
410
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
411
+ post_value || get_value
390
412
  end
391
413
 
392
414
  def base_url
393
415
  url = "#{scheme}://#{host}"
394
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
416
+ url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
395
417
  url
396
418
  end
397
419
 
@@ -417,7 +439,7 @@ module Rack
417
439
  end
418
440
 
419
441
  def trusted_proxy?(ip)
420
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
442
+ Rack::Request.ip_filter.call(ip)
421
443
  end
422
444
 
423
445
  # shortcut for <tt>request.params[key]</tt>
@@ -464,7 +486,7 @@ module Rack
464
486
  Utils.default_query_parser
465
487
  end
466
488
 
467
- def parse_query(qs, d='&')
489
+ def parse_query(qs, d = '&')
468
490
  query_parser.parse_nested_query(qs, d)
469
491
  end
470
492
 
@@ -476,21 +498,52 @@ module Rack
476
498
  ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
477
499
  end
478
500
 
501
+ def strip_port(ip_address)
502
+ # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
503
+ # returns: "2001:db8:cafe::17"
504
+ sep_start = ip_address.index('[')
505
+ sep_end = ip_address.index(']')
506
+ if (sep_start && sep_end)
507
+ return ip_address[sep_start + 1, sep_end - 1]
508
+ end
509
+
510
+ # IPv4 format with optional port: "192.0.2.43:47011"
511
+ # returns: "192.0.2.43"
512
+ sep = ip_address.index(':')
513
+ if (sep && ip_address.count(':') == 1)
514
+ return ip_address[0, sep]
515
+ end
516
+
517
+ ip_address
518
+ end
519
+
479
520
  def reject_trusted_ip_addresses(ip_addresses)
480
521
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
481
522
  end
482
523
 
483
524
  def forwarded_scheme
484
- scheme_headers = [
485
- get_header(HTTP_X_FORWARDED_SCHEME),
486
- get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
- ]
525
+ allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
526
+ allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
527
+ end
488
528
 
489
- scheme_headers.each do |header|
490
- return header if SCHEME_WHITELIST.include?(header)
529
+ def allowed_scheme(header)
530
+ header if ALLOWED_SCHEMES.include?(header)
531
+ end
532
+
533
+ def extract_proto_header(header)
534
+ if header
535
+ if (comma_index = header.index(','))
536
+ header[0, comma_index]
537
+ else
538
+ header
539
+ end
491
540
  end
541
+ end
492
542
 
493
- nil
543
+ def extract_port(uri)
544
+ if (colon_index = uri.index(':'))
545
+ uri[colon_index + 1, uri.length]
546
+ end
494
547
  end
495
548
  end
496
549
 
data/lib/rack/response.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/request'
2
4
  require 'rack/utils'
3
5
  require 'rack/body_proxy'
@@ -23,32 +25,34 @@ module Rack
23
25
  attr_reader :header
24
26
  alias headers header
25
27
 
26
- CHUNKED = 'chunked'.freeze
28
+ CHUNKED = 'chunked'
29
+ STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
27
30
 
28
- def initialize(body=[], status=200, header={})
31
+ def initialize(body = nil, status = 200, header = {})
29
32
  @status = status.to_i
30
- @header = Utils::HeaderHash.new.merge(header)
33
+ @header = Utils::HeaderHash.new(header)
31
34
 
32
- @writer = lambda { |x| @body << x }
33
- @block = nil
34
- @length = 0
35
+ @writer = self.method(:append)
35
36
 
36
- @body = []
37
+ @block = nil
38
+ @length = 0
37
39
 
38
- if body.respond_to? :to_str
39
- write body.to_str
40
- elsif body.respond_to?(:each)
41
- body.each { |part|
42
- write part.to_s
43
- }
40
+ # Keep track of whether we have expanded the user supplied body.
41
+ if body.nil?
42
+ @body = []
43
+ @buffered = true
44
+ elsif body.respond_to?(:to_str)
45
+ @body = [body]
46
+ @buffered = true
44
47
  else
45
- raise TypeError, "stringable or iterable required"
48
+ @body = body
49
+ @buffered = false
46
50
  end
47
51
 
48
- yield self if block_given?
52
+ yield self if block_given?
49
53
  end
50
54
 
51
- def redirect(target, status=302)
55
+ def redirect(target, status = 302)
52
56
  self.status = status
53
57
  self.location = target
54
58
  end
@@ -58,41 +62,45 @@ module Rack
58
62
  end
59
63
 
60
64
  def finish(&block)
61
- @block = block
62
-
63
- if [204, 304].include?(status.to_i)
65
+ if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
64
66
  delete_header CONTENT_TYPE
65
67
  delete_header CONTENT_LENGTH
66
68
  close
67
69
  [status.to_i, header, []]
68
70
  else
69
- [status.to_i, header, BodyProxy.new(self){}]
71
+ if block_given?
72
+ @block = block
73
+ [status.to_i, header, self]
74
+ else
75
+ [status.to_i, header, @body]
76
+ end
70
77
  end
71
78
  end
79
+
72
80
  alias to_a finish # For *response
73
- alias to_ary finish # For implicit-splat on Ruby 1.9.2
74
81
 
75
82
  def each(&callback)
76
83
  @body.each(&callback)
77
- @writer = callback
78
- @block.call(self) if @block
84
+ @buffered = true
85
+
86
+ if @block
87
+ @writer = callback
88
+ @block.call(self)
89
+ end
79
90
  end
80
91
 
81
92
  # Append to body and update Content-Length.
82
93
  #
83
94
  # NOTE: Do not mix #write and direct #body access!
84
95
  #
85
- def write(str)
86
- s = str.to_s
87
- @length += s.bytesize unless chunked?
88
- @writer.call s
96
+ def write(chunk)
97
+ buffered_body!
89
98
 
90
- set_header(CONTENT_LENGTH, @length.to_s) unless chunked?
91
- str
99
+ @writer.call(chunk.to_s)
92
100
  end
93
101
 
94
102
  def close
95
- body.close if body.respond_to?(:close)
103
+ @body.close if @body.respond_to?(:close)
96
104
  end
97
105
 
98
106
  def empty?
@@ -184,7 +192,7 @@ module Rack
184
192
  set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
185
193
  end
186
194
 
187
- def delete_cookie(key, value={})
195
+ def delete_cookie(key, value = {})
188
196
  set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
189
197
  end
190
198
 
@@ -211,6 +219,38 @@ module Rack
211
219
  def etag= v
212
220
  set_header ETAG, v
213
221
  end
222
+
223
+ protected
224
+
225
+ def buffered_body!
226
+ return if @buffered
227
+
228
+ if @body.is_a?(Array)
229
+ # The user supplied body was an array:
230
+ @body = @body.compact
231
+ else
232
+ # Turn the user supplied body into a buffered array:
233
+ body = @body
234
+ @body = Array.new
235
+
236
+ body.each do |part|
237
+ @writer.call(part.to_s)
238
+ end
239
+ end
240
+
241
+ @buffered = true
242
+ end
243
+
244
+ def append(chunk)
245
+ @body << chunk
246
+
247
+ unless chunked?
248
+ @length += chunk.bytesize
249
+ set_header(CONTENT_LENGTH, @length.to_s)
250
+ end
251
+
252
+ return chunk
253
+ end
214
254
  end
215
255
 
216
256
  include Helpers
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'tempfile'
3
5
  require 'rack/utils'
4
6
 
@@ -40,7 +42,7 @@ module Rack
40
42
  end
41
43
 
42
44
  # Closes this RewindableInput object without closing the originally
43
- # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
45
+ # wrapped IO object. Cleans up any temporary resources that this RewindableInput
44
46
  # has created.
45
47
  #
46
48
  # This method may be called multiple times. It does nothing on subsequent calls.
@@ -72,7 +74,7 @@ module Rack
72
74
  @unlinked = true
73
75
  end
74
76
 
75
- buffer = ""
77
+ buffer = "".dup
76
78
  while @io.read(1024 * 4, buffer)
77
79
  entire_buffer_written_out = false
78
80
  while !entire_buffer_written_out
data/lib/rack/runtime.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -8,8 +10,8 @@ module Rack
8
10
  # time, or before all the other middlewares to include time for them,
9
11
  # too.
10
12
  class Runtime
11
- FORMAT_STRING = "%0.6f".freeze # :nodoc:
12
- HEADER_NAME = "X-Runtime".freeze # :nodoc:
13
+ FORMAT_STRING = "%0.6f" # :nodoc:
14
+ HEADER_NAME = "X-Runtime" # :nodoc:
13
15
 
14
16
  def initialize(app, name = nil)
15
17
  @app = app