rack 2.2.8 → 3.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -83
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +309 -0
  6. data/SPEC.rdoc +174 -126
  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 +0 -2
  10. data/lib/rack/auth/digest/md5.rb +1 -131
  11. data/lib/rack/auth/digest/nonce.rb +1 -54
  12. data/lib/rack/auth/digest/params.rb +1 -54
  13. data/lib/rack/auth/digest/request.rb +1 -43
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +3 -1
  16. data/lib/rack/builder.rb +83 -63
  17. data/lib/rack/cascade.rb +2 -0
  18. data/lib/rack/chunked.rb +16 -13
  19. data/lib/rack/common_logger.rb +23 -18
  20. data/lib/rack/conditional_get.rb +18 -15
  21. data/lib/rack/constants.rb +64 -0
  22. data/lib/rack/content_length.rb +12 -16
  23. data/lib/rack/content_type.rb +8 -5
  24. data/lib/rack/deflater.rb +40 -26
  25. data/lib/rack/directory.rb +9 -3
  26. data/lib/rack/etag.rb +14 -23
  27. data/lib/rack/events.rb +4 -0
  28. data/lib/rack/file.rb +2 -0
  29. data/lib/rack/files.rb +15 -17
  30. data/lib/rack/head.rb +9 -8
  31. data/lib/rack/headers.rb +154 -0
  32. data/lib/rack/lint.rb +758 -646
  33. data/lib/rack/lock.rb +2 -5
  34. data/lib/rack/logger.rb +2 -0
  35. data/lib/rack/media_type.rb +9 -4
  36. data/lib/rack/method_override.rb +5 -1
  37. data/lib/rack/mime.rb +8 -0
  38. data/lib/rack/mock.rb +1 -271
  39. data/lib/rack/mock_request.rb +166 -0
  40. data/lib/rack/mock_response.rb +126 -0
  41. data/lib/rack/multipart/generator.rb +7 -5
  42. data/lib/rack/multipart/parser.rb +120 -64
  43. data/lib/rack/multipart/uploaded_file.rb +4 -0
  44. data/lib/rack/multipart.rb +20 -40
  45. data/lib/rack/null_logger.rb +9 -0
  46. data/lib/rack/query_parser.rb +78 -46
  47. data/lib/rack/recursive.rb +2 -0
  48. data/lib/rack/reloader.rb +0 -2
  49. data/lib/rack/request.rb +224 -106
  50. data/lib/rack/response.rb +138 -61
  51. data/lib/rack/rewindable_input.rb +24 -5
  52. data/lib/rack/runtime.rb +7 -6
  53. data/lib/rack/sendfile.rb +30 -25
  54. data/lib/rack/show_exceptions.rb +15 -2
  55. data/lib/rack/show_status.rb +17 -7
  56. data/lib/rack/static.rb +8 -8
  57. data/lib/rack/tempfile_reaper.rb +15 -4
  58. data/lib/rack/urlmap.rb +3 -1
  59. data/lib/rack/utils.rb +208 -178
  60. data/lib/rack/version.rb +9 -4
  61. data/lib/rack.rb +6 -76
  62. metadata +14 -34
  63. data/README.rdoc +0 -320
  64. data/Rakefile +0 -130
  65. data/bin/rackup +0 -5
  66. data/contrib/rack.png +0 -0
  67. data/contrib/rack.svg +0 -150
  68. data/contrib/rack_logo.svg +0 -164
  69. data/contrib/rdoc.css +0 -412
  70. data/example/lobster.ru +0 -6
  71. data/example/protectedlobster.rb +0 -16
  72. data/example/protectedlobster.ru +0 -10
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -204
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  87. 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
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'logger'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Sets up rack.logger to write to rack.errors stream
7
9
  class Logger
@@ -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,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",
@@ -617,6 +624,7 @@ module Rack
617
624
  ".wbs" => "application/vnd.criticaltools.wbs+xml",
618
625
  ".wbxml" => "application/vnd.wap.wbxml",
619
626
  ".webm" => "video/webm",
627
+ ".webp" => "image/webp",
620
628
  ".wm" => "video/x-ms-wm",
621
629
  ".wma" => "audio/x-ms-wma",
622
630
  ".wmd" => "application/x-ms-wmd",
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,166 @@
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
+ DEFAULT_ENV = {
45
+ RACK_INPUT => StringIO.new,
46
+ RACK_ERRORS => StringIO.new,
47
+ }.freeze
48
+
49
+ def initialize(app)
50
+ @app = app
51
+ end
52
+
53
+ # Make a GET request and return a MockResponse. See #request.
54
+ def get(uri, opts = {}) request(GET, uri, opts) end
55
+ # Make a POST request and return a MockResponse. See #request.
56
+ def post(uri, opts = {}) request(POST, uri, opts) end
57
+ # Make a PUT request and return a MockResponse. See #request.
58
+ def put(uri, opts = {}) request(PUT, uri, opts) end
59
+ # Make a PATCH request and return a MockResponse. See #request.
60
+ def patch(uri, opts = {}) request(PATCH, uri, opts) end
61
+ # Make a DELETE request and return a MockResponse. See #request.
62
+ def delete(uri, opts = {}) request(DELETE, uri, opts) end
63
+ # Make a HEAD request and return a MockResponse. See #request.
64
+ def head(uri, opts = {}) request(HEAD, uri, opts) end
65
+ # Make an OPTIONS request and return a MockResponse. See #request.
66
+ def options(uri, opts = {}) request(OPTIONS, uri, opts) end
67
+
68
+ # Make a request using the given request method for the given
69
+ # uri to the rack application and return a MockResponse.
70
+ # Options given are passed to MockRequest.env_for.
71
+ def request(method = GET, uri = "", opts = {})
72
+ env = self.class.env_for(uri, opts.merge(method: method))
73
+
74
+ if opts[:lint]
75
+ app = Rack::Lint.new(@app)
76
+ else
77
+ app = @app
78
+ end
79
+
80
+ errors = env[RACK_ERRORS]
81
+ status, headers, body = app.call(env)
82
+ MockResponse.new(status, headers, body, errors)
83
+ ensure
84
+ body.close if body.respond_to?(:close)
85
+ end
86
+
87
+ # For historical reasons, we're pinning to RFC 2396.
88
+ # URI::Parser = URI::RFC2396_Parser
89
+ def self.parse_uri_rfc2396(uri)
90
+ @parser ||= URI::Parser.new
91
+ @parser.parse(uri)
92
+ end
93
+
94
+ # Return the Rack environment used for a request to +uri+.
95
+ # All options that are strings are added to the returned environment.
96
+ # Options:
97
+ # :fatal :: Whether to raise an exception if request outputs to rack.errors
98
+ # :input :: The rack.input to set
99
+ # :http_version :: The SERVER_PROTOCOL to set
100
+ # :method :: The HTTP request method to use
101
+ # :params :: The params to use
102
+ # :script_name :: The SCRIPT_NAME to set
103
+ def self.env_for(uri = "", opts = {})
104
+ uri = parse_uri_rfc2396(uri)
105
+ uri.path = "/#{uri.path}" unless uri.path[0] == ?/
106
+
107
+ env = DEFAULT_ENV.dup
108
+
109
+ env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
110
+ env[SERVER_NAME] = (uri.host || "example.org").b
111
+ env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
112
+ env[SERVER_PROTOCOL] = opts[:http_version] || 'HTTP/1.1'
113
+ env[QUERY_STRING] = (uri.query.to_s).b
114
+ env[PATH_INFO] = (uri.path).b
115
+ env[RACK_URL_SCHEME] = (uri.scheme || "http").b
116
+ env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
117
+
118
+ env[SCRIPT_NAME] = opts[:script_name] || ""
119
+
120
+ if opts[:fatal]
121
+ env[RACK_ERRORS] = FatalWarner.new
122
+ else
123
+ env[RACK_ERRORS] = StringIO.new
124
+ end
125
+
126
+ if params = opts[:params]
127
+ if env[REQUEST_METHOD] == GET
128
+ params = Utils.parse_nested_query(params) if params.is_a?(String)
129
+ params.update(Utils.parse_nested_query(env[QUERY_STRING]))
130
+ env[QUERY_STRING] = Utils.build_nested_query(params)
131
+ elsif !opts.has_key?(:input)
132
+ opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
133
+ if params.is_a?(Hash)
134
+ if data = Rack::Multipart.build_multipart(params)
135
+ opts[:input] = data
136
+ opts["CONTENT_LENGTH"] ||= data.length.to_s
137
+ opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
138
+ else
139
+ opts[:input] = Utils.build_nested_query(params)
140
+ end
141
+ else
142
+ opts[:input] = params
143
+ end
144
+ end
145
+ end
146
+
147
+ opts[:input] ||= String.new
148
+ if String === opts[:input]
149
+ rack_input = StringIO.new(opts[:input])
150
+ else
151
+ rack_input = opts[:input]
152
+ end
153
+
154
+ rack_input.set_encoding(Encoding::BINARY)
155
+ env[RACK_INPUT] = rack_input
156
+
157
+ env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
158
+
159
+ opts.each { |field, value|
160
+ env[field] = value if String === field
161
+ }
162
+
163
+ env
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi/cookie'
4
+ require 'time'
5
+
6
+ require_relative 'response'
7
+
8
+ module Rack
9
+ # Rack::MockResponse provides useful helpers for testing your apps.
10
+ # Usually, you don't create the MockResponse on your own, but use
11
+ # MockRequest.
12
+
13
+ class MockResponse < Rack::Response
14
+ class << self
15
+ alias [] new
16
+ end
17
+
18
+ # Headers
19
+ attr_reader :original_headers, :cookies
20
+
21
+ # Errors
22
+ attr_accessor :errors
23
+
24
+ def initialize(status, headers, body, errors = nil)
25
+ @original_headers = headers
26
+
27
+ if errors
28
+ @errors = errors.string if errors.respond_to?(:string)
29
+ else
30
+ @errors = ""
31
+ end
32
+
33
+ super(body, status, headers)
34
+
35
+ @cookies = parse_cookies_from_header
36
+ buffered_body!
37
+ end
38
+
39
+ def =~(other)
40
+ body =~ other
41
+ end
42
+
43
+ def match(other)
44
+ body.match other
45
+ end
46
+
47
+ def body
48
+ return @buffered_body if defined?(@buffered_body)
49
+
50
+ # FIXME: apparently users of MockResponse expect the return value of
51
+ # MockResponse#body to be a string. However, the real response object
52
+ # returns the body as a list.
53
+ #
54
+ # See spec_showstatus.rb:
55
+ #
56
+ # should "not replace existing messages" do
57
+ # ...
58
+ # res.body.should == "foo!"
59
+ # end
60
+ buffer = @buffered_body = String.new
61
+
62
+ @body.each do |chunk|
63
+ buffer << chunk
64
+ end
65
+
66
+ return buffer
67
+ end
68
+
69
+ def empty?
70
+ [201, 204, 304].include? status
71
+ end
72
+
73
+ def cookie(name)
74
+ cookies.fetch(name, nil)
75
+ end
76
+
77
+ private
78
+
79
+ def parse_cookies_from_header
80
+ cookies = Hash.new
81
+ if headers.has_key? 'set-cookie'
82
+ set_cookie_header = headers.fetch('set-cookie')
83
+ Array(set_cookie_header).each do |header_value|
84
+ header_value.split("\n").each do |cookie|
85
+ cookie_name, cookie_filling = cookie.split('=', 2)
86
+ cookie_attributes = identify_cookie_attributes cookie_filling
87
+ parsed_cookie = CGI::Cookie.new(
88
+ 'name' => cookie_name.strip,
89
+ 'value' => cookie_attributes.fetch('value'),
90
+ 'path' => cookie_attributes.fetch('path', nil),
91
+ 'domain' => cookie_attributes.fetch('domain', nil),
92
+ 'expires' => cookie_attributes.fetch('expires', nil),
93
+ 'secure' => cookie_attributes.fetch('secure', false)
94
+ )
95
+ cookies.store(cookie_name, parsed_cookie)
96
+ end
97
+ end
98
+ end
99
+ cookies
100
+ end
101
+
102
+ def identify_cookie_attributes(cookie_filling)
103
+ cookie_bits = cookie_filling.split(';')
104
+ cookie_attributes = Hash.new
105
+ cookie_attributes.store('value', cookie_bits[0].strip)
106
+ cookie_bits.drop(1).each do |bit|
107
+ if bit.include? '='
108
+ cookie_attribute, attribute_value = bit.split('=', 2)
109
+ cookie_attributes.store(cookie_attribute.strip.downcase, attribute_value.strip)
110
+ end
111
+ if bit.include? 'secure'
112
+ cookie_attributes.store('secure', true)
113
+ end
114
+ end
115
+
116
+ if cookie_attributes.key? 'max-age'
117
+ cookie_attributes.store('expires', Time.now + cookie_attributes['max-age'].to_i)
118
+ elsif cookie_attributes.key? 'expires'
119
+ cookie_attributes.store('expires', Time.httpdate(cookie_attributes['expires']))
120
+ end
121
+
122
+ cookie_attributes
123
+ end
124
+
125
+ end
126
+ end