rack 3.0.8 → 3.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +106 -2
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -15
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +18 -2
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/constants.rb +3 -0
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +116 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/media_type.rb +9 -4
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +19 -14
- data/lib/rack/mock_response.rb +14 -16
- data/lib/rack/multipart/parser.rb +132 -63
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +15 -68
- data/lib/rack/request.rb +40 -21
- data/lib/rack/response.rb +21 -16
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +77 -101
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +10 -16
- metadata +4 -10
- data/lib/rack/auth/digest/md5.rb +0 -1
- data/lib/rack/auth/digest/nonce.rb +0 -1
- data/lib/rack/auth/digest/params.rb +0 -1
- data/lib/rack/auth/digest/request.rb +0 -1
- data/lib/rack/auth/digest.rb +0 -256
- data/lib/rack/chunked.rb +0 -120
- data/lib/rack/file.rb +0 -9
data/lib/rack/headers.rb
CHANGED
@@ -1,9 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack::Headers is a Hash subclass that downcases all keys. It's designed
|
3
5
|
# to be used by rack applications that don't implement the Rack 3 SPEC
|
4
6
|
# (by using non-lowercase response header keys), automatically handling
|
5
7
|
# the downcasing of keys.
|
6
8
|
class Headers < Hash
|
9
|
+
KNOWN_HEADERS = {}
|
10
|
+
%w(
|
11
|
+
Accept-CH
|
12
|
+
Accept-Patch
|
13
|
+
Accept-Ranges
|
14
|
+
Access-Control-Allow-Credentials
|
15
|
+
Access-Control-Allow-Headers
|
16
|
+
Access-Control-Allow-Methods
|
17
|
+
Access-Control-Allow-Origin
|
18
|
+
Access-Control-Expose-Headers
|
19
|
+
Access-Control-Max-Age
|
20
|
+
Age
|
21
|
+
Allow
|
22
|
+
Alt-Svc
|
23
|
+
Cache-Control
|
24
|
+
Connection
|
25
|
+
Content-Disposition
|
26
|
+
Content-Encoding
|
27
|
+
Content-Language
|
28
|
+
Content-Length
|
29
|
+
Content-Location
|
30
|
+
Content-MD5
|
31
|
+
Content-Range
|
32
|
+
Content-Security-Policy
|
33
|
+
Content-Security-Policy-Report-Only
|
34
|
+
Content-Type
|
35
|
+
Date
|
36
|
+
Delta-Base
|
37
|
+
ETag
|
38
|
+
Expect-CT
|
39
|
+
Expires
|
40
|
+
Feature-Policy
|
41
|
+
IM
|
42
|
+
Last-Modified
|
43
|
+
Link
|
44
|
+
Location
|
45
|
+
NEL
|
46
|
+
P3P
|
47
|
+
Permissions-Policy
|
48
|
+
Pragma
|
49
|
+
Preference-Applied
|
50
|
+
Proxy-Authenticate
|
51
|
+
Public-Key-Pins
|
52
|
+
Referrer-Policy
|
53
|
+
Refresh
|
54
|
+
Report-To
|
55
|
+
Retry-After
|
56
|
+
Server
|
57
|
+
Set-Cookie
|
58
|
+
Status
|
59
|
+
Strict-Transport-Security
|
60
|
+
Timing-Allow-Origin
|
61
|
+
Tk
|
62
|
+
Trailer
|
63
|
+
Transfer-Encoding
|
64
|
+
Upgrade
|
65
|
+
Vary
|
66
|
+
Via
|
67
|
+
WWW-Authenticate
|
68
|
+
Warning
|
69
|
+
X-Cascade
|
70
|
+
X-Content-Duration
|
71
|
+
X-Content-Security-Policy
|
72
|
+
X-Content-Type-Options
|
73
|
+
X-Correlation-ID
|
74
|
+
X-Correlation-Id
|
75
|
+
X-Download-Options
|
76
|
+
X-Frame-Options
|
77
|
+
X-Permitted-Cross-Domain-Policies
|
78
|
+
X-Powered-By
|
79
|
+
X-Redirect-By
|
80
|
+
X-Request-ID
|
81
|
+
X-Request-Id
|
82
|
+
X-Runtime
|
83
|
+
X-UA-Compatible
|
84
|
+
X-WebKit-CS
|
85
|
+
X-XSS-Protection
|
86
|
+
).each do |str|
|
87
|
+
downcased = str.downcase.freeze
|
88
|
+
KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
|
89
|
+
end
|
90
|
+
|
7
91
|
def self.[](*items)
|
8
92
|
if items.length % 2 != 0
|
9
93
|
if items.length == 1 && items.first.is_a?(Hash)
|
@@ -28,7 +112,7 @@ module Rack
|
|
28
112
|
end
|
29
113
|
|
30
114
|
def []=(key, value)
|
31
|
-
super(key.downcase.freeze, value)
|
115
|
+
super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
|
32
116
|
end
|
33
117
|
alias store []=
|
34
118
|
|
@@ -148,7 +232,7 @@ module Rack
|
|
148
232
|
private
|
149
233
|
|
150
234
|
def downcase_key(key)
|
151
|
-
key.is_a?(String) ? key.downcase : key
|
235
|
+
key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
|
152
236
|
end
|
153
237
|
end
|
154
238
|
end
|
data/lib/rack/lint.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
|
+
require 'uri'
|
4
5
|
|
5
6
|
require_relative 'constants'
|
6
7
|
require_relative 'utils'
|
@@ -10,6 +11,11 @@ module Rack
|
|
10
11
|
# responses according to the Rack spec.
|
11
12
|
|
12
13
|
class Lint
|
14
|
+
REQUEST_PATH_ORIGIN_FORM = /\A\/[^#]*\z/
|
15
|
+
REQUEST_PATH_ABSOLUTE_FORM = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
|
16
|
+
REQUEST_PATH_AUTHORITY_FORM = /\A[^\/:]+:\d+\z/
|
17
|
+
REQUEST_PATH_ASTERISK_FORM = '*'
|
18
|
+
|
13
19
|
def initialize(app)
|
14
20
|
@app = app
|
15
21
|
end
|
@@ -56,9 +62,6 @@ module Rack
|
|
56
62
|
raise LintError, "No env given" unless @env
|
57
63
|
check_environment(@env)
|
58
64
|
|
59
|
-
@env[RACK_INPUT] = InputWrapper.new(@env[RACK_INPUT])
|
60
|
-
@env[RACK_ERRORS] = ErrorWrapper.new(@env[RACK_ERRORS])
|
61
|
-
|
62
65
|
## and returns a non-frozen Array of exactly three values:
|
63
66
|
@response = @app.call(@env)
|
64
67
|
raise LintError, "response is not an Array, but #{@response.class}" unless @response.kind_of? Array
|
@@ -78,8 +81,9 @@ module Rack
|
|
78
81
|
end
|
79
82
|
|
80
83
|
## and the *body*.
|
81
|
-
|
82
|
-
|
84
|
+
check_content_type_header(@status, @headers)
|
85
|
+
check_content_length_header(@status, @headers)
|
86
|
+
check_rack_protocol_header(@status, @headers)
|
83
87
|
@head_request = @env[REQUEST_METHOD] == HEAD
|
84
88
|
|
85
89
|
@lint = (@env['rack.lint'] ||= []) << self
|
@@ -179,6 +183,16 @@ module Rack
|
|
179
183
|
## to +call+ that is used to perform a full
|
180
184
|
## hijack.
|
181
185
|
|
186
|
+
## <tt>rack.protocol</tt>:: An optional +Array+ of +String+, containing
|
187
|
+
## the protocols advertised by the client in
|
188
|
+
## the +upgrade+ header (HTTP/1) or the
|
189
|
+
## +:protocol+ pseudo-header (HTTP/2).
|
190
|
+
if protocols = @env['rack.protocol']
|
191
|
+
unless protocols.is_a?(Array) && protocols.all?{|protocol| protocol.is_a?(String)}
|
192
|
+
raise LintError, "rack.protocol must be an Array of Strings"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
182
196
|
## Additional environment specifications have approved to
|
183
197
|
## standardized middleware APIs. None of these are required to
|
184
198
|
## be implemented by the server.
|
@@ -265,11 +279,9 @@ module Rack
|
|
265
279
|
## is reserved for use with the Rack core distribution and other
|
266
280
|
## accepted specifications and must not be used otherwise.
|
267
281
|
##
|
268
|
-
|
269
|
-
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL
|
270
|
-
rack.input rack.errors].each { |header|
|
282
|
+
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL rack.errors].each do |header|
|
271
283
|
raise LintError, "env missing required key #{header}" unless env.include? header
|
272
|
-
|
284
|
+
end
|
273
285
|
|
274
286
|
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
275
287
|
server_port = env["SERVER_PORT"]
|
@@ -293,11 +305,6 @@ module Rack
|
|
293
305
|
raise LintError, "env[SERVER_PROTOCOL] does not match HTTP/\\d(\\.\\d)?"
|
294
306
|
end
|
295
307
|
|
296
|
-
## If the <tt>HTTP_VERSION</tt> is present, it must equal the <tt>SERVER_PROTOCOL</tt>.
|
297
|
-
if env['HTTP_VERSION'] && env['HTTP_VERSION'] != server_protocol
|
298
|
-
raise LintError, "env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL]"
|
299
|
-
end
|
300
|
-
|
301
308
|
## The environment must not contain the keys
|
302
309
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
303
310
|
## (use the versions without <tt>HTTP_</tt>).
|
@@ -328,12 +335,21 @@ module Rack
|
|
328
335
|
raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
|
329
336
|
end
|
330
337
|
|
331
|
-
## * There
|
332
|
-
|
338
|
+
## * There may be a valid input stream in <tt>rack.input</tt>.
|
339
|
+
if rack_input = env[RACK_INPUT]
|
340
|
+
check_input_stream(rack_input)
|
341
|
+
@env[RACK_INPUT] = InputWrapper.new(rack_input)
|
342
|
+
end
|
343
|
+
|
333
344
|
## * There must be a valid error stream in <tt>rack.errors</tt>.
|
334
|
-
|
345
|
+
rack_errors = env[RACK_ERRORS]
|
346
|
+
check_error_stream(rack_errors)
|
347
|
+
@env[RACK_ERRORS] = ErrorWrapper.new(rack_errors)
|
348
|
+
|
335
349
|
## * There may be a valid hijack callback in <tt>rack.hijack</tt>
|
336
350
|
check_hijack env
|
351
|
+
## * There may be a valid early hints callback in <tt>rack.early_hints</tt>
|
352
|
+
check_early_hints env
|
337
353
|
|
338
354
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
339
355
|
unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
@@ -344,10 +360,32 @@ module Rack
|
|
344
360
|
if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
|
345
361
|
raise LintError, "SCRIPT_NAME must start with /"
|
346
362
|
end
|
347
|
-
|
348
|
-
|
349
|
-
|
363
|
+
|
364
|
+
## * The <tt>PATH_INFO</tt>, if provided, must be a valid request target.
|
365
|
+
if env.include?(PATH_INFO)
|
366
|
+
case env[PATH_INFO]
|
367
|
+
when REQUEST_PATH_ASTERISK_FORM
|
368
|
+
## * Only <tt>OPTIONS</tt> requests may have <tt>PATH_INFO</tt> set to <tt>*</tt> (asterisk-form).
|
369
|
+
unless env[REQUEST_METHOD] == OPTIONS
|
370
|
+
raise LintError, "Only OPTIONS requests may have PATH_INFO set to '*' (asterisk-form)"
|
371
|
+
end
|
372
|
+
when REQUEST_PATH_AUTHORITY_FORM
|
373
|
+
## * Only <tt>CONNECT</tt> requests may have <tt>PATH_INFO</tt> set to an authority (authority-form). Note that in HTTP/2+, the authority-form is not a valid request target.
|
374
|
+
unless env[REQUEST_METHOD] == CONNECT
|
375
|
+
raise LintError, "Only CONNECT requests may have PATH_INFO set to an authority (authority-form)"
|
376
|
+
end
|
377
|
+
when REQUEST_PATH_ABSOLUTE_FORM
|
378
|
+
## * <tt>CONNECT</tt> and <tt>OPTIONS</tt> requests must not have <tt>PATH_INFO</tt> set to a URI (absolute-form).
|
379
|
+
if env[REQUEST_METHOD] == CONNECT || env[REQUEST_METHOD] == OPTIONS
|
380
|
+
raise LintError, "CONNECT and OPTIONS requests must not have PATH_INFO set to a URI (absolute-form)"
|
381
|
+
end
|
382
|
+
when REQUEST_PATH_ORIGIN_FORM
|
383
|
+
## * Otherwise, <tt>PATH_INFO</tt> must start with a <tt>/</tt> and must not include a fragment part starting with '#' (origin-form).
|
384
|
+
else
|
385
|
+
raise LintError, "PATH_INFO must start with a '/' and must not include a fragment part starting with '#' (origin-form)"
|
386
|
+
end
|
350
387
|
end
|
388
|
+
|
351
389
|
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
|
352
390
|
if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
|
353
391
|
raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
|
@@ -384,9 +422,9 @@ module Rack
|
|
384
422
|
##
|
385
423
|
## The input stream is an IO-like object which contains the raw HTTP
|
386
424
|
## POST data.
|
387
|
-
def
|
425
|
+
def check_input_stream(input)
|
388
426
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
389
|
-
## must be opened in binary mode
|
427
|
+
## must be opened in binary mode.
|
390
428
|
if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
|
391
429
|
raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
|
392
430
|
end
|
@@ -418,7 +456,7 @@ module Rack
|
|
418
456
|
v
|
419
457
|
end
|
420
458
|
|
421
|
-
## * +read+ behaves like IO#read
|
459
|
+
## * +read+ behaves like <tt>IO#read</tt>.
|
422
460
|
## Its signature is <tt>read([length, [buffer]])</tt>.
|
423
461
|
##
|
424
462
|
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
|
@@ -478,8 +516,8 @@ module Rack
|
|
478
516
|
}
|
479
517
|
end
|
480
518
|
|
481
|
-
## * +close+ can be called on the input stream to indicate that
|
482
|
-
##
|
519
|
+
## * +close+ can be called on the input stream to indicate that
|
520
|
+
## any remaining input is not needed.
|
483
521
|
def close(*args)
|
484
522
|
@input.close(*args)
|
485
523
|
end
|
@@ -488,7 +526,7 @@ module Rack
|
|
488
526
|
##
|
489
527
|
## === The Error Stream
|
490
528
|
##
|
491
|
-
def
|
529
|
+
def check_error_stream(error)
|
492
530
|
## The error stream must respond to +puts+, +write+ and +flush+.
|
493
531
|
[:puts, :write, :flush].each { |method|
|
494
532
|
unless error.respond_to? method
|
@@ -609,6 +647,30 @@ module Rack
|
|
609
647
|
nil
|
610
648
|
end
|
611
649
|
|
650
|
+
##
|
651
|
+
## === Early Hints
|
652
|
+
##
|
653
|
+
## The application or any middleware may call the <tt>rack.early_hints</tt>
|
654
|
+
## with an object which would be valid as the headers of a Rack response.
|
655
|
+
def check_early_hints(env)
|
656
|
+
if env[RACK_EARLY_HINTS]
|
657
|
+
##
|
658
|
+
## If <tt>rack.early_hints</tt> is present, it must respond to #call.
|
659
|
+
unless env[RACK_EARLY_HINTS].respond_to?(:call)
|
660
|
+
raise LintError, "rack.early_hints must respond to call"
|
661
|
+
end
|
662
|
+
|
663
|
+
original_callback = env[RACK_EARLY_HINTS]
|
664
|
+
env[RACK_EARLY_HINTS] = lambda do |headers|
|
665
|
+
## If <tt>rack.early_hints</tt> is called, it must be called with
|
666
|
+
## valid Rack response headers.
|
667
|
+
check_headers(headers)
|
668
|
+
original_callback.call(headers)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
##
|
612
674
|
## == The Response
|
613
675
|
##
|
614
676
|
## === The Status
|
@@ -672,9 +734,9 @@ module Rack
|
|
672
734
|
end
|
673
735
|
|
674
736
|
##
|
675
|
-
##
|
737
|
+
## ==== The +content-type+ Header
|
676
738
|
##
|
677
|
-
def
|
739
|
+
def check_content_type_header(status, headers)
|
678
740
|
headers.each { |key, value|
|
679
741
|
## There must not be a <tt>content-type</tt> header key when the +Status+ is 1xx,
|
680
742
|
## 204, or 304.
|
@@ -688,9 +750,9 @@ module Rack
|
|
688
750
|
end
|
689
751
|
|
690
752
|
##
|
691
|
-
##
|
753
|
+
## ==== The +content-length+ Header
|
692
754
|
##
|
693
|
-
def
|
755
|
+
def check_content_length_header(status, headers)
|
694
756
|
headers.each { |key, value|
|
695
757
|
if key == 'content-length'
|
696
758
|
## There must not be a <tt>content-length</tt> header key when the
|
@@ -715,6 +777,29 @@ module Rack
|
|
715
777
|
end
|
716
778
|
end
|
717
779
|
|
780
|
+
##
|
781
|
+
## ==== The +rack.protocol+ Header
|
782
|
+
##
|
783
|
+
def check_rack_protocol_header(status, headers)
|
784
|
+
## If the +rack.protocol+ header is present, it must be a +String+, and
|
785
|
+
## must be one of the values from the +rack.protocol+ array from the
|
786
|
+
## environment.
|
787
|
+
protocol = headers['rack.protocol']
|
788
|
+
|
789
|
+
if protocol
|
790
|
+
request_protocols = @env['rack.protocol']
|
791
|
+
|
792
|
+
if request_protocols.nil?
|
793
|
+
raise LintError, "rack.protocol header is #{protocol.inspect}, but rack.protocol was not set in request!"
|
794
|
+
elsif !request_protocols.include?(protocol)
|
795
|
+
raise LintError, "rack.protocol header is #{protocol.inspect}, but should be one of #{request_protocols.inspect} from the request!"
|
796
|
+
end
|
797
|
+
end
|
798
|
+
end
|
799
|
+
##
|
800
|
+
## Setting this value informs the server that it should perform a
|
801
|
+
## connection upgrade. In HTTP/1, this is done using the +upgrade+
|
802
|
+
## header. In HTTP/2, this is done by accepting the request.
|
718
803
|
##
|
719
804
|
## === The Body
|
720
805
|
##
|
@@ -782,7 +867,7 @@ module Rack
|
|
782
867
|
## It must only be called once.
|
783
868
|
raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil?
|
784
869
|
|
785
|
-
## It must not be called after being closed
|
870
|
+
## It must not be called after being closed,
|
786
871
|
raise LintError, "Response body is already closed" if @closed
|
787
872
|
|
788
873
|
@invoked = :each
|
@@ -793,9 +878,6 @@ module Rack
|
|
793
878
|
raise LintError, "Body yielded non-string value #{chunk.inspect}"
|
794
879
|
end
|
795
880
|
|
796
|
-
##
|
797
|
-
## The Body itself should not be an instance of String, as this will
|
798
|
-
## break in Ruby 1.9.
|
799
881
|
##
|
800
882
|
## Middleware must not call +each+ directly on the Body.
|
801
883
|
## Instead, middleware can return a new Body that calls +each+ on the
|
data/lib/rack/logger.rb
CHANGED
data/lib/rack/media_type.rb
CHANGED
@@ -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 =
|
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
|
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
|
36
|
+
k.downcase!
|
37
|
+
hsh[k] = strip_doublequotes(v)
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
data/lib/rack/mime.rb
CHANGED
@@ -290,7 +290,7 @@ module Rack
|
|
290
290
|
".jpg" => "image/jpeg",
|
291
291
|
".jpgv" => "video/jpeg",
|
292
292
|
".jpm" => "video/jpm",
|
293
|
-
".js" => "
|
293
|
+
".js" => "text/javascript",
|
294
294
|
".json" => "application/json",
|
295
295
|
".karbon" => "application/vnd.kde.karbon",
|
296
296
|
".kfo" => "application/vnd.kde.kformula",
|
@@ -338,6 +338,7 @@ module Rack
|
|
338
338
|
".mif" => "application/vnd.mif",
|
339
339
|
".mime" => "message/rfc822",
|
340
340
|
".mj2" => "video/mj2",
|
341
|
+
".mjs" => "text/javascript",
|
341
342
|
".mlp" => "application/vnd.dolby.mlp",
|
342
343
|
".mmd" => "application/vnd.chipnuts.karaoke-mmd",
|
343
344
|
".mmf" => "application/vnd.smaf",
|
@@ -409,7 +410,7 @@ module Rack
|
|
409
410
|
".ogx" => "application/ogg",
|
410
411
|
".org" => "application/vnd.lotus-organizer",
|
411
412
|
".otc" => "application/vnd.oasis.opendocument.chart-template",
|
412
|
-
".otf" => "
|
413
|
+
".otf" => "font/otf",
|
413
414
|
".otg" => "application/vnd.oasis.opendocument.graphics-template",
|
414
415
|
".oth" => "application/vnd.oasis.opendocument.text-web",
|
415
416
|
".oti" => "application/vnd.oasis.opendocument.image-template",
|
@@ -590,7 +591,7 @@ module Rack
|
|
590
591
|
".trm" => "application/x-msterminal",
|
591
592
|
".ts" => "video/mp2t",
|
592
593
|
".tsv" => "text/tab-separated-values",
|
593
|
-
".ttf" => "
|
594
|
+
".ttf" => "font/ttf",
|
594
595
|
".twd" => "application/vnd.simtech-mindmapper",
|
595
596
|
".txd" => "application/vnd.genomatix.tuxedo",
|
596
597
|
".txf" => "application/vnd.mobius.txf",
|
@@ -636,8 +637,8 @@ module Rack
|
|
636
637
|
".wmv" => "video/x-ms-wmv",
|
637
638
|
".wmx" => "video/x-ms-wmx",
|
638
639
|
".wmz" => "application/x-ms-wmz",
|
639
|
-
".woff" => "
|
640
|
-
".woff2" => "
|
640
|
+
".woff" => "font/woff",
|
641
|
+
".woff2" => "font/woff2",
|
641
642
|
".wpd" => "application/vnd.wordperfect",
|
642
643
|
".wpl" => "application/vnd.ms-wpl",
|
643
644
|
".wps" => "application/vnd.ms-works",
|
data/lib/rack/mock_request.rb
CHANGED
@@ -41,11 +41,6 @@ module Rack
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
DEFAULT_ENV = {
|
45
|
-
RACK_INPUT => StringIO.new,
|
46
|
-
RACK_ERRORS => StringIO.new,
|
47
|
-
}.freeze
|
48
|
-
|
49
44
|
def initialize(app)
|
50
45
|
@app = app
|
51
46
|
end
|
@@ -104,7 +99,7 @@ module Rack
|
|
104
99
|
uri = parse_uri_rfc2396(uri)
|
105
100
|
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
106
101
|
|
107
|
-
env =
|
102
|
+
env = {}
|
108
103
|
|
109
104
|
env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
|
110
105
|
env[SERVER_NAME] = (uri.host || "example.org").b
|
@@ -144,20 +139,30 @@ module Rack
|
|
144
139
|
end
|
145
140
|
end
|
146
141
|
|
147
|
-
opts[:input]
|
148
|
-
if String ===
|
149
|
-
rack_input = StringIO.new(
|
142
|
+
input = opts[:input]
|
143
|
+
if String === input
|
144
|
+
rack_input = StringIO.new(input)
|
145
|
+
rack_input.set_encoding(Encoding::BINARY)
|
150
146
|
else
|
151
|
-
|
147
|
+
if input.respond_to?(:encoding) && input.encoding != Encoding::BINARY
|
148
|
+
warn "input encoding not binary", uplevel: 1
|
149
|
+
if input.respond_to?(:set_encoding)
|
150
|
+
input.set_encoding(Encoding::BINARY)
|
151
|
+
else
|
152
|
+
raise ArgumentError, "could not coerce input to binary encoding"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
rack_input = input
|
152
156
|
end
|
153
157
|
|
154
|
-
rack_input
|
155
|
-
|
158
|
+
if rack_input
|
159
|
+
env[RACK_INPUT] = rack_input
|
156
160
|
|
157
|
-
|
161
|
+
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
|
162
|
+
end
|
158
163
|
|
159
164
|
opts.each { |field, value|
|
160
|
-
env[field] = value
|
165
|
+
env[field] = value if String === field
|
161
166
|
}
|
162
167
|
|
163
168
|
env
|
data/lib/rack/mock_response.rb
CHANGED
@@ -78,22 +78,20 @@ module Rack
|
|
78
78
|
|
79
79
|
def parse_cookies_from_header
|
80
80
|
cookies = Hash.new
|
81
|
-
|
82
|
-
|
83
|
-
Array(set_cookie_header).each do |
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
cookies.store(cookie_name, parsed_cookie)
|
96
|
-
end
|
81
|
+
set_cookie_header = headers['set-cookie']
|
82
|
+
if set_cookie_header && !set_cookie_header.empty?
|
83
|
+
Array(set_cookie_header).each do |cookie|
|
84
|
+
cookie_name, cookie_filling = cookie.split('=', 2)
|
85
|
+
cookie_attributes = identify_cookie_attributes cookie_filling
|
86
|
+
parsed_cookie = CGI::Cookie.new(
|
87
|
+
'name' => cookie_name.strip,
|
88
|
+
'value' => cookie_attributes.fetch('value'),
|
89
|
+
'path' => cookie_attributes.fetch('path', nil),
|
90
|
+
'domain' => cookie_attributes.fetch('domain', nil),
|
91
|
+
'expires' => cookie_attributes.fetch('expires', nil),
|
92
|
+
'secure' => cookie_attributes.fetch('secure', false)
|
93
|
+
)
|
94
|
+
cookies.store(cookie_name, parsed_cookie)
|
97
95
|
end
|
98
96
|
end
|
99
97
|
cookies
|