rack 1.0.1 → 2.2.21
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +829 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +355 -0
- data/Rakefile +64 -98
- data/SPEC.rdoc +292 -0
- data/bin/rackup +3 -174
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +164 -111
- data/contrib/rdoc.css +412 -0
- 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 +7 -5
- data/lib/rack/auth/abstract/request.rb +12 -2
- data/lib/rack/auth/basic.rb +6 -4
- data/lib/rack/auth/digest/md5.rb +20 -13
- data/lib/rack/auth/digest/nonce.rb +6 -4
- data/lib/rack/auth/digest/params.rb +11 -12
- data/lib/rack/auth/digest/request.rb +15 -12
- data/lib/rack/body_proxy.rb +45 -0
- data/lib/rack/builder.rb +213 -19
- data/lib/rack/cascade.rb +50 -18
- data/lib/rack/chunked.rb +95 -27
- data/lib/rack/common_logger.rb +84 -0
- data/lib/rack/conditional_get.rb +83 -0
- data/lib/rack/config.rb +22 -0
- data/lib/rack/content_length.rb +19 -10
- data/lib/rack/content_type.rb +12 -5
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +90 -42
- data/lib/rack/directory.rb +118 -72
- data/lib/rack/etag.rb +77 -0
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -85
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +22 -24
- data/lib/rack/handler/fastcgi.rb +34 -24
- data/lib/rack/handler/lsws.rb +16 -15
- data/lib/rack/handler/scgi.rb +38 -29
- data/lib/rack/handler/thin.rb +23 -7
- data/lib/rack/handler/webrick.rb +95 -37
- data/lib/rack/handler.rb +49 -14
- data/lib/rack/head.rb +19 -13
- data/lib/rack/lint.rb +466 -217
- data/lib/rack/lobster.rb +19 -14
- data/lib/rack/lock.rb +24 -8
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/media_type.rb +53 -0
- data/lib/rack/method_override.rb +52 -0
- data/lib/rack/mime.rb +652 -171
- data/lib/rack/mock.rb +203 -64
- data/lib/rack/multipart/generator.rb +97 -0
- data/lib/rack/multipart/parser.rb +440 -0
- data/lib/rack/multipart/uploaded_file.rb +41 -0
- data/lib/rack/multipart.rb +64 -0
- data/lib/rack/null_logger.rb +39 -0
- data/lib/rack/query_parser.rb +266 -0
- data/lib/rack/recursive.rb +18 -11
- data/lib/rack/reloader.rb +13 -5
- data/lib/rack/request.rb +607 -191
- data/lib/rack/response.rb +245 -110
- data/lib/rack/rewindable_input.rb +15 -21
- data/lib/rack/runtime.rb +34 -0
- data/lib/rack/sendfile.rb +192 -0
- data/lib/rack/server.rb +466 -0
- data/lib/rack/session/abstract/id.rb +449 -68
- data/lib/rack/session/cookie.rb +159 -47
- data/lib/rack/session/memcache.rb +4 -103
- data/lib/rack/session/pool.rb +39 -49
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
- data/lib/rack/static.rb +164 -14
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +60 -18
- data/lib/rack/utils.rb +508 -277
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +85 -34
- data/rack.gemspec +43 -51
- metadata +119 -207
- data/KNOWN-ISSUES +0 -18
- data/RDOX +0 -428
- data/README +0 -364
- data/SPEC +0 -164
- data/lib/rack/adapter/camping.rb +0 -22
- data/lib/rack/auth/openid.rb +0 -480
- data/lib/rack/commonlogger.rb +0 -61
- data/lib/rack/conditionalget.rb +0 -47
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -87
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/methodoverride.rb +0 -27
- data/lib/rack/showexceptions.rb +0 -349
- data/test/cgi/lighttpd.conf +0 -20
- data/test/cgi/test +0 -9
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -7
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +0 -10
- data/test/multipart/ie +0 -6
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -10
- data/test/spec_rack_auth_basic.rb +0 -73
- data/test/spec_rack_auth_digest.rb +0 -226
- data/test/spec_rack_auth_openid.rb +0 -84
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -51
- data/test/spec_rack_cascade.rb +0 -50
- data/test/spec_rack_cgi.rb +0 -89
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -32
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_directory.rb +0 -61
- data/test/spec_rack_fastcgi.rb +0 -89
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lint.rb +0 -521
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_mock.rb +0 -157
- data/test/spec_rack_mongrel.rb +0 -189
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -504
- data/test/spec_rack_response.rb +0 -218
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_session_cookie.rb +0 -82
- data/test/spec_rack_session_memcache.rb +0 -240
- data/test/spec_rack_session_pool.rb +0 -172
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_thin.rb +0 -91
- data/test/spec_rack_urlmap.rb +0 -185
- data/test/spec_rack_utils.rb +0 -387
- data/test/spec_rack_webrick.rb +0 -130
- data/test/testrequest.rb +0 -57
- 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,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
2
4
|
|
|
3
5
|
module Rack
|
|
4
6
|
# Rack::Lint validates your application and the requests and
|
|
@@ -7,14 +9,15 @@ module Rack
|
|
|
7
9
|
class Lint
|
|
8
10
|
def initialize(app)
|
|
9
11
|
@app = app
|
|
12
|
+
@content_length = nil
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
# :stopdoc:
|
|
13
16
|
|
|
14
17
|
class LintError < RuntimeError; end
|
|
15
18
|
module Assertion
|
|
16
|
-
def assert(message
|
|
17
|
-
unless
|
|
19
|
+
def assert(message)
|
|
20
|
+
unless yield
|
|
18
21
|
raise LintError, message
|
|
19
22
|
end
|
|
20
23
|
end
|
|
@@ -29,40 +32,50 @@ module Rack
|
|
|
29
32
|
|
|
30
33
|
## = Rack applications
|
|
31
34
|
|
|
32
|
-
## A Rack application is
|
|
35
|
+
## A Rack application is a Ruby object (not a class) that
|
|
33
36
|
## responds to +call+.
|
|
34
|
-
def call(env=nil)
|
|
37
|
+
def call(env = nil)
|
|
35
38
|
dup._call(env)
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def _call(env)
|
|
39
42
|
## It takes exactly one argument, the *environment*
|
|
40
|
-
|
|
43
|
+
raise LintError, "No env given" unless env
|
|
41
44
|
check_env env
|
|
42
45
|
|
|
43
|
-
env[
|
|
44
|
-
env[
|
|
46
|
+
env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
|
|
47
|
+
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
|
|
45
48
|
|
|
46
49
|
## and returns an Array of exactly three values:
|
|
47
|
-
|
|
50
|
+
ary = @app.call(env)
|
|
51
|
+
raise LintError, "response is not an Array, but #{ary.class}" unless ary.kind_of? Array
|
|
52
|
+
raise LintError, "response array has #{ary.size} elements instead of 3" unless ary.size == 3
|
|
53
|
+
|
|
54
|
+
status, headers, @body = ary
|
|
48
55
|
## The *status*,
|
|
49
56
|
check_status status
|
|
50
57
|
## the *headers*,
|
|
51
58
|
check_headers headers
|
|
59
|
+
|
|
60
|
+
hijack_proc = check_hijack_response headers, env
|
|
61
|
+
if hijack_proc && headers.is_a?(Hash)
|
|
62
|
+
headers[RACK_HIJACK] = hijack_proc
|
|
63
|
+
end
|
|
64
|
+
|
|
52
65
|
## and the *body*.
|
|
53
66
|
check_content_type status, headers
|
|
54
|
-
check_content_length status, headers
|
|
67
|
+
check_content_length status, headers
|
|
68
|
+
@head_request = env[REQUEST_METHOD] == HEAD
|
|
55
69
|
[status, headers, self]
|
|
56
70
|
end
|
|
57
71
|
|
|
58
72
|
## == The Environment
|
|
59
73
|
def check_env(env)
|
|
60
|
-
## The environment must be an
|
|
61
|
-
##
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
74
|
+
## The environment must be an unfrozen instance of Hash that includes
|
|
75
|
+
## CGI-like headers. The application is free to modify the
|
|
76
|
+
## environment.
|
|
77
|
+
raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash
|
|
78
|
+
raise LintError, "env should not be frozen, but is" if env.frozen?
|
|
66
79
|
|
|
67
80
|
##
|
|
68
81
|
## The environment is required to include these variables
|
|
@@ -89,14 +102,26 @@ module Rack
|
|
|
89
102
|
## empty string, if the request URL targets
|
|
90
103
|
## the application root and does not have a
|
|
91
104
|
## trailing slash. This value may be
|
|
92
|
-
## percent-encoded when
|
|
105
|
+
## percent-encoded when originating from
|
|
93
106
|
## a URL.
|
|
94
107
|
|
|
95
108
|
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
|
|
96
109
|
## follows the <tt>?</tt>, if any. May be
|
|
97
110
|
## empty, but is always required!
|
|
98
111
|
|
|
99
|
-
## <tt>SERVER_NAME</tt
|
|
112
|
+
## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
|
113
|
+
## <tt>PATH_INFO</tt>, these variables can be
|
|
114
|
+
## used to complete the URL. Note, however,
|
|
115
|
+
## that <tt>HTTP_HOST</tt>, if present,
|
|
116
|
+
## should be used in preference to
|
|
117
|
+
## <tt>SERVER_NAME</tt> for reconstructing
|
|
118
|
+
## the request URL.
|
|
119
|
+
## <tt>SERVER_NAME</tt> can never be an empty
|
|
120
|
+
## string, and so is always required.
|
|
121
|
+
|
|
122
|
+
## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
|
123
|
+
## server is running on. Should be specified if
|
|
124
|
+
## the server is running on a non-standard port.
|
|
100
125
|
|
|
101
126
|
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
|
102
127
|
## client-supplied HTTP request
|
|
@@ -106,46 +131,131 @@ module Rack
|
|
|
106
131
|
## variables should correspond with
|
|
107
132
|
## the presence or absence of the
|
|
108
133
|
## appropriate HTTP header in the
|
|
109
|
-
## request.
|
|
134
|
+
## request. See
|
|
135
|
+
## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
|
|
136
|
+
## for specific behavior.
|
|
110
137
|
|
|
111
138
|
## In addition to this, the Rack environment must include these
|
|
112
139
|
## Rack-specific variables:
|
|
113
140
|
|
|
114
|
-
## <tt>rack.version</tt>:: The Array
|
|
115
|
-
##
|
|
141
|
+
## <tt>rack.version</tt>:: The Array representing this version of Rack
|
|
142
|
+
## See Rack::VERSION, that corresponds to
|
|
143
|
+
## the version of this SPEC.
|
|
144
|
+
|
|
145
|
+
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
|
|
146
|
+
## request URL.
|
|
147
|
+
|
|
116
148
|
## <tt>rack.input</tt>:: See below, the input stream.
|
|
149
|
+
|
|
117
150
|
## <tt>rack.errors</tt>:: See below, the error stream.
|
|
118
|
-
|
|
119
|
-
## <tt>rack.
|
|
120
|
-
##
|
|
121
|
-
##
|
|
151
|
+
|
|
152
|
+
## <tt>rack.multithread</tt>:: true if the application object may be
|
|
153
|
+
## simultaneously invoked by another thread
|
|
154
|
+
## in the same process, false otherwise.
|
|
155
|
+
|
|
156
|
+
## <tt>rack.multiprocess</tt>:: true if an equivalent application object
|
|
157
|
+
## may be simultaneously invoked by another
|
|
158
|
+
## process, false otherwise.
|
|
159
|
+
|
|
160
|
+
## <tt>rack.run_once</tt>:: true if the server expects
|
|
161
|
+
## (but does not guarantee!) that the
|
|
162
|
+
## application will only be invoked this one
|
|
163
|
+
## time during the life of its containing
|
|
164
|
+
## process. Normally, this will only be true
|
|
165
|
+
## for a server based on CGI
|
|
166
|
+
## (or something similar).
|
|
167
|
+
|
|
168
|
+
## <tt>rack.hijack?</tt>:: present and true if the server supports
|
|
169
|
+
## connection hijacking. See below, hijacking.
|
|
170
|
+
|
|
171
|
+
## <tt>rack.hijack</tt>:: an object responding to #call that must be
|
|
172
|
+
## called at least once before using
|
|
173
|
+
## rack.hijack_io.
|
|
174
|
+
## It is recommended #call return rack.hijack_io
|
|
175
|
+
## as well as setting it in env if necessary.
|
|
176
|
+
|
|
177
|
+
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
|
|
178
|
+
## has received #call, this will contain
|
|
179
|
+
## an object resembling an IO. See hijacking.
|
|
122
180
|
|
|
123
181
|
## Additional environment specifications have approved to
|
|
124
182
|
## standardized middleware APIs. None of these are required to
|
|
125
183
|
## be implemented by the server.
|
|
126
184
|
|
|
127
|
-
## <tt>rack.session</tt>:: A hash like interface for storing
|
|
185
|
+
## <tt>rack.session</tt>:: A hash like interface for storing
|
|
186
|
+
## request session data.
|
|
128
187
|
## The store must implement:
|
|
129
|
-
if session = env[
|
|
188
|
+
if session = env[RACK_SESSION]
|
|
130
189
|
## store(key, value) (aliased as []=);
|
|
131
|
-
|
|
132
|
-
session.
|
|
133
|
-
|
|
190
|
+
unless session.respond_to?(:store) && session.respond_to?(:[]=)
|
|
191
|
+
raise LintError, "session #{session.inspect} must respond to store and []="
|
|
192
|
+
end
|
|
134
193
|
|
|
135
194
|
## fetch(key, default = nil) (aliased as []);
|
|
136
|
-
|
|
137
|
-
session.
|
|
138
|
-
|
|
195
|
+
unless session.respond_to?(:fetch) && session.respond_to?(:[])
|
|
196
|
+
raise LintError, "session #{session.inspect} must respond to fetch and []"
|
|
197
|
+
end
|
|
139
198
|
|
|
140
199
|
## delete(key);
|
|
141
|
-
|
|
142
|
-
session.
|
|
143
|
-
|
|
200
|
+
unless session.respond_to?(:delete)
|
|
201
|
+
raise LintError, "session #{session.inspect} must respond to delete"
|
|
202
|
+
end
|
|
144
203
|
|
|
145
204
|
## clear;
|
|
146
|
-
|
|
147
|
-
session.
|
|
148
|
-
|
|
205
|
+
unless session.respond_to?(:clear)
|
|
206
|
+
raise LintError, "session #{session.inspect} must respond to clear"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
## to_hash (returning unfrozen Hash instance);
|
|
210
|
+
unless session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
|
211
|
+
raise LintError, "session #{session.inspect} must respond to to_hash and return unfrozen Hash instance"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
|
216
|
+
## The object must implement:
|
|
217
|
+
if logger = env[RACK_LOGGER]
|
|
218
|
+
## info(message, &block)
|
|
219
|
+
unless logger.respond_to?(:info)
|
|
220
|
+
raise LintError, "logger #{logger.inspect} must respond to info"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
## debug(message, &block)
|
|
224
|
+
unless logger.respond_to?(:debug)
|
|
225
|
+
raise LintError, "logger #{logger.inspect} must respond to debug"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
## warn(message, &block)
|
|
229
|
+
unless logger.respond_to?(:warn)
|
|
230
|
+
raise LintError, "logger #{logger.inspect} must respond to warn"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
## error(message, &block)
|
|
234
|
+
unless logger.respond_to?(:error)
|
|
235
|
+
raise LintError, "logger #{logger.inspect} must respond to error"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
## fatal(message, &block)
|
|
239
|
+
unless logger.respond_to?(:fatal)
|
|
240
|
+
raise LintError, "logger #{logger.inspect} must respond to fatal"
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
|
|
245
|
+
if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
|
|
246
|
+
unless bufsize.is_a?(Integer) && bufsize > 0
|
|
247
|
+
raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
## <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.
|
|
252
|
+
if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
|
|
253
|
+
raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call)
|
|
254
|
+
env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
|
|
255
|
+
io = tempfile_factory.call(filename, content_type)
|
|
256
|
+
raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<)
|
|
257
|
+
io
|
|
258
|
+
end
|
|
149
259
|
end
|
|
150
260
|
|
|
151
261
|
## The server or the application can store their own data in the
|
|
@@ -155,79 +265,97 @@ module Rack
|
|
|
155
265
|
## accepted specifications and must not be used otherwise.
|
|
156
266
|
##
|
|
157
267
|
|
|
158
|
-
%w[REQUEST_METHOD SERVER_NAME
|
|
159
|
-
QUERY_STRING
|
|
268
|
+
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
|
160
269
|
rack.version rack.input rack.errors
|
|
161
270
|
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
|
162
|
-
|
|
271
|
+
raise LintError, "env missing required key #{header}" unless env.include? header
|
|
163
272
|
}
|
|
164
273
|
|
|
274
|
+
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
|
275
|
+
server_port = env["SERVER_PORT"]
|
|
276
|
+
unless server_port.nil? || (Integer(server_port) rescue false)
|
|
277
|
+
raise LintError, "env[SERVER_PORT] is not an Integer"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
|
281
|
+
unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false)
|
|
282
|
+
raise LintError, "#{env[SERVER_NAME]} must be a valid authority"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
|
286
|
+
unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false)
|
|
287
|
+
raise LintError, "#{env[HTTP_HOST]} must be a valid authority"
|
|
288
|
+
end
|
|
289
|
+
|
|
165
290
|
## The environment must not contain the keys
|
|
166
291
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
|
167
292
|
## (use the versions without <tt>HTTP_</tt>).
|
|
168
293
|
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
294
|
+
if env.include? header
|
|
295
|
+
raise LintError, "env contains #{header}, must use #{header[5, -1]}"
|
|
296
|
+
end
|
|
172
297
|
}
|
|
173
298
|
|
|
174
299
|
## The CGI keys (named without a period) must have String values.
|
|
300
|
+
## If the string values for CGI keys contain non-ASCII characters,
|
|
301
|
+
## they should use ASCII-8BIT encoding.
|
|
175
302
|
env.each { |key, value|
|
|
176
303
|
next if key.include? "." # Skip extensions
|
|
177
|
-
|
|
178
|
-
value.
|
|
179
|
-
|
|
304
|
+
unless value.kind_of? String
|
|
305
|
+
raise LintError, "env variable #{key} has non-string value #{value.inspect}"
|
|
306
|
+
end
|
|
307
|
+
next if value.encoding == Encoding::ASCII_8BIT
|
|
308
|
+
unless value.b !~ /[\x80-\xff]/n
|
|
309
|
+
raise LintError, "env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}"
|
|
310
|
+
end
|
|
180
311
|
}
|
|
181
312
|
|
|
182
|
-
##
|
|
183
313
|
## There are the following restrictions:
|
|
184
314
|
|
|
185
315
|
## * <tt>rack.version</tt> must be an array of Integers.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
316
|
+
unless env[RACK_VERSION].kind_of? Array
|
|
317
|
+
raise LintError, "rack.version must be an Array, was #{env[RACK_VERSION].class}"
|
|
318
|
+
end
|
|
189
319
|
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
320
|
+
unless %w[http https].include?(env[RACK_URL_SCHEME])
|
|
321
|
+
raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
|
|
322
|
+
end
|
|
193
323
|
|
|
194
324
|
## * There must be a valid input stream in <tt>rack.input</tt>.
|
|
195
|
-
check_input env[
|
|
325
|
+
check_input env[RACK_INPUT]
|
|
196
326
|
## * There must be a valid error stream in <tt>rack.errors</tt>.
|
|
197
|
-
check_error env[
|
|
327
|
+
check_error env[RACK_ERRORS]
|
|
328
|
+
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
|
|
329
|
+
check_hijack env
|
|
198
330
|
|
|
199
331
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
332
|
+
unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
|
333
|
+
raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}"
|
|
334
|
+
end
|
|
203
335
|
|
|
204
336
|
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
env["SCRIPT_NAME"] =~ /\A\//
|
|
209
|
-
}
|
|
337
|
+
if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
|
|
338
|
+
raise LintError, "SCRIPT_NAME must start with /"
|
|
339
|
+
end
|
|
210
340
|
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
env["PATH_INFO"] =~ /\A\//
|
|
215
|
-
}
|
|
341
|
+
if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
|
|
342
|
+
raise LintError, "PATH_INFO must start with /"
|
|
343
|
+
end
|
|
216
344
|
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
345
|
+
if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
|
|
346
|
+
raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
|
|
347
|
+
end
|
|
220
348
|
|
|
221
349
|
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
|
|
222
350
|
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
|
|
223
351
|
## <tt>SCRIPT_NAME</tt> is empty.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
352
|
+
unless env[SCRIPT_NAME] || env[PATH_INFO]
|
|
353
|
+
raise LintError, "One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)"
|
|
354
|
+
end
|
|
227
355
|
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
356
|
+
unless env[SCRIPT_NAME] != "/"
|
|
357
|
+
raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
|
|
358
|
+
end
|
|
231
359
|
end
|
|
232
360
|
|
|
233
361
|
## === The Input Stream
|
|
@@ -237,18 +365,18 @@ module Rack
|
|
|
237
365
|
def check_input(input)
|
|
238
366
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
|
239
367
|
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
|
240
|
-
|
|
241
|
-
input
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
input
|
|
245
|
-
|
|
246
|
-
|
|
368
|
+
if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
|
|
369
|
+
raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
|
|
370
|
+
end
|
|
371
|
+
if input.respond_to?(:binmode?) && !input.binmode?
|
|
372
|
+
raise LintError, "rack.input #{input} is not opened in binary mode"
|
|
373
|
+
end
|
|
374
|
+
|
|
247
375
|
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
|
|
248
376
|
[:gets, :each, :read, :rewind].each { |method|
|
|
249
|
-
|
|
250
|
-
input
|
|
251
|
-
|
|
377
|
+
unless input.respond_to? method
|
|
378
|
+
raise LintError, "rack.input #{input} does not respond to ##{method}"
|
|
379
|
+
end
|
|
252
380
|
}
|
|
253
381
|
end
|
|
254
382
|
|
|
@@ -259,93 +387,95 @@ module Rack
|
|
|
259
387
|
@input = input
|
|
260
388
|
end
|
|
261
389
|
|
|
262
|
-
def size
|
|
263
|
-
@input.size
|
|
264
|
-
end
|
|
265
|
-
|
|
266
390
|
## * +gets+ must be called without arguments and return a string,
|
|
267
391
|
## or +nil+ on EOF.
|
|
268
392
|
def gets(*args)
|
|
269
|
-
|
|
393
|
+
raise LintError, "rack.input#gets called with arguments" unless args.size == 0
|
|
270
394
|
v = @input.gets
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
395
|
+
unless v.nil? or v.kind_of? String
|
|
396
|
+
raise LintError, "rack.input#gets didn't return a String"
|
|
397
|
+
end
|
|
274
398
|
v
|
|
275
399
|
end
|
|
276
400
|
|
|
277
|
-
## * +read+ behaves like IO#read.
|
|
278
|
-
##
|
|
279
|
-
##
|
|
280
|
-
##
|
|
281
|
-
##
|
|
282
|
-
##
|
|
283
|
-
##
|
|
284
|
-
##
|
|
285
|
-
##
|
|
401
|
+
## * +read+ behaves like IO#read.
|
|
402
|
+
## Its signature is <tt>read([length, [buffer]])</tt>.
|
|
403
|
+
##
|
|
404
|
+
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
|
|
405
|
+
## and +buffer+ must be a String and may not be nil.
|
|
406
|
+
##
|
|
407
|
+
## If +length+ is given and not nil, then this method reads at most
|
|
408
|
+
## +length+ bytes from the input stream.
|
|
409
|
+
##
|
|
410
|
+
## If +length+ is not given or nil, then this method reads
|
|
411
|
+
## all data until EOF.
|
|
412
|
+
##
|
|
413
|
+
## When EOF is reached, this method returns nil if +length+ is given
|
|
414
|
+
## and not nil, or "" if +length+ is not given or is nil.
|
|
415
|
+
##
|
|
416
|
+
## If +buffer+ is given, then the read data will be placed
|
|
417
|
+
## into +buffer+ instead of a newly created String object.
|
|
286
418
|
def read(*args)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
419
|
+
unless args.size <= 2
|
|
420
|
+
raise LintError, "rack.input#read called with too many arguments"
|
|
421
|
+
end
|
|
290
422
|
if args.size >= 1
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
423
|
+
unless args.first.kind_of?(Integer) || args.first.nil?
|
|
424
|
+
raise LintError, "rack.input#read called with non-integer and non-nil length"
|
|
425
|
+
end
|
|
426
|
+
unless args.first.nil? || args.first >= 0
|
|
427
|
+
raise LintError, "rack.input#read called with a negative length"
|
|
428
|
+
end
|
|
297
429
|
end
|
|
298
430
|
if args.size >= 2
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
431
|
+
unless args[1].kind_of?(String)
|
|
432
|
+
raise LintError, "rack.input#read called with non-String buffer"
|
|
433
|
+
end
|
|
302
434
|
end
|
|
303
|
-
|
|
435
|
+
|
|
304
436
|
v = @input.read(*args)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
437
|
+
|
|
438
|
+
unless v.nil? or v.kind_of? String
|
|
439
|
+
raise LintError, "rack.input#read didn't return nil or a String"
|
|
440
|
+
end
|
|
309
441
|
if args[0].nil?
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
442
|
+
unless !v.nil?
|
|
443
|
+
raise LintError, "rack.input#read(nil) returned nil on EOF"
|
|
444
|
+
end
|
|
313
445
|
end
|
|
314
|
-
|
|
446
|
+
|
|
315
447
|
v
|
|
316
448
|
end
|
|
317
449
|
|
|
318
450
|
## * +each+ must be called without arguments and only yield Strings.
|
|
319
451
|
def each(*args)
|
|
320
|
-
|
|
452
|
+
raise LintError, "rack.input#each called with arguments" unless args.size == 0
|
|
321
453
|
@input.each { |line|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
454
|
+
unless line.kind_of? String
|
|
455
|
+
raise LintError, "rack.input#each didn't yield a String"
|
|
456
|
+
end
|
|
325
457
|
yield line
|
|
326
458
|
}
|
|
327
459
|
end
|
|
328
|
-
|
|
460
|
+
|
|
329
461
|
## * +rewind+ must be called without arguments. It rewinds the input
|
|
330
462
|
## stream back to the beginning. It must not raise Errno::ESPIPE:
|
|
331
463
|
## that is, it may not be a pipe or a socket. Therefore, handler
|
|
332
464
|
## developers must buffer the input data into some rewindable object
|
|
333
465
|
## if the underlying input stream is not rewindable.
|
|
334
466
|
def rewind(*args)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
end
|
|
343
|
-
}
|
|
467
|
+
raise LintError, "rack.input#rewind called with arguments" unless args.size == 0
|
|
468
|
+
begin
|
|
469
|
+
@input.rewind
|
|
470
|
+
true
|
|
471
|
+
rescue Errno::ESPIPE
|
|
472
|
+
raise LintError, "rack.input#rewind raised Errno::ESPIPE"
|
|
473
|
+
end
|
|
344
474
|
end
|
|
345
475
|
|
|
346
476
|
## * +close+ must never be called on the input stream.
|
|
347
477
|
def close(*args)
|
|
348
|
-
|
|
478
|
+
raise LintError, "rack.input#close must not be called"
|
|
349
479
|
end
|
|
350
480
|
end
|
|
351
481
|
|
|
@@ -353,9 +483,9 @@ module Rack
|
|
|
353
483
|
def check_error(error)
|
|
354
484
|
## The error stream must respond to +puts+, +write+ and +flush+.
|
|
355
485
|
[:puts, :write, :flush].each { |method|
|
|
356
|
-
|
|
357
|
-
error
|
|
358
|
-
|
|
486
|
+
unless error.respond_to? method
|
|
487
|
+
raise LintError, "rack.error #{error} does not respond to ##{method}"
|
|
488
|
+
end
|
|
359
489
|
}
|
|
360
490
|
end
|
|
361
491
|
|
|
@@ -373,7 +503,7 @@ module Rack
|
|
|
373
503
|
|
|
374
504
|
## * +write+ must be called with a single argument that is a String.
|
|
375
505
|
def write(str)
|
|
376
|
-
|
|
506
|
+
raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
|
|
377
507
|
@error.write str
|
|
378
508
|
end
|
|
379
509
|
|
|
@@ -385,50 +515,177 @@ module Rack
|
|
|
385
515
|
|
|
386
516
|
## * +close+ must never be called on the error stream.
|
|
387
517
|
def close(*args)
|
|
388
|
-
|
|
518
|
+
raise LintError, "rack.errors#close must not be called"
|
|
389
519
|
end
|
|
390
520
|
end
|
|
391
521
|
|
|
522
|
+
class HijackWrapper
|
|
523
|
+
include Assertion
|
|
524
|
+
extend Forwardable
|
|
525
|
+
|
|
526
|
+
REQUIRED_METHODS = [
|
|
527
|
+
:read, :write, :read_nonblock, :write_nonblock, :flush, :close,
|
|
528
|
+
:close_read, :close_write, :closed?
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
def_delegators :@io, *REQUIRED_METHODS
|
|
532
|
+
|
|
533
|
+
def initialize(io)
|
|
534
|
+
@io = io
|
|
535
|
+
REQUIRED_METHODS.each do |meth|
|
|
536
|
+
raise LintError, "rack.hijack_io must respond to #{meth}" unless io.respond_to? meth
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
## === Hijacking
|
|
542
|
+
#
|
|
543
|
+
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
|
|
544
|
+
# should not be removed. The whitespace creates paragraphs in the RDoc
|
|
545
|
+
# output.
|
|
546
|
+
#
|
|
547
|
+
## ==== Request (before status)
|
|
548
|
+
def check_hijack(env)
|
|
549
|
+
if env[RACK_IS_HIJACK]
|
|
550
|
+
## If rack.hijack? is true then rack.hijack must respond to #call.
|
|
551
|
+
original_hijack = env[RACK_HIJACK]
|
|
552
|
+
raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
|
|
553
|
+
env[RACK_HIJACK] = proc do
|
|
554
|
+
## rack.hijack must return the io that will also be assigned (or is
|
|
555
|
+
## already present, in rack.hijack_io.
|
|
556
|
+
io = original_hijack.call
|
|
557
|
+
HijackWrapper.new(io)
|
|
558
|
+
##
|
|
559
|
+
## rack.hijack_io must respond to:
|
|
560
|
+
## <tt>read, write, read_nonblock, write_nonblock, flush, close,
|
|
561
|
+
## close_read, close_write, closed?</tt>
|
|
562
|
+
##
|
|
563
|
+
## The semantics of these IO methods must be a best effort match to
|
|
564
|
+
## those of a normal ruby IO or Socket object, using standard
|
|
565
|
+
## arguments and raising standard exceptions. Servers are encouraged
|
|
566
|
+
## to simply pass on real IO objects, although it is recognized that
|
|
567
|
+
## this approach is not directly compatible with SPDY and HTTP 2.0.
|
|
568
|
+
##
|
|
569
|
+
## IO provided in rack.hijack_io should preference the
|
|
570
|
+
## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
|
|
571
|
+
##
|
|
572
|
+
## There is a deliberate lack of full specification around
|
|
573
|
+
## rack.hijack_io, as semantics will change from server to server.
|
|
574
|
+
## Users are encouraged to utilize this API with a knowledge of their
|
|
575
|
+
## server choice, and servers may extend the functionality of
|
|
576
|
+
## hijack_io to provide additional features to users. The purpose of
|
|
577
|
+
## rack.hijack is for Rack to "get out of the way", as such, Rack only
|
|
578
|
+
## provides the minimum of specification and support.
|
|
579
|
+
env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
|
|
580
|
+
io
|
|
581
|
+
end
|
|
582
|
+
else
|
|
583
|
+
##
|
|
584
|
+
## If rack.hijack? is false, then rack.hijack should not be set.
|
|
585
|
+
raise LintError, "rack.hijack? is false, but rack.hijack is present" unless env[RACK_HIJACK].nil?
|
|
586
|
+
##
|
|
587
|
+
## If rack.hijack? is false, then rack.hijack_io should not be set.
|
|
588
|
+
raise LintError, "rack.hijack? is false, but rack.hijack_io is present" unless env[RACK_HIJACK_IO].nil?
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
## ==== Response (after headers)
|
|
593
|
+
## It is also possible to hijack a response after the status and headers
|
|
594
|
+
## have been sent.
|
|
595
|
+
def check_hijack_response(headers, env)
|
|
596
|
+
|
|
597
|
+
# this check uses headers like a hash, but the spec only requires
|
|
598
|
+
# headers respond to #each
|
|
599
|
+
headers = Rack::Utils::HeaderHash[headers]
|
|
600
|
+
|
|
601
|
+
## In order to do this, an application may set the special header
|
|
602
|
+
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
|
603
|
+
## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
|
|
604
|
+
## protocol.
|
|
605
|
+
##
|
|
606
|
+
## After the headers have been sent, and this hijack callback has been
|
|
607
|
+
## called, the application is now responsible for the remaining lifecycle
|
|
608
|
+
## of the IO. The application is also responsible for maintaining HTTP
|
|
609
|
+
## semantics. Of specific note, in almost all cases in the current SPEC,
|
|
610
|
+
## applications will have wanted to specify the header Connection:close in
|
|
611
|
+
## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
|
|
612
|
+
## returning hijacked sockets to the web server. For that purpose, use the
|
|
613
|
+
## body streaming API instead (progressively yielding strings via each).
|
|
614
|
+
##
|
|
615
|
+
## Servers must ignore the <tt>body</tt> part of the response tuple when
|
|
616
|
+
## the <tt>rack.hijack</tt> response API is in use.
|
|
617
|
+
|
|
618
|
+
if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
|
|
619
|
+
unless headers[RACK_HIJACK].respond_to? :call
|
|
620
|
+
raise LintError, 'rack.hijack header must respond to #call'
|
|
621
|
+
end
|
|
622
|
+
original_hijack = headers[RACK_HIJACK]
|
|
623
|
+
proc do |io|
|
|
624
|
+
original_hijack.call HijackWrapper.new(io)
|
|
625
|
+
end
|
|
626
|
+
else
|
|
627
|
+
##
|
|
628
|
+
## The special response header <tt>rack.hijack</tt> must only be set
|
|
629
|
+
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
|
|
630
|
+
unless headers[RACK_HIJACK].nil?
|
|
631
|
+
raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
nil
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
## ==== Conventions
|
|
638
|
+
## * Middleware should not use hijack unless it is handling the whole
|
|
639
|
+
## response.
|
|
640
|
+
## * Middleware may wrap the IO object for the response pattern.
|
|
641
|
+
## * Middleware should not wrap the IO object for the request pattern. The
|
|
642
|
+
## request pattern is intended to provide the hijacker with "raw tcp".
|
|
643
|
+
|
|
392
644
|
## == The Response
|
|
393
645
|
|
|
394
646
|
## === The Status
|
|
395
647
|
def check_status(status)
|
|
396
648
|
## This is an HTTP status. When parsed as integer (+to_i+), it must be
|
|
397
649
|
## greater than or equal to 100.
|
|
398
|
-
|
|
650
|
+
unless status.to_i >= 100
|
|
651
|
+
raise LintError, "Status must be >=100 seen as integer"
|
|
652
|
+
end
|
|
399
653
|
end
|
|
400
654
|
|
|
401
655
|
## === The Headers
|
|
402
656
|
def check_headers(header)
|
|
403
657
|
## The header must respond to +each+, and yield values of key and value.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
658
|
+
unless header.respond_to? :each
|
|
659
|
+
raise LintError, "headers object should respond to #each, but doesn't (got #{header.class} as headers)"
|
|
660
|
+
end
|
|
661
|
+
|
|
407
662
|
header.each { |key, value|
|
|
408
663
|
## The header keys must be Strings.
|
|
409
|
-
|
|
410
|
-
key.
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
##
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
##
|
|
420
|
-
|
|
664
|
+
unless key.kind_of? String
|
|
665
|
+
raise LintError, "header key must be a string, was #{key.class}"
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
## Special headers starting "rack." are for communicating with the
|
|
669
|
+
## server, and must not be sent back to the client.
|
|
670
|
+
next if key =~ /^rack\..+$/
|
|
671
|
+
|
|
672
|
+
## The header must not contain a +Status+ key.
|
|
673
|
+
raise LintError, "header must not contain Status" if key.downcase == "status"
|
|
674
|
+
## The header must conform to RFC7230 token specification, i.e. cannot
|
|
675
|
+
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
|
|
676
|
+
raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
|
|
421
677
|
|
|
422
678
|
## The values of the header must be Strings,
|
|
423
|
-
|
|
424
|
-
"'#{key}' is a #{value.class}"
|
|
679
|
+
unless value.kind_of? String
|
|
680
|
+
raise LintError, "a header value must be a String, but the value of '#{key}' is a #{value.class}"
|
|
681
|
+
end
|
|
425
682
|
## consisting of lines (for multiple header values, e.g. multiple
|
|
426
|
-
## <tt>Set-Cookie</tt> values)
|
|
683
|
+
## <tt>Set-Cookie</tt> values) separated by "\\n".
|
|
427
684
|
value.split("\n").each { |item|
|
|
428
685
|
## The lines must not contain characters below 037.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
686
|
+
if item =~ /[\000-\037]/
|
|
687
|
+
raise LintError, "invalid header value #{key}: #{item.inspect}"
|
|
688
|
+
end
|
|
432
689
|
}
|
|
433
690
|
}
|
|
434
691
|
end
|
|
@@ -436,79 +693,71 @@ module Rack
|
|
|
436
693
|
## === The Content-Type
|
|
437
694
|
def check_content_type(status, headers)
|
|
438
695
|
headers.each { |key, value|
|
|
439
|
-
## There must be a <tt>Content-Type</tt>,
|
|
440
|
-
##
|
|
441
|
-
## given.
|
|
696
|
+
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
|
|
697
|
+
## 204 or 304.
|
|
442
698
|
if key.downcase == "content-type"
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
699
|
+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
|
700
|
+
raise LintError, "Content-Type header found in #{status} response, not allowed"
|
|
701
|
+
end
|
|
446
702
|
return
|
|
447
703
|
end
|
|
448
704
|
}
|
|
449
|
-
assert("No Content-Type header found") {
|
|
450
|
-
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
|
|
451
|
-
}
|
|
452
705
|
end
|
|
453
706
|
|
|
454
707
|
## === The Content-Length
|
|
455
|
-
def check_content_length(status, headers
|
|
708
|
+
def check_content_length(status, headers)
|
|
456
709
|
headers.each { |key, value|
|
|
457
710
|
if key.downcase == 'content-length'
|
|
458
711
|
## There must not be a <tt>Content-Length</tt> header when the
|
|
459
712
|
## +Status+ is 1xx, 204 or 304.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
bytes = 0
|
|
465
|
-
string_body = true
|
|
466
|
-
|
|
467
|
-
if @body.respond_to?(:to_ary)
|
|
468
|
-
@body.each { |part|
|
|
469
|
-
unless part.kind_of?(String)
|
|
470
|
-
string_body = false
|
|
471
|
-
break
|
|
472
|
-
end
|
|
473
|
-
|
|
474
|
-
bytes += Rack::Utils.bytesize(part)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if env["REQUEST_METHOD"] == "HEAD"
|
|
478
|
-
assert("Response body was given for HEAD request, but should be empty") {
|
|
479
|
-
bytes == 0
|
|
480
|
-
}
|
|
481
|
-
else
|
|
482
|
-
if string_body
|
|
483
|
-
assert("Content-Length header was #{value}, but should be #{bytes}") {
|
|
484
|
-
value == bytes.to_s
|
|
485
|
-
}
|
|
486
|
-
end
|
|
487
|
-
end
|
|
713
|
+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
|
714
|
+
raise LintError, "Content-Length header found in #{status} response, not allowed"
|
|
488
715
|
end
|
|
489
|
-
|
|
490
|
-
return
|
|
716
|
+
@content_length = value
|
|
491
717
|
end
|
|
492
718
|
}
|
|
493
719
|
end
|
|
494
720
|
|
|
721
|
+
def verify_content_length(bytes)
|
|
722
|
+
if @head_request
|
|
723
|
+
unless bytes == 0
|
|
724
|
+
raise LintError, "Response body was given for HEAD request, but should be empty"
|
|
725
|
+
end
|
|
726
|
+
elsif @content_length
|
|
727
|
+
unless @content_length == bytes.to_s
|
|
728
|
+
raise LintError, "Content-Length header was #{@content_length}, but should be #{bytes}"
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
495
733
|
## === The Body
|
|
496
734
|
def each
|
|
497
735
|
@closed = false
|
|
736
|
+
bytes = 0
|
|
737
|
+
|
|
498
738
|
## The Body must respond to +each+
|
|
739
|
+
unless @body.respond_to?(:each)
|
|
740
|
+
raise LintError, "Response body must respond to each"
|
|
741
|
+
end
|
|
742
|
+
|
|
499
743
|
@body.each { |part|
|
|
500
744
|
## and must only yield String values.
|
|
501
|
-
|
|
502
|
-
part.
|
|
503
|
-
|
|
745
|
+
unless part.kind_of? String
|
|
746
|
+
raise LintError, "Body yielded non-string value #{part.inspect}"
|
|
747
|
+
end
|
|
748
|
+
bytes += part.bytesize
|
|
504
749
|
yield part
|
|
505
750
|
}
|
|
751
|
+
verify_content_length(bytes)
|
|
752
|
+
|
|
506
753
|
##
|
|
507
754
|
## The Body itself should not be an instance of String, as this will
|
|
508
755
|
## break in Ruby 1.9.
|
|
509
756
|
##
|
|
510
|
-
## If the Body responds to +close+, it will be called after iteration.
|
|
511
|
-
|
|
757
|
+
## If the Body responds to +close+, it will be called after iteration. If
|
|
758
|
+
## the body is replaced by a middleware after action, the original body
|
|
759
|
+
## must be closed first, if it responds to close.
|
|
760
|
+
# XXX howto: raise LintError, "Body has not been closed" unless @closed
|
|
512
761
|
|
|
513
762
|
|
|
514
763
|
##
|
|
@@ -519,9 +768,9 @@ module Rack
|
|
|
519
768
|
## transport the response.
|
|
520
769
|
|
|
521
770
|
if @body.respond_to?(:to_path)
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
771
|
+
unless ::File.exist? @body.to_path
|
|
772
|
+
raise LintError, "The file identified by body.to_path does not exist"
|
|
773
|
+
end
|
|
525
774
|
end
|
|
526
775
|
|
|
527
776
|
##
|