rack 2.2.8.1 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +301 -81
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +1 -4
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +102 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +23 -18
  15. data/lib/rack/conditional_get.rb +18 -15
  16. data/lib/rack/constants.rb +67 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +14 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +838 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -271
  32. data/lib/rack/mock_request.rb +171 -0
  33. data/lib/rack/mock_response.rb +124 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +215 -90
  36. data/lib/rack/multipart/uploaded_file.rb +4 -0
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +81 -102
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +250 -123
  43. data/lib/rack/response.rb +146 -66
  44. data/lib/rack/rewindable_input.rb +24 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +30 -25
  47. data/lib/rack/show_exceptions.rb +21 -4
  48. data/lib/rack/show_status.rb +17 -7
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +232 -233
  53. data/lib/rack/version.rb +1 -9
  54. data/lib/rack.rb +13 -89
  55. metadata +15 -41
  56. data/README.rdoc +0 -320
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -54
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -204
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. data/rack.gemspec +0 -46
data/lib/rack/lock.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thread'
3
+ require_relative 'body_proxy'
4
4
 
5
5
  module Rack
6
6
  # Rack::Lock locks every request inside a mutex, so that every request
@@ -12,10 +12,8 @@ module Rack
12
12
 
13
13
  def call(env)
14
14
  @mutex.lock
15
- @env = env
16
- @old_rack_multithread = env[RACK_MULTITHREAD]
17
15
  begin
18
- response = @app.call(env.merge!(RACK_MULTITHREAD => false))
16
+ response = @app.call(env)
19
17
  returned = response << BodyProxy.new(response.pop) { unlock }
20
18
  ensure
21
19
  unlock unless returned
@@ -26,7 +24,6 @@ module Rack
26
24
 
27
25
  def unlock
28
26
  @mutex.unlock
29
- @env[RACK_MULTITHREAD] = @old_rack_multithread
30
27
  end
31
28
  end
32
29
  end
data/lib/rack/logger.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'logger'
4
+ require_relative 'constants'
5
+
6
+ warn "Rack::Logger is deprecated and will be removed in Rack 3.2.", uplevel: 1
4
7
 
5
8
  module Rack
6
9
  # Sets up rack.logger to write to rack.errors stream
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'request'
5
+ require_relative 'utils'
6
+
3
7
  module Rack
4
8
  class MethodOverride
5
9
  HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
@@ -42,7 +46,7 @@ module Rack
42
46
  end
43
47
 
44
48
  def method_override_param(req)
45
- req.POST[METHOD_OVERRIDE_PARAM_KEY]
49
+ req.POST[METHOD_OVERRIDE_PARAM_KEY] if req.form_data? || req.parseable_data?
46
50
  rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError
47
51
  req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
48
52
  rescue EOFError
data/lib/rack/mime.rb CHANGED
@@ -63,6 +63,7 @@ module Rack
63
63
  ".aif" => "audio/x-aiff",
64
64
  ".aiff" => "audio/x-aiff",
65
65
  ".ami" => "application/vnd.amiga.ami",
66
+ ".apng" => "image/apng",
66
67
  ".appcache" => "text/cache-manifest",
67
68
  ".apr" => "application/vnd.lotus-approach",
68
69
  ".asc" => "application/pgp-signature",
@@ -77,6 +78,7 @@ module Rack
77
78
  ".atx" => "application/vnd.antix.game-component",
78
79
  ".au" => "audio/basic",
79
80
  ".avi" => "video/x-msvideo",
81
+ ".avif" => "image/avif",
80
82
  ".bat" => "application/x-msdownload",
81
83
  ".bcpio" => "application/x-bcpio",
82
84
  ".bdm" => "application/vnd.syncml.dm+wbxml",
@@ -197,6 +199,7 @@ module Rack
197
199
  ".fe_launch" => "application/vnd.denovo.fcselayout-link",
198
200
  ".fg5" => "application/vnd.fujitsu.oasysgp",
199
201
  ".fli" => "video/x-fli",
202
+ ".flif" => "image/flif",
200
203
  ".flo" => "application/vnd.micrografx.flo",
201
204
  ".flv" => "video/x-flv",
202
205
  ".flw" => "application/vnd.kde.kivio",
@@ -237,6 +240,10 @@ module Rack
237
240
  ".h264" => "video/h264",
238
241
  ".hbci" => "application/vnd.hbci",
239
242
  ".hdf" => "application/x-hdf",
243
+ ".heic" => "image/heic",
244
+ ".heics" => "image/heic-sequence",
245
+ ".heif" => "image/heif",
246
+ ".heifs" => "image/heif-sequence",
240
247
  ".hh" => "text/x-c",
241
248
  ".hlp" => "application/winhlp",
242
249
  ".hpgl" => "application/vnd.hp-hpgl",
@@ -283,7 +290,7 @@ module Rack
283
290
  ".jpg" => "image/jpeg",
284
291
  ".jpgv" => "video/jpeg",
285
292
  ".jpm" => "video/jpm",
286
- ".js" => "application/javascript",
293
+ ".js" => "text/javascript",
287
294
  ".json" => "application/json",
288
295
  ".karbon" => "application/vnd.kde.karbon",
289
296
  ".kfo" => "application/vnd.kde.kformula",
@@ -331,6 +338,7 @@ module Rack
331
338
  ".mif" => "application/vnd.mif",
332
339
  ".mime" => "message/rfc822",
333
340
  ".mj2" => "video/mj2",
341
+ ".mjs" => "text/javascript",
334
342
  ".mlp" => "application/vnd.dolby.mlp",
335
343
  ".mmd" => "application/vnd.chipnuts.karaoke-mmd",
336
344
  ".mmf" => "application/vnd.smaf",
@@ -402,7 +410,7 @@ module Rack
402
410
  ".ogx" => "application/ogg",
403
411
  ".org" => "application/vnd.lotus-organizer",
404
412
  ".otc" => "application/vnd.oasis.opendocument.chart-template",
405
- ".otf" => "application/vnd.oasis.opendocument.formula-template",
413
+ ".otf" => "font/otf",
406
414
  ".otg" => "application/vnd.oasis.opendocument.graphics-template",
407
415
  ".oth" => "application/vnd.oasis.opendocument.text-web",
408
416
  ".oti" => "application/vnd.oasis.opendocument.image-template",
@@ -583,7 +591,7 @@ module Rack
583
591
  ".trm" => "application/x-msterminal",
584
592
  ".ts" => "video/mp2t",
585
593
  ".tsv" => "text/tab-separated-values",
586
- ".ttf" => "application/octet-stream",
594
+ ".ttf" => "font/ttf",
587
595
  ".twd" => "application/vnd.simtech-mindmapper",
588
596
  ".txd" => "application/vnd.genomatix.tuxedo",
589
597
  ".txf" => "application/vnd.mobius.txf",
@@ -617,6 +625,7 @@ module Rack
617
625
  ".wbs" => "application/vnd.criticaltools.wbs+xml",
618
626
  ".wbxml" => "application/vnd.wap.wbxml",
619
627
  ".webm" => "video/webm",
628
+ ".webp" => "image/webp",
620
629
  ".wm" => "video/x-ms-wm",
621
630
  ".wma" => "audio/x-ms-wma",
622
631
  ".wmd" => "application/x-ms-wmd",
@@ -628,8 +637,8 @@ module Rack
628
637
  ".wmv" => "video/x-ms-wmv",
629
638
  ".wmx" => "video/x-ms-wmx",
630
639
  ".wmz" => "application/x-ms-wmz",
631
- ".woff" => "application/font-woff",
632
- ".woff2" => "application/font-woff2",
640
+ ".woff" => "font/woff",
641
+ ".woff2" => "font/woff2",
633
642
  ".wpd" => "application/vnd.wordperfect",
634
643
  ".wpl" => "application/vnd.ms-wpl",
635
644
  ".wps" => "application/vnd.ms-works",
data/lib/rack/mock.rb CHANGED
@@ -1,273 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'uri'
4
- require 'stringio'
5
- require_relative '../rack'
6
- require 'cgi/cookie'
7
-
8
- module Rack
9
- # Rack::MockRequest helps testing your Rack application without
10
- # actually using HTTP.
11
- #
12
- # After performing a request on a URL with get/post/put/patch/delete, it
13
- # returns a MockResponse with useful helper methods for effective
14
- # testing.
15
- #
16
- # You can pass a hash with additional configuration to the
17
- # get/post/put/patch/delete.
18
- # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
19
- # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
20
- # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
21
-
22
- class MockRequest
23
- class FatalWarning < RuntimeError
24
- end
25
-
26
- class FatalWarner
27
- def puts(warning)
28
- raise FatalWarning, warning
29
- end
30
-
31
- def write(warning)
32
- raise FatalWarning, warning
33
- end
34
-
35
- def flush
36
- end
37
-
38
- def string
39
- ""
40
- end
41
- end
42
-
43
- DEFAULT_ENV = {
44
- RACK_VERSION => Rack::VERSION,
45
- RACK_INPUT => StringIO.new,
46
- RACK_ERRORS => StringIO.new,
47
- RACK_MULTITHREAD => true,
48
- RACK_MULTIPROCESS => true,
49
- RACK_RUNONCE => false,
50
- }.freeze
51
-
52
- def initialize(app)
53
- @app = app
54
- end
55
-
56
- # Make a GET request and return a MockResponse. See #request.
57
- def get(uri, opts = {}) request(GET, uri, opts) end
58
- # Make a POST request and return a MockResponse. See #request.
59
- def post(uri, opts = {}) request(POST, uri, opts) end
60
- # Make a PUT request and return a MockResponse. See #request.
61
- def put(uri, opts = {}) request(PUT, uri, opts) end
62
- # Make a PATCH request and return a MockResponse. See #request.
63
- def patch(uri, opts = {}) request(PATCH, uri, opts) end
64
- # Make a DELETE request and return a MockResponse. See #request.
65
- def delete(uri, opts = {}) request(DELETE, uri, opts) end
66
- # Make a HEAD request and return a MockResponse. See #request.
67
- def head(uri, opts = {}) request(HEAD, uri, opts) end
68
- # Make an OPTIONS request and return a MockResponse. See #request.
69
- def options(uri, opts = {}) request(OPTIONS, uri, opts) end
70
-
71
- # Make a request using the given request method for the given
72
- # uri to the rack application and return a MockResponse.
73
- # Options given are passed to MockRequest.env_for.
74
- def request(method = GET, uri = "", opts = {})
75
- env = self.class.env_for(uri, opts.merge(method: method))
76
-
77
- if opts[:lint]
78
- app = Rack::Lint.new(@app)
79
- else
80
- app = @app
81
- end
82
-
83
- errors = env[RACK_ERRORS]
84
- status, headers, body = app.call(env)
85
- MockResponse.new(status, headers, body, errors)
86
- ensure
87
- body.close if body.respond_to?(:close)
88
- end
89
-
90
- # For historical reasons, we're pinning to RFC 2396.
91
- # URI::Parser = URI::RFC2396_Parser
92
- def self.parse_uri_rfc2396(uri)
93
- @parser ||= URI::Parser.new
94
- @parser.parse(uri)
95
- end
96
-
97
- # Return the Rack environment used for a request to +uri+.
98
- # All options that are strings are added to the returned environment.
99
- # Options:
100
- # :fatal :: Whether to raise an exception if request outputs to rack.errors
101
- # :input :: The rack.input to set
102
- # :method :: The HTTP request method to use
103
- # :params :: The params to use
104
- # :script_name :: The SCRIPT_NAME to set
105
- def self.env_for(uri = "", opts = {})
106
- uri = parse_uri_rfc2396(uri)
107
- uri.path = "/#{uri.path}" unless uri.path[0] == ?/
108
-
109
- env = DEFAULT_ENV.dup
110
-
111
- env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
112
- env[SERVER_NAME] = (uri.host || "example.org").b
113
- env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
114
- env[QUERY_STRING] = (uri.query.to_s).b
115
- env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
116
- env[RACK_URL_SCHEME] = (uri.scheme || "http").b
117
- env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
118
-
119
- env[SCRIPT_NAME] = opts[:script_name] || ""
120
-
121
- if opts[:fatal]
122
- env[RACK_ERRORS] = FatalWarner.new
123
- else
124
- env[RACK_ERRORS] = StringIO.new
125
- end
126
-
127
- if params = opts[:params]
128
- if env[REQUEST_METHOD] == GET
129
- params = Utils.parse_nested_query(params) if params.is_a?(String)
130
- params.update(Utils.parse_nested_query(env[QUERY_STRING]))
131
- env[QUERY_STRING] = Utils.build_nested_query(params)
132
- elsif !opts.has_key?(:input)
133
- opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
134
- if params.is_a?(Hash)
135
- if data = Rack::Multipart.build_multipart(params)
136
- opts[:input] = data
137
- opts["CONTENT_LENGTH"] ||= data.length.to_s
138
- opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
139
- else
140
- opts[:input] = Utils.build_nested_query(params)
141
- end
142
- else
143
- opts[:input] = params
144
- end
145
- end
146
- end
147
-
148
- empty_str = String.new
149
- opts[:input] ||= empty_str
150
- if String === opts[:input]
151
- rack_input = StringIO.new(opts[:input])
152
- else
153
- rack_input = opts[:input]
154
- end
155
-
156
- rack_input.set_encoding(Encoding::BINARY)
157
- env[RACK_INPUT] = rack_input
158
-
159
- env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
160
-
161
- opts.each { |field, value|
162
- env[field] = value if String === field
163
- }
164
-
165
- env
166
- end
167
- end
168
-
169
- # Rack::MockResponse provides useful helpers for testing your apps.
170
- # Usually, you don't create the MockResponse on your own, but use
171
- # MockRequest.
172
-
173
- class MockResponse < Rack::Response
174
- class << self
175
- alias [] new
176
- end
177
-
178
- # Headers
179
- attr_reader :original_headers, :cookies
180
-
181
- # Errors
182
- attr_accessor :errors
183
-
184
- def initialize(status, headers, body, errors = StringIO.new(""))
185
- @original_headers = headers
186
- @errors = errors.string if errors.respond_to?(:string)
187
- @cookies = parse_cookies_from_header
188
-
189
- super(body, status, headers)
190
-
191
- buffered_body!
192
- end
193
-
194
- def =~(other)
195
- body =~ other
196
- end
197
-
198
- def match(other)
199
- body.match other
200
- end
201
-
202
- def body
203
- # FIXME: apparently users of MockResponse expect the return value of
204
- # MockResponse#body to be a string. However, the real response object
205
- # returns the body as a list.
206
- #
207
- # See spec_showstatus.rb:
208
- #
209
- # should "not replace existing messages" do
210
- # ...
211
- # res.body.should == "foo!"
212
- # end
213
- buffer = String.new
214
-
215
- super.each do |chunk|
216
- buffer << chunk
217
- end
218
-
219
- return buffer
220
- end
221
-
222
- def empty?
223
- [201, 204, 304].include? status
224
- end
225
-
226
- def cookie(name)
227
- cookies.fetch(name, nil)
228
- end
229
-
230
- private
231
-
232
- def parse_cookies_from_header
233
- cookies = Hash.new
234
- if original_headers.has_key? 'Set-Cookie'
235
- set_cookie_header = original_headers.fetch('Set-Cookie')
236
- set_cookie_header.split("\n").each do |cookie|
237
- cookie_name, cookie_filling = cookie.split('=', 2)
238
- cookie_attributes = identify_cookie_attributes cookie_filling
239
- parsed_cookie = CGI::Cookie.new(
240
- 'name' => cookie_name.strip,
241
- 'value' => cookie_attributes.fetch('value'),
242
- 'path' => cookie_attributes.fetch('path', nil),
243
- 'domain' => cookie_attributes.fetch('domain', nil),
244
- 'expires' => cookie_attributes.fetch('expires', nil),
245
- 'secure' => cookie_attributes.fetch('secure', false)
246
- )
247
- cookies.store(cookie_name, parsed_cookie)
248
- end
249
- end
250
- cookies
251
- end
252
-
253
- def identify_cookie_attributes(cookie_filling)
254
- cookie_bits = cookie_filling.split(';')
255
- cookie_attributes = Hash.new
256
- cookie_attributes.store('value', cookie_bits[0].strip)
257
- cookie_bits.each do |bit|
258
- if bit.include? '='
259
- cookie_attribute, attribute_value = bit.split('=')
260
- cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
261
- if cookie_attribute.include? 'max-age'
262
- cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
263
- end
264
- end
265
- if bit.include? 'secure'
266
- cookie_attributes.store('secure', true)
267
- end
268
- end
269
- cookie_attributes
270
- end
271
-
272
- end
273
- end
3
+ require_relative 'mock_request'
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'stringio'
5
+
6
+ require_relative 'constants'
7
+ require_relative 'mock_response'
8
+
9
+ module Rack
10
+ # Rack::MockRequest helps testing your Rack application without
11
+ # actually using HTTP.
12
+ #
13
+ # After performing a request on a URL with get/post/put/patch/delete, it
14
+ # returns a MockResponse with useful helper methods for effective
15
+ # testing.
16
+ #
17
+ # You can pass a hash with additional configuration to the
18
+ # get/post/put/patch/delete.
19
+ # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
20
+ # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
21
+ # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
22
+
23
+ class MockRequest
24
+ class FatalWarning < RuntimeError
25
+ end
26
+
27
+ class FatalWarner
28
+ def puts(warning)
29
+ raise FatalWarning, warning
30
+ end
31
+
32
+ def write(warning)
33
+ raise FatalWarning, warning
34
+ end
35
+
36
+ def flush
37
+ end
38
+
39
+ def string
40
+ ""
41
+ end
42
+ end
43
+
44
+ def initialize(app)
45
+ @app = app
46
+ end
47
+
48
+ # Make a GET request and return a MockResponse. See #request.
49
+ def get(uri, opts = {}) request(GET, uri, opts) end
50
+ # Make a POST request and return a MockResponse. See #request.
51
+ def post(uri, opts = {}) request(POST, uri, opts) end
52
+ # Make a PUT request and return a MockResponse. See #request.
53
+ def put(uri, opts = {}) request(PUT, uri, opts) end
54
+ # Make a PATCH request and return a MockResponse. See #request.
55
+ def patch(uri, opts = {}) request(PATCH, uri, opts) end
56
+ # Make a DELETE request and return a MockResponse. See #request.
57
+ def delete(uri, opts = {}) request(DELETE, uri, opts) end
58
+ # Make a HEAD request and return a MockResponse. See #request.
59
+ def head(uri, opts = {}) request(HEAD, uri, opts) end
60
+ # Make an OPTIONS request and return a MockResponse. See #request.
61
+ def options(uri, opts = {}) request(OPTIONS, uri, opts) end
62
+
63
+ # Make a request using the given request method for the given
64
+ # uri to the rack application and return a MockResponse.
65
+ # Options given are passed to MockRequest.env_for.
66
+ def request(method = GET, uri = "", opts = {})
67
+ env = self.class.env_for(uri, opts.merge(method: method))
68
+
69
+ if opts[:lint]
70
+ app = Rack::Lint.new(@app)
71
+ else
72
+ app = @app
73
+ end
74
+
75
+ errors = env[RACK_ERRORS]
76
+ status, headers, body = app.call(env)
77
+ MockResponse.new(status, headers, body, errors)
78
+ ensure
79
+ body.close if body.respond_to?(:close)
80
+ end
81
+
82
+ # For historical reasons, we're pinning to RFC 2396.
83
+ # URI::Parser = URI::RFC2396_Parser
84
+ def self.parse_uri_rfc2396(uri)
85
+ @parser ||= URI::Parser.new
86
+ @parser.parse(uri)
87
+ end
88
+
89
+ # Return the Rack environment used for a request to +uri+.
90
+ # All options that are strings are added to the returned environment.
91
+ # Options:
92
+ # :fatal :: Whether to raise an exception if request outputs to rack.errors
93
+ # :input :: The rack.input to set
94
+ # :http_version :: The SERVER_PROTOCOL to set
95
+ # :method :: The HTTP request method to use
96
+ # :params :: The params to use
97
+ # :script_name :: The SCRIPT_NAME to set
98
+ def self.env_for(uri = "", opts = {})
99
+ uri = parse_uri_rfc2396(uri)
100
+ uri.path = "/#{uri.path}" unless uri.path[0] == ?/
101
+
102
+ env = {}
103
+
104
+ env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
105
+ env[SERVER_NAME] = (uri.host || "example.org").b
106
+ env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
107
+ env[SERVER_PROTOCOL] = opts[:http_version] || 'HTTP/1.1'
108
+ env[QUERY_STRING] = (uri.query.to_s).b
109
+ env[PATH_INFO] = (uri.path).b
110
+ env[RACK_URL_SCHEME] = (uri.scheme || "http").b
111
+ env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
112
+
113
+ env[SCRIPT_NAME] = opts[:script_name] || ""
114
+
115
+ if opts[:fatal]
116
+ env[RACK_ERRORS] = FatalWarner.new
117
+ else
118
+ env[RACK_ERRORS] = StringIO.new
119
+ end
120
+
121
+ if params = opts[:params]
122
+ if env[REQUEST_METHOD] == GET
123
+ params = Utils.parse_nested_query(params) if params.is_a?(String)
124
+ params.update(Utils.parse_nested_query(env[QUERY_STRING]))
125
+ env[QUERY_STRING] = Utils.build_nested_query(params)
126
+ elsif !opts.has_key?(:input)
127
+ opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
128
+ if params.is_a?(Hash)
129
+ if data = Rack::Multipart.build_multipart(params)
130
+ opts[:input] = data
131
+ opts["CONTENT_LENGTH"] ||= data.length.to_s
132
+ opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
133
+ else
134
+ opts[:input] = Utils.build_nested_query(params)
135
+ end
136
+ else
137
+ opts[:input] = params
138
+ end
139
+ end
140
+ end
141
+
142
+ input = opts[:input]
143
+ if String === input
144
+ rack_input = StringIO.new(input)
145
+ rack_input.set_encoding(Encoding::BINARY)
146
+ else
147
+ if input.respond_to?(:encoding) && input.encoding != Encoding::BINARY
148
+ warn "input encoding not binary", uplevel: 1
149
+ if input.respond_to?(:set_encoding)
150
+ input.set_encoding(Encoding::BINARY)
151
+ else
152
+ raise ArgumentError, "could not coerce input to binary encoding"
153
+ end
154
+ end
155
+ rack_input = input
156
+ end
157
+
158
+ if rack_input
159
+ env[RACK_INPUT] = rack_input
160
+
161
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
162
+ end
163
+
164
+ opts.each { |field, value|
165
+ env[field] = value if String === field
166
+ }
167
+
168
+ env
169
+ end
170
+ end
171
+ end