rack 1.6.13 → 2.0.9
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/COPYING +1 -1
- data/HISTORY.md +138 -8
- data/README.rdoc +18 -28
- data/Rakefile +6 -14
- data/SPEC +3 -3
- data/contrib/rack_logo.svg +164 -111
- data/example/protectedlobster.rb +1 -1
- data/example/protectedlobster.ru +1 -1
- data/lib/rack/auth/abstract/request.rb +5 -1
- data/lib/rack/auth/digest/params.rb +2 -3
- data/lib/rack/auth/digest/request.rb +1 -1
- data/lib/rack/body_proxy.rb +14 -9
- data/lib/rack/builder.rb +3 -3
- data/lib/rack/chunked.rb +5 -5
- data/lib/rack/{commonlogger.rb → common_logger.rb} +3 -3
- data/lib/rack/content_length.rb +2 -2
- data/lib/rack/deflater.rb +4 -39
- data/lib/rack/directory.rb +66 -54
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +154 -0
- data/lib/rack/file.rb +64 -40
- data/lib/rack/handler/cgi.rb +15 -16
- data/lib/rack/handler/fastcgi.rb +13 -14
- data/lib/rack/handler/lsws.rb +11 -11
- data/lib/rack/handler/scgi.rb +15 -15
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +24 -26
- data/lib/rack/handler.rb +3 -25
- data/lib/rack/head.rb +15 -17
- data/lib/rack/lint.rb +40 -40
- data/lib/rack/lobster.rb +1 -1
- data/lib/rack/lock.rb +15 -10
- data/lib/rack/logger.rb +2 -2
- data/lib/rack/media_type.rb +38 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +6 -6
- data/lib/rack/mime.rb +18 -5
- data/lib/rack/mock.rb +36 -54
- data/lib/rack/multipart/generator.rb +5 -5
- data/lib/rack/multipart/parser.rb +270 -157
- data/lib/rack/multipart/uploaded_file.rb +1 -2
- data/lib/rack/multipart.rb +35 -6
- data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
- data/lib/rack/query_parser.rb +192 -0
- data/lib/rack/recursive.rb +8 -8
- data/lib/rack/request.rb +394 -305
- data/lib/rack/response.rb +130 -57
- data/lib/rack/rewindable_input.rb +1 -12
- data/lib/rack/runtime.rb +10 -18
- data/lib/rack/sendfile.rb +5 -7
- data/lib/rack/server.rb +30 -23
- data/lib/rack/session/abstract/id.rb +110 -75
- data/lib/rack/session/cookie.rb +24 -17
- data/lib/rack/session/memcache.rb +9 -9
- data/lib/rack/session/pool.rb +8 -8
- data/lib/rack/show_exceptions.rb +386 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
- data/lib/rack/static.rb +30 -5
- data/lib/rack/tempfile_reaper.rb +2 -2
- data/lib/rack/urlmap.rb +15 -14
- data/lib/rack/utils.rb +138 -211
- data/lib/rack.rb +70 -21
- data/rack.gemspec +10 -9
- data/test/builder/an_underscore_app.rb +5 -0
- data/test/builder/options.ru +1 -1
- data/test/cgi/test.fcgi +1 -0
- data/test/cgi/test.gz +0 -0
- data/test/helper.rb +34 -0
- data/test/multipart/filename_with_encoded_words +7 -0
- data/test/multipart/filename_with_single_quote +7 -0
- data/test/multipart/quoted +15 -0
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/unity3d_wwwform +11 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
- data/test/spec_auth_basic.rb +27 -19
- data/test/spec_auth_digest.rb +47 -46
- data/test/spec_body_proxy.rb +27 -27
- data/test/spec_builder.rb +51 -41
- data/test/spec_cascade.rb +24 -22
- data/test/spec_cgi.rb +49 -67
- data/test/spec_chunked.rb +37 -35
- data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
- data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
- data/test/spec_config.rb +3 -2
- data/test/spec_content_length.rb +18 -17
- data/test/spec_content_type.rb +13 -12
- data/test/spec_deflater.rb +85 -49
- data/test/spec_directory.rb +87 -27
- data/test/spec_etag.rb +32 -31
- data/test/spec_events.rb +133 -0
- data/test/spec_fastcgi.rb +50 -72
- data/test/spec_file.rb +120 -77
- data/test/spec_handler.rb +19 -34
- data/test/spec_head.rb +15 -14
- data/test/spec_lint.rb +164 -199
- data/test/spec_lobster.rb +24 -23
- data/test/spec_lock.rb +79 -39
- data/test/spec_logger.rb +4 -3
- data/test/spec_media_type.rb +42 -0
- data/test/{spec_methodoverride.rb → spec_method_override.rb} +34 -35
- data/test/spec_mime.rb +19 -19
- data/test/spec_mock.rb +206 -144
- data/test/spec_multipart.rb +322 -200
- data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
- data/test/spec_recursive.rb +17 -14
- data/test/spec_request.rb +780 -605
- data/test/spec_response.rb +233 -112
- data/test/spec_rewindable_input.rb +50 -40
- data/test/spec_runtime.rb +11 -10
- data/test/spec_sendfile.rb +30 -35
- data/test/spec_server.rb +78 -52
- data/test/spec_session_abstract_id.rb +11 -33
- data/test/spec_session_abstract_session_hash.rb +45 -0
- data/test/spec_session_cookie.rb +99 -67
- data/test/spec_session_memcache.rb +67 -68
- data/test/spec_session_pool.rb +52 -51
- data/test/{spec_showexceptions.rb → spec_show_exceptions.rb} +23 -28
- data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
- data/test/spec_static.rb +71 -32
- data/test/spec_tempfile_reaper.rb +11 -10
- data/test/spec_thin.rb +55 -50
- data/test/spec_urlmap.rb +79 -78
- data/test/spec_utils.rb +441 -346
- data/test/spec_version.rb +2 -8
- data/test/spec_webrick.rb +93 -71
- data/test/static/foo.html +1 -0
- data/test/testrequest.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
- metadata +57 -36
- 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/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/spec_mongrel.rb +0 -182
- /data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
data/test/spec_utils.rb
CHANGED
@@ -1,288 +1,334 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
require 'minitest/autorun'
|
2
3
|
require 'rack/utils'
|
3
4
|
require 'rack/mock'
|
4
5
|
require 'timeout'
|
5
6
|
|
6
7
|
describe Rack::Utils do
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def assert_sets exp, act
|
10
|
+
exp = Set.new exp.split '&'
|
11
|
+
act = Set.new act.split '&'
|
12
|
+
|
13
|
+
assert_equal exp, act
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_query exp, act
|
17
|
+
assert_sets exp, Rack::Utils.build_query(act)
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def assert_nested_query exp, act
|
21
|
+
assert_sets exp, Rack::Utils.build_nested_query(act)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can be mixed in and used' do
|
25
|
+
instance = Class.new {
|
26
|
+
include Rack::Utils
|
27
|
+
|
28
|
+
public :parse_nested_query
|
29
|
+
public :parse_query
|
30
|
+
}.new
|
31
|
+
|
32
|
+
assert_equal({ "foo" => "bar" }, instance.parse_nested_query("foo=bar"))
|
33
|
+
assert_equal({ "foo" => "bar" }, instance.parse_query("foo=bar"))
|
22
34
|
end
|
23
35
|
|
24
|
-
|
36
|
+
it "round trip binary data" do
|
25
37
|
r = [218, 0].pack 'CC'
|
26
|
-
if defined?(::Encoding)
|
27
38
|
z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY)
|
28
|
-
|
29
|
-
z = Rack::Utils.unescape(Rack::Utils.escape(r))
|
30
|
-
end
|
31
|
-
r.should.equal z
|
39
|
+
r.must_equal z
|
32
40
|
end
|
33
41
|
|
34
|
-
|
35
|
-
Rack::Utils.escape("fo<o>bar").
|
36
|
-
Rack::Utils.escape("a space").
|
42
|
+
it "escape correctly" do
|
43
|
+
Rack::Utils.escape("fo<o>bar").must_equal "fo%3Co%3Ebar"
|
44
|
+
Rack::Utils.escape("a space").must_equal "a+space"
|
37
45
|
Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
|
38
|
-
|
46
|
+
must_equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
|
39
47
|
end
|
40
48
|
|
41
|
-
|
49
|
+
it "escape correctly for multibyte characters" do
|
42
50
|
matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
|
43
|
-
matz_name.force_encoding(
|
44
|
-
Rack::Utils.escape(matz_name).
|
51
|
+
matz_name.force_encoding(Encoding::UTF_8)
|
52
|
+
Rack::Utils.escape(matz_name).must_equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
|
45
53
|
matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
|
46
54
|
matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
|
47
|
-
Rack::Utils.escape(matz_name_sep).
|
48
|
-
end
|
49
|
-
|
50
|
-
if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
|
51
|
-
should "escape correctly for multibyte characters if $KCODE is set to 'U'" do
|
52
|
-
kcodeu do
|
53
|
-
matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
|
54
|
-
matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
|
55
|
-
Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
|
56
|
-
matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
|
57
|
-
matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
|
58
|
-
Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
|
59
|
-
end
|
60
|
-
end
|
55
|
+
Rack::Utils.escape(matz_name_sep).must_equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
|
56
|
+
end
|
61
57
|
|
62
|
-
|
63
|
-
|
64
|
-
Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
|
65
|
-
"\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
|
66
|
-
end
|
67
|
-
end
|
58
|
+
it "escape objects that responds to to_s" do
|
59
|
+
Rack::Utils.escape(:id).must_equal "id"
|
68
60
|
end
|
69
61
|
|
70
|
-
|
71
|
-
|
72
|
-
Rack::Utils.escape(:id).should.equal "id"
|
73
|
-
end
|
62
|
+
it "escape non-UTF8 strings" do
|
63
|
+
Rack::Utils.escape("ø".encode("ISO-8859-1")).must_equal "%F8"
|
74
64
|
end
|
75
65
|
|
76
|
-
|
77
|
-
|
78
|
-
|
66
|
+
it "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do
|
67
|
+
Timeout.timeout(1) do
|
68
|
+
lambda {
|
69
|
+
URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %"
|
70
|
+
}.must_raise ArgumentError
|
79
71
|
end
|
80
72
|
end
|
81
|
-
|
82
|
-
should "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do
|
83
|
-
lambda {
|
84
|
-
timeout(1) do
|
85
|
-
lambda {
|
86
|
-
URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %"
|
87
|
-
}.should.raise(ArgumentError)
|
88
|
-
end
|
89
|
-
}.should.not.raise(Timeout::Error)
|
90
|
-
end
|
91
73
|
|
92
|
-
|
93
|
-
Rack::Utils.escape_path("foo bar").
|
74
|
+
it "escape path spaces with %20" do
|
75
|
+
Rack::Utils.escape_path("foo bar").must_equal "foo%20bar"
|
94
76
|
end
|
95
77
|
|
96
|
-
|
97
|
-
Rack::Utils.unescape("fo%3Co%3Ebar").
|
98
|
-
Rack::Utils.unescape("a+space").
|
99
|
-
Rack::Utils.unescape("a%20space").
|
78
|
+
it "unescape correctly" do
|
79
|
+
Rack::Utils.unescape("fo%3Co%3Ebar").must_equal "fo<o>bar"
|
80
|
+
Rack::Utils.unescape("a+space").must_equal "a space"
|
81
|
+
Rack::Utils.unescape("a%20space").must_equal "a space"
|
100
82
|
Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
|
101
|
-
|
83
|
+
must_equal "q1!2\"'w$5&7/z8)?\\"
|
102
84
|
end
|
103
85
|
|
104
|
-
|
86
|
+
it "parse query strings correctly" do
|
105
87
|
Rack::Utils.parse_query("foo=bar").
|
106
|
-
|
88
|
+
must_equal "foo" => "bar"
|
107
89
|
Rack::Utils.parse_query("foo=\"bar\"").
|
108
|
-
|
90
|
+
must_equal "foo" => "\"bar\""
|
109
91
|
Rack::Utils.parse_query("foo=bar&foo=quux").
|
110
|
-
|
92
|
+
must_equal "foo" => ["bar", "quux"]
|
111
93
|
Rack::Utils.parse_query("foo=1&bar=2").
|
112
|
-
|
94
|
+
must_equal "foo" => "1", "bar" => "2"
|
113
95
|
Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
|
114
|
-
|
115
|
-
Rack::Utils.parse_query("foo%3Dbaz=bar").
|
116
|
-
Rack::Utils.parse_query("=").
|
117
|
-
Rack::Utils.parse_query("=value").
|
118
|
-
Rack::Utils.parse_query("key=").
|
119
|
-
Rack::Utils.parse_query("&key&").
|
120
|
-
Rack::Utils.parse_query(";key;", ";,").
|
121
|
-
Rack::Utils.parse_query(",key,", ";,").
|
122
|
-
Rack::Utils.parse_query(";foo=bar,;", ";,").
|
123
|
-
Rack::Utils.parse_query(",foo=bar;,", ";,").
|
124
|
-
end
|
125
|
-
|
126
|
-
|
96
|
+
must_equal "my weird field" => "q1!2\"'w$5&7/z8)?"
|
97
|
+
Rack::Utils.parse_query("foo%3Dbaz=bar").must_equal "foo=baz" => "bar"
|
98
|
+
Rack::Utils.parse_query("=").must_equal "" => ""
|
99
|
+
Rack::Utils.parse_query("=value").must_equal "" => "value"
|
100
|
+
Rack::Utils.parse_query("key=").must_equal "key" => ""
|
101
|
+
Rack::Utils.parse_query("&key&").must_equal "key" => nil
|
102
|
+
Rack::Utils.parse_query(";key;", ";,").must_equal "key" => nil
|
103
|
+
Rack::Utils.parse_query(",key,", ";,").must_equal "key" => nil
|
104
|
+
Rack::Utils.parse_query(";foo=bar,;", ";,").must_equal "foo" => "bar"
|
105
|
+
Rack::Utils.parse_query(",foo=bar;,", ";,").must_equal "foo" => "bar"
|
106
|
+
end
|
107
|
+
|
108
|
+
it "not create infinite loops with cycle structures" do
|
127
109
|
ex = { "foo" => nil }
|
128
110
|
ex["foo"] = ex
|
129
111
|
|
130
|
-
params = Rack::Utils::KeySpaceConstrainedParams.new
|
112
|
+
params = Rack::Utils::KeySpaceConstrainedParams.new(65536)
|
131
113
|
params['foo'] = params
|
132
|
-
|
133
|
-
params.to_params_hash.to_s.should.equal ex.to_s
|
134
|
-
}.should.not.raise
|
114
|
+
params.to_params_hash.to_s.must_equal ex.to_s
|
135
115
|
end
|
136
116
|
|
137
|
-
|
117
|
+
it "parse nil as an empty query string" do
|
118
|
+
Rack::Utils.parse_nested_query(nil).must_equal({})
|
119
|
+
end
|
120
|
+
|
121
|
+
it "raise an exception if the params are too deep" do
|
138
122
|
len = Rack::Utils.param_depth_limit
|
139
123
|
|
140
124
|
lambda {
|
141
125
|
Rack::Utils.parse_nested_query("foo#{"[a]" * len}=bar")
|
142
|
-
}.
|
126
|
+
}.must_raise(RangeError)
|
143
127
|
|
144
|
-
|
145
|
-
Rack::Utils.parse_nested_query("foo#{"[a]" * (len - 1)}=bar")
|
146
|
-
}.should.not.raise
|
128
|
+
Rack::Utils.parse_nested_query("foo#{"[a]" * (len - 1)}=bar")
|
147
129
|
end
|
148
130
|
|
149
|
-
|
131
|
+
it "parse nested query strings correctly" do
|
150
132
|
Rack::Utils.parse_nested_query("foo").
|
151
|
-
|
133
|
+
must_equal "foo" => nil
|
152
134
|
Rack::Utils.parse_nested_query("foo=").
|
153
|
-
|
135
|
+
must_equal "foo" => ""
|
154
136
|
Rack::Utils.parse_nested_query("foo=bar").
|
155
|
-
|
137
|
+
must_equal "foo" => "bar"
|
156
138
|
Rack::Utils.parse_nested_query("foo=\"bar\"").
|
157
|
-
|
139
|
+
must_equal "foo" => "\"bar\""
|
158
140
|
|
159
141
|
Rack::Utils.parse_nested_query("foo=bar&foo=quux").
|
160
|
-
|
142
|
+
must_equal "foo" => "quux"
|
161
143
|
Rack::Utils.parse_nested_query("foo&foo=").
|
162
|
-
|
144
|
+
must_equal "foo" => ""
|
163
145
|
Rack::Utils.parse_nested_query("foo=1&bar=2").
|
164
|
-
|
146
|
+
must_equal "foo" => "1", "bar" => "2"
|
165
147
|
Rack::Utils.parse_nested_query("&foo=1&&bar=2").
|
166
|
-
|
148
|
+
must_equal "foo" => "1", "bar" => "2"
|
167
149
|
Rack::Utils.parse_nested_query("foo&bar=").
|
168
|
-
|
150
|
+
must_equal "foo" => nil, "bar" => ""
|
169
151
|
Rack::Utils.parse_nested_query("foo=bar&baz=").
|
170
|
-
|
152
|
+
must_equal "foo" => "bar", "baz" => ""
|
171
153
|
Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
|
172
|
-
|
154
|
+
must_equal "my weird field" => "q1!2\"'w$5&7/z8)?"
|
173
155
|
|
174
156
|
Rack::Utils.parse_nested_query("a=b&pid%3D1234=1023").
|
175
|
-
|
157
|
+
must_equal "pid=1234" => "1023", "a" => "b"
|
176
158
|
|
177
159
|
Rack::Utils.parse_nested_query("foo[]").
|
178
|
-
|
160
|
+
must_equal "foo" => [nil]
|
179
161
|
Rack::Utils.parse_nested_query("foo[]=").
|
180
|
-
|
162
|
+
must_equal "foo" => [""]
|
181
163
|
Rack::Utils.parse_nested_query("foo[]=bar").
|
182
|
-
|
164
|
+
must_equal "foo" => ["bar"]
|
183
165
|
Rack::Utils.parse_nested_query("foo[]=bar&foo").
|
184
|
-
|
166
|
+
must_equal "foo" => nil
|
185
167
|
Rack::Utils.parse_nested_query("foo[]=bar&foo[").
|
186
|
-
|
168
|
+
must_equal "foo" => ["bar"], "foo[" => nil
|
187
169
|
Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz").
|
188
|
-
|
170
|
+
must_equal "foo" => ["bar"], "foo[" => "baz"
|
189
171
|
Rack::Utils.parse_nested_query("foo[]=bar&foo[]").
|
190
|
-
|
172
|
+
must_equal "foo" => ["bar", nil]
|
191
173
|
Rack::Utils.parse_nested_query("foo[]=bar&foo[]=").
|
192
|
-
|
174
|
+
must_equal "foo" => ["bar", ""]
|
193
175
|
|
194
176
|
Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
|
195
|
-
|
177
|
+
must_equal "foo" => ["1", "2"]
|
196
178
|
Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
|
197
|
-
|
179
|
+
must_equal "foo" => "bar", "baz" => ["1", "2", "3"]
|
198
180
|
Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
|
199
|
-
|
181
|
+
must_equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
|
200
182
|
|
201
183
|
Rack::Utils.parse_nested_query("x[y][z]=1").
|
202
|
-
|
184
|
+
must_equal "x" => {"y" => {"z" => "1"}}
|
203
185
|
Rack::Utils.parse_nested_query("x[y][z][]=1").
|
204
|
-
|
186
|
+
must_equal "x" => {"y" => {"z" => ["1"]}}
|
205
187
|
Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
|
206
|
-
|
188
|
+
must_equal "x" => {"y" => {"z" => "2"}}
|
207
189
|
Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
|
208
|
-
|
190
|
+
must_equal "x" => {"y" => {"z" => ["1", "2"]}}
|
209
191
|
|
210
192
|
Rack::Utils.parse_nested_query("x[y][][z]=1").
|
211
|
-
|
193
|
+
must_equal "x" => {"y" => [{"z" => "1"}]}
|
212
194
|
Rack::Utils.parse_nested_query("x[y][][z][]=1").
|
213
|
-
|
195
|
+
must_equal "x" => {"y" => [{"z" => ["1"]}]}
|
214
196
|
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
|
215
|
-
|
197
|
+
must_equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
|
216
198
|
|
217
199
|
Rack::Utils.parse_nested_query("x[y][][v][w]=1").
|
218
|
-
|
200
|
+
must_equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
|
219
201
|
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
|
220
|
-
|
202
|
+
must_equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
|
221
203
|
|
222
204
|
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
|
223
|
-
|
205
|
+
must_equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
|
224
206
|
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
|
225
|
-
|
207
|
+
must_equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
|
208
|
+
|
209
|
+
Rack::Utils.parse_nested_query("x[][y]=1&x[][z][w]=a&x[][y]=2&x[][z][w]=b").
|
210
|
+
must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
|
211
|
+
Rack::Utils.parse_nested_query("x[][z][w]=a&x[][y]=1&x[][z][w]=b&x[][y]=2").
|
212
|
+
must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
|
213
|
+
|
214
|
+
Rack::Utils.parse_nested_query("data[books][][data][page]=1&data[books][][data][page]=2").
|
215
|
+
must_equal "data" => { "books" => [{ "data" => { "page" => "1"}}, { "data" => { "page" => "2"}}] }
|
226
216
|
|
227
217
|
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
|
228
|
-
|
229
|
-
message.
|
218
|
+
must_raise(Rack::Utils::ParameterTypeError).
|
219
|
+
message.must_equal "expected Hash (got String) for param `y'"
|
230
220
|
|
231
221
|
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
|
232
|
-
|
233
|
-
message.
|
222
|
+
must_raise(Rack::Utils::ParameterTypeError).
|
223
|
+
message.must_match(/expected Array \(got [^)]*\) for param `x'/)
|
234
224
|
|
235
225
|
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
|
236
|
-
|
237
|
-
message.
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
226
|
+
must_raise(Rack::Utils::ParameterTypeError).
|
227
|
+
message.must_equal "expected Array (got String) for param `y'"
|
228
|
+
|
229
|
+
lambda { Rack::Utils.parse_nested_query("foo%81E=1") }.
|
230
|
+
must_raise(Rack::Utils::InvalidParameterError).
|
231
|
+
message.must_equal "invalid byte sequence in UTF-8"
|
232
|
+
end
|
233
|
+
|
234
|
+
it "only moves to a new array when the full key has been seen" do
|
235
|
+
Rack::Utils.parse_nested_query("x[][y][][z]=1&x[][y][][w]=2").
|
236
|
+
must_equal "x" => [{"y" => [{"z" => "1", "w" => "2"}]}]
|
237
|
+
|
238
|
+
Rack::Utils.parse_nested_query(
|
239
|
+
"x[][id]=1&x[][y][a]=5&x[][y][b]=7&x[][z][id]=3&x[][z][w]=0&x[][id]=2&x[][y][a]=6&x[][y][b]=8&x[][z][id]=4&x[][z][w]=0"
|
240
|
+
).must_equal "x" => [
|
241
|
+
{"id" => "1", "y" => {"a" => "5", "b" => "7"}, "z" => {"id" => "3", "w" => "0"}},
|
242
|
+
{"id" => "2", "y" => {"a" => "6", "b" => "8"}, "z" => {"id" => "4", "w" => "0"}},
|
243
|
+
]
|
244
|
+
end
|
245
|
+
|
246
|
+
it "allow setting the params hash class to use for parsing query strings" do
|
247
|
+
begin
|
248
|
+
default_parser = Rack::Utils.default_query_parser
|
249
|
+
param_parser_class = Class.new(Rack::QueryParser::Params) do
|
250
|
+
def initialize(*)
|
251
|
+
super
|
252
|
+
@params = Hash.new{|h,k| h[k.to_s] if k.is_a?(Symbol)}
|
253
|
+
end
|
254
|
+
end
|
255
|
+
Rack::Utils.default_query_parser = Rack::QueryParser.new(param_parser_class, 65536, 100)
|
256
|
+
Rack::Utils.parse_query(",foo=bar;,", ";,")[:foo].must_equal "bar"
|
257
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2")[:x][:y][0][:z].must_equal "1"
|
258
|
+
ensure
|
259
|
+
Rack::Utils.default_query_parser = default_parser
|
243
260
|
end
|
244
261
|
end
|
245
262
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
Rack::Utils.build_nested_query("foo" =>
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
Rack::Utils.build_nested_query("foo" => [
|
269
|
-
|
270
|
-
Rack::Utils.build_nested_query(
|
271
|
-
|
272
|
-
Rack::Utils.build_nested_query(
|
273
|
-
|
274
|
-
Rack::Utils.build_nested_query('foo' =>
|
275
|
-
|
276
|
-
Rack::Utils.build_nested_query('foo' =>
|
277
|
-
|
278
|
-
Rack::Utils.build_nested_query('foo' => '
|
279
|
-
|
280
|
-
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' =>
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
263
|
+
it "build query strings correctly" do
|
264
|
+
assert_query "foo=bar", "foo" => "bar"
|
265
|
+
assert_query "foo=bar&foo=quux", "foo" => ["bar", "quux"]
|
266
|
+
assert_query "foo=1&bar=2", "foo" => "1", "bar" => "2"
|
267
|
+
assert_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F",
|
268
|
+
"my weird field" => "q1!2\"'w$5&7/z8)?")
|
269
|
+
end
|
270
|
+
|
271
|
+
it "build nested query strings correctly" do
|
272
|
+
Rack::Utils.build_nested_query("foo" => nil).must_equal "foo"
|
273
|
+
Rack::Utils.build_nested_query("foo" => "").must_equal "foo="
|
274
|
+
Rack::Utils.build_nested_query("foo" => "bar").must_equal "foo=bar"
|
275
|
+
|
276
|
+
assert_nested_query("foo=1&bar=2",
|
277
|
+
"foo" => "1", "bar" => "2")
|
278
|
+
assert_nested_query("foo=1&bar=2",
|
279
|
+
"foo" => 1, "bar" => 2)
|
280
|
+
assert_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F",
|
281
|
+
"my weird field" => "q1!2\"'w$5&7/z8)?")
|
282
|
+
|
283
|
+
Rack::Utils.build_nested_query("foo" => [nil]).must_equal "foo[]"
|
284
|
+
Rack::Utils.build_nested_query("foo" => [""]).must_equal "foo[]="
|
285
|
+
Rack::Utils.build_nested_query("foo" => ["bar"]).must_equal "foo[]=bar"
|
286
|
+
Rack::Utils.build_nested_query('foo' => []).must_equal ''
|
287
|
+
Rack::Utils.build_nested_query('foo' => {}).must_equal ''
|
288
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).must_equal 'foo=bar'
|
289
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).must_equal 'foo=bar'
|
290
|
+
|
291
|
+
Rack::Utils.build_nested_query('foo' => nil, 'bar' => '').
|
292
|
+
must_equal 'foo&bar='
|
293
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => '').
|
294
|
+
must_equal 'foo=bar&baz='
|
295
|
+
Rack::Utils.build_nested_query('foo' => ['1', '2']).
|
296
|
+
must_equal 'foo[]=1&foo[]=2'
|
297
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => ['1', '2', '3']).
|
298
|
+
must_equal 'foo=bar&baz[]=1&baz[]=2&baz[]=3'
|
299
|
+
Rack::Utils.build_nested_query('foo' => ['bar'], 'baz' => ['1', '2', '3']).
|
300
|
+
must_equal 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3'
|
301
|
+
Rack::Utils.build_nested_query('foo' => ['bar'], 'baz' => ['1', '2', '3']).
|
302
|
+
must_equal 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3'
|
303
|
+
Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => '1' } }).
|
304
|
+
must_equal 'x[y][z]=1'
|
305
|
+
Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => ['1'] } }).
|
306
|
+
must_equal 'x[y][z][]=1'
|
307
|
+
Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => ['1', '2'] } }).
|
308
|
+
must_equal 'x[y][z][]=1&x[y][z][]=2'
|
309
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1' }] }).
|
310
|
+
must_equal 'x[y][][z]=1'
|
311
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => ['1'] }] }).
|
312
|
+
must_equal 'x[y][][z][]=1'
|
313
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => '2' }] }).
|
314
|
+
must_equal 'x[y][][z]=1&x[y][][w]=2'
|
315
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'v' => { 'w' => '1' } }] }).
|
316
|
+
must_equal 'x[y][][v][w]=1'
|
317
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'v' => { 'w' => '2' } }] }).
|
318
|
+
must_equal 'x[y][][z]=1&x[y][][v][w]=2'
|
319
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1' }, { 'z' => '2' }] }).
|
320
|
+
must_equal 'x[y][][z]=1&x[y][][z]=2'
|
321
|
+
Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => 'a' }, { 'z' => '2', 'w' => '3' }] }).
|
322
|
+
must_equal 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3'
|
323
|
+
Rack::Utils.build_nested_query({"foo" => ["1", ["2"]]}).
|
324
|
+
must_equal 'foo[]=1&foo[][]=2'
|
325
|
+
|
326
|
+
lambda { Rack::Utils.build_nested_query("foo=bar") }.
|
327
|
+
must_raise(ArgumentError).
|
328
|
+
message.must_equal "value must be a Hash"
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'performs the inverse function of #parse_nested_query' do
|
286
332
|
[{"foo" => nil, "bar" => ""},
|
287
333
|
{"foo" => "bar", "baz" => ""},
|
288
334
|
{"foo" => ["1", "2"]},
|
@@ -299,296 +345,345 @@ describe Rack::Utils do
|
|
299
345
|
{"x" => {"y" => [{"v" => {"w" => "1"}}]}},
|
300
346
|
{"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
|
301
347
|
{"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
|
302
|
-
{"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
|
348
|
+
{"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}},
|
349
|
+
{"foo" => ["1", ["2"]]},
|
303
350
|
].each { |params|
|
304
351
|
qs = Rack::Utils.build_nested_query(params)
|
305
|
-
Rack::Utils.parse_nested_query(qs).
|
352
|
+
Rack::Utils.parse_nested_query(qs).must_equal params
|
306
353
|
}
|
307
354
|
|
308
355
|
lambda { Rack::Utils.build_nested_query("foo=bar") }.
|
309
|
-
|
310
|
-
message.
|
356
|
+
must_raise(ArgumentError).
|
357
|
+
message.must_equal "value must be a Hash"
|
311
358
|
end
|
312
359
|
|
313
|
-
|
360
|
+
it "parse query strings that have a non-existent value" do
|
314
361
|
key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
|
315
|
-
Rack::Utils.parse_query(key).
|
362
|
+
Rack::Utils.parse_query(key).must_equal Rack::Utils.unescape(key) => nil
|
316
363
|
end
|
317
364
|
|
318
|
-
|
365
|
+
it "build query strings without = with non-existent values" do
|
319
366
|
key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
|
320
367
|
key = Rack::Utils.unescape(key)
|
321
|
-
Rack::Utils.build_query(key => nil).
|
368
|
+
Rack::Utils.build_query(key => nil).must_equal Rack::Utils.escape(key)
|
322
369
|
end
|
323
370
|
|
324
|
-
|
371
|
+
it "parse q-values" do
|
325
372
|
# XXX handle accept-extension
|
326
|
-
Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").
|
373
|
+
Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").must_equal [
|
327
374
|
[ 'foo', 0.5 ],
|
328
375
|
[ 'bar', 1.0 ],
|
329
376
|
[ 'baz', 0.9 ]
|
330
377
|
]
|
331
378
|
end
|
332
379
|
|
333
|
-
|
334
|
-
Rack::Utils.best_q_match("text/html", %w[text/html]).
|
380
|
+
it "select best quality match" do
|
381
|
+
Rack::Utils.best_q_match("text/html", %w[text/html]).must_equal "text/html"
|
335
382
|
|
336
383
|
# More specific matches are preferred
|
337
|
-
Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).
|
384
|
+
Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).must_equal "text/html"
|
338
385
|
|
339
386
|
# Higher quality matches are preferred
|
340
|
-
Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).
|
387
|
+
Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).must_equal "text/plain"
|
341
388
|
|
342
389
|
# Respect requested content type
|
343
|
-
Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).
|
390
|
+
Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).must_equal "application/json"
|
344
391
|
|
345
392
|
# All else equal, the available mimes are preferred in order
|
346
|
-
Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).
|
347
|
-
Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).
|
393
|
+
Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).must_equal "text/html"
|
394
|
+
Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).must_equal "text/html"
|
348
395
|
|
349
396
|
# When there are no matches, return nil:
|
350
|
-
Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).
|
397
|
+
Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).must_be_nil
|
351
398
|
end
|
352
399
|
|
353
|
-
|
354
|
-
Rack::Utils.escape_html("foo").
|
355
|
-
Rack::Utils.escape_html("f&o").
|
356
|
-
Rack::Utils.escape_html("f<o").
|
357
|
-
Rack::Utils.escape_html("f>o").
|
358
|
-
Rack::Utils.escape_html("f'o").
|
359
|
-
Rack::Utils.escape_html('f"o').
|
360
|
-
Rack::Utils.escape_html("f/o").
|
361
|
-
Rack::Utils.escape_html("<foo></foo>").
|
400
|
+
it "escape html entities [&><'\"/]" do
|
401
|
+
Rack::Utils.escape_html("foo").must_equal "foo"
|
402
|
+
Rack::Utils.escape_html("f&o").must_equal "f&o"
|
403
|
+
Rack::Utils.escape_html("f<o").must_equal "f<o"
|
404
|
+
Rack::Utils.escape_html("f>o").must_equal "f>o"
|
405
|
+
Rack::Utils.escape_html("f'o").must_equal "f'o"
|
406
|
+
Rack::Utils.escape_html('f"o').must_equal "f"o"
|
407
|
+
Rack::Utils.escape_html("f/o").must_equal "f/o"
|
408
|
+
Rack::Utils.escape_html("<foo></foo>").must_equal "<foo></foo>"
|
362
409
|
end
|
363
410
|
|
364
|
-
|
411
|
+
it "escape html entities even on MRI when it's bugged" do
|
365
412
|
test_escape = lambda do
|
366
|
-
|
367
|
-
Rack::Utils.escape_html("\300<").should.equal "\300<"
|
368
|
-
end
|
413
|
+
Rack::Utils.escape_html("\300<").must_equal "\300<"
|
369
414
|
end
|
370
415
|
|
371
|
-
|
372
|
-
test_escape.call
|
373
|
-
else
|
374
|
-
test_escape.should.raise(ArgumentError)
|
375
|
-
end
|
416
|
+
test_escape.must_raise ArgumentError
|
376
417
|
end
|
377
418
|
|
378
|
-
|
379
|
-
should "escape html entities in unicode strings" do
|
419
|
+
it "escape html entities in unicode strings" do
|
380
420
|
# the following will cause warnings if the regex is poorly encoded:
|
381
|
-
|
382
|
-
end
|
421
|
+
Rack::Utils.escape_html("☃").must_equal "☃"
|
383
422
|
end
|
384
423
|
|
385
|
-
|
424
|
+
it "figure out which encodings are acceptable" do
|
386
425
|
helper = lambda do |a, b|
|
387
426
|
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
|
388
427
|
Rack::Utils.select_best_encoding(a, b)
|
389
428
|
end
|
390
429
|
|
391
|
-
helper.call(%w(), [["x", 1]]).
|
392
|
-
helper.call(%w(identity), [["identity", 0.0]]).
|
393
|
-
helper.call(%w(identity), [["*", 0.0]]).
|
430
|
+
helper.call(%w(), [["x", 1]]).must_be_nil
|
431
|
+
helper.call(%w(identity), [["identity", 0.0]]).must_be_nil
|
432
|
+
helper.call(%w(identity), [["*", 0.0]]).must_be_nil
|
433
|
+
|
434
|
+
helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).must_equal "identity"
|
435
|
+
|
436
|
+
helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).must_equal "compress"
|
437
|
+
helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).must_equal "gzip"
|
394
438
|
|
395
|
-
helper.call(%w(identity), [
|
439
|
+
helper.call(%w(foo bar identity), []).must_equal "identity"
|
440
|
+
helper.call(%w(foo bar identity), [["*", 1.0]]).must_equal "foo"
|
441
|
+
helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).must_equal "bar"
|
442
|
+
|
443
|
+
helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).must_equal "identity"
|
444
|
+
helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).must_equal "identity"
|
445
|
+
end
|
446
|
+
|
447
|
+
it "should perform constant time string comparison" do
|
448
|
+
Rack::Utils.secure_compare('a', 'a').must_equal true
|
449
|
+
Rack::Utils.secure_compare('a', 'b').must_equal false
|
450
|
+
end
|
451
|
+
|
452
|
+
it "return status code for integer" do
|
453
|
+
Rack::Utils.status_code(200).must_equal 200
|
454
|
+
end
|
396
455
|
|
397
|
-
|
398
|
-
|
456
|
+
it "return status code for string" do
|
457
|
+
Rack::Utils.status_code("200").must_equal 200
|
458
|
+
end
|
399
459
|
|
400
|
-
|
401
|
-
|
402
|
-
|
460
|
+
it "return status code for symbol" do
|
461
|
+
Rack::Utils.status_code(:ok).must_equal 200
|
462
|
+
end
|
403
463
|
|
404
|
-
|
405
|
-
|
464
|
+
it "return rfc2822 format from rfc2822 helper" do
|
465
|
+
Rack::Utils.rfc2822(Time.at(0).gmtime).must_equal "Thu, 01 Jan 1970 00:00:00 -0000"
|
406
466
|
end
|
407
467
|
|
408
|
-
|
409
|
-
Rack::Utils.
|
468
|
+
it "return rfc2109 format from rfc2109 helper" do
|
469
|
+
Rack::Utils.rfc2109(Time.at(0).gmtime).must_equal "Thu, 01-Jan-1970 00:00:00 GMT"
|
410
470
|
end
|
411
471
|
|
412
|
-
|
413
|
-
Rack::Utils.
|
414
|
-
Rack::Utils.
|
472
|
+
it "clean directory traversal" do
|
473
|
+
Rack::Utils.clean_path_info("/cgi/../cgi/test").must_equal "/cgi/test"
|
474
|
+
Rack::Utils.clean_path_info(".").must_be_empty
|
475
|
+
Rack::Utils.clean_path_info("test/..").must_be_empty
|
415
476
|
end
|
416
477
|
|
417
|
-
|
418
|
-
Rack::Utils.
|
478
|
+
it "clean unsafe directory traversal to safe path" do
|
479
|
+
Rack::Utils.clean_path_info("/../README.rdoc").must_equal "/README.rdoc"
|
480
|
+
Rack::Utils.clean_path_info("../test/spec_utils.rb").must_equal "test/spec_utils.rb"
|
419
481
|
end
|
420
482
|
|
421
|
-
|
422
|
-
Rack::Utils.
|
483
|
+
it "not clean directory traversal with encoded periods" do
|
484
|
+
Rack::Utils.clean_path_info("/%2E%2E/README").must_equal "/%2E%2E/README"
|
423
485
|
end
|
424
486
|
|
425
|
-
|
426
|
-
Rack::Utils.
|
487
|
+
it "clean slash only paths" do
|
488
|
+
Rack::Utils.clean_path_info("/").must_equal "/"
|
427
489
|
end
|
490
|
+
end
|
491
|
+
|
492
|
+
describe Rack::Utils, "cookies" do
|
493
|
+
it "parses cookies" do
|
494
|
+
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "zoo=m")
|
495
|
+
Rack::Utils.parse_cookies(env).must_equal({"zoo" => "m"})
|
496
|
+
|
497
|
+
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%")
|
498
|
+
Rack::Utils.parse_cookies(env).must_equal({"foo" => "%"})
|
499
|
+
|
500
|
+
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;foo=car")
|
501
|
+
Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar"})
|
502
|
+
|
503
|
+
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
|
504
|
+
Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar", "quux" => "h&m"})
|
428
505
|
|
429
|
-
|
430
|
-
Rack::Utils.
|
506
|
+
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar").freeze
|
507
|
+
Rack::Utils.parse_cookies(env).must_equal({"foo" => "bar"})
|
431
508
|
end
|
432
509
|
|
433
|
-
|
434
|
-
Rack::Utils.
|
510
|
+
it "adds new cookies to nil header" do
|
511
|
+
Rack::Utils.add_cookie_to_header(nil, 'name', 'value').must_equal 'name=value'
|
435
512
|
end
|
436
513
|
|
437
|
-
|
438
|
-
|
439
|
-
Rack::Utils.
|
440
|
-
|
514
|
+
it "adds new cookies to blank header" do
|
515
|
+
header = ''
|
516
|
+
Rack::Utils.add_cookie_to_header(header, 'name', 'value').must_equal 'name=value'
|
517
|
+
header.must_equal ''
|
441
518
|
end
|
442
519
|
|
443
|
-
|
444
|
-
|
445
|
-
Rack::Utils.
|
520
|
+
it "adds new cookies to string header" do
|
521
|
+
header = 'existing-cookie'
|
522
|
+
Rack::Utils.add_cookie_to_header(header, 'name', 'value').must_equal "existing-cookie\nname=value"
|
523
|
+
header.must_equal 'existing-cookie'
|
446
524
|
end
|
447
525
|
|
448
|
-
|
449
|
-
|
526
|
+
it "adds new cookies to array header" do
|
527
|
+
header = %w[ existing-cookie ]
|
528
|
+
Rack::Utils.add_cookie_to_header(header, 'name', 'value').must_equal "existing-cookie\nname=value"
|
529
|
+
header.must_equal %w[ existing-cookie ]
|
450
530
|
end
|
451
531
|
|
452
|
-
|
453
|
-
|
532
|
+
it "adds new cookies to an unrecognized header" do
|
533
|
+
lambda {
|
534
|
+
Rack::Utils.add_cookie_to_header(Object.new, 'name', 'value')
|
535
|
+
}.must_raise ArgumentError
|
454
536
|
end
|
455
537
|
end
|
456
538
|
|
457
539
|
describe Rack::Utils, "byte_range" do
|
458
|
-
|
459
|
-
Rack::Utils.byte_ranges({},500).
|
460
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).
|
461
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).
|
462
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).
|
463
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).
|
464
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).
|
540
|
+
it "ignore missing or syntactically invalid byte ranges" do
|
541
|
+
Rack::Utils.byte_ranges({},500).must_be_nil
|
542
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).must_be_nil
|
543
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).must_be_nil
|
544
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).must_be_nil
|
545
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).must_be_nil
|
546
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).must_be_nil
|
465
547
|
# A range of non-positive length is syntactically invalid and ignored:
|
466
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).
|
467
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).
|
548
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).must_be_nil
|
549
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).must_be_nil
|
468
550
|
end
|
469
551
|
|
470
|
-
|
471
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).
|
472
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).
|
473
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).
|
474
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).
|
475
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).
|
552
|
+
it "parse simple byte ranges" do
|
553
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).must_equal [(123..456)]
|
554
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).must_equal [(123..499)]
|
555
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).must_equal [(400..499)]
|
556
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).must_equal [(0..0)]
|
557
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).must_equal [(499..499)]
|
476
558
|
end
|
477
559
|
|
478
|
-
|
479
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).
|
560
|
+
it "parse several byte ranges" do
|
561
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).must_equal [(500..600),(601..999)]
|
480
562
|
end
|
481
563
|
|
482
|
-
|
483
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).
|
484
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).
|
485
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).
|
564
|
+
it "truncate byte ranges" do
|
565
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).must_equal [(123..499)]
|
566
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).must_equal []
|
567
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).must_equal [(0..499)]
|
486
568
|
end
|
487
569
|
|
488
|
-
|
489
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).
|
490
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).
|
491
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).
|
492
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).
|
570
|
+
it "ignore unsatisfiable byte ranges" do
|
571
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).must_equal []
|
572
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).must_equal []
|
573
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).must_equal []
|
574
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).must_equal []
|
493
575
|
end
|
494
576
|
|
495
|
-
|
496
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).
|
497
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).
|
498
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).
|
499
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).
|
500
|
-
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).
|
577
|
+
it "handle byte ranges of empty files" do
|
578
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).must_equal []
|
579
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).must_equal []
|
580
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).must_equal []
|
581
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).must_equal []
|
582
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).must_equal []
|
501
583
|
end
|
502
584
|
end
|
503
585
|
|
504
586
|
describe Rack::Utils::HeaderHash do
|
505
|
-
|
587
|
+
it "retain header case" do
|
506
588
|
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
|
507
589
|
h['ETag'] = 'Boo!'
|
508
|
-
h.to_hash.
|
590
|
+
h.to_hash.must_equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
|
509
591
|
end
|
510
592
|
|
511
|
-
|
593
|
+
it "check existence of keys case insensitively" do
|
512
594
|
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
|
513
|
-
h.
|
514
|
-
h.
|
595
|
+
h.must_include 'content-md5'
|
596
|
+
h.wont_include 'ETag'
|
597
|
+
end
|
598
|
+
|
599
|
+
it "create deep HeaderHash copy on dup" do
|
600
|
+
h1 = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
|
601
|
+
h2 = h1.dup
|
602
|
+
|
603
|
+
h1.must_include 'content-md5'
|
604
|
+
h2.must_include 'content-md5'
|
605
|
+
|
606
|
+
h2.delete("Content-MD5")
|
607
|
+
|
608
|
+
h2.wont_include 'content-md5'
|
609
|
+
h1.must_include 'content-md5'
|
515
610
|
end
|
516
611
|
|
517
|
-
|
612
|
+
it "merge case-insensitively" do
|
518
613
|
h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
|
519
614
|
merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
|
520
|
-
merged.
|
615
|
+
merged.must_equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
|
521
616
|
end
|
522
617
|
|
523
|
-
|
618
|
+
it "overwrite case insensitively and assume the new key's case" do
|
524
619
|
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
|
525
620
|
h["foo-bar"] = "bizzle"
|
526
|
-
h["FOO-BAR"].
|
527
|
-
h.length.
|
528
|
-
h.to_hash.
|
621
|
+
h["FOO-BAR"].must_equal "bizzle"
|
622
|
+
h.length.must_equal 1
|
623
|
+
h.to_hash.must_equal "foo-bar" => "bizzle"
|
529
624
|
end
|
530
625
|
|
531
|
-
|
626
|
+
it "be converted to real Hash" do
|
532
627
|
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
533
|
-
h.to_hash.
|
628
|
+
h.to_hash.must_be_instance_of Hash
|
534
629
|
end
|
535
630
|
|
536
|
-
|
631
|
+
it "convert Array values to Strings when converting to Hash" do
|
537
632
|
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
|
538
|
-
h.to_hash.
|
633
|
+
h.to_hash.must_equal({ "foo" => "bar\nbaz" })
|
539
634
|
end
|
540
635
|
|
541
|
-
|
636
|
+
it "replace hashes correctly" do
|
542
637
|
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
|
543
638
|
j = {"foo" => "bar"}
|
544
639
|
h.replace(j)
|
545
|
-
h["foo"].
|
640
|
+
h["foo"].must_equal "bar"
|
546
641
|
end
|
547
642
|
|
548
|
-
|
643
|
+
it "be able to delete the given key case-sensitively" do
|
549
644
|
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
550
645
|
h.delete("foo")
|
551
|
-
h["foo"].
|
552
|
-
h["FOO"].
|
646
|
+
h["foo"].must_be_nil
|
647
|
+
h["FOO"].must_be_nil
|
553
648
|
end
|
554
649
|
|
555
|
-
|
650
|
+
it "be able to delete the given key case-insensitively" do
|
556
651
|
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
557
652
|
h.delete("FOO")
|
558
|
-
h["foo"].
|
559
|
-
h["FOO"].
|
653
|
+
h["foo"].must_be_nil
|
654
|
+
h["FOO"].must_be_nil
|
560
655
|
end
|
561
656
|
|
562
|
-
|
657
|
+
it "return the deleted value when #delete is called on an existing key" do
|
563
658
|
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
564
|
-
h.delete("Foo").
|
659
|
+
h.delete("Foo").must_equal "bar"
|
565
660
|
end
|
566
661
|
|
567
|
-
|
662
|
+
it "return nil when #delete is called on a non-existant key" do
|
568
663
|
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
569
|
-
h.delete("Hello").
|
664
|
+
h.delete("Hello").must_be_nil
|
570
665
|
end
|
571
666
|
|
572
|
-
|
667
|
+
it "avoid unnecessary object creation if possible" do
|
573
668
|
a = Rack::Utils::HeaderHash.new("foo" => "bar")
|
574
669
|
b = Rack::Utils::HeaderHash.new(a)
|
575
|
-
b.object_id.
|
576
|
-
b.
|
670
|
+
b.object_id.must_equal a.object_id
|
671
|
+
b.must_equal a
|
577
672
|
end
|
578
673
|
|
579
|
-
|
674
|
+
it "convert Array values to Strings when responding to #each" do
|
580
675
|
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
|
581
676
|
h.each do |k,v|
|
582
|
-
k.
|
583
|
-
v.
|
677
|
+
k.must_equal "foo"
|
678
|
+
v.must_equal "bar\nbaz"
|
584
679
|
end
|
585
680
|
end
|
586
681
|
|
587
|
-
|
682
|
+
it "not create headers out of thin air" do
|
588
683
|
h = Rack::Utils::HeaderHash.new
|
589
684
|
h['foo']
|
590
|
-
h['foo'].
|
591
|
-
h.
|
685
|
+
h['foo'].must_be_nil
|
686
|
+
h.wont_include 'foo'
|
592
687
|
end
|
593
688
|
end
|
594
689
|
|
@@ -605,27 +700,27 @@ describe Rack::Utils::Context do
|
|
605
700
|
test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
|
606
701
|
test_app = ContextTest.new test_target4
|
607
702
|
|
608
|
-
|
609
|
-
test_app.app.
|
703
|
+
it "set context correctly" do
|
704
|
+
test_app.app.must_equal test_target4
|
610
705
|
c1 = Rack::Utils::Context.new(test_app, test_target1)
|
611
|
-
c1.for.
|
612
|
-
c1.app.
|
706
|
+
c1.for.must_equal test_app
|
707
|
+
c1.app.must_equal test_target1
|
613
708
|
c2 = Rack::Utils::Context.new(test_app, test_target2)
|
614
|
-
c2.for.
|
615
|
-
c2.app.
|
709
|
+
c2.for.must_equal test_app
|
710
|
+
c2.app.must_equal test_target2
|
616
711
|
end
|
617
712
|
|
618
|
-
|
713
|
+
it "alter app on recontexting" do
|
619
714
|
c1 = Rack::Utils::Context.new(test_app, test_target1)
|
620
715
|
c2 = c1.recontext(test_target2)
|
621
|
-
c2.for.
|
622
|
-
c2.app.
|
716
|
+
c2.for.must_equal test_app
|
717
|
+
c2.app.must_equal test_target2
|
623
718
|
c3 = c2.recontext(test_target3)
|
624
|
-
c3.for.
|
625
|
-
c3.app.
|
719
|
+
c3.for.must_equal test_app
|
720
|
+
c3.app.must_equal test_target3
|
626
721
|
end
|
627
722
|
|
628
|
-
|
723
|
+
it "run different apps" do
|
629
724
|
c1 = Rack::Utils::Context.new test_app, test_target1
|
630
725
|
c2 = c1.recontext test_target2
|
631
726
|
c3 = c2.recontext test_target3
|
@@ -633,15 +728,15 @@ describe Rack::Utils::Context do
|
|
633
728
|
a4 = Rack::Lint.new c4
|
634
729
|
a5 = Rack::Lint.new test_app
|
635
730
|
r1 = c1.call('hello')
|
636
|
-
r1.
|
731
|
+
r1.must_equal 'hello world'
|
637
732
|
r2 = c2.call(2)
|
638
|
-
r2.
|
733
|
+
r2.must_equal 4
|
639
734
|
r3 = c3.call(:misc_symbol)
|
640
|
-
r3.
|
735
|
+
r3.must_be_nil
|
641
736
|
r4 = Rack::MockRequest.new(a4).get('/')
|
642
|
-
r4.status.
|
737
|
+
r4.status.must_equal 200
|
643
738
|
r5 = Rack::MockRequest.new(a5).get('/')
|
644
|
-
r5.status.
|
645
|
-
r4.body.
|
739
|
+
r5.status.must_equal 200
|
740
|
+
r4.body.must_equal r5.body
|
646
741
|
end
|
647
742
|
end
|