rack 1.6.11 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +694 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +157 -163
- data/Rakefile +38 -32
- data/{SPEC → SPEC.rdoc} +41 -13
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +6 -2
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +6 -4
- data/lib/rack/body_proxy.rb +21 -15
- data/lib/rack/builder.rb +119 -26
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +70 -22
- data/lib/rack/common_logger.rb +80 -0
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +9 -8
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +60 -70
- data/lib/rack/directory.rb +117 -85
- data/lib/rack/etag.rb +9 -7
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +17 -19
- data/lib/rack/handler/fastcgi.rb +17 -18
- data/lib/rack/handler/lsws.rb +14 -14
- data/lib/rack/handler/scgi.rb +22 -21
- data/lib/rack/handler/thin.rb +6 -3
- data/lib/rack/handler/webrick.rb +39 -32
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +16 -18
- data/lib/rack/lint.rb +110 -64
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +17 -11
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +124 -65
- data/lib/rack/multipart/generator.rb +20 -16
- data/lib/rack/multipart/parser.rb +273 -162
- data/lib/rack/multipart/uploaded_file.rb +15 -8
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +217 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +553 -305
- data/lib/rack/response.rb +244 -88
- data/lib/rack/rewindable_input.rb +5 -15
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +17 -15
- data/lib/rack/server.rb +125 -47
- data/lib/rack/session/abstract/id.rb +217 -93
- data/lib/rack/session/cookie.rb +46 -31
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +26 -17
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +12 -12
- data/lib/rack/static.rb +48 -11
- data/lib/rack/tempfile_reaper.rb +3 -3
- data/lib/rack/urlmap.rb +26 -19
- data/lib/rack/utils.rb +212 -294
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +76 -33
- data/rack.gemspec +43 -30
- metadata +65 -186
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/commonlogger.rb +0 -72
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/lint.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'forwardable'
|
3
4
|
|
4
5
|
module Rack
|
@@ -15,8 +16,8 @@ module Rack
|
|
15
16
|
|
16
17
|
class LintError < RuntimeError; end
|
17
18
|
module Assertion
|
18
|
-
def assert(message
|
19
|
-
unless
|
19
|
+
def assert(message)
|
20
|
+
unless yield
|
20
21
|
raise LintError, message
|
21
22
|
end
|
22
23
|
end
|
@@ -33,7 +34,7 @@ module Rack
|
|
33
34
|
|
34
35
|
## A Rack application is a Ruby object (not a class) that
|
35
36
|
## responds to +call+.
|
36
|
-
def call(env=nil)
|
37
|
+
def call(env = nil)
|
37
38
|
dup._call(env)
|
38
39
|
end
|
39
40
|
|
@@ -42,33 +43,47 @@ module Rack
|
|
42
43
|
assert("No env given") { env }
|
43
44
|
check_env env
|
44
45
|
|
45
|
-
env[
|
46
|
-
env[
|
46
|
+
env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
|
47
|
+
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
|
47
48
|
|
48
49
|
## and returns an Array of exactly three values:
|
49
|
-
|
50
|
+
ary = @app.call(env)
|
51
|
+
assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
|
52
|
+
ary.kind_of? Array
|
53
|
+
}
|
54
|
+
assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
|
55
|
+
ary.size == 3
|
56
|
+
}
|
57
|
+
|
58
|
+
status, headers, @body = ary
|
50
59
|
## The *status*,
|
51
60
|
check_status status
|
52
61
|
## the *headers*,
|
53
62
|
check_headers headers
|
54
63
|
|
55
|
-
check_hijack_response headers, env
|
64
|
+
hijack_proc = check_hijack_response headers, env
|
65
|
+
if hijack_proc && headers.is_a?(Hash)
|
66
|
+
headers[RACK_HIJACK] = hijack_proc
|
67
|
+
end
|
56
68
|
|
57
69
|
## and the *body*.
|
58
70
|
check_content_type status, headers
|
59
71
|
check_content_length status, headers
|
60
|
-
@head_request = env[REQUEST_METHOD] ==
|
72
|
+
@head_request = env[REQUEST_METHOD] == HEAD
|
61
73
|
[status, headers, self]
|
62
74
|
end
|
63
75
|
|
64
76
|
## == The Environment
|
65
77
|
def check_env(env)
|
66
|
-
## The environment must be an instance of Hash that includes
|
78
|
+
## The environment must be an unfrozen instance of Hash that includes
|
67
79
|
## CGI-like headers. The application is free to modify the
|
68
80
|
## environment.
|
69
81
|
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
|
70
82
|
env.kind_of? Hash
|
71
83
|
}
|
84
|
+
assert("env should not be frozen, but is") {
|
85
|
+
!env.frozen?
|
86
|
+
}
|
72
87
|
|
73
88
|
##
|
74
89
|
## The environment is required to include these variables
|
@@ -95,24 +110,26 @@ module Rack
|
|
95
110
|
## empty string, if the request URL targets
|
96
111
|
## the application root and does not have a
|
97
112
|
## trailing slash. This value may be
|
98
|
-
## percent-encoded when
|
113
|
+
## percent-encoded when originating from
|
99
114
|
## a URL.
|
100
115
|
|
101
116
|
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
|
102
117
|
## follows the <tt>?</tt>, if any. May be
|
103
118
|
## empty, but is always required!
|
104
119
|
|
105
|
-
## <tt>SERVER_NAME</tt
|
106
|
-
## When combined with <tt>SCRIPT_NAME</tt> and
|
120
|
+
## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
107
121
|
## <tt>PATH_INFO</tt>, these variables can be
|
108
122
|
## used to complete the URL. Note, however,
|
109
123
|
## that <tt>HTTP_HOST</tt>, if present,
|
110
124
|
## should be used in preference to
|
111
125
|
## <tt>SERVER_NAME</tt> for reconstructing
|
112
126
|
## the request URL.
|
113
|
-
## <tt>SERVER_NAME</tt>
|
114
|
-
##
|
115
|
-
|
127
|
+
## <tt>SERVER_NAME</tt> can never be an empty
|
128
|
+
## string, and so is always required.
|
129
|
+
|
130
|
+
## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
131
|
+
## server is running on. Should be specified if
|
132
|
+
## the server is running on a non-standard port.
|
116
133
|
|
117
134
|
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
118
135
|
## client-supplied HTTP request
|
@@ -123,9 +140,8 @@ module Rack
|
|
123
140
|
## the presence or absence of the
|
124
141
|
## appropriate HTTP header in the
|
125
142
|
## request. See
|
126
|
-
##
|
127
|
-
##
|
128
|
-
## specific behavior.
|
143
|
+
## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
|
144
|
+
## for specific behavior.
|
129
145
|
|
130
146
|
## In addition to this, the Rack environment must include these
|
131
147
|
## Rack-specific variables:
|
@@ -177,7 +193,7 @@ module Rack
|
|
177
193
|
## <tt>rack.session</tt>:: A hash like interface for storing
|
178
194
|
## request session data.
|
179
195
|
## The store must implement:
|
180
|
-
if session = env[
|
196
|
+
if session = env[RACK_SESSION]
|
181
197
|
## store(key, value) (aliased as []=);
|
182
198
|
assert("session #{session.inspect} must respond to store and []=") {
|
183
199
|
session.respond_to?(:store) && session.respond_to?(:[]=)
|
@@ -197,11 +213,16 @@ module Rack
|
|
197
213
|
assert("session #{session.inspect} must respond to clear") {
|
198
214
|
session.respond_to?(:clear)
|
199
215
|
}
|
216
|
+
|
217
|
+
## to_hash (returning unfrozen Hash instance);
|
218
|
+
assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
|
219
|
+
session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
220
|
+
}
|
200
221
|
end
|
201
222
|
|
202
223
|
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
203
224
|
## The object must implement:
|
204
|
-
if logger = env[
|
225
|
+
if logger = env[RACK_LOGGER]
|
205
226
|
## info(message, &block)
|
206
227
|
assert("logger #{logger.inspect} must respond to info") {
|
207
228
|
logger.respond_to?(:info)
|
@@ -229,16 +250,16 @@ module Rack
|
|
229
250
|
end
|
230
251
|
|
231
252
|
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
|
232
|
-
if bufsize = env[
|
253
|
+
if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
|
233
254
|
assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
|
234
255
|
bufsize.is_a?(Integer) && bufsize > 0
|
235
256
|
}
|
236
257
|
end
|
237
258
|
|
238
259
|
## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
|
239
|
-
if tempfile_factory = env[
|
260
|
+
if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
|
240
261
|
assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
|
241
|
-
env[
|
262
|
+
env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
|
242
263
|
io = tempfile_factory.call(filename, content_type)
|
243
264
|
assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
|
244
265
|
io
|
@@ -252,64 +273,85 @@ module Rack
|
|
252
273
|
## accepted specifications and must not be used otherwise.
|
253
274
|
##
|
254
275
|
|
255
|
-
%w[REQUEST_METHOD SERVER_NAME
|
256
|
-
QUERY_STRING
|
276
|
+
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
257
277
|
rack.version rack.input rack.errors
|
258
278
|
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
259
279
|
assert("env missing required key #{header}") { env.include? header }
|
260
280
|
}
|
261
281
|
|
282
|
+
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
283
|
+
assert("env[SERVER_PORT] is not an Integer") do
|
284
|
+
server_port = env["SERVER_PORT"]
|
285
|
+
server_port.nil? || (Integer(server_port) rescue false)
|
286
|
+
end
|
287
|
+
|
288
|
+
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
289
|
+
assert("#{env[SERVER_NAME]} must be a valid authority") do
|
290
|
+
URI.parse("http://#{env[SERVER_NAME]}/") rescue false
|
291
|
+
end
|
292
|
+
|
293
|
+
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
294
|
+
assert("#{env[HTTP_HOST]} must be a valid authority") do
|
295
|
+
URI.parse("http://#{env[HTTP_HOST]}/") rescue false
|
296
|
+
end
|
297
|
+
|
262
298
|
## The environment must not contain the keys
|
263
299
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
264
300
|
## (use the versions without <tt>HTTP_</tt>).
|
265
301
|
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
|
266
|
-
assert("env contains #{header}, must use #{header[5
|
302
|
+
assert("env contains #{header}, must use #{header[5, -1]}") {
|
267
303
|
not env.include? header
|
268
304
|
}
|
269
305
|
}
|
270
306
|
|
271
307
|
## The CGI keys (named without a period) must have String values.
|
308
|
+
## If the string values for CGI keys contain non-ASCII characters,
|
309
|
+
## they should use ASCII-8BIT encoding.
|
272
310
|
env.each { |key, value|
|
273
311
|
next if key.include? "." # Skip extensions
|
274
312
|
assert("env variable #{key} has non-string value #{value.inspect}") {
|
275
313
|
value.kind_of? String
|
276
314
|
}
|
315
|
+
next if value.encoding == Encoding::ASCII_8BIT
|
316
|
+
assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
|
317
|
+
value.b !~ /[\x80-\xff]/n
|
318
|
+
}
|
277
319
|
}
|
278
320
|
|
279
321
|
## There are the following restrictions:
|
280
322
|
|
281
323
|
## * <tt>rack.version</tt> must be an array of Integers.
|
282
|
-
assert("rack.version must be an Array, was #{env[
|
283
|
-
env[
|
324
|
+
assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
|
325
|
+
env[RACK_VERSION].kind_of? Array
|
284
326
|
}
|
285
327
|
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
|
286
|
-
assert("rack.url_scheme unknown: #{env[
|
287
|
-
%w[http https].include?
|
328
|
+
assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
|
329
|
+
%w[http https].include?(env[RACK_URL_SCHEME])
|
288
330
|
}
|
289
331
|
|
290
332
|
## * There must be a valid input stream in <tt>rack.input</tt>.
|
291
|
-
check_input env[
|
333
|
+
check_input env[RACK_INPUT]
|
292
334
|
## * There must be a valid error stream in <tt>rack.errors</tt>.
|
293
|
-
check_error env[
|
335
|
+
check_error env[RACK_ERRORS]
|
294
336
|
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
|
295
337
|
check_hijack env
|
296
338
|
|
297
339
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
298
340
|
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
|
299
|
-
env[
|
341
|
+
env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
300
342
|
}
|
301
343
|
|
302
344
|
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
|
303
345
|
assert("SCRIPT_NAME must start with /") {
|
304
|
-
!env.include?(
|
305
|
-
env[
|
306
|
-
env[
|
346
|
+
!env.include?(SCRIPT_NAME) ||
|
347
|
+
env[SCRIPT_NAME] == "" ||
|
348
|
+
env[SCRIPT_NAME] =~ /\A\//
|
307
349
|
}
|
308
350
|
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
|
309
351
|
assert("PATH_INFO must start with /") {
|
310
|
-
!env.include?(
|
311
|
-
env[
|
312
|
-
env[
|
352
|
+
!env.include?(PATH_INFO) ||
|
353
|
+
env[PATH_INFO] == "" ||
|
354
|
+
env[PATH_INFO] =~ /\A\//
|
313
355
|
}
|
314
356
|
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
|
315
357
|
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
|
@@ -320,11 +362,11 @@ module Rack
|
|
320
362
|
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
|
321
363
|
## <tt>SCRIPT_NAME</tt> is empty.
|
322
364
|
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
|
323
|
-
env[
|
365
|
+
env[SCRIPT_NAME] || env[PATH_INFO]
|
324
366
|
}
|
325
367
|
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
|
326
368
|
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
|
327
|
-
env[
|
369
|
+
env[SCRIPT_NAME] != "/"
|
328
370
|
}
|
329
371
|
end
|
330
372
|
|
@@ -336,7 +378,7 @@ module Rack
|
|
336
378
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
337
379
|
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
338
380
|
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
|
339
|
-
input.external_encoding
|
381
|
+
input.external_encoding == Encoding::ASCII_8BIT
|
340
382
|
} if input.respond_to?(:external_encoding)
|
341
383
|
assert("rack.input #{input} is not opened in binary mode") {
|
342
384
|
input.binmode?
|
@@ -518,11 +560,11 @@ module Rack
|
|
518
560
|
#
|
519
561
|
## ==== Request (before status)
|
520
562
|
def check_hijack(env)
|
521
|
-
if env[
|
563
|
+
if env[RACK_IS_HIJACK]
|
522
564
|
## If rack.hijack? is true then rack.hijack must respond to #call.
|
523
|
-
original_hijack = env[
|
565
|
+
original_hijack = env[RACK_HIJACK]
|
524
566
|
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
|
525
|
-
env[
|
567
|
+
env[RACK_HIJACK] = proc do
|
526
568
|
## rack.hijack must return the io that will also be assigned (or is
|
527
569
|
## already present, in rack.hijack_io.
|
528
570
|
io = original_hijack.call
|
@@ -548,16 +590,16 @@ module Rack
|
|
548
590
|
## hijack_io to provide additional features to users. The purpose of
|
549
591
|
## rack.hijack is for Rack to "get out of the way", as such, Rack only
|
550
592
|
## provides the minimum of specification and support.
|
551
|
-
env[
|
593
|
+
env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
|
552
594
|
io
|
553
595
|
end
|
554
596
|
else
|
555
597
|
##
|
556
598
|
## If rack.hijack? is false, then rack.hijack should not be set.
|
557
|
-
assert("rack.hijack? is false, but rack.hijack is present") { env[
|
599
|
+
assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
|
558
600
|
##
|
559
601
|
## If rack.hijack? is false, then rack.hijack_io should not be set.
|
560
|
-
assert("rack.hijack? is false, but rack.hijack_io is present") { env[
|
602
|
+
assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
|
561
603
|
end
|
562
604
|
end
|
563
605
|
|
@@ -568,7 +610,7 @@ module Rack
|
|
568
610
|
|
569
611
|
# this check uses headers like a hash, but the spec only requires
|
570
612
|
# headers respond to #each
|
571
|
-
headers = Rack::Utils::HeaderHash
|
613
|
+
headers = Rack::Utils::HeaderHash[headers]
|
572
614
|
|
573
615
|
## In order to do this, an application may set the special header
|
574
616
|
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
@@ -587,12 +629,12 @@ module Rack
|
|
587
629
|
## Servers must ignore the <tt>body</tt> part of the response tuple when
|
588
630
|
## the <tt>rack.hijack</tt> response API is in use.
|
589
631
|
|
590
|
-
if env[
|
632
|
+
if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
|
591
633
|
assert('rack.hijack header must respond to #call') {
|
592
|
-
headers[
|
634
|
+
headers[RACK_HIJACK].respond_to? :call
|
593
635
|
}
|
594
|
-
original_hijack = headers[
|
595
|
-
|
636
|
+
original_hijack = headers[RACK_HIJACK]
|
637
|
+
proc do |io|
|
596
638
|
original_hijack.call HijackWrapper.new(io)
|
597
639
|
end
|
598
640
|
else
|
@@ -600,8 +642,10 @@ module Rack
|
|
600
642
|
## The special response header <tt>rack.hijack</tt> must only be set
|
601
643
|
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
|
602
644
|
assert('rack.hijack header must not be present if server does not support hijacking') {
|
603
|
-
headers[
|
645
|
+
headers[RACK_HIJACK].nil?
|
604
646
|
}
|
647
|
+
|
648
|
+
nil
|
605
649
|
end
|
606
650
|
end
|
607
651
|
## ==== Conventions
|
@@ -626,15 +670,17 @@ module Rack
|
|
626
670
|
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
|
627
671
|
header.respond_to? :each
|
628
672
|
}
|
629
|
-
header.each { |key, value|
|
630
|
-
## Special headers starting "rack." are for communicating with the
|
631
|
-
## server, and must not be sent back to the client.
|
632
|
-
next if key =~ /^rack\..+$/
|
633
673
|
|
674
|
+
header.each { |key, value|
|
634
675
|
## The header keys must be Strings.
|
635
676
|
assert("header key must be a string, was #{key.class}") {
|
636
677
|
key.kind_of? String
|
637
678
|
}
|
679
|
+
|
680
|
+
## Special headers starting "rack." are for communicating with the
|
681
|
+
## server, and must not be sent back to the client.
|
682
|
+
next if key =~ /^rack\..+$/
|
683
|
+
|
638
684
|
## The header must not contain a +Status+ key.
|
639
685
|
assert("header must not contain Status") { key.downcase != "status" }
|
640
686
|
## The header must conform to RFC7230 token specification, i.e. cannot
|
@@ -659,10 +705,10 @@ module Rack
|
|
659
705
|
def check_content_type(status, headers)
|
660
706
|
headers.each { |key, value|
|
661
707
|
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
|
662
|
-
## 204
|
708
|
+
## 204 or 304.
|
663
709
|
if key.downcase == "content-type"
|
664
710
|
assert("Content-Type header found in #{status} response, not allowed") {
|
665
|
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.
|
711
|
+
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
666
712
|
}
|
667
713
|
return
|
668
714
|
end
|
@@ -674,9 +720,9 @@ module Rack
|
|
674
720
|
headers.each { |key, value|
|
675
721
|
if key.downcase == 'content-length'
|
676
722
|
## There must not be a <tt>Content-Length</tt> header when the
|
677
|
-
## +Status+ is 1xx, 204
|
723
|
+
## +Status+ is 1xx, 204 or 304.
|
678
724
|
assert("Content-Length header found in #{status} response, not allowed") {
|
679
|
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.
|
725
|
+
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
680
726
|
}
|
681
727
|
@content_length = value
|
682
728
|
end
|
@@ -710,7 +756,7 @@ module Rack
|
|
710
756
|
assert("Body yielded non-string value #{part.inspect}") {
|
711
757
|
part.kind_of? String
|
712
758
|
}
|
713
|
-
bytes +=
|
759
|
+
bytes += part.bytesize
|
714
760
|
yield part
|
715
761
|
}
|
716
762
|
verify_content_length(bytes)
|
data/lib/rack/lobster.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'rack/response'
|
3
|
+
require 'zlib'
|
5
4
|
|
6
5
|
module Rack
|
7
6
|
# Paste has a Pony, Rack has a Lobster!
|
@@ -25,8 +24,8 @@ module Rack
|
|
25
24
|
content = ["<title>Lobstericious!</title>",
|
26
25
|
"<pre>", lobster, "</pre>",
|
27
26
|
"<a href='#{href}'>flip!</a>"]
|
28
|
-
length = content.inject(0) { |a,e| a+e.size }.to_s
|
29
|
-
[200, {CONTENT_TYPE => "text/html", CONTENT_LENGTH => length}, content]
|
27
|
+
length = content.inject(0) { |a, e| a + e.size }.to_s
|
28
|
+
[200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
|
30
29
|
}
|
31
30
|
|
32
31
|
def call(env)
|
@@ -37,8 +36,8 @@ module Rack
|
|
37
36
|
gsub('\\', 'TEMP').
|
38
37
|
gsub('/', '\\').
|
39
38
|
gsub('TEMP', '/').
|
40
|
-
gsub('{','}').
|
41
|
-
gsub('(',')')
|
39
|
+
gsub('{', '}').
|
40
|
+
gsub('(', ')')
|
42
41
|
end.join("\n")
|
43
42
|
href = "?flip=right"
|
44
43
|
elsif req.GET["flip"] == "crash"
|
@@ -62,9 +61,10 @@ module Rack
|
|
62
61
|
end
|
63
62
|
|
64
63
|
if $0 == __FILE__
|
65
|
-
|
66
|
-
|
64
|
+
# :nocov:
|
65
|
+
require_relative '../rack'
|
67
66
|
Rack::Server.start(
|
68
|
-
:
|
67
|
+
app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
|
69
68
|
)
|
69
|
+
# :nocov:
|
70
70
|
end
|
data/lib/rack/lock.rb
CHANGED
@@ -1,26 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
|
-
require 'rack/body_proxy'
|
3
4
|
|
4
5
|
module Rack
|
5
6
|
# Rack::Lock locks every request inside a mutex, so that every request
|
6
7
|
# will effectively be executed synchronously.
|
7
8
|
class Lock
|
8
|
-
FLAG = 'rack.multithread'.freeze
|
9
|
-
|
10
9
|
def initialize(app, mutex = Mutex.new)
|
11
10
|
@app, @mutex = app, mutex
|
12
11
|
end
|
13
12
|
|
14
13
|
def call(env)
|
15
|
-
old, env[FLAG] = env[FLAG], false
|
16
14
|
@mutex.lock
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
@env = env
|
16
|
+
@old_rack_multithread = env[RACK_MULTITHREAD]
|
17
|
+
begin
|
18
|
+
response = @app.call(env.merge!(RACK_MULTITHREAD => false))
|
19
|
+
returned = response << BodyProxy.new(response.pop) { unlock }
|
20
|
+
ensure
|
21
|
+
unlock unless returned
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def unlock
|
28
|
+
@mutex.unlock
|
29
|
+
@env[RACK_MULTITHREAD] = @old_rack_multithread
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
data/lib/rack/logger.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
module Rack
|
@@ -8,10 +10,10 @@ module Rack
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def call(env)
|
11
|
-
logger = ::Logger.new(env[
|
13
|
+
logger = ::Logger.new(env[RACK_ERRORS])
|
12
14
|
logger.level = @level
|
13
15
|
|
14
|
-
env[
|
16
|
+
env[RACK_LOGGER] = logger
|
15
17
|
@app.call(env)
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Rack::MediaType parse media type and parameters out of content_type string
|
5
|
+
|
6
|
+
class MediaType
|
7
|
+
SPLIT_PATTERN = %r{\s*[;,]\s*}
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
11
|
+
# without any media type parameters. e.g., when CONTENT_TYPE is
|
12
|
+
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
13
|
+
#
|
14
|
+
# For more information on the use of media types in HTTP, see:
|
15
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
16
|
+
def type(content_type)
|
17
|
+
return nil unless content_type
|
18
|
+
content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
|
19
|
+
end
|
20
|
+
|
21
|
+
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
22
|
+
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
23
|
+
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
24
|
+
# this method responds with the following Hash:
|
25
|
+
# { 'charset' => 'utf-8' }
|
26
|
+
def params(content_type)
|
27
|
+
return {} if content_type.nil?
|
28
|
+
|
29
|
+
content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
|
30
|
+
k, v = s.split('=', 2)
|
31
|
+
|
32
|
+
hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def strip_doublequotes(str)
|
39
|
+
(str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MethodOverride
|
3
|
-
HTTP_METHODS = %w
|
5
|
+
HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
|
4
6
|
|
5
|
-
METHOD_OVERRIDE_PARAM_KEY = "_method"
|
6
|
-
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
|
7
|
-
ALLOWED_METHODS = [
|
7
|
+
METHOD_OVERRIDE_PARAM_KEY = "_method"
|
8
|
+
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
|
9
|
+
ALLOWED_METHODS = %w[POST]
|
8
10
|
|
9
11
|
def initialize(app)
|
10
12
|
@app = app
|
@@ -14,7 +16,7 @@ module Rack
|
|
14
16
|
if allowed_methods.include?(env[REQUEST_METHOD])
|
15
17
|
method = method_override(env)
|
16
18
|
if HTTP_METHODS.include?(method)
|
17
|
-
env[
|
19
|
+
env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD]
|
18
20
|
env[REQUEST_METHOD] = method
|
19
21
|
end
|
20
22
|
end
|
@@ -29,7 +31,7 @@ module Rack
|
|
29
31
|
begin
|
30
32
|
method.to_s.upcase
|
31
33
|
rescue ArgumentError
|
32
|
-
env[
|
34
|
+
env[RACK_ERRORS].puts "Invalid string for method"
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -42,9 +44,9 @@ module Rack
|
|
42
44
|
def method_override_param(req)
|
43
45
|
req.POST[METHOD_OVERRIDE_PARAM_KEY]
|
44
46
|
rescue Utils::InvalidParameterError, Utils::ParameterTypeError
|
45
|
-
req.
|
47
|
+
req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
|
46
48
|
rescue EOFError
|
47
|
-
req.
|
49
|
+
req.get_header(RACK_ERRORS).puts "Bad request content body"
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|