lack 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/bin/rackup +5 -0
  3. data/lib/rack.rb +26 -0
  4. data/lib/rack/body_proxy.rb +39 -0
  5. data/lib/rack/builder.rb +166 -0
  6. data/lib/rack/handler.rb +63 -0
  7. data/lib/rack/handler/webrick.rb +120 -0
  8. data/lib/rack/mime.rb +661 -0
  9. data/lib/rack/mock.rb +198 -0
  10. data/lib/rack/multipart.rb +31 -0
  11. data/lib/rack/multipart/generator.rb +93 -0
  12. data/lib/rack/multipart/parser.rb +239 -0
  13. data/lib/rack/multipart/uploaded_file.rb +34 -0
  14. data/lib/rack/request.rb +394 -0
  15. data/lib/rack/response.rb +160 -0
  16. data/lib/rack/server.rb +258 -0
  17. data/lib/rack/server/options.rb +121 -0
  18. data/lib/rack/utils.rb +653 -0
  19. data/lib/rack/version.rb +3 -0
  20. data/spec/spec_helper.rb +1 -0
  21. data/test/builder/anything.rb +5 -0
  22. data/test/builder/comment.ru +4 -0
  23. data/test/builder/end.ru +5 -0
  24. data/test/builder/line.ru +1 -0
  25. data/test/builder/options.ru +2 -0
  26. data/test/multipart/bad_robots +259 -0
  27. data/test/multipart/binary +0 -0
  28. data/test/multipart/content_type_and_no_filename +6 -0
  29. data/test/multipart/empty +10 -0
  30. data/test/multipart/fail_16384_nofile +814 -0
  31. data/test/multipart/file1.txt +1 -0
  32. data/test/multipart/filename_and_modification_param +7 -0
  33. data/test/multipart/filename_and_no_name +6 -0
  34. data/test/multipart/filename_with_escaped_quotes +6 -0
  35. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  36. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  37. data/test/multipart/filename_with_unescaped_percentages +6 -0
  38. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  39. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  40. data/test/multipart/filename_with_unescaped_quotes +6 -0
  41. data/test/multipart/ie +6 -0
  42. data/test/multipart/invalid_character +6 -0
  43. data/test/multipart/mixed_files +21 -0
  44. data/test/multipart/nested +10 -0
  45. data/test/multipart/none +9 -0
  46. data/test/multipart/semicolon +6 -0
  47. data/test/multipart/text +15 -0
  48. data/test/multipart/webkit +32 -0
  49. data/test/rackup/config.ru +31 -0
  50. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  51. data/test/spec_body_proxy.rb +69 -0
  52. data/test/spec_builder.rb +223 -0
  53. data/test/spec_chunked.rb +101 -0
  54. data/test/spec_file.rb +221 -0
  55. data/test/spec_handler.rb +59 -0
  56. data/test/spec_head.rb +45 -0
  57. data/test/spec_lint.rb +522 -0
  58. data/test/spec_mime.rb +51 -0
  59. data/test/spec_mock.rb +277 -0
  60. data/test/spec_multipart.rb +547 -0
  61. data/test/spec_recursive.rb +72 -0
  62. data/test/spec_request.rb +1199 -0
  63. data/test/spec_response.rb +343 -0
  64. data/test/spec_rewindable_input.rb +118 -0
  65. data/test/spec_sendfile.rb +130 -0
  66. data/test/spec_server.rb +167 -0
  67. data/test/spec_utils.rb +635 -0
  68. data/test/spec_webrick.rb +184 -0
  69. data/test/testrequest.rb +78 -0
  70. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  71. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  72. metadata +240 -0
@@ -0,0 +1 @@
1
+ contents
@@ -0,0 +1,7 @@
1
+ --AaB03x
2
+ Content-Type: image/jpeg
3
+ Content-Disposition: attachment; name="files"; filename=genome.jpeg; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
4
+ Content-Description: a complete map of the human genome
5
+
6
+ contents
7
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; filename="file1.txt"
3
+ Content-Type: text/plain
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="escape \"quotes"
3
+ Content-Type: application/octet-stream
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,7 @@
1
+ --AaB03x
2
+ Content-Type: image/jpeg
3
+ Content-Disposition: attachment; name="files"; filename=""human" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
4
+ Content-Description: a complete map of the human genome
5
+
6
+ contents
7
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="escape %22quotes"
3
+ Content-Type: application/octet-stream
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al
2
+ Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"
3
+ Content-Type: image/jpeg
4
+
5
+ contents
6
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al--
@@ -0,0 +1,6 @@
1
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al
2
+ Content-Disposition: form-data; name="document[attachment]"; filename="100%a"
3
+ Content-Type: image/jpeg
4
+
5
+ contents
6
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al--
@@ -0,0 +1,6 @@
1
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al
2
+ Content-Disposition: form-data; name="document[attachment]"; filename="100%"
3
+ Content-Type: image/jpeg
4
+
5
+ contents
6
+ ------WebKitFormBoundary2NHc7OhsgU68l3Al--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="escape "quotes"
3
+ Content-Type: application/octet-stream
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"
3
+ Content-Type: text/plain
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="invalid�.txt"
3
+ Content-Type: text/plain
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,21 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="foo"
3
+
4
+ bar
5
+ --AaB03x
6
+ Content-Disposition: form-data; name="files"
7
+ Content-Type: multipart/mixed, boundary=BbC04y
8
+
9
+ --BbC04y
10
+ Content-Disposition: attachment; filename="file.txt"
11
+ Content-Type: text/plain
12
+
13
+ contents
14
+ --BbC04y
15
+ Content-Disposition: attachment; filename="flowers.jpg"
16
+ Content-Type: image/jpeg
17
+ Content-Transfer-Encoding: binary
18
+
19
+ contents
20
+ --BbC04y--
21
+ --AaB03x--
@@ -0,0 +1,10 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="foo[submit-name]"
3
+
4
+ Larry
5
+ --AaB03x
6
+ Content-Disposition: form-data; name="foo[files]"; filename="file1.txt"
7
+ Content-Type: text/plain
8
+
9
+ contents
10
+ --AaB03x--
@@ -0,0 +1,9 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="submit-name"
3
+
4
+ Larry
5
+ --AaB03x
6
+ Content-Disposition: form-data; name="files"; filename=""
7
+
8
+
9
+ --AaB03x--
@@ -0,0 +1,6 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="files"; filename="fi;le1.txt"
3
+ Content-Type: text/plain
4
+
5
+ contents
6
+ --AaB03x--
@@ -0,0 +1,15 @@
1
+ --AaB03x
2
+ Content-Disposition: form-data; name="submit-name"
3
+
4
+ Larry
5
+ --AaB03x
6
+ Content-Disposition: form-data; name="submit-name-with-content"
7
+ Content-Type: text/plain
8
+
9
+ Berry
10
+ --AaB03x
11
+ Content-Disposition: form-data; name="files"; filename="file1.txt"
12
+ Content-Type: text/plain
13
+
14
+ contents
15
+ --AaB03x--
@@ -0,0 +1,32 @@
1
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
2
+ Content-Disposition: form-data; name="_method"
3
+
4
+ put
5
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
6
+ Content-Disposition: form-data; name="profile[blog]"
7
+
8
+
9
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
10
+ Content-Disposition: form-data; name="profile[public_email]"
11
+
12
+
13
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
14
+ Content-Disposition: form-data; name="profile[interests]"
15
+
16
+
17
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
18
+ Content-Disposition: form-data; name="profile[bio]"
19
+
20
+ hello
21
+
22
+ "quote"
23
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
24
+ Content-Disposition: form-data; name="media"; filename=""
25
+ Content-Type: application/octet-stream
26
+
27
+
28
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR
29
+ Content-Disposition: form-data; name="commit"
30
+
31
+ Save
32
+ ------WebKitFormBoundaryWLHCs9qmcJJoyjKR--
@@ -0,0 +1,31 @@
1
+ require "#{File.dirname(__FILE__)}/../testrequest"
2
+
3
+ $stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w")
4
+
5
+ class EnvMiddleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ # provides a way to test that lint is present
12
+ if env["PATH_INFO"] == "/broken_lint"
13
+ return [200, {}, ["Broken Lint"]]
14
+ # provides a way to kill the process without knowing the pid
15
+ elsif env["PATH_INFO"] == "/die"
16
+ exit!
17
+ end
18
+
19
+ env["test.$DEBUG"] = $DEBUG
20
+ env["test.$EVAL"] = BUKKIT if defined?(BUKKIT)
21
+ env["test.$VERBOSE"] = $VERBOSE
22
+ env["test.$LOAD_PATH"] = $LOAD_PATH
23
+ env["test.stderr"] = File.expand_path($stderr.path)
24
+ env["test.Ping"] = defined?(Ping)
25
+ env["test.pid"] = Process.pid
26
+ @app.call(env)
27
+ end
28
+ end
29
+
30
+ use EnvMiddleware
31
+ run TestRequest.new
@@ -0,0 +1,8 @@
1
+ module Rack
2
+ module Handler
3
+ class RegisteringMyself
4
+ end
5
+
6
+ register :registering_myself, RegisteringMyself
7
+ end
8
+ end
@@ -0,0 +1,69 @@
1
+ require 'rack/body_proxy'
2
+ require 'stringio'
3
+
4
+ describe Rack::BodyProxy do
5
+ should 'call each on the wrapped body' do
6
+ called = false
7
+ proxy = Rack::BodyProxy.new(['foo']) { }
8
+ proxy.each do |str|
9
+ called = true
10
+ str.should.equal 'foo'
11
+ end
12
+ called.should.equal true
13
+ end
14
+
15
+ should 'call close on the wrapped body' do
16
+ body = StringIO.new
17
+ proxy = Rack::BodyProxy.new(body) { }
18
+ proxy.close
19
+ body.should.be.closed
20
+ end
21
+
22
+ should 'only call close on the wrapped body if it responds to close' do
23
+ body = []
24
+ proxy = Rack::BodyProxy.new(body) { }
25
+ proc { proxy.close }.should.not.raise
26
+ end
27
+
28
+ should 'call the passed block on close' do
29
+ called = false
30
+ proxy = Rack::BodyProxy.new([]) { called = true }
31
+ called.should.equal false
32
+ proxy.close
33
+ called.should.equal true
34
+ end
35
+
36
+ should 'call the passed block on close even if there is an exception' do
37
+ object = Object.new
38
+ def object.close() raise "No!" end
39
+ called = false
40
+
41
+ begin
42
+ proxy = Rack::BodyProxy.new(object) { called = true }
43
+ called.should.equal false
44
+ proxy.close
45
+ rescue RuntimeError => e
46
+ end
47
+
48
+ raise "Expected exception to have been raised" unless e
49
+ called.should.equal true
50
+ end
51
+
52
+ should 'not close more than one time' do
53
+ count = 0
54
+ proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 }
55
+ 2.times { proxy.close }
56
+ count.should.equal 1
57
+ end
58
+
59
+ should 'be closed when the callback is triggered' do
60
+ closed = false
61
+ proxy = Rack::BodyProxy.new([]) { closed = proxy.closed? }
62
+ proxy.close
63
+ closed.should.equal true
64
+ end
65
+
66
+ should 'provide an #each method' do
67
+ Rack::BodyProxy.method_defined?(:each).should.equal true
68
+ end
69
+ end
@@ -0,0 +1,223 @@
1
+ require 'rack/builder'
2
+ require 'rack/lint'
3
+ require 'rack/mock'
4
+ require 'rack/showexceptions'
5
+ require 'rack/urlmap'
6
+
7
+ class NothingMiddleware
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+ def call(env)
12
+ @@env = env
13
+ response = @app.call(env)
14
+ response
15
+ end
16
+ def self.env
17
+ @@env
18
+ end
19
+ end
20
+
21
+ describe Rack::Builder do
22
+ def builder(&block)
23
+ Rack::Lint.new Rack::Builder.new(&block)
24
+ end
25
+
26
+ def builder_to_app(&block)
27
+ Rack::Lint.new Rack::Builder.new(&block).to_app
28
+ end
29
+
30
+ it "supports mapping" do
31
+ app = builder_to_app do
32
+ map '/' do |outer_env|
33
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
34
+ end
35
+ map '/sub' do
36
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
37
+ end
38
+ end
39
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
40
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
41
+ end
42
+
43
+ it "doesn't dupe env even when mapping" do
44
+ app = builder_to_app do
45
+ use NothingMiddleware
46
+ map '/' do |outer_env|
47
+ run lambda { |inner_env|
48
+ inner_env['new_key'] = 'new_value'
49
+ [200, {"Content-Type" => "text/plain"}, ['root']]
50
+ }
51
+ end
52
+ end
53
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
54
+ NothingMiddleware.env['new_key'].should.equal 'new_value'
55
+ end
56
+
57
+ it "chains apps by default" do
58
+ app = builder_to_app do
59
+ use Rack::ShowExceptions
60
+ run lambda { |env| raise "bzzzt" }
61
+ end
62
+
63
+ Rack::MockRequest.new(app).get("/").should.be.server_error
64
+ Rack::MockRequest.new(app).get("/").should.be.server_error
65
+ Rack::MockRequest.new(app).get("/").should.be.server_error
66
+ end
67
+
68
+ it "has implicit #to_app" do
69
+ app = builder do
70
+ use Rack::ShowExceptions
71
+ run lambda { |env| raise "bzzzt" }
72
+ end
73
+
74
+ Rack::MockRequest.new(app).get("/").should.be.server_error
75
+ Rack::MockRequest.new(app).get("/").should.be.server_error
76
+ Rack::MockRequest.new(app).get("/").should.be.server_error
77
+ end
78
+
79
+ it "supports blocks on use" do
80
+ app = builder do
81
+ use Rack::ShowExceptions
82
+ use Rack::Auth::Basic do |username, password|
83
+ 'secret' == password
84
+ end
85
+
86
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
87
+ end
88
+
89
+ response = Rack::MockRequest.new(app).get("/")
90
+ response.should.be.client_error
91
+ response.status.should.equal 401
92
+
93
+ # with auth...
94
+ response = Rack::MockRequest.new(app).get("/",
95
+ 'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
96
+ response.status.should.equal 200
97
+ response.body.to_s.should.equal 'Hi Boss'
98
+ end
99
+
100
+ it "has explicit #to_app" do
101
+ app = builder do
102
+ use Rack::ShowExceptions
103
+ run lambda { |env| raise "bzzzt" }
104
+ end
105
+
106
+ Rack::MockRequest.new(app).get("/").should.be.server_error
107
+ Rack::MockRequest.new(app).get("/").should.be.server_error
108
+ Rack::MockRequest.new(app).get("/").should.be.server_error
109
+ end
110
+
111
+ it "can mix map and run for endpoints" do
112
+ app = builder do
113
+ map '/sub' do
114
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
115
+ end
116
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
117
+ end
118
+
119
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
120
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
121
+ end
122
+
123
+ it "accepts middleware-only map blocks" do
124
+ app = builder do
125
+ map('/foo') { use Rack::ShowExceptions }
126
+ run lambda { |env| raise "bzzzt" }
127
+ end
128
+
129
+ proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
130
+ Rack::MockRequest.new(app).get("/foo").should.be.server_error
131
+ end
132
+
133
+ it "yields the generated app to a block for warmup" do
134
+ warmed_up_app = nil
135
+
136
+ app = Rack::Builder.new do
137
+ warmup { |a| warmed_up_app = a }
138
+ run lambda { |env| [200, {}, []] }
139
+ end.to_app
140
+
141
+ warmed_up_app.should.equal app
142
+ end
143
+
144
+ should "initialize apps once" do
145
+ app = builder do
146
+ class AppClass
147
+ def initialize
148
+ @called = 0
149
+ end
150
+ def call(env)
151
+ raise "bzzzt" if @called > 0
152
+ @called += 1
153
+ [200, {'Content-Type' => 'text/plain'}, ['OK']]
154
+ end
155
+ end
156
+
157
+ use Rack::ShowExceptions
158
+ run AppClass.new
159
+ end
160
+
161
+ Rack::MockRequest.new(app).get("/").status.should.equal 200
162
+ Rack::MockRequest.new(app).get("/").should.be.server_error
163
+ end
164
+
165
+ it "allows use after run" do
166
+ app = builder do
167
+ run lambda { |env| raise "bzzzt" }
168
+ use Rack::ShowExceptions
169
+ end
170
+
171
+ Rack::MockRequest.new(app).get("/").should.be.server_error
172
+ Rack::MockRequest.new(app).get("/").should.be.server_error
173
+ Rack::MockRequest.new(app).get("/").should.be.server_error
174
+ end
175
+
176
+ it 'complains about a missing run' do
177
+ proc do
178
+ Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
179
+ end.should.raise(RuntimeError)
180
+ end
181
+
182
+ describe "parse_file" do
183
+ def config_file(name)
184
+ File.join(File.dirname(__FILE__), 'builder', name)
185
+ end
186
+
187
+ it "parses commented options" do
188
+ app, options = Rack::Builder.parse_file config_file('options.ru')
189
+ options[:debug].should.be.true
190
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
191
+ end
192
+
193
+ it "removes __END__ before evaluating app" do
194
+ app, _ = Rack::Builder.parse_file config_file('end.ru')
195
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
196
+ end
197
+
198
+ it "supports multi-line comments" do
199
+ lambda {
200
+ Rack::Builder.parse_file config_file('comment.ru')
201
+ }.should.not.raise(SyntaxError)
202
+ end
203
+
204
+ it "requires anything not ending in .ru" do
205
+ $: << File.dirname(__FILE__)
206
+ app, * = Rack::Builder.parse_file 'builder/anything'
207
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
208
+ $:.pop
209
+ end
210
+
211
+ it "sets __LINE__ correctly" do
212
+ app, _ = Rack::Builder.parse_file config_file('line.ru')
213
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal '1'
214
+ end
215
+ end
216
+
217
+ describe 'new_from_string' do
218
+ it "builds a rack app from string" do
219
+ app, = Rack::Builder.new_from_string "run lambda{|env| [200, {'Content-Type' => 'text/plane'}, ['OK']] }"
220
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
221
+ end
222
+ end
223
+ end