lack 2.0.0
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/bin/rackup +5 -0
- data/lib/rack.rb +26 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +166 -0
- data/lib/rack/handler.rb +63 -0
- data/lib/rack/handler/webrick.rb +120 -0
- data/lib/rack/mime.rb +661 -0
- data/lib/rack/mock.rb +198 -0
- data/lib/rack/multipart.rb +31 -0
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +239 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/request.rb +394 -0
- data/lib/rack/response.rb +160 -0
- data/lib/rack/server.rb +258 -0
- data/lib/rack/server/options.rb +121 -0
- data/lib/rack/utils.rb +653 -0
- data/lib/rack/version.rb +3 -0
- data/spec/spec_helper.rb +1 -0
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/spec_body_proxy.rb +69 -0
- data/test/spec_builder.rb +223 -0
- data/test/spec_chunked.rb +101 -0
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +59 -0
- data/test/spec_head.rb +45 -0
- data/test/spec_lint.rb +522 -0
- data/test/spec_mime.rb +51 -0
- data/test/spec_mock.rb +277 -0
- data/test/spec_multipart.rb +547 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1199 -0
- data/test/spec_response.rb +343 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_utils.rb +635 -0
- data/test/spec_webrick.rb +184 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +240 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/sendfile'
|
4
|
+
require 'rack/mock'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
describe Rack::File do
|
8
|
+
should "respond to #to_path" do
|
9
|
+
Rack::File.new(Dir.pwd).should.respond_to :to_path
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Rack::Sendfile do
|
14
|
+
def sendfile_body
|
15
|
+
FileUtils.touch File.join(Dir.tmpdir, "rack_sendfile")
|
16
|
+
res = ['Hello World']
|
17
|
+
def res.to_path ; File.join(Dir.tmpdir, "rack_sendfile") ; end
|
18
|
+
res
|
19
|
+
end
|
20
|
+
|
21
|
+
def simple_app(body=sendfile_body)
|
22
|
+
lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def sendfile_app(body, mappings = [])
|
26
|
+
Rack::Lint.new Rack::Sendfile.new(simple_app(body), nil, mappings)
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(headers={}, body=sendfile_body, mappings=[])
|
30
|
+
yield Rack::MockRequest.new(sendfile_app(body, mappings)).get('/', headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
def open_file(path)
|
34
|
+
Class.new(File) do
|
35
|
+
unless method_defined?(:to_path)
|
36
|
+
alias :to_path :path
|
37
|
+
end
|
38
|
+
end.open(path, 'wb+')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "does nothing when no X-Sendfile-Type header present" do
|
42
|
+
request do |response|
|
43
|
+
response.should.be.ok
|
44
|
+
response.body.should.equal 'Hello World'
|
45
|
+
response.headers.should.not.include 'X-Sendfile'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "sets X-Sendfile response header and discards body" do
|
50
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
|
51
|
+
response.should.be.ok
|
52
|
+
response.body.should.be.empty
|
53
|
+
response.headers['Content-Length'].should.equal '0'
|
54
|
+
response.headers['X-Sendfile'].should.equal File.join(Dir.tmpdir, "rack_sendfile")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "sets X-Lighttpd-Send-File response header and discards body" do
|
59
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
|
60
|
+
response.should.be.ok
|
61
|
+
response.body.should.be.empty
|
62
|
+
response.headers['Content-Length'].should.equal '0'
|
63
|
+
response.headers['X-Lighttpd-Send-File'].should.equal File.join(Dir.tmpdir, "rack_sendfile")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "sets X-Accel-Redirect response header and discards body" do
|
68
|
+
headers = {
|
69
|
+
'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
|
70
|
+
'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar/"
|
71
|
+
}
|
72
|
+
request headers do |response|
|
73
|
+
response.should.be.ok
|
74
|
+
response.body.should.be.empty
|
75
|
+
response.headers['Content-Length'].should.equal '0'
|
76
|
+
response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'writes to rack.error when no X-Accel-Mapping is specified' do
|
81
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
|
82
|
+
response.should.be.ok
|
83
|
+
response.body.should.equal 'Hello World'
|
84
|
+
response.headers.should.not.include 'X-Accel-Redirect'
|
85
|
+
response.errors.should.include 'X-Accel-Mapping'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'does nothing when body does not respond to #to_path' do
|
90
|
+
request({'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile'}, ['Not a file...']) do |response|
|
91
|
+
response.body.should.equal 'Not a file...'
|
92
|
+
response.headers.should.not.include 'X-Sendfile'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "sets X-Accel-Redirect response header and discards body when initialized with multiple mappings" do
|
97
|
+
begin
|
98
|
+
dir1 = Dir.mktmpdir
|
99
|
+
dir2 = Dir.mktmpdir
|
100
|
+
|
101
|
+
first_body = open_file(File.join(dir1, 'rack_sendfile'))
|
102
|
+
first_body.puts 'hello world'
|
103
|
+
|
104
|
+
second_body = open_file(File.join(dir2, 'rack_sendfile'))
|
105
|
+
second_body.puts 'goodbye world'
|
106
|
+
|
107
|
+
mappings = [
|
108
|
+
["#{dir1}/", '/foo/bar/'],
|
109
|
+
["#{dir2}/", '/wibble/']
|
110
|
+
]
|
111
|
+
|
112
|
+
request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, first_body, mappings) do |response|
|
113
|
+
response.should.be.ok
|
114
|
+
response.body.should.be.empty
|
115
|
+
response.headers['Content-Length'].should.equal '0'
|
116
|
+
response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile'
|
117
|
+
end
|
118
|
+
|
119
|
+
request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, second_body, mappings) do |response|
|
120
|
+
response.should.be.ok
|
121
|
+
response.body.should.be.empty
|
122
|
+
response.headers['Content-Length'].should.equal '0'
|
123
|
+
response.headers['X-Accel-Redirect'].should.equal '/wibble/rack_sendfile'
|
124
|
+
end
|
125
|
+
ensure
|
126
|
+
FileUtils.remove_entry_secure dir1
|
127
|
+
FileUtils.remove_entry_secure dir2
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/test/spec_server.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/server'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'socket'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
describe Rack::Server do
|
8
|
+
|
9
|
+
def app
|
10
|
+
lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_stderr
|
14
|
+
old, $stderr = $stderr, StringIO.new
|
15
|
+
yield $stderr
|
16
|
+
ensure
|
17
|
+
$stderr = old
|
18
|
+
end
|
19
|
+
|
20
|
+
it "overrides :config if :app is passed in" do
|
21
|
+
server = Rack::Server.new(:app => "FOO")
|
22
|
+
server.app.should.equal "FOO"
|
23
|
+
end
|
24
|
+
|
25
|
+
should "prefer to use :builder when it is passed in" do
|
26
|
+
server = Rack::Server.new(:builder => "run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }")
|
27
|
+
server.app.class.should.equal Proc
|
28
|
+
Rack::MockRequest.new(server.app).get("/").body.to_s.should.equal 'success'
|
29
|
+
end
|
30
|
+
|
31
|
+
should "allow subclasses to override middleware" do
|
32
|
+
server = Class.new(Rack::Server).class_eval { def middleware; Hash.new [] end; self }
|
33
|
+
server.middleware['deployment'].should.not.equal []
|
34
|
+
server.new(:app => 'foo').middleware['deployment'].should.equal []
|
35
|
+
end
|
36
|
+
|
37
|
+
should "allow subclasses to override default middleware" do
|
38
|
+
server = Class.new(Rack::Server).instance_eval { def default_middleware_by_environment; Hash.new [] end; self }
|
39
|
+
server.middleware['deployment'].should.equal []
|
40
|
+
server.new(:app => 'foo').middleware['deployment'].should.equal []
|
41
|
+
end
|
42
|
+
|
43
|
+
should "only provide default middleware for development and deployment environments" do
|
44
|
+
Rack::Server.default_middleware_by_environment.keys.sort.should.equal %w(deployment development)
|
45
|
+
end
|
46
|
+
|
47
|
+
should "always return an empty array for unknown environments" do
|
48
|
+
server = Rack::Server.new(:app => 'foo')
|
49
|
+
server.middleware['production'].should.equal []
|
50
|
+
end
|
51
|
+
|
52
|
+
should "not include Rack::Lint in deployment environment" do
|
53
|
+
server = Rack::Server.new(:app => 'foo')
|
54
|
+
server.middleware['deployment'].flatten.should.not.include(Rack::Lint)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "not include Rack::ShowExceptions in deployment environment" do
|
58
|
+
server = Rack::Server.new(:app => 'foo')
|
59
|
+
server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "include Rack::TempfileReaper in deployment environment" do
|
63
|
+
server = Rack::Server.new(:app => 'foo')
|
64
|
+
server.middleware['deployment'].flatten.should.include(Rack::TempfileReaper)
|
65
|
+
end
|
66
|
+
|
67
|
+
should "support CGI" do
|
68
|
+
begin
|
69
|
+
o, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], 'foo'
|
70
|
+
server = Rack::Server.new(:app => 'foo')
|
71
|
+
server.server.name =~ /CGI/
|
72
|
+
Rack::Server.logging_middleware.call(server).should.eql(nil)
|
73
|
+
ensure
|
74
|
+
ENV['REQUEST_METHOD'] = o
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
should "be quiet if said so" do
|
79
|
+
server = Rack::Server.new(:app => "FOO", :quiet => true)
|
80
|
+
Rack::Server.logging_middleware.call(server).should.eql(nil)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "use a full path to the pidfile" do
|
84
|
+
# avoids issues with daemonize chdir
|
85
|
+
opts = Rack::Server.new.send(:parse_options, %w[--pid testing.pid])
|
86
|
+
opts[:pid].should.eql(::File.expand_path('testing.pid'))
|
87
|
+
end
|
88
|
+
|
89
|
+
should "run a server" do
|
90
|
+
pidfile = Tempfile.open('pidfile') { |f| break f }.path
|
91
|
+
FileUtils.rm pidfile
|
92
|
+
server = Rack::Server.new(
|
93
|
+
:app => app,
|
94
|
+
:environment => 'none',
|
95
|
+
:pid => pidfile,
|
96
|
+
:Port => TCPServer.open('127.0.0.1', 0){|s| s.addr[1] },
|
97
|
+
:Host => '127.0.0.1',
|
98
|
+
:daemonize => false,
|
99
|
+
:server => 'webrick'
|
100
|
+
)
|
101
|
+
t = Thread.new { server.start { |s| Thread.current[:server] = s } }
|
102
|
+
t.join(0.01) until t[:server] && t[:server].status != :Stop
|
103
|
+
body = open("http://127.0.0.1:#{server.options[:Port]}/") { |f| f.read }
|
104
|
+
body.should.eql('success')
|
105
|
+
|
106
|
+
Process.kill(:INT, $$)
|
107
|
+
t.join
|
108
|
+
open(pidfile) { |f| f.read.should.eql $$.to_s }
|
109
|
+
end
|
110
|
+
|
111
|
+
should "check pid file presence and running process" do
|
112
|
+
pidfile = Tempfile.open('pidfile') { |f| f.write($$); break f }.path
|
113
|
+
server = Rack::Server.new(:pid => pidfile)
|
114
|
+
server.send(:pidfile_process_status).should.eql :running
|
115
|
+
end
|
116
|
+
|
117
|
+
should "check pid file presence and dead process" do
|
118
|
+
dead_pid = `echo $$`.to_i
|
119
|
+
pidfile = Tempfile.open('pidfile') { |f| f.write(dead_pid); break f }.path
|
120
|
+
server = Rack::Server.new(:pid => pidfile)
|
121
|
+
server.send(:pidfile_process_status).should.eql :dead
|
122
|
+
end
|
123
|
+
|
124
|
+
should "check pid file presence and exited process" do
|
125
|
+
pidfile = Tempfile.open('pidfile') { |f| break f }.path
|
126
|
+
::File.delete(pidfile)
|
127
|
+
server = Rack::Server.new(:pid => pidfile)
|
128
|
+
server.send(:pidfile_process_status).should.eql :exited
|
129
|
+
end
|
130
|
+
|
131
|
+
should "check pid file presence and not owned process" do
|
132
|
+
pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
|
133
|
+
server = Rack::Server.new(:pid => pidfile)
|
134
|
+
server.send(:pidfile_process_status).should.eql :not_owned
|
135
|
+
end
|
136
|
+
|
137
|
+
should "not write pid file when it is created after check" do
|
138
|
+
pidfile = Tempfile.open('pidfile') { |f| break f }.path
|
139
|
+
::File.delete(pidfile)
|
140
|
+
server = Rack::Server.new(:pid => pidfile)
|
141
|
+
::File.open(pidfile, 'w') { |f| f.write(1) }
|
142
|
+
with_stderr do |err|
|
143
|
+
should.raise(SystemExit) do
|
144
|
+
server.send(:write_pid)
|
145
|
+
end
|
146
|
+
err.rewind
|
147
|
+
output = err.read
|
148
|
+
output.should.match(/already running/)
|
149
|
+
output.should.include? pidfile
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
should "inform the user about existing pidfiles with running processes" do
|
154
|
+
pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
|
155
|
+
server = Rack::Server.new(:pid => pidfile)
|
156
|
+
with_stderr do |err|
|
157
|
+
should.raise(SystemExit) do
|
158
|
+
server.start
|
159
|
+
end
|
160
|
+
err.rewind
|
161
|
+
output = err.read
|
162
|
+
output.should.match(/already running/)
|
163
|
+
output.should.include? pidfile
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
data/test/spec_utils.rb
ADDED
@@ -0,0 +1,635 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'rack/mock'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
describe Rack::Utils do
|
7
|
+
|
8
|
+
# A helper method which checks
|
9
|
+
# if certain query parameters
|
10
|
+
# are equal.
|
11
|
+
def equal_query_to(query)
|
12
|
+
parts = query.split('&')
|
13
|
+
lambda{|other| (parts & other.split('&')) == parts }
|
14
|
+
end
|
15
|
+
|
16
|
+
def kcodeu
|
17
|
+
one8 = RUBY_VERSION.to_f < 1.9
|
18
|
+
default_kcode, $KCODE = $KCODE, 'U' if one8
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
$KCODE = default_kcode if one8
|
22
|
+
end
|
23
|
+
|
24
|
+
should "round trip binary data" do
|
25
|
+
r = [218, 0].pack 'CC'
|
26
|
+
if defined?(::Encoding)
|
27
|
+
z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY)
|
28
|
+
else
|
29
|
+
z = Rack::Utils.unescape(Rack::Utils.escape(r))
|
30
|
+
end
|
31
|
+
r.should.equal z
|
32
|
+
end
|
33
|
+
|
34
|
+
should "escape correctly" do
|
35
|
+
Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
|
36
|
+
Rack::Utils.escape("a space").should.equal "a+space"
|
37
|
+
Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
|
38
|
+
should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
|
39
|
+
end
|
40
|
+
|
41
|
+
should "escape correctly for multibyte characters" do
|
42
|
+
matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
|
43
|
+
matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
|
44
|
+
Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
|
45
|
+
matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
|
46
|
+
matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
|
47
|
+
Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
|
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
|
61
|
+
|
62
|
+
should "unescape multibyte characters correctly if $KCODE is set to 'U'" do
|
63
|
+
kcodeu do
|
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
|
68
|
+
end
|
69
|
+
|
70
|
+
should "escape objects that responds to to_s" do
|
71
|
+
kcodeu do
|
72
|
+
Rack::Utils.escape(:id).should.equal "id"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if "".respond_to?(:encode)
|
77
|
+
should "escape non-UTF8 strings" do
|
78
|
+
Rack::Utils.escape("ø".encode("ISO-8859-1")).should.equal "%F8"
|
79
|
+
end
|
80
|
+
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
|
+
|
92
|
+
should "escape path spaces with %20" do
|
93
|
+
Rack::Utils.escape_path("foo bar").should.equal "foo%20bar"
|
94
|
+
end
|
95
|
+
|
96
|
+
should "unescape correctly" do
|
97
|
+
Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
|
98
|
+
Rack::Utils.unescape("a+space").should.equal "a space"
|
99
|
+
Rack::Utils.unescape("a%20space").should.equal "a space"
|
100
|
+
Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
|
101
|
+
should.equal "q1!2\"'w$5&7/z8)?\\"
|
102
|
+
end
|
103
|
+
|
104
|
+
should "parse query strings correctly" do
|
105
|
+
Rack::Utils.parse_query("foo=bar").
|
106
|
+
should.equal "foo" => "bar"
|
107
|
+
Rack::Utils.parse_query("foo=\"bar\"").
|
108
|
+
should.equal "foo" => "\"bar\""
|
109
|
+
Rack::Utils.parse_query("foo=bar&foo=quux").
|
110
|
+
should.equal "foo" => ["bar", "quux"]
|
111
|
+
Rack::Utils.parse_query("foo=1&bar=2").
|
112
|
+
should.equal "foo" => "1", "bar" => "2"
|
113
|
+
Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
|
114
|
+
should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
|
115
|
+
Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
|
116
|
+
Rack::Utils.parse_query("=").should.equal "" => ""
|
117
|
+
Rack::Utils.parse_query("=value").should.equal "" => "value"
|
118
|
+
Rack::Utils.parse_query("key=").should.equal "key" => ""
|
119
|
+
Rack::Utils.parse_query("&key&").should.equal "key" => nil
|
120
|
+
Rack::Utils.parse_query(";key;", ";,").should.equal "key" => nil
|
121
|
+
Rack::Utils.parse_query(",key,", ";,").should.equal "key" => nil
|
122
|
+
Rack::Utils.parse_query(";foo=bar,;", ";,").should.equal "foo" => "bar"
|
123
|
+
Rack::Utils.parse_query(",foo=bar;,", ";,").should.equal "foo" => "bar"
|
124
|
+
end
|
125
|
+
|
126
|
+
should "not create infinite loops with cycle structures" do
|
127
|
+
ex = { "foo" => nil }
|
128
|
+
ex["foo"] = ex
|
129
|
+
|
130
|
+
params = Rack::Utils::KeySpaceConstrainedParams.new
|
131
|
+
params['foo'] = params
|
132
|
+
lambda {
|
133
|
+
params.to_params_hash.to_s.should.equal ex.to_s
|
134
|
+
}.should.not.raise
|
135
|
+
end
|
136
|
+
|
137
|
+
should "parse nested query strings correctly" do
|
138
|
+
Rack::Utils.parse_nested_query("foo").
|
139
|
+
should.equal "foo" => nil
|
140
|
+
Rack::Utils.parse_nested_query("foo=").
|
141
|
+
should.equal "foo" => ""
|
142
|
+
Rack::Utils.parse_nested_query("foo=bar").
|
143
|
+
should.equal "foo" => "bar"
|
144
|
+
Rack::Utils.parse_nested_query("foo=\"bar\"").
|
145
|
+
should.equal "foo" => "\"bar\""
|
146
|
+
|
147
|
+
Rack::Utils.parse_nested_query("foo=bar&foo=quux").
|
148
|
+
should.equal "foo" => "quux"
|
149
|
+
Rack::Utils.parse_nested_query("foo&foo=").
|
150
|
+
should.equal "foo" => ""
|
151
|
+
Rack::Utils.parse_nested_query("foo=1&bar=2").
|
152
|
+
should.equal "foo" => "1", "bar" => "2"
|
153
|
+
Rack::Utils.parse_nested_query("&foo=1&&bar=2").
|
154
|
+
should.equal "foo" => "1", "bar" => "2"
|
155
|
+
Rack::Utils.parse_nested_query("foo&bar=").
|
156
|
+
should.equal "foo" => nil, "bar" => ""
|
157
|
+
Rack::Utils.parse_nested_query("foo=bar&baz=").
|
158
|
+
should.equal "foo" => "bar", "baz" => ""
|
159
|
+
Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
|
160
|
+
should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
|
161
|
+
|
162
|
+
Rack::Utils.parse_nested_query("a=b&pid%3D1234=1023").
|
163
|
+
should.equal "pid=1234" => "1023", "a" => "b"
|
164
|
+
|
165
|
+
Rack::Utils.parse_nested_query("foo[]").
|
166
|
+
should.equal "foo" => [nil]
|
167
|
+
Rack::Utils.parse_nested_query("foo[]=").
|
168
|
+
should.equal "foo" => [""]
|
169
|
+
Rack::Utils.parse_nested_query("foo[]=bar").
|
170
|
+
should.equal "foo" => ["bar"]
|
171
|
+
Rack::Utils.parse_nested_query("foo[]=bar&foo").
|
172
|
+
should.equal "foo" => nil
|
173
|
+
Rack::Utils.parse_nested_query("foo[]=bar&foo[").
|
174
|
+
should.equal "foo" => ["bar"], "foo[" => nil
|
175
|
+
Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz").
|
176
|
+
should.equal "foo" => ["bar"], "foo[" => "baz"
|
177
|
+
Rack::Utils.parse_nested_query("foo[]=bar&foo[]").
|
178
|
+
should.equal "foo" => ["bar", nil]
|
179
|
+
Rack::Utils.parse_nested_query("foo[]=bar&foo[]=").
|
180
|
+
should.equal "foo" => ["bar", ""]
|
181
|
+
|
182
|
+
Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
|
183
|
+
should.equal "foo" => ["1", "2"]
|
184
|
+
Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
|
185
|
+
should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
|
186
|
+
Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
|
187
|
+
should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
|
188
|
+
|
189
|
+
Rack::Utils.parse_nested_query("x[y][z]=1").
|
190
|
+
should.equal "x" => {"y" => {"z" => "1"}}
|
191
|
+
Rack::Utils.parse_nested_query("x[y][z][]=1").
|
192
|
+
should.equal "x" => {"y" => {"z" => ["1"]}}
|
193
|
+
Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
|
194
|
+
should.equal "x" => {"y" => {"z" => "2"}}
|
195
|
+
Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
|
196
|
+
should.equal "x" => {"y" => {"z" => ["1", "2"]}}
|
197
|
+
|
198
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1").
|
199
|
+
should.equal "x" => {"y" => [{"z" => "1"}]}
|
200
|
+
Rack::Utils.parse_nested_query("x[y][][z][]=1").
|
201
|
+
should.equal "x" => {"y" => [{"z" => ["1"]}]}
|
202
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
|
203
|
+
should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
|
204
|
+
|
205
|
+
Rack::Utils.parse_nested_query("x[y][][v][w]=1").
|
206
|
+
should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
|
207
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
|
208
|
+
should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
|
209
|
+
|
210
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
|
211
|
+
should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
|
212
|
+
Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
|
213
|
+
should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
|
214
|
+
|
215
|
+
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
|
216
|
+
should.raise(Rack::Utils::ParameterTypeError).
|
217
|
+
message.should.equal "expected Hash (got String) for param `y'"
|
218
|
+
|
219
|
+
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
|
220
|
+
should.raise(Rack::Utils::ParameterTypeError).
|
221
|
+
message.should.match(/expected Array \(got [^)]*\) for param `x'/)
|
222
|
+
|
223
|
+
lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
|
224
|
+
should.raise(Rack::Utils::ParameterTypeError).
|
225
|
+
message.should.equal "expected Array (got String) for param `y'"
|
226
|
+
|
227
|
+
if RUBY_VERSION.to_f > 1.9
|
228
|
+
lambda { Rack::Utils.parse_nested_query("foo%81E=1") }.
|
229
|
+
should.raise(Rack::Utils::InvalidParameterError).
|
230
|
+
message.should.equal "invalid byte sequence in UTF-8"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
should "build query strings correctly" do
|
235
|
+
Rack::Utils.build_query("foo" => "bar").should.be equal_query_to("foo=bar")
|
236
|
+
Rack::Utils.build_query("foo" => ["bar", "quux"]).
|
237
|
+
should.be equal_query_to("foo=bar&foo=quux")
|
238
|
+
Rack::Utils.build_query("foo" => "1", "bar" => "2").
|
239
|
+
should.be equal_query_to("foo=1&bar=2")
|
240
|
+
Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
|
241
|
+
should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
|
242
|
+
end
|
243
|
+
|
244
|
+
should "build nested query strings correctly" do
|
245
|
+
Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
|
246
|
+
Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
|
247
|
+
Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
|
248
|
+
|
249
|
+
Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
|
250
|
+
should.be equal_query_to("foo=1&bar=2")
|
251
|
+
Rack::Utils.build_nested_query("foo" => 1, "bar" => 2).
|
252
|
+
should.be equal_query_to("foo=1&bar=2")
|
253
|
+
Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
|
254
|
+
should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
|
255
|
+
|
256
|
+
Rack::Utils.build_nested_query("foo" => [nil]).
|
257
|
+
should.equal "foo[]"
|
258
|
+
Rack::Utils.build_nested_query("foo" => [""]).
|
259
|
+
should.equal "foo[]="
|
260
|
+
Rack::Utils.build_nested_query("foo" => ["bar"]).
|
261
|
+
should.equal "foo[]=bar"
|
262
|
+
Rack::Utils.build_nested_query('foo' => []).
|
263
|
+
should.equal ''
|
264
|
+
Rack::Utils.build_nested_query('foo' => {}).
|
265
|
+
should.equal ''
|
266
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).
|
267
|
+
should.equal 'foo=bar'
|
268
|
+
Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).
|
269
|
+
should.equal 'foo=bar'
|
270
|
+
|
271
|
+
# The ordering of the output query string is unpredictable with 1.8's
|
272
|
+
# unordered hash. Test that build_nested_query performs the inverse
|
273
|
+
# function of parse_nested_query.
|
274
|
+
[{"foo" => nil, "bar" => ""},
|
275
|
+
{"foo" => "bar", "baz" => ""},
|
276
|
+
{"foo" => ["1", "2"]},
|
277
|
+
{"foo" => "bar", "baz" => ["1", "2", "3"]},
|
278
|
+
{"foo" => ["bar"], "baz" => ["1", "2", "3"]},
|
279
|
+
{"foo" => ["1", "2"]},
|
280
|
+
{"foo" => "bar", "baz" => ["1", "2", "3"]},
|
281
|
+
{"x" => {"y" => {"z" => "1"}}},
|
282
|
+
{"x" => {"y" => {"z" => ["1"]}}},
|
283
|
+
{"x" => {"y" => {"z" => ["1", "2"]}}},
|
284
|
+
{"x" => {"y" => [{"z" => "1"}]}},
|
285
|
+
{"x" => {"y" => [{"z" => ["1"]}]}},
|
286
|
+
{"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
|
287
|
+
{"x" => {"y" => [{"v" => {"w" => "1"}}]}},
|
288
|
+
{"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
|
289
|
+
{"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
|
290
|
+
{"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
|
291
|
+
].each { |params|
|
292
|
+
qs = Rack::Utils.build_nested_query(params)
|
293
|
+
Rack::Utils.parse_nested_query(qs).should.equal params
|
294
|
+
}
|
295
|
+
|
296
|
+
lambda { Rack::Utils.build_nested_query("foo=bar") }.
|
297
|
+
should.raise(ArgumentError).
|
298
|
+
message.should.equal "value must be a Hash"
|
299
|
+
end
|
300
|
+
|
301
|
+
should "parse query strings that have a non-existent value" do
|
302
|
+
key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
|
303
|
+
Rack::Utils.parse_query(key).should.equal Rack::Utils.unescape(key) => nil
|
304
|
+
end
|
305
|
+
|
306
|
+
should "build query strings without = with non-existent values" do
|
307
|
+
key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
|
308
|
+
key = Rack::Utils.unescape(key)
|
309
|
+
Rack::Utils.build_query(key => nil).should.equal Rack::Utils.escape(key)
|
310
|
+
end
|
311
|
+
|
312
|
+
should "parse q-values" do
|
313
|
+
# XXX handle accept-extension
|
314
|
+
Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").should.equal [
|
315
|
+
[ 'foo', 0.5 ],
|
316
|
+
[ 'bar', 1.0 ],
|
317
|
+
[ 'baz', 0.9 ]
|
318
|
+
]
|
319
|
+
end
|
320
|
+
|
321
|
+
should "select best quality match" do
|
322
|
+
Rack::Utils.best_q_match("text/html", %w[text/html]).should.equal "text/html"
|
323
|
+
|
324
|
+
# More specific matches are preferred
|
325
|
+
Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).should.equal "text/html"
|
326
|
+
|
327
|
+
# Higher quality matches are preferred
|
328
|
+
Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).should.equal "text/plain"
|
329
|
+
|
330
|
+
# Respect requested content type
|
331
|
+
Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).should.equal "application/json"
|
332
|
+
|
333
|
+
# All else equal, the available mimes are preferred in order
|
334
|
+
Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).should.equal "text/html"
|
335
|
+
Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).should.equal "text/html"
|
336
|
+
|
337
|
+
# When there are no matches, return nil:
|
338
|
+
Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).should.equal nil
|
339
|
+
end
|
340
|
+
|
341
|
+
should "escape html entities [&><'\"/]" do
|
342
|
+
Rack::Utils.escape_html("foo").should.equal "foo"
|
343
|
+
Rack::Utils.escape_html("f&o").should.equal "f&o"
|
344
|
+
Rack::Utils.escape_html("f<o").should.equal "f<o"
|
345
|
+
Rack::Utils.escape_html("f>o").should.equal "f>o"
|
346
|
+
Rack::Utils.escape_html("f'o").should.equal "f'o"
|
347
|
+
Rack::Utils.escape_html('f"o').should.equal "f"o"
|
348
|
+
Rack::Utils.escape_html("f/o").should.equal "f/o"
|
349
|
+
Rack::Utils.escape_html("<foo></foo>").should.equal "<foo></foo>"
|
350
|
+
end
|
351
|
+
|
352
|
+
should "escape html entities even on MRI when it's bugged" do
|
353
|
+
test_escape = lambda do
|
354
|
+
kcodeu do
|
355
|
+
Rack::Utils.escape_html("\300<").should.equal "\300<"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
if RUBY_VERSION.to_f < 1.9
|
360
|
+
test_escape.call
|
361
|
+
else
|
362
|
+
test_escape.should.raise(ArgumentError)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
if "".respond_to?(:encode)
|
367
|
+
should "escape html entities in unicode strings" do
|
368
|
+
# the following will cause warnings if the regex is poorly encoded:
|
369
|
+
Rack::Utils.escape_html("☃").should.equal "☃"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
should "figure out which encodings are acceptable" do
|
374
|
+
helper = lambda do |a, b|
|
375
|
+
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
|
376
|
+
Rack::Utils.select_best_encoding(a, b)
|
377
|
+
end
|
378
|
+
|
379
|
+
helper.call(%w(), [["x", 1]]).should.equal(nil)
|
380
|
+
helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
|
381
|
+
helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
|
382
|
+
|
383
|
+
helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
|
384
|
+
|
385
|
+
helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
|
386
|
+
helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
|
387
|
+
|
388
|
+
helper.call(%w(foo bar identity), []).should.equal("identity")
|
389
|
+
helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
|
390
|
+
helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
|
391
|
+
|
392
|
+
helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
|
393
|
+
helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
|
394
|
+
end
|
395
|
+
|
396
|
+
should "return the bytesize of String" do
|
397
|
+
Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
|
398
|
+
end
|
399
|
+
|
400
|
+
should "should perform constant time string comparison" do
|
401
|
+
Rack::Utils.secure_compare('a', 'a').should.equal true
|
402
|
+
Rack::Utils.secure_compare('a', 'b').should.equal false
|
403
|
+
end
|
404
|
+
|
405
|
+
should "return status code for integer" do
|
406
|
+
Rack::Utils.status_code(200).should.equal 200
|
407
|
+
end
|
408
|
+
|
409
|
+
should "return status code for string" do
|
410
|
+
Rack::Utils.status_code("200").should.equal 200
|
411
|
+
end
|
412
|
+
|
413
|
+
should "return status code for symbol" do
|
414
|
+
Rack::Utils.status_code(:ok).should.equal 200
|
415
|
+
end
|
416
|
+
|
417
|
+
should "return rfc2822 format from rfc2822 helper" do
|
418
|
+
Rack::Utils.rfc2822(Time.at(0).gmtime).should == "Thu, 01 Jan 1970 00:00:00 -0000"
|
419
|
+
end
|
420
|
+
|
421
|
+
should "return rfc2109 format from rfc2109 helper" do
|
422
|
+
Rack::Utils.rfc2109(Time.at(0).gmtime).should == "Thu, 01-Jan-1970 00:00:00 GMT"
|
423
|
+
end
|
424
|
+
|
425
|
+
should "clean directory traversal" do
|
426
|
+
Rack::Utils.clean_path_info("/cgi/../cgi/test").should.equal "/cgi/test"
|
427
|
+
Rack::Utils.clean_path_info(".").should.empty
|
428
|
+
Rack::Utils.clean_path_info("test/..").should.empty
|
429
|
+
end
|
430
|
+
|
431
|
+
should "clean unsafe directory traversal to safe path" do
|
432
|
+
Rack::Utils.clean_path_info("/../README.rdoc").should.equal "/README.rdoc"
|
433
|
+
Rack::Utils.clean_path_info("../test/spec_utils.rb").should.equal "test/spec_utils.rb"
|
434
|
+
end
|
435
|
+
|
436
|
+
should "not clean directory traversal with encoded periods" do
|
437
|
+
Rack::Utils.clean_path_info("/%2E%2E/README").should.equal "/%2E%2E/README"
|
438
|
+
end
|
439
|
+
|
440
|
+
should "clean slash only paths" do
|
441
|
+
Rack::Utils.clean_path_info("/").should.equal "/"
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe Rack::Utils, "byte_range" do
|
446
|
+
should "ignore missing or syntactically invalid byte ranges" do
|
447
|
+
Rack::Utils.byte_ranges({},500).should.equal nil
|
448
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).should.equal nil
|
449
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).should.equal nil
|
450
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).should.equal nil
|
451
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).should.equal nil
|
452
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).should.equal nil
|
453
|
+
# A range of non-positive length is syntactically invalid and ignored:
|
454
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).should.equal nil
|
455
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).should.equal nil
|
456
|
+
end
|
457
|
+
|
458
|
+
should "parse simple byte ranges" do
|
459
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).should.equal [(123..456)]
|
460
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).should.equal [(123..499)]
|
461
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).should.equal [(400..499)]
|
462
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).should.equal [(0..0)]
|
463
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).should.equal [(499..499)]
|
464
|
+
end
|
465
|
+
|
466
|
+
should "parse several byte ranges" do
|
467
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).should.equal [(500..600),(601..999)]
|
468
|
+
end
|
469
|
+
|
470
|
+
should "truncate byte ranges" do
|
471
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).should.equal [(123..499)]
|
472
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).should.equal []
|
473
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).should.equal [(0..499)]
|
474
|
+
end
|
475
|
+
|
476
|
+
should "ignore unsatisfiable byte ranges" do
|
477
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).should.equal []
|
478
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).should.equal []
|
479
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).should.equal []
|
480
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).should.equal []
|
481
|
+
end
|
482
|
+
|
483
|
+
should "handle byte ranges of empty files" do
|
484
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).should.equal []
|
485
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).should.equal []
|
486
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).should.equal []
|
487
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).should.equal []
|
488
|
+
Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).should.equal []
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
describe Rack::Utils::HeaderHash do
|
493
|
+
should "retain header case" do
|
494
|
+
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
|
495
|
+
h['ETag'] = 'Boo!'
|
496
|
+
h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
|
497
|
+
end
|
498
|
+
|
499
|
+
should "check existence of keys case insensitively" do
|
500
|
+
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
|
501
|
+
h.should.include 'content-md5'
|
502
|
+
h.should.not.include 'ETag'
|
503
|
+
end
|
504
|
+
|
505
|
+
should "merge case-insensitively" do
|
506
|
+
h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
|
507
|
+
merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
|
508
|
+
merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
|
509
|
+
end
|
510
|
+
|
511
|
+
should "overwrite case insensitively and assume the new key's case" do
|
512
|
+
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
|
513
|
+
h["foo-bar"] = "bizzle"
|
514
|
+
h["FOO-BAR"].should.equal "bizzle"
|
515
|
+
h.length.should.equal 1
|
516
|
+
h.to_hash.should.equal "foo-bar" => "bizzle"
|
517
|
+
end
|
518
|
+
|
519
|
+
should "be converted to real Hash" do
|
520
|
+
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
521
|
+
h.to_hash.should.be.instance_of Hash
|
522
|
+
end
|
523
|
+
|
524
|
+
should "convert Array values to Strings when converting to Hash" do
|
525
|
+
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
|
526
|
+
h.to_hash.should.equal({ "foo" => "bar\nbaz" })
|
527
|
+
end
|
528
|
+
|
529
|
+
should "replace hashes correctly" do
|
530
|
+
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
|
531
|
+
j = {"foo" => "bar"}
|
532
|
+
h.replace(j)
|
533
|
+
h["foo"].should.equal "bar"
|
534
|
+
end
|
535
|
+
|
536
|
+
should "be able to delete the given key case-sensitively" do
|
537
|
+
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
538
|
+
h.delete("foo")
|
539
|
+
h["foo"].should.be.nil
|
540
|
+
h["FOO"].should.be.nil
|
541
|
+
end
|
542
|
+
|
543
|
+
should "be able to delete the given key case-insensitively" do
|
544
|
+
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
545
|
+
h.delete("FOO")
|
546
|
+
h["foo"].should.be.nil
|
547
|
+
h["FOO"].should.be.nil
|
548
|
+
end
|
549
|
+
|
550
|
+
should "return the deleted value when #delete is called on an existing key" do
|
551
|
+
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
552
|
+
h.delete("Foo").should.equal("bar")
|
553
|
+
end
|
554
|
+
|
555
|
+
should "return nil when #delete is called on a non-existant key" do
|
556
|
+
h = Rack::Utils::HeaderHash.new("foo" => "bar")
|
557
|
+
h.delete("Hello").should.be.nil
|
558
|
+
end
|
559
|
+
|
560
|
+
should "avoid unnecessary object creation if possible" do
|
561
|
+
a = Rack::Utils::HeaderHash.new("foo" => "bar")
|
562
|
+
b = Rack::Utils::HeaderHash.new(a)
|
563
|
+
b.object_id.should.equal(a.object_id)
|
564
|
+
b.should.equal(a)
|
565
|
+
end
|
566
|
+
|
567
|
+
should "convert Array values to Strings when responding to #each" do
|
568
|
+
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
|
569
|
+
h.each do |k,v|
|
570
|
+
k.should.equal("foo")
|
571
|
+
v.should.equal("bar\nbaz")
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
should "not create headers out of thin air" do
|
576
|
+
h = Rack::Utils::HeaderHash.new
|
577
|
+
h['foo']
|
578
|
+
h['foo'].should.be.nil
|
579
|
+
h.should.not.include 'foo'
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
describe Rack::Utils::Context do
|
584
|
+
class ContextTest
|
585
|
+
attr_reader :app
|
586
|
+
def initialize app; @app=app; end
|
587
|
+
def call env; context env; end
|
588
|
+
def context env, app=@app; app.call(env); end
|
589
|
+
end
|
590
|
+
test_target1 = proc{|e| e.to_s+' world' }
|
591
|
+
test_target2 = proc{|e| e.to_i+2 }
|
592
|
+
test_target3 = proc{|e| nil }
|
593
|
+
test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
|
594
|
+
test_app = ContextTest.new test_target4
|
595
|
+
|
596
|
+
should "set context correctly" do
|
597
|
+
test_app.app.should.equal test_target4
|
598
|
+
c1 = Rack::Utils::Context.new(test_app, test_target1)
|
599
|
+
c1.for.should.equal test_app
|
600
|
+
c1.app.should.equal test_target1
|
601
|
+
c2 = Rack::Utils::Context.new(test_app, test_target2)
|
602
|
+
c2.for.should.equal test_app
|
603
|
+
c2.app.should.equal test_target2
|
604
|
+
end
|
605
|
+
|
606
|
+
should "alter app on recontexting" do
|
607
|
+
c1 = Rack::Utils::Context.new(test_app, test_target1)
|
608
|
+
c2 = c1.recontext(test_target2)
|
609
|
+
c2.for.should.equal test_app
|
610
|
+
c2.app.should.equal test_target2
|
611
|
+
c3 = c2.recontext(test_target3)
|
612
|
+
c3.for.should.equal test_app
|
613
|
+
c3.app.should.equal test_target3
|
614
|
+
end
|
615
|
+
|
616
|
+
should "run different apps" do
|
617
|
+
c1 = Rack::Utils::Context.new test_app, test_target1
|
618
|
+
c2 = c1.recontext test_target2
|
619
|
+
c3 = c2.recontext test_target3
|
620
|
+
c4 = c3.recontext test_target4
|
621
|
+
a4 = Rack::Lint.new c4
|
622
|
+
a5 = Rack::Lint.new test_app
|
623
|
+
r1 = c1.call('hello')
|
624
|
+
r1.should.equal 'hello world'
|
625
|
+
r2 = c2.call(2)
|
626
|
+
r2.should.equal 4
|
627
|
+
r3 = c3.call(:misc_symbol)
|
628
|
+
r3.should.be.nil
|
629
|
+
r4 = Rack::MockRequest.new(a4).get('/')
|
630
|
+
r4.status.should.equal 200
|
631
|
+
r5 = Rack::MockRequest.new(a5).get('/')
|
632
|
+
r5.status.should.equal 200
|
633
|
+
r4.body.should.equal r5.body
|
634
|
+
end
|
635
|
+
end
|