rack 2.1.0 → 3.1.0

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.

Potentially problematic release.


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

Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +377 -16
  3. data/CONTRIBUTING.md +144 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +365 -0
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +2 -2
  9. data/lib/rack/auth/basic.rb +4 -7
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +34 -12
  12. data/lib/rack/builder.rb +162 -59
  13. data/lib/rack/cascade.rb +24 -10
  14. data/lib/rack/common_logger.rb +43 -28
  15. data/lib/rack/conditional_get.rb +30 -25
  16. data/lib/rack/constants.rb +66 -0
  17. data/lib/rack/content_length.rb +10 -16
  18. data/lib/rack/content_type.rb +9 -7
  19. data/lib/rack/deflater.rb +78 -50
  20. data/lib/rack/directory.rb +86 -63
  21. data/lib/rack/etag.rb +14 -22
  22. data/lib/rack/events.rb +18 -17
  23. data/lib/rack/files.rb +99 -61
  24. data/lib/rack/head.rb +8 -9
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +868 -642
  27. data/lib/rack/lock.rb +2 -6
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +9 -4
  30. data/lib/rack/method_override.rb +6 -2
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -253
  33. data/lib/rack/mock_request.rb +171 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +15 -8
  36. data/lib/rack/multipart/parser.rb +238 -107
  37. data/lib/rack/multipart/uploaded_file.rb +17 -7
  38. data/lib/rack/multipart.rb +54 -42
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +87 -105
  41. data/lib/rack/recursive.rb +3 -1
  42. data/lib/rack/reloader.rb +0 -4
  43. data/lib/rack/request.rb +366 -135
  44. data/lib/rack/response.rb +186 -68
  45. data/lib/rack/rewindable_input.rb +24 -6
  46. data/lib/rack/runtime.rb +8 -7
  47. data/lib/rack/sendfile.rb +29 -27
  48. data/lib/rack/show_exceptions.rb +27 -12
  49. data/lib/rack/show_status.rb +21 -13
  50. data/lib/rack/static.rb +19 -12
  51. data/lib/rack/tempfile_reaper.rb +14 -5
  52. data/lib/rack/urlmap.rb +5 -6
  53. data/lib/rack/utils.rb +274 -260
  54. data/lib/rack/version.rb +21 -0
  55. data/lib/rack.rb +18 -103
  56. metadata +25 -52
  57. data/README.rdoc +0 -262
  58. data/Rakefile +0 -123
  59. data/SPEC +0 -263
  60. data/bin/rackup +0 -5
  61. data/contrib/rack.png +0 -0
  62. data/contrib/rack.svg +0 -150
  63. data/contrib/rack_logo.svg +0 -164
  64. data/contrib/rdoc.css +0 -412
  65. data/example/lobster.ru +0 -6
  66. data/example/protectedlobster.rb +0 -16
  67. data/example/protectedlobster.ru +0 -10
  68. data/lib/rack/auth/digest/md5.rb +0 -131
  69. data/lib/rack/auth/digest/nonce.rb +0 -54
  70. data/lib/rack/auth/digest/params.rb +0 -54
  71. data/lib/rack/auth/digest/request.rb +0 -43
  72. data/lib/rack/chunked.rb +0 -92
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/file.rb +0 -8
  75. data/lib/rack/handler/cgi.rb +0 -62
  76. data/lib/rack/handler/fastcgi.rb +0 -102
  77. data/lib/rack/handler/lsws.rb +0 -63
  78. data/lib/rack/handler/scgi.rb +0 -73
  79. data/lib/rack/handler/thin.rb +0 -38
  80. data/lib/rack/handler/webrick.rb +0 -122
  81. data/lib/rack/handler.rb +0 -104
  82. data/lib/rack/lobster.rb +0 -72
  83. data/lib/rack/server.rb +0 -467
  84. data/lib/rack/session/abstract/id.rb +0 -528
  85. data/lib/rack/session/cookie.rb +0 -205
  86. data/lib/rack/session/memcache.rb +0 -10
  87. data/lib/rack/session/pool.rb +0 -85
  88. data/rack.gemspec +0 -44
data/lib/rack/lock.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thread'
4
- require 'rack/body_proxy'
3
+ require_relative 'body_proxy'
5
4
 
6
5
  module Rack
7
6
  # Rack::Lock locks every request inside a mutex, so that every request
@@ -13,10 +12,8 @@ module Rack
13
12
 
14
13
  def call(env)
15
14
  @mutex.lock
16
- @env = env
17
- @old_rack_multithread = env[RACK_MULTITHREAD]
18
15
  begin
19
- response = @app.call(env.merge!(RACK_MULTITHREAD => false))
16
+ response = @app.call(env)
20
17
  returned = response << BodyProxy.new(response.pop) { unlock }
21
18
  ensure
22
19
  unlock unless returned
@@ -27,7 +24,6 @@ module Rack
27
24
 
28
25
  def unlock
29
26
  @mutex.unlock
30
- @env[RACK_MULTITHREAD] = @old_rack_multithread
31
27
  end
32
28
  end
33
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
@@ -4,7 +4,7 @@ module Rack
4
4
  # Rack::MediaType parse media type and parameters out of content_type string
5
5
 
6
6
  class MediaType
7
- SPLIT_PATTERN = %r{\s*[;,]\s*}
7
+ SPLIT_PATTERN = /[;,]/
8
8
 
9
9
  class << self
10
10
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -15,7 +15,11 @@ module Rack
15
15
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
16
16
  def type(content_type)
17
17
  return nil unless content_type
18
- content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
18
+ if type = content_type.split(SPLIT_PATTERN, 2).first
19
+ type.rstrip!
20
+ type.downcase!
21
+ type
22
+ end
19
23
  end
20
24
 
21
25
  # The media type parameters provided in CONTENT_TYPE as a Hash, or
@@ -27,9 +31,10 @@ module Rack
27
31
  return {} if content_type.nil?
28
32
 
29
33
  content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
34
+ s.strip!
30
35
  k, v = s.split('=', 2)
31
-
32
- hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
36
+ k.downcase!
37
+ hsh[k] = strip_doublequotes(v)
33
38
  end
34
39
  end
35
40
 
@@ -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,8 +46,8 @@ module Rack
42
46
  end
43
47
 
44
48
  def method_override_param(req)
45
- req.POST[METHOD_OVERRIDE_PARAM_KEY]
46
- rescue Utils::InvalidParameterError, Utils::ParameterTypeError
49
+ req.POST[METHOD_OVERRIDE_PARAM_KEY] if req.form_data? || req.parseable_data?
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
49
53
  req.get_header(RACK_ERRORS).puts "Bad request content body"
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,255 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'uri'
4
- require 'stringio'
5
- require 'rack'
6
- require 'rack/lint'
7
- require 'rack/utils'
8
- require 'rack/response'
9
- require 'cgi/cookie'
10
-
11
- module Rack
12
- # Rack::MockRequest helps testing your Rack application without
13
- # actually using HTTP.
14
- #
15
- # After performing a request on a URL with get/post/put/patch/delete, it
16
- # returns a MockResponse with useful helper methods for effective
17
- # testing.
18
- #
19
- # You can pass a hash with additional configuration to the
20
- # get/post/put/patch/delete.
21
- # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
22
- # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
23
- # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
24
-
25
- class MockRequest
26
- class FatalWarning < RuntimeError
27
- end
28
-
29
- class FatalWarner
30
- def puts(warning)
31
- raise FatalWarning, warning
32
- end
33
-
34
- def write(warning)
35
- raise FatalWarning, warning
36
- end
37
-
38
- def flush
39
- end
40
-
41
- def string
42
- ""
43
- end
44
- end
45
-
46
- DEFAULT_ENV = {
47
- RACK_VERSION => Rack::VERSION,
48
- RACK_INPUT => StringIO.new,
49
- RACK_ERRORS => StringIO.new,
50
- RACK_MULTITHREAD => true,
51
- RACK_MULTIPROCESS => true,
52
- RACK_RUNONCE => false,
53
- }.freeze
54
-
55
- def initialize(app)
56
- @app = app
57
- end
58
-
59
- def get(uri, opts = {}) request(GET, uri, opts) end
60
- def post(uri, opts = {}) request(POST, uri, opts) end
61
- def put(uri, opts = {}) request(PUT, uri, opts) end
62
- def patch(uri, opts = {}) request(PATCH, uri, opts) end
63
- def delete(uri, opts = {}) request(DELETE, uri, opts) end
64
- def head(uri, opts = {}) request(HEAD, uri, opts) end
65
- def options(uri, opts = {}) request(OPTIONS, uri, opts) end
66
-
67
- def request(method = GET, uri = "", opts = {})
68
- env = self.class.env_for(uri, opts.merge(method: method))
69
-
70
- if opts[:lint]
71
- app = Rack::Lint.new(@app)
72
- else
73
- app = @app
74
- end
75
-
76
- errors = env[RACK_ERRORS]
77
- status, headers, body = app.call(env)
78
- MockResponse.new(status, headers, body, errors)
79
- ensure
80
- body.close if body.respond_to?(:close)
81
- end
82
-
83
- # For historical reasons, we're pinning to RFC 2396.
84
- # URI::Parser = URI::RFC2396_Parser
85
- def self.parse_uri_rfc2396(uri)
86
- @parser ||= URI::Parser.new
87
- @parser.parse(uri)
88
- end
89
-
90
- # Return the Rack environment used for a request to +uri+.
91
- def self.env_for(uri = "", opts = {})
92
- uri = parse_uri_rfc2396(uri)
93
- uri.path = "/#{uri.path}" unless uri.path[0] == ?/
94
-
95
- env = DEFAULT_ENV.dup
96
-
97
- env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
98
- env[SERVER_NAME] = (uri.host || "example.org").b
99
- env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
100
- env[QUERY_STRING] = (uri.query.to_s).b
101
- env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
102
- env[RACK_URL_SCHEME] = (uri.scheme || "http").b
103
- env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
104
-
105
- env[SCRIPT_NAME] = opts[:script_name] || ""
106
-
107
- if opts[:fatal]
108
- env[RACK_ERRORS] = FatalWarner.new
109
- else
110
- env[RACK_ERRORS] = StringIO.new
111
- end
112
-
113
- if params = opts[:params]
114
- if env[REQUEST_METHOD] == GET
115
- params = Utils.parse_nested_query(params) if params.is_a?(String)
116
- params.update(Utils.parse_nested_query(env[QUERY_STRING]))
117
- env[QUERY_STRING] = Utils.build_nested_query(params)
118
- elsif !opts.has_key?(:input)
119
- opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
120
- if params.is_a?(Hash)
121
- if data = Rack::Multipart.build_multipart(params)
122
- opts[:input] = data
123
- opts["CONTENT_LENGTH"] ||= data.length.to_s
124
- opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
125
- else
126
- opts[:input] = Utils.build_nested_query(params)
127
- end
128
- else
129
- opts[:input] = params
130
- end
131
- end
132
- end
133
-
134
- empty_str = String.new
135
- opts[:input] ||= empty_str
136
- if String === opts[:input]
137
- rack_input = StringIO.new(opts[:input])
138
- else
139
- rack_input = opts[:input]
140
- end
141
-
142
- rack_input.set_encoding(Encoding::BINARY)
143
- env[RACK_INPUT] = rack_input
144
-
145
- env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
146
-
147
- opts.each { |field, value|
148
- env[field] = value if String === field
149
- }
150
-
151
- env
152
- end
153
- end
154
-
155
- # Rack::MockResponse provides useful helpers for testing your apps.
156
- # Usually, you don't create the MockResponse on your own, but use
157
- # MockRequest.
158
-
159
- class MockResponse < Rack::Response
160
- # Headers
161
- attr_reader :original_headers, :cookies
162
-
163
- # Errors
164
- attr_accessor :errors
165
-
166
- def initialize(status, headers, body, errors = StringIO.new(""))
167
- @original_headers = headers
168
- @errors = errors.string if errors.respond_to?(:string)
169
- @cookies = parse_cookies_from_header
170
-
171
- super(body, status, headers)
172
-
173
- buffered_body!
174
- end
175
-
176
- def =~(other)
177
- body =~ other
178
- end
179
-
180
- def match(other)
181
- body.match other
182
- end
183
-
184
- def body
185
- # FIXME: apparently users of MockResponse expect the return value of
186
- # MockResponse#body to be a string. However, the real response object
187
- # returns the body as a list.
188
- #
189
- # See spec_showstatus.rb:
190
- #
191
- # should "not replace existing messages" do
192
- # ...
193
- # res.body.should == "foo!"
194
- # end
195
- buffer = String.new
196
-
197
- super.each do |chunk|
198
- buffer << chunk
199
- end
200
-
201
- return buffer
202
- end
203
-
204
- def empty?
205
- [201, 204, 304].include? status
206
- end
207
-
208
- def cookie(name)
209
- cookies.fetch(name, nil)
210
- end
211
-
212
- private
213
-
214
- def parse_cookies_from_header
215
- cookies = Hash.new
216
- if original_headers.has_key? 'Set-Cookie'
217
- set_cookie_header = original_headers.fetch('Set-Cookie')
218
- set_cookie_header.split("\n").each do |cookie|
219
- cookie_name, cookie_filling = cookie.split('=', 2)
220
- cookie_attributes = identify_cookie_attributes cookie_filling
221
- parsed_cookie = CGI::Cookie.new(
222
- 'name' => cookie_name.strip,
223
- 'value' => cookie_attributes.fetch('value'),
224
- 'path' => cookie_attributes.fetch('path', nil),
225
- 'domain' => cookie_attributes.fetch('domain', nil),
226
- 'expires' => cookie_attributes.fetch('expires', nil),
227
- 'secure' => cookie_attributes.fetch('secure', false)
228
- )
229
- cookies.store(cookie_name, parsed_cookie)
230
- end
231
- end
232
- cookies
233
- end
234
-
235
- def identify_cookie_attributes(cookie_filling)
236
- cookie_bits = cookie_filling.split(';')
237
- cookie_attributes = Hash.new
238
- cookie_attributes.store('value', cookie_bits[0].strip)
239
- cookie_bits.each do |bit|
240
- if bit.include? '='
241
- cookie_attribute, attribute_value = bit.split('=')
242
- cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
243
- if cookie_attribute.include? 'max-age'
244
- cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
245
- end
246
- end
247
- if bit.include? 'secure'
248
- cookie_attributes.store('secure', true)
249
- end
250
- end
251
- cookie_attributes
252
- end
253
-
254
- end
255
- 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