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.
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