rack 1.1.6 → 1.6.9

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 (212) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +1 -1
  3. data/HISTORY.md +375 -0
  4. data/KNOWN-ISSUES +23 -0
  5. data/README.rdoc +312 -0
  6. data/Rakefile +124 -0
  7. data/SPEC +125 -32
  8. data/contrib/rack.png +0 -0
  9. data/contrib/rack.svg +150 -0
  10. data/contrib/rack_logo.svg +1 -1
  11. data/contrib/rdoc.css +412 -0
  12. data/example/protectedlobster.rb +1 -1
  13. data/lib/rack/auth/abstract/handler.rb +4 -4
  14. data/lib/rack/auth/abstract/request.rb +7 -5
  15. data/lib/rack/auth/basic.rb +1 -1
  16. data/lib/rack/auth/digest/md5.rb +7 -3
  17. data/lib/rack/auth/digest/nonce.rb +1 -1
  18. data/lib/rack/auth/digest/params.rb +7 -9
  19. data/lib/rack/auth/digest/request.rb +10 -9
  20. data/lib/rack/backports/uri/common_18.rb +56 -0
  21. data/lib/rack/backports/uri/common_192.rb +52 -0
  22. data/lib/rack/backports/uri/common_193.rb +29 -0
  23. data/lib/rack/body_proxy.rb +39 -0
  24. data/lib/rack/builder.rb +106 -22
  25. data/lib/rack/cascade.rb +17 -6
  26. data/lib/rack/chunked.rb +44 -24
  27. data/lib/rack/commonlogger.rb +36 -13
  28. data/lib/rack/conditionalget.rb +49 -17
  29. data/lib/rack/config.rb +5 -0
  30. data/lib/rack/content_length.rb +14 -6
  31. data/lib/rack/content_type.rb +7 -1
  32. data/lib/rack/deflater.rb +73 -15
  33. data/lib/rack/directory.rb +18 -8
  34. data/lib/rack/etag.rb +59 -9
  35. data/lib/rack/file.rb +106 -44
  36. data/lib/rack/handler/cgi.rb +11 -11
  37. data/lib/rack/handler/fastcgi.rb +18 -6
  38. data/lib/rack/handler/lsws.rb +2 -4
  39. data/lib/rack/handler/mongrel.rb +22 -6
  40. data/lib/rack/handler/scgi.rb +16 -8
  41. data/lib/rack/handler/thin.rb +19 -4
  42. data/lib/rack/handler/webrick.rb +72 -19
  43. data/lib/rack/handler.rb +47 -14
  44. data/lib/rack/head.rb +10 -2
  45. data/lib/rack/lint.rb +260 -75
  46. data/lib/rack/lobster.rb +13 -8
  47. data/lib/rack/lock.rb +13 -3
  48. data/lib/rack/logger.rb +0 -2
  49. data/lib/rack/methodoverride.rb +27 -8
  50. data/lib/rack/mime.rb +625 -167
  51. data/lib/rack/mock.rb +78 -53
  52. data/lib/rack/multipart/generator.rb +93 -0
  53. data/lib/rack/multipart/parser.rb +253 -0
  54. data/lib/rack/multipart/uploaded_file.rb +34 -0
  55. data/lib/rack/multipart.rb +34 -0
  56. data/lib/rack/nulllogger.rb +21 -2
  57. data/lib/rack/recursive.rb +10 -5
  58. data/lib/rack/reloader.rb +3 -2
  59. data/lib/rack/request.rb +201 -74
  60. data/lib/rack/response.rb +41 -28
  61. data/lib/rack/rewindable_input.rb +15 -11
  62. data/lib/rack/runtime.rb +16 -3
  63. data/lib/rack/sendfile.rb +47 -29
  64. data/lib/rack/server.rb +223 -47
  65. data/lib/rack/session/abstract/id.rb +289 -30
  66. data/lib/rack/session/cookie.rb +133 -44
  67. data/lib/rack/session/memcache.rb +30 -56
  68. data/lib/rack/session/pool.rb +19 -43
  69. data/lib/rack/showexceptions.rb +53 -15
  70. data/lib/rack/showstatus.rb +14 -7
  71. data/lib/rack/static.rb +124 -12
  72. data/lib/rack/tempfile_reaper.rb +22 -0
  73. data/lib/rack/urlmap.rb +49 -15
  74. data/lib/rack/utils/okjson.rb +600 -0
  75. data/lib/rack/utils.rb +363 -361
  76. data/lib/rack.rb +17 -23
  77. data/rack.gemspec +11 -20
  78. data/test/builder/anything.rb +5 -0
  79. data/test/builder/comment.ru +4 -0
  80. data/test/builder/end.ru +5 -0
  81. data/test/builder/line.ru +1 -0
  82. data/test/builder/options.ru +2 -0
  83. data/test/cgi/assets/folder/test.js +1 -0
  84. data/test/cgi/assets/fonts/font.eot +1 -0
  85. data/test/cgi/assets/images/image.png +1 -0
  86. data/test/cgi/assets/index.html +1 -0
  87. data/test/cgi/assets/javascripts/app.js +1 -0
  88. data/test/cgi/assets/stylesheets/app.css +1 -0
  89. data/test/cgi/lighttpd.conf +26 -0
  90. data/test/cgi/rackup_stub.rb +6 -0
  91. data/test/cgi/sample_rackup.ru +5 -0
  92. data/test/cgi/test +9 -0
  93. data/test/cgi/test+directory/test+file +1 -0
  94. data/test/cgi/test.fcgi +8 -0
  95. data/test/cgi/test.ru +5 -0
  96. data/test/gemloader.rb +10 -0
  97. data/test/multipart/bad_robots +259 -0
  98. data/test/multipart/binary +0 -0
  99. data/test/multipart/content_type_and_no_filename +6 -0
  100. data/test/multipart/empty +10 -0
  101. data/test/multipart/fail_16384_nofile +814 -0
  102. data/test/multipart/file1.txt +1 -0
  103. data/test/multipart/filename_and_modification_param +7 -0
  104. data/test/multipart/filename_and_no_name +6 -0
  105. data/test/multipart/filename_with_escaped_quotes +6 -0
  106. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  107. data/test/multipart/filename_with_null_byte +7 -0
  108. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  109. data/test/multipart/filename_with_unescaped_percentages +6 -0
  110. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  111. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  112. data/test/multipart/filename_with_unescaped_quotes +6 -0
  113. data/test/multipart/ie +6 -0
  114. data/test/multipart/invalid_character +6 -0
  115. data/test/multipart/mixed_files +21 -0
  116. data/test/multipart/nested +10 -0
  117. data/test/multipart/none +9 -0
  118. data/test/multipart/semicolon +6 -0
  119. data/test/multipart/text +15 -0
  120. data/test/multipart/three_files_three_fields +31 -0
  121. data/test/multipart/webkit +32 -0
  122. data/test/rackup/config.ru +31 -0
  123. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  124. data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
  125. data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
  126. data/test/spec_body_proxy.rb +85 -0
  127. data/test/spec_builder.rb +223 -0
  128. data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
  129. data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
  130. data/test/spec_chunked.rb +101 -0
  131. data/test/spec_commonlogger.rb +93 -0
  132. data/test/spec_conditionalget.rb +102 -0
  133. data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
  134. data/test/spec_content_length.rb +85 -0
  135. data/test/spec_content_type.rb +45 -0
  136. data/test/spec_deflater.rb +339 -0
  137. data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
  138. data/test/spec_etag.rb +107 -0
  139. data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
  140. data/test/spec_file.rb +221 -0
  141. data/test/spec_handler.rb +72 -0
  142. data/test/spec_head.rb +45 -0
  143. data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
  144. data/test/spec_lobster.rb +58 -0
  145. data/test/spec_lock.rb +164 -0
  146. data/test/spec_logger.rb +23 -0
  147. data/test/spec_methodoverride.rb +95 -0
  148. data/test/spec_mime.rb +51 -0
  149. data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
  150. data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
  151. data/test/spec_multipart.rb +600 -0
  152. data/test/spec_nulllogger.rb +20 -0
  153. data/test/spec_recursive.rb +72 -0
  154. data/test/spec_request.rb +1227 -0
  155. data/test/spec_response.rb +407 -0
  156. data/test/spec_rewindable_input.rb +118 -0
  157. data/test/spec_runtime.rb +49 -0
  158. data/test/spec_sendfile.rb +130 -0
  159. data/test/spec_server.rb +167 -0
  160. data/test/spec_session_abstract_id.rb +53 -0
  161. data/test/spec_session_cookie.rb +410 -0
  162. data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
  163. data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
  164. data/test/spec_showexceptions.rb +85 -0
  165. data/test/spec_showstatus.rb +103 -0
  166. data/test/spec_static.rb +145 -0
  167. data/test/spec_tempfile_reaper.rb +63 -0
  168. data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
  169. data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
  170. data/test/spec_utils.rb +647 -0
  171. data/test/spec_version.rb +17 -0
  172. data/test/spec_webrick.rb +184 -0
  173. data/test/static/another/index.html +1 -0
  174. data/test/static/index.html +1 -0
  175. data/test/testrequest.rb +78 -0
  176. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  177. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  178. metadata +220 -239
  179. data/RDOX +0 -0
  180. data/README +0 -592
  181. data/lib/rack/adapter/camping.rb +0 -22
  182. data/test/spec_auth.rb +0 -57
  183. data/test/spec_rack_builder.rb +0 -84
  184. data/test/spec_rack_camping.rb +0 -55
  185. data/test/spec_rack_chunked.rb +0 -62
  186. data/test/spec_rack_commonlogger.rb +0 -61
  187. data/test/spec_rack_conditionalget.rb +0 -41
  188. data/test/spec_rack_content_length.rb +0 -43
  189. data/test/spec_rack_content_type.rb +0 -30
  190. data/test/spec_rack_deflater.rb +0 -127
  191. data/test/spec_rack_etag.rb +0 -17
  192. data/test/spec_rack_file.rb +0 -75
  193. data/test/spec_rack_handler.rb +0 -43
  194. data/test/spec_rack_head.rb +0 -30
  195. data/test/spec_rack_lobster.rb +0 -45
  196. data/test/spec_rack_lock.rb +0 -38
  197. data/test/spec_rack_logger.rb +0 -21
  198. data/test/spec_rack_methodoverride.rb +0 -60
  199. data/test/spec_rack_nulllogger.rb +0 -13
  200. data/test/spec_rack_recursive.rb +0 -77
  201. data/test/spec_rack_request.rb +0 -594
  202. data/test/spec_rack_response.rb +0 -221
  203. data/test/spec_rack_rewindable_input.rb +0 -118
  204. data/test/spec_rack_runtime.rb +0 -35
  205. data/test/spec_rack_sendfile.rb +0 -86
  206. data/test/spec_rack_session_cookie.rb +0 -92
  207. data/test/spec_rack_showexceptions.rb +0 -21
  208. data/test/spec_rack_showstatus.rb +0 -72
  209. data/test/spec_rack_static.rb +0 -37
  210. data/test/spec_rack_utils.rb +0 -557
  211. data/test/spec_rack_webrick.rb +0 -130
  212. data/test/spec_rackup.rb +0 -164
@@ -0,0 +1,1227 @@
1
+ require 'stringio'
2
+ require 'cgi'
3
+ require 'rack/request'
4
+ require 'rack/mock'
5
+ require 'securerandom'
6
+
7
+ describe Rack::Request do
8
+ should "wrap the rack variables" do
9
+ req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/"))
10
+
11
+ req.body.should.respond_to? :gets
12
+ req.scheme.should.equal "http"
13
+ req.request_method.should.equal "GET"
14
+
15
+ req.should.be.get
16
+ req.should.not.be.post
17
+ req.should.not.be.put
18
+ req.should.not.be.delete
19
+ req.should.not.be.head
20
+ req.should.not.be.patch
21
+
22
+ req.script_name.should.equal ""
23
+ req.path_info.should.equal "/"
24
+ req.query_string.should.equal ""
25
+
26
+ req.host.should.equal "example.com"
27
+ req.port.should.equal 8080
28
+
29
+ req.content_length.should.equal "0"
30
+ req.content_type.should.be.nil
31
+ end
32
+
33
+ should "figure out the correct host" do
34
+ req = Rack::Request.new \
35
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
36
+ req.host.should.equal "www2.example.org"
37
+
38
+ req = Rack::Request.new \
39
+ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292")
40
+ req.host.should.equal "example.org"
41
+
42
+ req = Rack::Request.new \
43
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292")
44
+ req.host.should.equal "example.org"
45
+
46
+ env = Rack::MockRequest.env_for("/", "SERVER_ADDR" => "192.168.1.1", "SERVER_PORT" => "9292")
47
+ env.delete("SERVER_NAME")
48
+ req = Rack::Request.new(env)
49
+ req.host.should.equal "192.168.1.1"
50
+
51
+ env = Rack::MockRequest.env_for("/")
52
+ env.delete("SERVER_NAME")
53
+ req = Rack::Request.new(env)
54
+ req.host.should.equal ""
55
+ end
56
+
57
+ should "figure out the correct port" do
58
+ req = Rack::Request.new \
59
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
60
+ req.port.should.equal 80
61
+
62
+ req = Rack::Request.new \
63
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org:81")
64
+ req.port.should.equal 81
65
+
66
+ req = Rack::Request.new \
67
+ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292")
68
+ req.port.should.equal 9292
69
+
70
+ req = Rack::Request.new \
71
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292")
72
+ req.port.should.equal 9292
73
+
74
+ req = Rack::Request.new \
75
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org")
76
+ req.port.should.equal 80
77
+
78
+ req = Rack::Request.new \
79
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_SSL" => "on")
80
+ req.port.should.equal 443
81
+
82
+ req = Rack::Request.new \
83
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PROTO" => "https")
84
+ req.port.should.equal 443
85
+
86
+ req = Rack::Request.new \
87
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PORT" => "9393")
88
+ req.port.should.equal 9393
89
+
90
+ req = Rack::Request.new \
91
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9393", "SERVER_PORT" => "80")
92
+ req.port.should.equal 9393
93
+
94
+ req = Rack::Request.new \
95
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393")
96
+ req.port.should.equal 80
97
+
98
+ req = Rack::Request.new \
99
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https", "SERVER_PORT" => "80")
100
+ req.port.should.equal 443
101
+
102
+ req = Rack::Request.new \
103
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https,https", "SERVER_PORT" => "80")
104
+ req.port.should.equal 443
105
+ end
106
+
107
+ should "figure out the correct host with port" do
108
+ req = Rack::Request.new \
109
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
110
+ req.host_with_port.should.equal "www2.example.org"
111
+
112
+ req = Rack::Request.new \
113
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81")
114
+ req.host_with_port.should.equal "localhost:81"
115
+
116
+ req = Rack::Request.new \
117
+ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292")
118
+ req.host_with_port.should.equal "example.org:9292"
119
+
120
+ req = Rack::Request.new \
121
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292")
122
+ req.host_with_port.should.equal "example.org:9292"
123
+
124
+ req = Rack::Request.new \
125
+ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393")
126
+ req.host_with_port.should.equal "example.org"
127
+ end
128
+
129
+ should "parse the query string" do
130
+ req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla"))
131
+ req.query_string.should.equal "foo=bar&quux=bla"
132
+ req.GET.should.equal "foo" => "bar", "quux" => "bla"
133
+ req.POST.should.be.empty
134
+ req.params.should.equal "foo" => "bar", "quux" => "bla"
135
+ end
136
+
137
+ should "not truncate query strings containing semi-colons #543 only in POST" do
138
+ mr = Rack::MockRequest.env_for("/",
139
+ "REQUEST_METHOD" => 'POST',
140
+ :input => "foo=bar&quux=b;la")
141
+ req = Rack::Request.new mr
142
+ req.query_string.should.equal ""
143
+ req.GET.should.be.empty
144
+ req.POST.should.equal "foo" => "bar", "quux" => "b;la"
145
+ req.params.should.equal req.GET.merge(req.POST)
146
+ end
147
+
148
+ should "use semi-colons as separators for query strings in GET" do
149
+ req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=b;la;wun=duh"))
150
+ req.query_string.should.equal "foo=bar&quux=b;la;wun=duh"
151
+ req.GET.should.equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh"
152
+ req.POST.should.be.empty
153
+ req.params.should.equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh"
154
+ end
155
+
156
+ should "limit the keys from the GET query string" do
157
+ env = Rack::MockRequest.env_for("/?foo=bar")
158
+
159
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
160
+ begin
161
+ req = Rack::Request.new(env)
162
+ lambda { req.GET }.should.raise(RangeError)
163
+ ensure
164
+ Rack::Utils.key_space_limit = old
165
+ end
166
+ end
167
+
168
+ should "limit the key size per nested params hash" do
169
+ nested_query = Rack::MockRequest.env_for("/?foo%5Bbar%5D%5Bbaz%5D%5Bqux%5D=1")
170
+ plain_query = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1")
171
+
172
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
173
+ begin
174
+ lambda { Rack::Request.new(nested_query).GET }.should.not.raise(RangeError)
175
+ lambda { Rack::Request.new(plain_query).GET }.should.raise(RangeError)
176
+ ensure
177
+ Rack::Utils.key_space_limit = old
178
+ end
179
+ end
180
+
181
+ should "not unify GET and POST when calling params" do
182
+ mr = Rack::MockRequest.env_for("/?foo=quux",
183
+ "REQUEST_METHOD" => 'POST',
184
+ :input => "foo=bar&quux=bla"
185
+ )
186
+ req = Rack::Request.new mr
187
+
188
+ req.params
189
+
190
+ req.GET.should.equal "foo" => "quux"
191
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
192
+ req.params.should.equal req.GET.merge(req.POST)
193
+ end
194
+
195
+ should "raise if input params has invalid %-encoding" do
196
+ mr = Rack::MockRequest.env_for("/?foo=quux",
197
+ "REQUEST_METHOD" => 'POST',
198
+ :input => "a%=1"
199
+ )
200
+ req = Rack::Request.new mr
201
+
202
+ lambda { req.POST }.
203
+ should.raise(Rack::Utils::InvalidParameterError).
204
+ message.should.equal "invalid %-encoding (a%)"
205
+ end
206
+
207
+ should "raise if rack.input is missing" do
208
+ req = Rack::Request.new({})
209
+ lambda { req.POST }.should.raise(RuntimeError)
210
+ end
211
+
212
+ should "parse POST data when method is POST and no Content-Type given" do
213
+ req = Rack::Request.new \
214
+ Rack::MockRequest.env_for("/?foo=quux",
215
+ "REQUEST_METHOD" => 'POST',
216
+ :input => "foo=bar&quux=bla")
217
+ req.content_type.should.be.nil
218
+ req.media_type.should.be.nil
219
+ req.query_string.should.equal "foo=quux"
220
+ req.GET.should.equal "foo" => "quux"
221
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
222
+ req.params.should.equal "foo" => "bar", "quux" => "bla"
223
+ end
224
+
225
+ should "limit the keys from the POST form data" do
226
+ env = Rack::MockRequest.env_for("",
227
+ "REQUEST_METHOD" => 'POST',
228
+ :input => "foo=bar&quux=bla")
229
+
230
+ old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1
231
+ begin
232
+ req = Rack::Request.new(env)
233
+ lambda { req.POST }.should.raise(RangeError)
234
+ ensure
235
+ Rack::Utils.key_space_limit = old
236
+ end
237
+ end
238
+
239
+ should "parse POST data with explicit content type regardless of method" do
240
+ req = Rack::Request.new \
241
+ Rack::MockRequest.env_for("/",
242
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
243
+ :input => "foo=bar&quux=bla")
244
+ req.content_type.should.equal 'application/x-www-form-urlencoded;foo=bar'
245
+ req.media_type.should.equal 'application/x-www-form-urlencoded'
246
+ req.media_type_params['foo'].should.equal 'bar'
247
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
248
+ req.params.should.equal "foo" => "bar", "quux" => "bla"
249
+ end
250
+
251
+ should "not parse POST data when media type is not form-data" do
252
+ req = Rack::Request.new \
253
+ Rack::MockRequest.env_for("/?foo=quux",
254
+ "REQUEST_METHOD" => 'POST',
255
+ "CONTENT_TYPE" => 'text/plain;charset=utf-8',
256
+ :input => "foo=bar&quux=bla")
257
+ req.content_type.should.equal 'text/plain;charset=utf-8'
258
+ req.media_type.should.equal 'text/plain'
259
+ req.media_type_params['charset'].should.equal 'utf-8'
260
+ req.POST.should.be.empty
261
+ req.params.should.equal "foo" => "quux"
262
+ req.body.read.should.equal "foo=bar&quux=bla"
263
+ end
264
+
265
+ should "parse POST data on PUT when media type is form-data" do
266
+ req = Rack::Request.new \
267
+ Rack::MockRequest.env_for("/?foo=quux",
268
+ "REQUEST_METHOD" => 'PUT',
269
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded',
270
+ :input => "foo=bar&quux=bla")
271
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
272
+ req.body.read.should.equal "foo=bar&quux=bla"
273
+ end
274
+
275
+ should "rewind input after parsing POST data" do
276
+ input = StringIO.new("foo=bar&quux=bla")
277
+ req = Rack::Request.new \
278
+ Rack::MockRequest.env_for("/",
279
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
280
+ :input => input)
281
+ req.params.should.equal "foo" => "bar", "quux" => "bla"
282
+ input.read.should.equal "foo=bar&quux=bla"
283
+ end
284
+
285
+ should "clean up Safari's ajax POST body" do
286
+ req = Rack::Request.new \
287
+ Rack::MockRequest.env_for("/",
288
+ 'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0")
289
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
290
+ end
291
+
292
+ should "get value by key from params with #[]" do
293
+ req = Rack::Request.new \
294
+ Rack::MockRequest.env_for("?foo=quux")
295
+ req['foo'].should.equal 'quux'
296
+ req[:foo].should.equal 'quux'
297
+ end
298
+
299
+ should "set value to key on params with #[]=" do
300
+ req = Rack::Request.new \
301
+ Rack::MockRequest.env_for("?foo=duh")
302
+ req['foo'].should.equal 'duh'
303
+ req[:foo].should.equal 'duh'
304
+ req.params.should.equal 'foo' => 'duh'
305
+
306
+ req['foo'] = 'bar'
307
+ req.params.should.equal 'foo' => 'bar'
308
+ req['foo'].should.equal 'bar'
309
+ req[:foo].should.equal 'bar'
310
+
311
+ req[:foo] = 'jaz'
312
+ req.params.should.equal 'foo' => 'jaz'
313
+ req['foo'].should.equal 'jaz'
314
+ req[:foo].should.equal 'jaz'
315
+ end
316
+
317
+ should "return values for the keys in the order given from values_at" do
318
+ req = Rack::Request.new \
319
+ Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful")
320
+ req.values_at('foo').should.equal ['baz']
321
+ req.values_at('foo', 'wun').should.equal ['baz', 'der']
322
+ req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der']
323
+ end
324
+
325
+ should "extract referrer correctly" do
326
+ req = Rack::Request.new \
327
+ Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path")
328
+ req.referer.should.equal "/some/path"
329
+
330
+ req = Rack::Request.new \
331
+ Rack::MockRequest.env_for("/")
332
+ req.referer.should.equal nil
333
+ end
334
+
335
+ should "extract user agent correctly" do
336
+ req = Rack::Request.new \
337
+ Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)")
338
+ req.user_agent.should.equal "Mozilla/4.0 (compatible)"
339
+
340
+ req = Rack::Request.new \
341
+ Rack::MockRequest.env_for("/")
342
+ req.user_agent.should.equal nil
343
+ end
344
+
345
+ should "treat missing content type as nil" do
346
+ req = Rack::Request.new \
347
+ Rack::MockRequest.env_for("/")
348
+ req.content_type.should.equal nil
349
+ end
350
+
351
+ should "treat empty content type as nil" do
352
+ req = Rack::Request.new \
353
+ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "")
354
+ req.content_type.should.equal nil
355
+ end
356
+
357
+ should "return nil media type for empty content type" do
358
+ req = Rack::Request.new \
359
+ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "")
360
+ req.media_type.should.equal nil
361
+ end
362
+
363
+ should "cache, but invalidates the cache" do
364
+ req = Rack::Request.new \
365
+ Rack::MockRequest.env_for("/?foo=quux",
366
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
367
+ :input => "foo=bar&quux=bla")
368
+ req.GET.should.equal "foo" => "quux"
369
+ req.GET.should.equal "foo" => "quux"
370
+ req.env["QUERY_STRING"] = "bla=foo"
371
+ req.GET.should.equal "bla" => "foo"
372
+ req.GET.should.equal "bla" => "foo"
373
+
374
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
375
+ req.POST.should.equal "foo" => "bar", "quux" => "bla"
376
+ req.env["rack.input"] = StringIO.new("foo=bla&quux=bar")
377
+ req.POST.should.equal "foo" => "bla", "quux" => "bar"
378
+ req.POST.should.equal "foo" => "bla", "quux" => "bar"
379
+ end
380
+
381
+ should "figure out if called via XHR" do
382
+ req = Rack::Request.new(Rack::MockRequest.env_for(""))
383
+ req.should.not.be.xhr
384
+
385
+ req = Rack::Request.new \
386
+ Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
387
+ req.should.be.xhr
388
+ end
389
+
390
+ should "ssl detection" do
391
+ request = Rack::Request.new(Rack::MockRequest.env_for("/"))
392
+ request.scheme.should.equal "http"
393
+ request.should.not.be.ssl?
394
+
395
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTPS' => 'on'))
396
+ request.scheme.should.equal "https"
397
+ request.should.be.ssl?
398
+
399
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'rack.url_scheme' => 'https'))
400
+ request.scheme.should.equal "https"
401
+ request.should.be.ssl?
402
+
403
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8080'))
404
+ request.scheme.should.equal "http"
405
+ request.should.not.be.ssl?
406
+
407
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on'))
408
+ request.scheme.should.equal "https"
409
+ request.should.be.ssl?
410
+
411
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTP_X_FORWARDED_SSL' => 'on'))
412
+ request.scheme.should.equal "https"
413
+ request.should.be.ssl?
414
+
415
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https'))
416
+ request.scheme.should.equal "https"
417
+ request.should.be.ssl?
418
+
419
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https'))
420
+ request.scheme.should.equal "https"
421
+ request.should.be.ssl?
422
+
423
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http'))
424
+ request.scheme.should.equal "https"
425
+ request.should.be.ssl?
426
+ end
427
+
428
+ should "parse cookies" do
429
+ req = Rack::Request.new \
430
+ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
431
+ req.cookies.should.equal "foo" => "bar", "quux" => "h&m"
432
+ req.cookies.should.equal "foo" => "bar", "quux" => "h&m"
433
+ req.env.delete("HTTP_COOKIE")
434
+ req.cookies.should.equal({})
435
+ end
436
+
437
+ should "always return the same hash object" do
438
+ req = Rack::Request.new \
439
+ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
440
+ hash = req.cookies
441
+ req.env.delete("HTTP_COOKIE")
442
+ req.cookies.should.equal(hash)
443
+ req.env["HTTP_COOKIE"] = "zoo=m"
444
+ req.cookies.should.equal(hash)
445
+ end
446
+
447
+ should "modify the cookies hash in place" do
448
+ req = Rack::Request.new(Rack::MockRequest.env_for(""))
449
+ req.cookies.should.equal({})
450
+ req.cookies['foo'] = 'bar'
451
+ req.cookies.should.equal 'foo' => 'bar'
452
+ end
453
+
454
+ should "not modify the params hash in place" do
455
+ e = Rack::MockRequest.env_for("")
456
+ req1 = Rack::Request.new(e)
457
+ req1.params.should.equal({})
458
+ req1.params['foo'] = 'bar'
459
+ req1.params.should.equal 'foo' => 'bar'
460
+ req2 = Rack::Request.new(e)
461
+ req2.params.should.equal({})
462
+ end
463
+
464
+ should "modify params hash if param is in GET" do
465
+ e = Rack::MockRequest.env_for("?foo=duh")
466
+ req1 = Rack::Request.new(e)
467
+ req1.params.should.equal 'foo' => 'duh'
468
+ req1.update_param 'foo', 'bar'
469
+ req1.params.should.equal 'foo' => 'bar'
470
+ req2 = Rack::Request.new(e)
471
+ req2.params.should.equal 'foo' => 'bar'
472
+ end
473
+
474
+ should "modify params hash if param is in POST" do
475
+ e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=duh')
476
+ req1 = Rack::Request.new(e)
477
+ req1.params.should.equal 'foo' => 'duh'
478
+ req1.update_param 'foo', 'bar'
479
+ req1.params.should.equal 'foo' => 'bar'
480
+ req2 = Rack::Request.new(e)
481
+ req2.params.should.equal 'foo' => 'bar'
482
+ end
483
+
484
+ should "modify params hash, even if param didn't exist before" do
485
+ e = Rack::MockRequest.env_for("")
486
+ req1 = Rack::Request.new(e)
487
+ req1.params.should.equal({})
488
+ req1.update_param 'foo', 'bar'
489
+ req1.params.should.equal 'foo' => 'bar'
490
+ req2 = Rack::Request.new(e)
491
+ req2.params.should.equal 'foo' => 'bar'
492
+ end
493
+
494
+ should "modify params hash by changing only GET" do
495
+ e = Rack::MockRequest.env_for("?foo=duhget")
496
+ req = Rack::Request.new(e)
497
+ req.GET.should.equal 'foo' => 'duhget'
498
+ req.POST.should.equal({})
499
+ req.update_param 'foo', 'bar'
500
+ req.GET.should.equal 'foo' => 'bar'
501
+ req.POST.should.equal({})
502
+ end
503
+
504
+ should "modify params hash by changing only POST" do
505
+ e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost")
506
+ req = Rack::Request.new(e)
507
+ req.GET.should.equal({})
508
+ req.POST.should.equal 'foo' => 'duhpost'
509
+ req.update_param 'foo', 'bar'
510
+ req.GET.should.equal({})
511
+ req.POST.should.equal 'foo' => 'bar'
512
+ end
513
+
514
+ should "modify params hash, even if param is defined in both POST and GET" do
515
+ e = Rack::MockRequest.env_for("?foo=duhget", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost")
516
+ req1 = Rack::Request.new(e)
517
+ req1.GET.should.equal 'foo' => 'duhget'
518
+ req1.POST.should.equal 'foo' => 'duhpost'
519
+ req1.params.should.equal 'foo' => 'duhpost'
520
+ req1.update_param 'foo', 'bar'
521
+ req1.GET.should.equal 'foo' => 'bar'
522
+ req1.POST.should.equal 'foo' => 'bar'
523
+ req1.params.should.equal 'foo' => 'bar'
524
+ req2 = Rack::Request.new(e)
525
+ req2.GET.should.equal 'foo' => 'bar'
526
+ req2.POST.should.equal 'foo' => 'bar'
527
+ req2.params.should.equal 'foo' => 'bar'
528
+ req2.params.should.equal 'foo' => 'bar'
529
+ end
530
+
531
+ should "allow deleting from params hash if param is in GET" do
532
+ e = Rack::MockRequest.env_for("?foo=bar")
533
+ req1 = Rack::Request.new(e)
534
+ req1.params.should.equal 'foo' => 'bar'
535
+ req1.delete_param('foo').should.equal 'bar'
536
+ req1.params.should.equal({})
537
+ req2 = Rack::Request.new(e)
538
+ req2.params.should.equal({})
539
+ end
540
+
541
+ should "allow deleting from params hash if param is in POST" do
542
+ e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=bar')
543
+ req1 = Rack::Request.new(e)
544
+ req1.params.should.equal 'foo' => 'bar'
545
+ req1.delete_param('foo').should.equal 'bar'
546
+ req1.params.should.equal({})
547
+ req2 = Rack::Request.new(e)
548
+ req2.params.should.equal({})
549
+ end
550
+
551
+ should "pass through non-uri escaped cookies as-is" do
552
+ req = Rack::Request.new Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%")
553
+ req.cookies["foo"].should == "%"
554
+ end
555
+
556
+ should "parse cookies according to RFC 2109" do
557
+ req = Rack::Request.new \
558
+ Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
559
+ req.cookies.should.equal 'foo' => 'bar'
560
+ end
561
+
562
+ should 'parse cookies with quotes' do
563
+ req = Rack::Request.new Rack::MockRequest.env_for('', {
564
+ 'HTTP_COOKIE' => '$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"'
565
+ })
566
+ req.cookies.should.equal({
567
+ '$Version' => '"1"',
568
+ 'Customer' => '"WILE_E_COYOTE"',
569
+ '$Path' => '"/acme"',
570
+ 'Part_Number' => '"Rocket_Launcher_0001"',
571
+ })
572
+ end
573
+
574
+ should "provide setters" do
575
+ req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
576
+ req.script_name.should.equal ""
577
+ req.script_name = "/foo"
578
+ req.script_name.should.equal "/foo"
579
+ e["SCRIPT_NAME"].should.equal "/foo"
580
+
581
+ req.path_info.should.equal "/"
582
+ req.path_info = "/foo"
583
+ req.path_info.should.equal "/foo"
584
+ e["PATH_INFO"].should.equal "/foo"
585
+ end
586
+
587
+ should "provide the original env" do
588
+ req = Rack::Request.new(e = Rack::MockRequest.env_for(""))
589
+ req.env.should == e
590
+ end
591
+
592
+ should "restore the base URL" do
593
+ Rack::Request.new(Rack::MockRequest.env_for("")).base_url.
594
+ should.equal "http://example.org"
595
+ Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).base_url.
596
+ should.equal "http://example.org"
597
+ end
598
+
599
+ should "restore the URL" do
600
+ Rack::Request.new(Rack::MockRequest.env_for("")).url.
601
+ should.equal "http://example.org/"
602
+ Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url.
603
+ should.equal "http://example.org/foo/"
604
+ Rack::Request.new(Rack::MockRequest.env_for("/foo")).url.
605
+ should.equal "http://example.org/foo"
606
+ Rack::Request.new(Rack::MockRequest.env_for("?foo")).url.
607
+ should.equal "http://example.org/?foo"
608
+ Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).url.
609
+ should.equal "http://example.org:8080/"
610
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).url.
611
+ should.equal "https://example.org/"
612
+ Rack::Request.new(Rack::MockRequest.env_for("coffee://example.org/")).url.
613
+ should.equal "coffee://example.org/"
614
+ Rack::Request.new(Rack::MockRequest.env_for("coffee://example.org:443/")).url.
615
+ should.equal "coffee://example.org:443/"
616
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url.
617
+ should.equal "https://example.com:8080/foo?foo"
618
+ end
619
+
620
+ should "restore the full path" do
621
+ Rack::Request.new(Rack::MockRequest.env_for("")).fullpath.
622
+ should.equal "/"
623
+ Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath.
624
+ should.equal "/foo/"
625
+ Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath.
626
+ should.equal "/foo"
627
+ Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath.
628
+ should.equal "/?foo"
629
+ Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath.
630
+ should.equal "/"
631
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath.
632
+ should.equal "/"
633
+
634
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath.
635
+ should.equal "/foo?foo"
636
+ end
637
+
638
+ should "handle multiple media type parameters" do
639
+ req = Rack::Request.new \
640
+ Rack::MockRequest.env_for("/",
641
+ "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam;blong="boo";zump="zoo\"o";weird=lol"')
642
+ req.should.not.be.form_data
643
+ req.media_type_params.should.include 'foo'
644
+ req.media_type_params['foo'].should.equal 'BAR'
645
+ req.media_type_params.should.include 'baz'
646
+ req.media_type_params['baz'].should.equal 'bizzle dizzle'
647
+ req.media_type_params.should.not.include 'BLING'
648
+ req.media_type_params.should.include 'bling'
649
+ req.media_type_params['bling'].should.equal 'bam'
650
+ req.media_type_params['blong'].should.equal 'boo'
651
+ req.media_type_params['zump'].should.equal 'zoo\"o'
652
+ req.media_type_params['weird'].should.equal 'lol"'
653
+ end
654
+
655
+ should "parse with junk before boundry" do
656
+ # Adapted from RFC 1867.
657
+ input = <<EOF
658
+ blah blah\r
659
+ \r
660
+ --AaB03x\r
661
+ content-disposition: form-data; name="reply"\r
662
+ \r
663
+ yes\r
664
+ --AaB03x\r
665
+ content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
666
+ Content-Type: image/jpeg\r
667
+ Content-Transfer-Encoding: base64\r
668
+ \r
669
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
670
+ --AaB03x--\r
671
+ EOF
672
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
673
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
674
+ "CONTENT_LENGTH" => input.size,
675
+ :input => input)
676
+
677
+ req.POST.should.include "fileupload"
678
+ req.POST.should.include "reply"
679
+
680
+ req.should.be.form_data
681
+ req.content_length.should.equal input.size
682
+ req.media_type.should.equal 'multipart/form-data'
683
+ req.media_type_params.should.include 'boundary'
684
+ req.media_type_params['boundary'].should.equal 'AaB03x'
685
+
686
+ req.POST["reply"].should.equal "yes"
687
+
688
+ f = req.POST["fileupload"]
689
+ f.should.be.kind_of Hash
690
+ f[:type].should.equal "image/jpeg"
691
+ f[:filename].should.equal "dj.jpg"
692
+ f.should.include :tempfile
693
+ f[:tempfile].size.should.equal 76
694
+ end
695
+
696
+ should "not infinite loop with a malformed HTTP request" do
697
+ # Adapted from RFC 1867.
698
+ input = <<EOF
699
+ --AaB03x
700
+ content-disposition: form-data; name="reply"
701
+
702
+ yes
703
+ --AaB03x
704
+ content-disposition: form-data; name="fileupload"; filename="dj.jpg"
705
+ Content-Type: image/jpeg
706
+ Content-Transfer-Encoding: base64
707
+
708
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg
709
+ --AaB03x--
710
+ EOF
711
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
712
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
713
+ "CONTENT_LENGTH" => input.size,
714
+ :input => input)
715
+
716
+ lambda{req.POST}.should.raise(EOFError)
717
+ end
718
+
719
+
720
+ should "parse multipart form data" do
721
+ # Adapted from RFC 1867.
722
+ input = <<EOF
723
+ --AaB03x\r
724
+ content-disposition: form-data; name="reply"\r
725
+ \r
726
+ yes\r
727
+ --AaB03x\r
728
+ content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
729
+ Content-Type: image/jpeg\r
730
+ Content-Transfer-Encoding: base64\r
731
+ \r
732
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
733
+ --AaB03x--\r
734
+ EOF
735
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
736
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
737
+ "CONTENT_LENGTH" => input.size,
738
+ :input => input)
739
+
740
+ req.POST.should.include "fileupload"
741
+ req.POST.should.include "reply"
742
+
743
+ req.should.be.form_data
744
+ req.content_length.should.equal input.size
745
+ req.media_type.should.equal 'multipart/form-data'
746
+ req.media_type_params.should.include 'boundary'
747
+ req.media_type_params['boundary'].should.equal 'AaB03x'
748
+
749
+ req.POST["reply"].should.equal "yes"
750
+
751
+ f = req.POST["fileupload"]
752
+ f.should.be.kind_of Hash
753
+ f[:type].should.equal "image/jpeg"
754
+ f[:filename].should.equal "dj.jpg"
755
+ f.should.include :tempfile
756
+ f[:tempfile].size.should.equal 76
757
+ end
758
+
759
+ should "MultipartPartLimitError when request has too many multipart parts if limit set" do
760
+ begin
761
+ data = 10000.times.map { "--AaB03x\r\nContent-Type: text/plain\r\nContent-Disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n")
762
+ data += "--AaB03x--\r"
763
+
764
+ options = {
765
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
766
+ "CONTENT_LENGTH" => data.length.to_s,
767
+ :input => StringIO.new(data)
768
+ }
769
+
770
+ request = Rack::Request.new Rack::MockRequest.env_for("/", options)
771
+ lambda { request.POST }.should.raise(Rack::Multipart::MultipartPartLimitError)
772
+ end
773
+ end
774
+
775
+ should "parse big multipart form data" do
776
+ input = <<EOF
777
+ --AaB03x\r
778
+ content-disposition: form-data; name="huge"; filename="huge"\r
779
+ \r
780
+ #{"x"*32768}\r
781
+ --AaB03x\r
782
+ content-disposition: form-data; name="mean"; filename="mean"\r
783
+ \r
784
+ --AaB03xha\r
785
+ --AaB03x--\r
786
+ EOF
787
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
788
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
789
+ "CONTENT_LENGTH" => input.size,
790
+ :input => input)
791
+
792
+ req.POST["huge"][:tempfile].size.should.equal 32768
793
+ req.POST["mean"][:tempfile].size.should.equal 10
794
+ req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
795
+ end
796
+
797
+ should "record tempfiles from multipart form data in env[rack.tempfiles]" do
798
+ input = <<EOF
799
+ --AaB03x\r
800
+ content-disposition: form-data; name="fileupload"; filename="foo.jpg"\r
801
+ Content-Type: image/jpeg\r
802
+ Content-Transfer-Encoding: base64\r
803
+ \r
804
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
805
+ --AaB03x\r
806
+ content-disposition: form-data; name="fileupload"; filename="bar.jpg"\r
807
+ Content-Type: image/jpeg\r
808
+ Content-Transfer-Encoding: base64\r
809
+ \r
810
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
811
+ --AaB03x--\r
812
+ EOF
813
+ env = Rack::MockRequest.env_for("/",
814
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
815
+ "CONTENT_LENGTH" => input.size,
816
+ :input => input)
817
+ req = Rack::Request.new(env)
818
+ req.params
819
+ env['rack.tempfiles'].size.should.equal(2)
820
+ end
821
+
822
+ should "detect invalid multipart form data" do
823
+ input = <<EOF
824
+ --AaB03x\r
825
+ content-disposition: form-data; name="huge"; filename="huge"\r
826
+ EOF
827
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
828
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
829
+ "CONTENT_LENGTH" => input.size,
830
+ :input => input)
831
+
832
+ lambda { req.POST }.should.raise(EOFError)
833
+
834
+ input = <<EOF
835
+ --AaB03x\r
836
+ content-disposition: form-data; name="huge"; filename="huge"\r
837
+ \r
838
+ foo\r
839
+ EOF
840
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
841
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
842
+ "CONTENT_LENGTH" => input.size,
843
+ :input => input)
844
+
845
+ lambda { req.POST }.should.raise(EOFError)
846
+
847
+ input = <<EOF
848
+ --AaB03x\r
849
+ content-disposition: form-data; name="huge"; filename="huge"\r
850
+ \r
851
+ foo\r
852
+ EOF
853
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
854
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
855
+ "CONTENT_LENGTH" => input.size,
856
+ :input => input)
857
+
858
+ lambda { req.POST }.should.raise(EOFError)
859
+ end
860
+
861
+ should "consistently raise EOFError on bad multipart form data" do
862
+ input = <<EOF
863
+ --AaB03x\r
864
+ content-disposition: form-data; name="huge"; filename="huge"\r
865
+ EOF
866
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
867
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
868
+ "CONTENT_LENGTH" => input.size,
869
+ :input => input)
870
+
871
+ lambda { req.POST }.should.raise(EOFError)
872
+ lambda { req.POST }.should.raise(EOFError)
873
+ end
874
+
875
+ should "correctly parse the part name from Content-Id header" do
876
+ input = <<EOF
877
+ --AaB03x\r
878
+ Content-Type: text/xml; charset=utf-8\r
879
+ Content-Id: <soap-start>\r
880
+ Content-Transfer-Encoding: 7bit\r
881
+ \r
882
+ foo\r
883
+ --AaB03x--\r
884
+ EOF
885
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
886
+ "CONTENT_TYPE" => "multipart/related, boundary=AaB03x",
887
+ "CONTENT_LENGTH" => input.size,
888
+ :input => input)
889
+
890
+ req.params.keys.should.equal ["<soap-start>"]
891
+ end
892
+
893
+ should "not try to interpret binary as utf8" do
894
+ if /regexp/.respond_to?(:kcode) # < 1.9
895
+ begin
896
+ original_kcode = $KCODE
897
+ $KCODE='UTF8'
898
+
899
+ input = <<EOF
900
+ --AaB03x\r
901
+ content-disposition: form-data; name="fileupload"; filename="junk.a"\r
902
+ content-type: application/octet-stream\r
903
+ \r
904
+ #{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
905
+ --AaB03x--\r
906
+ EOF
907
+
908
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
909
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
910
+ "CONTENT_LENGTH" => input.size,
911
+ :input => input)
912
+
913
+ lambda{req.POST}.should.not.raise(EOFError)
914
+ req.POST["fileupload"][:tempfile].size.should.equal 4
915
+ ensure
916
+ $KCODE = original_kcode
917
+ end
918
+ else # >= 1.9
919
+ input = <<EOF
920
+ --AaB03x\r
921
+ content-disposition: form-data; name="fileupload"; filename="junk.a"\r
922
+ content-type: application/octet-stream\r
923
+ \r
924
+ #{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
925
+ --AaB03x--\r
926
+ EOF
927
+
928
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
929
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
930
+ "CONTENT_LENGTH" => input.size,
931
+ :input => input)
932
+
933
+ lambda{req.POST}.should.not.raise(EOFError)
934
+ req.POST["fileupload"][:tempfile].size.should.equal 4
935
+ end
936
+ end
937
+
938
+ should "work around buggy 1.8.* Tempfile equality" do
939
+ input = <<EOF
940
+ --AaB03x\r
941
+ content-disposition: form-data; name="huge"; filename="huge"\r
942
+ \r
943
+ foo\r
944
+ --AaB03x--
945
+ EOF
946
+
947
+ rack_input = Tempfile.new("rackspec")
948
+ rack_input.write(input)
949
+ rack_input.rewind
950
+
951
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
952
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
953
+ "CONTENT_LENGTH" => input.size,
954
+ :input => rack_input)
955
+
956
+ lambda{ req.POST }.should.not.raise
957
+ lambda{ req.POST }.should.not.raise("input re-processed!")
958
+ end
959
+
960
+ should "use form_hash when form_input is a Tempfile" do
961
+ input = "{foo: 'bar'}"
962
+
963
+ rack_input = Tempfile.new("rackspec")
964
+ rack_input.write(input)
965
+ rack_input.rewind
966
+
967
+ req = Rack::Request.new Rack::MockRequest.env_for("/",
968
+ "rack.request.form_hash" => {'foo' => 'bar'},
969
+ "rack.request.form_input" => rack_input,
970
+ :input => rack_input)
971
+
972
+ req.POST.should.equal(req.env['rack.request.form_hash'])
973
+ end
974
+
975
+ should "conform to the Rack spec" do
976
+ app = lambda { |env|
977
+ content = Rack::Request.new(env).POST["file"].inspect
978
+ size = content.respond_to?(:bytesize) ? content.bytesize : content.size
979
+ [200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]]
980
+ }
981
+
982
+ input = <<EOF
983
+ --AaB03x\r
984
+ content-disposition: form-data; name="reply"\r
985
+ \r
986
+ yes\r
987
+ --AaB03x\r
988
+ content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
989
+ Content-Type: image/jpeg\r
990
+ Content-Transfer-Encoding: base64\r
991
+ \r
992
+ /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
993
+ --AaB03x--\r
994
+ EOF
995
+ input.force_encoding("ASCII-8BIT") if input.respond_to? :force_encoding
996
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/",
997
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
998
+ "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input)
999
+
1000
+ res.should.be.ok
1001
+ end
1002
+
1003
+ should "parse Accept-Encoding correctly" do
1004
+ parser = lambda do |x|
1005
+ Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding
1006
+ end
1007
+
1008
+ parser.call(nil).should.equal([])
1009
+
1010
+ parser.call("compress, gzip").should.equal([["compress", 1.0], ["gzip", 1.0]])
1011
+ parser.call("").should.equal([])
1012
+ parser.call("*").should.equal([["*", 1.0]])
1013
+ parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]])
1014
+ parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ])
1015
+
1016
+ parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]])
1017
+ parser.call("gzip ; deflate").should.equal([["gzip", 1.0]])
1018
+ end
1019
+
1020
+ should "parse Accept-Language correctly" do
1021
+ parser = lambda do |x|
1022
+ Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_LANGUAGE" => x)).accept_language
1023
+ end
1024
+
1025
+ parser.call(nil).should.equal([])
1026
+
1027
+ parser.call("fr, en").should.equal([["fr", 1.0], ["en", 1.0]])
1028
+ parser.call("").should.equal([])
1029
+ parser.call("*").should.equal([["*", 1.0]])
1030
+ parser.call("fr;q=0.5, en;q=1.0").should.equal([["fr", 0.5], ["en", 1.0]])
1031
+ parser.call("fr;q=1.0, en; q=0.5, *;q=0").should.equal([["fr", 1.0], ["en", 0.5], ["*", 0] ])
1032
+
1033
+ parser.call("fr ; q=0.9").should.equal([["fr", 0.9]])
1034
+ parser.call("fr").should.equal([["fr", 1.0]])
1035
+ end
1036
+
1037
+ ip_app = lambda { |env|
1038
+ request = Rack::Request.new(env)
1039
+ response = Rack::Response.new
1040
+ response.write request.ip
1041
+ response.finish
1042
+ }
1043
+
1044
+ should 'provide ip information' do
1045
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
1046
+
1047
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4'
1048
+ res.body.should.equal '1.2.3.4'
1049
+
1050
+ res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329'
1051
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
1052
+
1053
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
1054
+ res.body.should.equal '1.2.3.4'
1055
+ end
1056
+
1057
+ should 'deals with proxies' do
1058
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
1059
+
1060
+ res = mock.get '/',
1061
+ 'REMOTE_ADDR' => '1.2.3.4',
1062
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
1063
+ res.body.should.equal '1.2.3.4'
1064
+
1065
+ res = mock.get '/',
1066
+ 'REMOTE_ADDR' => '1.2.3.4',
1067
+ 'HTTP_X_FORWARDED_FOR' => 'unknown'
1068
+ res.body.should.equal '1.2.3.4'
1069
+
1070
+ res = mock.get '/',
1071
+ 'REMOTE_ADDR' => '127.0.0.1',
1072
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
1073
+ res.body.should.equal '3.4.5.6'
1074
+
1075
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
1076
+ res.body.should.equal '3.4.5.6'
1077
+
1078
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
1079
+ res.body.should.equal '3.4.5.6'
1080
+
1081
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
1082
+ res.body.should.equal '3.4.5.6'
1083
+
1084
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
1085
+ res.body.should.equal '3.4.5.6'
1086
+
1087
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6'
1088
+ res.body.should.equal '3.4.5.6'
1089
+
1090
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
1091
+ res.body.should.equal 'unknown'
1092
+
1093
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1'
1094
+ res.body.should.equal 'unknown'
1095
+
1096
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1'
1097
+ res.body.should.equal 'unknown'
1098
+
1099
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
1100
+ res.body.should.equal '3.4.5.6'
1101
+
1102
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11'
1103
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
1104
+
1105
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1'
1106
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
1107
+
1108
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11'
1109
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
1110
+
1111
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000'
1112
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
1113
+
1114
+ res = mock.get '/',
1115
+ 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1',
1116
+ 'HTTP_CLIENT_IP' => '1.1.1.1'
1117
+ res.body.should.equal '1.1.1.1'
1118
+
1119
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
1120
+ res.body.should.equal '9.9.9.9'
1121
+
1122
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329'
1123
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
1124
+
1125
+ # Unix Sockets
1126
+ res = mock.get '/',
1127
+ 'REMOTE_ADDR' => 'unix',
1128
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
1129
+ res.body.should.equal '3.4.5.6'
1130
+
1131
+ res = mock.get '/',
1132
+ 'REMOTE_ADDR' => 'unix:/tmp/foo',
1133
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
1134
+ res.body.should.equal '3.4.5.6'
1135
+ end
1136
+
1137
+ should "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do
1138
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
1139
+
1140
+ # IP Spoofing attempt:
1141
+ # Client sends X-Forwarded-For: 6.6.6.6
1142
+ # Client-IP: 6.6.6.6
1143
+ # Load balancer adds X-Forwarded-For: 2.2.2.3, 192.168.0.7
1144
+ # App receives: X-Forwarded-For: 6.6.6.6
1145
+ # X-Forwarded-For: 2.2.2.3, 192.168.0.7
1146
+ # Client-IP: 6.6.6.6
1147
+ # Rack env: HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7'
1148
+ # HTTP_CLIENT_IP: '6.6.6.6'
1149
+ res = mock.get '/',
1150
+ 'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
1151
+ 'HTTP_CLIENT_IP' => '6.6.6.6'
1152
+ res.body.should.equal '2.2.2.3'
1153
+ end
1154
+
1155
+ should "regard local addresses as proxies" do
1156
+ req = Rack::Request.new(Rack::MockRequest.env_for("/"))
1157
+ req.trusted_proxy?('127.0.0.1').should.equal 0
1158
+ req.trusted_proxy?('10.0.0.1').should.equal 0
1159
+ req.trusted_proxy?('172.16.0.1').should.equal 0
1160
+ req.trusted_proxy?('172.20.0.1').should.equal 0
1161
+ req.trusted_proxy?('172.30.0.1').should.equal 0
1162
+ req.trusted_proxy?('172.31.0.1').should.equal 0
1163
+ req.trusted_proxy?('192.168.0.1').should.equal 0
1164
+ req.trusted_proxy?('::1').should.equal 0
1165
+ req.trusted_proxy?('fd00::').should.equal 0
1166
+ req.trusted_proxy?('localhost').should.equal 0
1167
+ req.trusted_proxy?('unix').should.equal 0
1168
+ req.trusted_proxy?('unix:/tmp/sock').should.equal 0
1169
+
1170
+ req.trusted_proxy?("unix.example.org").should.equal nil
1171
+ req.trusted_proxy?("example.org\n127.0.0.1").should.equal nil
1172
+ req.trusted_proxy?("127.0.0.1\nexample.org").should.equal nil
1173
+ req.trusted_proxy?("11.0.0.1").should.equal nil
1174
+ req.trusted_proxy?("172.15.0.1").should.equal nil
1175
+ req.trusted_proxy?("172.32.0.1").should.equal nil
1176
+ req.trusted_proxy?("2001:470:1f0b:18f8::1").should.equal nil
1177
+ end
1178
+
1179
+ class MyRequest < Rack::Request
1180
+ def params
1181
+ {:foo => "bar"}
1182
+ end
1183
+ end
1184
+
1185
+ should "allow subclass request to be instantiated after parent request" do
1186
+ env = Rack::MockRequest.env_for("/?foo=bar")
1187
+
1188
+ req1 = Rack::Request.new(env)
1189
+ req1.GET.should.equal "foo" => "bar"
1190
+ req1.params.should.equal "foo" => "bar"
1191
+
1192
+ req2 = MyRequest.new(env)
1193
+ req2.GET.should.equal "foo" => "bar"
1194
+ req2.params.should.equal :foo => "bar"
1195
+ end
1196
+
1197
+ should "allow parent request to be instantiated after subclass request" do
1198
+ env = Rack::MockRequest.env_for("/?foo=bar")
1199
+
1200
+ req1 = MyRequest.new(env)
1201
+ req1.GET.should.equal "foo" => "bar"
1202
+ req1.params.should.equal :foo => "bar"
1203
+
1204
+ req2 = Rack::Request.new(env)
1205
+ req2.GET.should.equal "foo" => "bar"
1206
+ req2.params.should.equal "foo" => "bar"
1207
+ end
1208
+
1209
+ should "raise TypeError every time if request parameters are broken" do
1210
+ broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1")
1211
+ req = Rack::Request.new(broken_query)
1212
+ lambda{req.GET}.should.raise(TypeError)
1213
+ lambda{req.params}.should.raise(TypeError)
1214
+ end
1215
+
1216
+ (0x20...0x7E).collect { |a|
1217
+ b = a.chr
1218
+ c = CGI.escape(b)
1219
+ should "not strip '#{a}' => '#{c}' => '#{b}' escaped character from parameters when accessed as string" do
1220
+ url = "/?foo=#{c}bar#{c}"
1221
+ env = Rack::MockRequest.env_for(url)
1222
+ req2 = Rack::Request.new(env)
1223
+ req2.GET.should.equal "foo" => "#{b}bar#{b}"
1224
+ req2.params.should.equal "foo" => "#{b}bar#{b}"
1225
+ end
1226
+ }
1227
+ end