rack 2.0.7 → 2.2.4

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 (190) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +708 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -148
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +35 -10
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +7 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +36 -25
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +59 -34
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +7 -4
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +3 -3
  40. data/lib/rack/handler/webrick.rb +15 -6
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +72 -26
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +2 -1
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +4 -2
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +97 -20
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +56 -57
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +5 -4
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +59 -30
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +220 -61
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +13 -9
  64. data/lib/rack/server.rb +95 -24
  65. data/lib/rack/session/abstract/id.rb +100 -22
  66. data/lib/rack/session/cookie.rb +22 -14
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +18 -9
  69. data/lib/rack/show_exceptions.rb +21 -17
  70. data/lib/rack/show_status.rb +9 -9
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +12 -6
  74. data/lib/rack/utils.rb +107 -111
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +36 -178
  79. data/HISTORY.md +0 -505
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -95
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -515
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -722
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1407
  166. data/test/spec_response.rb +0 -510
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -320
  175. data/test/spec_session_pool.rb +0 -210
  176. data/test/spec_show_exceptions.rb +0 -93
  177. data/test/spec_show_status.rb +0 -104
  178. data/test/spec_static.rb +0 -184
  179. data/test/spec_tempfile_reaper.rb +0 -64
  180. data/test/spec_thin.rb +0 -96
  181. data/test/spec_urlmap.rb +0 -237
  182. data/test/spec_utils.rb +0 -742
  183. data/test/spec_version.rb +0 -11
  184. data/test/spec_webrick.rb +0 -206
  185. data/test/static/another/index.html +0 -1
  186. data/test/static/foo.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,16 +1,22 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
2
4
 
3
5
  module Rack
4
6
  module Multipart
5
7
  class MultipartPartLimitError < Errno::EMFILE; end
6
8
 
7
9
  class Parser
8
- BUFSIZE = 16384
10
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
11
+
12
+ BUFSIZE = 1_048_576
9
13
  TEXT_PLAIN = "text/plain"
10
14
  TEMPFILE_FACTORY = lambda { |filename, content_type|
11
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
15
+ Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
12
16
  }
13
17
 
18
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
19
+
14
20
  class BoundedIO # :nodoc:
15
21
  def initialize(io, content_length)
16
22
  @io = io
@@ -18,15 +24,15 @@ module Rack
18
24
  @cursor = 0
19
25
  end
20
26
 
21
- def read(size)
27
+ def read(size, outbuf = nil)
22
28
  return if @cursor >= @content_length
23
29
 
24
30
  left = @content_length - @cursor
25
31
 
26
32
  str = if left < size
27
- @io.read left
33
+ @io.read left, outbuf
28
34
  else
29
- @io.read size
35
+ @io.read size, outbuf
30
36
  end
31
37
 
32
38
  if str
@@ -61,13 +67,14 @@ module Rack
61
67
  return EMPTY unless boundary
62
68
 
63
69
  io = BoundedIO.new(io, content_length) if content_length
70
+ outbuf = String.new
64
71
 
65
72
  parser = new(boundary, tmpfile, bufsize, qp)
66
- parser.on_read io.read(bufsize)
73
+ parser.on_read io.read(bufsize, outbuf)
67
74
 
68
75
  loop do
69
76
  break if parser.state == :DONE
70
- parser.on_read io.read(bufsize)
77
+ parser.on_read io.read(bufsize, outbuf)
71
78
  end
72
79
 
73
80
  io.rewind
@@ -90,14 +97,8 @@ module Rack
90
97
  # those which give the lone filename.
91
98
  fn = filename.split(/[\/\\]/).last
92
99
 
93
- data = {:filename => fn, :type => content_type,
94
- :name => name, :tempfile => body, :head => head}
95
- elsif !filename && content_type && body.is_a?(IO)
96
- body.rewind
97
-
98
- # Generic multipart cases, not coming from a form
99
- data = {:type => content_type,
100
- :name => name, :tempfile => body, :head => head}
100
+ data = { filename: fn, type: content_type,
101
+ name: name, tempfile: body, head: head }
101
102
  end
102
103
 
103
104
  yield data
@@ -116,7 +117,7 @@ module Rack
116
117
 
117
118
  include Enumerable
118
119
 
119
- def initialize tempfile
120
+ def initialize(tempfile)
120
121
  @tempfile = tempfile
121
122
  @mime_parts = []
122
123
  @open_files = 0
@@ -126,7 +127,7 @@ module Rack
126
127
  @mime_parts.each { |part| yield part }
127
128
  end
128
129
 
129
- def on_mime_head mime_index, head, filename, content_type, name
130
+ def on_mime_head(mime_index, head, filename, content_type, name)
130
131
  if filename
131
132
  body = @tempfile.call(filename, content_type)
132
133
  body.binmode if body.respond_to?(:binmode)
@@ -138,14 +139,15 @@ module Rack
138
139
  end
139
140
 
140
141
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
142
+
141
143
  check_open_files
142
144
  end
143
145
 
144
- def on_mime_body mime_index, content
146
+ def on_mime_body(mime_index, content)
145
147
  @mime_parts[mime_index].body << content
146
148
  end
147
149
 
148
- def on_mime_finish mime_index
150
+ def on_mime_finish(mime_index)
149
151
  end
150
152
 
151
153
  private
@@ -163,25 +165,26 @@ module Rack
163
165
  attr_reader :state
164
166
 
165
167
  def initialize(boundary, tempfile, bufsize, query_parser)
166
- @buf = String.new
167
-
168
168
  @query_parser = query_parser
169
169
  @params = query_parser.make_params
170
170
  @boundary = "--#{boundary}"
171
171
  @bufsize = bufsize
172
172
 
173
- @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
174
- @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
175
173
  @full_boundary = @boundary
176
174
  @end_boundary = @boundary + '--'
177
175
  @state = :FAST_FORWARD
178
176
  @mime_index = 0
179
177
  @collector = Collector.new tempfile
178
+
179
+ @sbuf = StringScanner.new("".dup)
180
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
181
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
182
+ @head_regex = /(.*?#{EOL})#{EOL}/m
180
183
  end
181
184
 
182
- def on_read content
185
+ def on_read(content)
183
186
  handle_empty_content!(content)
184
- @buf << content
187
+ @sbuf.concat content
185
188
  run_parser
186
189
  end
187
190
 
@@ -192,7 +195,6 @@ module Rack
192
195
  @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
193
196
  end
194
197
  end
195
-
196
198
  MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
197
199
  end
198
200
 
@@ -219,7 +221,7 @@ module Rack
219
221
  if consume_boundary
220
222
  @state = :MIME_HEAD
221
223
  else
222
- raise EOFError, "bad content body" if @buf.bytesize >= @bufsize
224
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
223
225
  :want_read
224
226
  end
225
227
  end
@@ -227,19 +229,16 @@ module Rack
227
229
  def handle_consume_token
228
230
  tok = consume_boundary
229
231
  # break if we're at the end of a buffer, but not if it is the end of a field
230
- if tok == :END_BOUNDARY || (@buf.empty? && tok != :BOUNDARY)
231
- @state = :DONE
232
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
233
+ :DONE
232
234
  else
233
- @state = :MIME_HEAD
235
+ :MIME_HEAD
234
236
  end
235
237
  end
236
238
 
237
239
  def handle_mime_head
238
- if @buf.index(EOL + EOL)
239
- i = @buf.index(EOL+EOL)
240
- head = @buf.slice!(0, i+2) # First \r\n
241
- @buf.slice!(0, 2) # Second \r\n
242
-
240
+ if @sbuf.scan_until(@head_regex)
241
+ head = @sbuf[1]
243
242
  content_type = head[MULTIPART_CONTENT_TYPE, 1]
244
243
  if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
245
244
  name = Rack::Auth::Digest::Params::dequote(name)
@@ -250,7 +249,7 @@ module Rack
250
249
  filename = get_filename(head)
251
250
 
252
251
  if name.nil? || name.empty?
253
- name = filename || "#{content_type || TEXT_PLAIN}[]"
252
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
254
253
  end
255
254
 
256
255
  @collector.on_mime_head @mime_index, head, filename, content_type, name
@@ -261,16 +260,19 @@ module Rack
261
260
  end
262
261
 
263
262
  def handle_mime_body
264
- if i = @buf.index(rx)
265
- # Save the rest.
266
- @collector.on_mime_body @mime_index, @buf.slice!(0, i)
267
- @buf.slice!(0, 2) # Remove \r\n after the content
263
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
264
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
265
+ @collector.on_mime_body @mime_index, body
266
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
268
267
  @state = :CONSUME_TOKEN
269
268
  @mime_index += 1
270
269
  else
271
- # Save the read body part.
272
- if @rx_max_size < @buf.size
273
- @collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
270
+ # Save what we have so far
271
+ if @rx_max_size < @sbuf.rest_size
272
+ delta = @sbuf.rest_size - @rx_max_size
273
+ @collector.on_mime_body @mime_index, @sbuf.peek(delta)
274
+ @sbuf.pos += delta
275
+ @sbuf.string = @sbuf.rest
274
276
  end
275
277
  :want_read
276
278
  end
@@ -278,16 +280,13 @@ module Rack
278
280
 
279
281
  def full_boundary; @full_boundary; end
280
282
 
281
- def rx; @rx; end
282
-
283
283
  def consume_boundary
284
- while @buf.gsub!(/\A([^\n]*(?:\n|\Z))/, '')
285
- read_buffer = $1
284
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
286
285
  case read_buffer.strip
287
286
  when full_boundary then return :BOUNDARY
288
287
  when @end_boundary then return :END_BOUNDARY
289
288
  end
290
- return if @buf.empty?
289
+ return if @sbuf.eos?
291
290
  end
292
291
  end
293
292
 
@@ -302,14 +301,15 @@ module Rack
302
301
  elsif filename = params['filename*']
303
302
  encoding, _, filename = filename.split("'", 3)
304
303
  end
305
- when BROKEN_QUOTED, BROKEN_UNQUOTED
304
+ when BROKEN
306
305
  filename = $1
306
+ filename = $1 if filename =~ /^"(.*)"$/
307
307
  end
308
308
 
309
309
  return unless filename
310
310
 
311
- if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
312
- filename = Utils.unescape(filename)
311
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
312
+ filename = Utils.unescape_path(filename)
313
313
  end
314
314
 
315
315
  filename.scrub!
@@ -325,7 +325,7 @@ module Rack
325
325
  filename
326
326
  end
327
327
 
328
- CHARSET = "charset"
328
+ CHARSET = "charset"
329
329
 
330
330
  def tag_multipart_encoding(filename, content_type, name, body)
331
331
  name = name.to_s
@@ -340,12 +340,12 @@ module Rack
340
340
  type_subtype = list.first
341
341
  type_subtype.strip!
342
342
  if TEXT_PLAIN == type_subtype
343
- rest = list.drop 1
343
+ rest = list.drop 1
344
344
  rest.each do |param|
345
- k,v = param.split('=', 2)
345
+ k, v = param.split('=', 2)
346
346
  k.strip!
347
347
  v.strip!
348
- v = v[1..-2] if v[0] == '"' && v[-1] == '"'
348
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
349
349
  encoding = Encoding.find v if k == CHARSET
350
350
  end
351
351
  end
@@ -355,7 +355,6 @@ module Rack
355
355
  body.force_encoding(encoding)
356
356
  end
357
357
 
358
-
359
358
  def handle_empty_content!(content)
360
359
  if content.nil? || content.empty?
361
360
  raise EOFError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Multipart
3
5
  class UploadedFile
@@ -7,17 +9,23 @@ module Rack
7
9
  # The content type of the "uploaded" file
8
10
  attr_accessor :content_type
9
11
 
10
- def initialize(path, content_type = "text/plain", binary = false)
11
- raise "#{path} file does not exist" unless ::File.exist?(path)
12
+ def initialize(filepath = nil, ct = "text/plain", bin = false,
13
+ path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
14
+ if io
15
+ @tempfile = io
16
+ @original_filename = filename
17
+ else
18
+ raise "#{path} file does not exist" unless ::File.exist?(path)
19
+ @original_filename = filename || ::File.basename(path)
20
+ @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
21
+ @tempfile.binmode if binary
22
+ FileUtils.copy_file(path, @tempfile.path)
23
+ end
12
24
  @content_type = content_type
13
- @original_filename = ::File.basename(path)
14
- @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
15
- @tempfile.binmode if binary
16
- FileUtils.copy_file(path, @tempfile.path)
17
25
  end
18
26
 
19
27
  def path
20
- @tempfile.path
28
+ @tempfile.path if @tempfile.respond_to?(:path)
21
29
  end
22
30
  alias_method :local_path, :path
23
31
 
@@ -1,4 +1,6 @@
1
- require 'rack/multipart/parser'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'multipart/parser'
2
4
 
3
5
  module Rack
4
6
  # A multipart form data parser, adapted from IOWA.
@@ -14,10 +16,9 @@ module Rack
14
16
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
15
17
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
16
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
17
- BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
18
- BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
19
+ BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
19
20
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
20
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
21
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
21
22
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
22
23
  # Updated definitions from RFC 2231
23
24
  ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class NullLogger
3
5
  def initialize(app)
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class QueryParser
5
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
6
+
3
7
  DEFAULT_SEP = /[&;] */n
4
8
  COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
5
9
 
@@ -12,6 +16,10 @@ module Rack
12
16
  # sequence.
13
17
  class InvalidParameterError < ArgumentError; end
14
18
 
19
+ # ParamsTooDeepError is the error that is raised when params are recursively
20
+ # nested over the specified limit.
21
+ class ParamsTooDeepError < RangeError; end
22
+
15
23
  def self.make_default(key_space_limit, param_depth_limit)
16
24
  new Params, key_space_limit, param_depth_limit
17
25
  end
@@ -36,7 +44,7 @@ module Rack
36
44
 
37
45
  (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
38
46
  next if p.empty?
39
- k, v = p.split('='.freeze, 2).map!(&unescaper)
47
+ k, v = p.split('=', 2).map!(&unescaper)
40
48
 
41
49
  if cur = params[k]
42
50
  if cur.class == Array
@@ -49,7 +57,7 @@ module Rack
49
57
  end
50
58
  end
51
59
 
52
- return params.to_params_hash
60
+ return params.to_h
53
61
  end
54
62
 
55
63
  # parse_nested_query expands a query string into structural types. Supported
@@ -58,43 +66,44 @@ module Rack
58
66
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
59
67
  # case.
60
68
  def parse_nested_query(qs, d = nil)
61
- return {} if qs.nil? || qs.empty?
62
69
  params = make_params
63
70
 
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) }
71
+ unless qs.nil? || qs.empty?
72
+ (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
73
+ k, v = p.split('=', 2).map! { |s| unescape(s) }
66
74
 
67
- normalize_params(params, k, v, param_depth_limit)
75
+ normalize_params(params, k, v, param_depth_limit)
76
+ end
68
77
  end
69
78
 
70
- return params.to_params_hash
79
+ return params.to_h
71
80
  rescue ArgumentError => e
72
- raise InvalidParameterError, e.message
81
+ raise InvalidParameterError, e.message, e.backtrace
73
82
  end
74
83
 
75
84
  # normalize_params recursively expands parameters into structural types. If
76
85
  # the structural types represented by two different parameter names are in
77
86
  # conflict, a ParameterTypeError is raised.
78
87
  def normalize_params(params, name, v, depth)
79
- raise RangeError if depth <= 0
88
+ raise ParamsTooDeepError if depth <= 0
80
89
 
81
90
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
- k = $1 || ''.freeze
83
- after = $' || ''.freeze
91
+ k = $1 || ''
92
+ after = $' || ''
84
93
 
85
94
  if k.empty?
86
- if !v.nil? && name == "[]".freeze
95
+ if !v.nil? && name == "[]"
87
96
  return Array(v)
88
97
  else
89
98
  return
90
99
  end
91
100
  end
92
101
 
93
- if after == ''.freeze
102
+ if after == ''
94
103
  params[k] = v
95
- elsif after == "[".freeze
104
+ elsif after == "["
96
105
  params[name] = v
97
- elsif after == "[]".freeze
106
+ elsif after == "[]"
98
107
  params[k] ||= []
99
108
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
100
109
  params[k] << v
@@ -135,7 +144,7 @@ module Rack
135
144
  end
136
145
 
137
146
  def params_hash_has_key?(hash, key)
138
- return false if key =~ /\[\]/
147
+ return false if /\[\]/.match?(key)
139
148
 
140
149
  key.split(/[\[\]]+/).inject(hash) do |h, part|
141
150
  next h if part == ''
@@ -163,7 +172,7 @@ module Rack
163
172
 
164
173
  def []=(key, value)
165
174
  @size += key.size if key && !@params.key?(key)
166
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
175
+ raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
167
176
  @params[key] = value
168
177
  end
169
178
 
@@ -171,22 +180,42 @@ module Rack
171
180
  @params.key?(key)
172
181
  end
173
182
 
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}
183
+ # Recursively unwraps nested `Params` objects and constructs an object
184
+ # of the same shape, but using the objects' internal representations
185
+ # (Ruby hashes) in place of the objects. The result is a hash consisting
186
+ # purely of Ruby primitives.
187
+ #
188
+ # Mutation warning!
189
+ #
190
+ # 1. This method mutates the internal representation of the `Params`
191
+ # objects in order to save object allocations.
192
+ #
193
+ # 2. The value you get back is a reference to the internal hash
194
+ # representation, not a copy.
195
+ #
196
+ # 3. Because the `Params` object's internal representation is mutable
197
+ # through the `#[]=` method, it is not thread safe. The result of
198
+ # getting the hash representation while another thread is adding a
199
+ # key to it is non-deterministic.
200
+ #
201
+ def to_h
202
+ @params.each do |key, value|
203
+ case value
204
+ when self
205
+ # Handle circular references gracefully.
206
+ @params[key] = @params
207
+ when Params
208
+ @params[key] = value.to_h
209
+ when Array
210
+ value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
211
+ else
212
+ # Ignore anything that is not a `Params` object or
213
+ # a collection that can contain one.
186
214
  end
187
215
  end
188
- hash
216
+ @params
189
217
  end
218
+ alias_method :to_params_hash, :to_h
190
219
  end
191
220
  end
192
221
  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,6 +1,8 @@
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
 
@@ -20,6 +22,8 @@ module Rack
20
22
  # It is performing a check/reload cycle at the start of every request, but
21
23
  # also respects a cool down time, during which nothing will be done.
22
24
  class Reloader
25
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
26
+
23
27
  def initialize(app, cooldown = 10, backend = Stat)
24
28
  @app = app
25
29
  @cooldown = cooldown
@@ -69,7 +73,7 @@ module Rack
69
73
  paths = ['./', *$LOAD_PATH].uniq
70
74
 
71
75
  files.map{|file|
72
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
76
+ next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
73
77
 
74
78
  found, stat = figure_path(file, paths)
75
79
  next unless found && stat && mtime = stat.mtime