rack 1.5.5 → 1.6.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/KNOWN-ISSUES +14 -0
  3. data/README.rdoc +10 -6
  4. data/Rakefile +3 -4
  5. data/SPEC +59 -23
  6. data/lib/rack.rb +2 -1
  7. data/lib/rack/auth/abstract/request.rb +1 -1
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/md5.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +1 -1
  11. data/lib/rack/builder.rb +19 -4
  12. data/lib/rack/cascade.rb +2 -2
  13. data/lib/rack/chunked.rb +12 -1
  14. data/lib/rack/commonlogger.rb +13 -5
  15. data/lib/rack/conditionalget.rb +14 -2
  16. data/lib/rack/content_length.rb +5 -1
  17. data/lib/rack/deflater.rb +52 -13
  18. data/lib/rack/directory.rb +8 -2
  19. data/lib/rack/etag.rb +14 -6
  20. data/lib/rack/file.rb +10 -14
  21. data/lib/rack/handler.rb +2 -0
  22. data/lib/rack/handler/fastcgi.rb +4 -1
  23. data/lib/rack/handler/mongrel.rb +8 -2
  24. data/lib/rack/handler/scgi.rb +4 -1
  25. data/lib/rack/handler/thin.rb +8 -2
  26. data/lib/rack/handler/webrick.rb +46 -6
  27. data/lib/rack/head.rb +7 -2
  28. data/lib/rack/lint.rb +73 -25
  29. data/lib/rack/lobster.rb +8 -3
  30. data/lib/rack/methodoverride.rb +14 -3
  31. data/lib/rack/mime.rb +1 -15
  32. data/lib/rack/mock.rb +15 -7
  33. data/lib/rack/multipart.rb +2 -2
  34. data/lib/rack/multipart/parser.rb +107 -53
  35. data/lib/rack/multipart/uploaded_file.rb +2 -2
  36. data/lib/rack/nulllogger.rb +21 -2
  37. data/lib/rack/request.rb +38 -24
  38. data/lib/rack/response.rb +5 -0
  39. data/lib/rack/sendfile.rb +10 -5
  40. data/lib/rack/server.rb +45 -17
  41. data/lib/rack/session/abstract/id.rb +7 -6
  42. data/lib/rack/session/cookie.rb +17 -7
  43. data/lib/rack/session/memcache.rb +4 -4
  44. data/lib/rack/session/pool.rb +3 -6
  45. data/lib/rack/showexceptions.rb +20 -11
  46. data/lib/rack/showstatus.rb +1 -1
  47. data/lib/rack/static.rb +27 -30
  48. data/lib/rack/tempfile_reaper.rb +22 -0
  49. data/lib/rack/urlmap.rb +17 -3
  50. data/lib/rack/utils.rb +78 -47
  51. data/lib/rack/utils/okjson.rb +90 -91
  52. data/rack.gemspec +3 -3
  53. data/test/multipart/filename_and_no_name +6 -0
  54. data/test/multipart/invalid_character +6 -0
  55. data/test/spec_builder.rb +13 -4
  56. data/test/spec_chunked.rb +16 -0
  57. data/test/spec_commonlogger.rb +36 -0
  58. data/test/spec_content_length.rb +3 -1
  59. data/test/spec_deflater.rb +283 -148
  60. data/test/spec_etag.rb +11 -2
  61. data/test/spec_file.rb +11 -3
  62. data/test/spec_head.rb +2 -0
  63. data/test/spec_lobster.rb +1 -1
  64. data/test/spec_mock.rb +8 -0
  65. data/test/spec_multipart.rb +111 -49
  66. data/test/spec_request.rb +109 -25
  67. data/test/spec_response.rb +30 -0
  68. data/test/spec_server.rb +20 -5
  69. data/test/spec_session_cookie.rb +45 -2
  70. data/test/spec_session_memcache.rb +1 -1
  71. data/test/spec_showexceptions.rb +29 -36
  72. data/test/spec_showstatus.rb +19 -0
  73. data/test/spec_tempfile_reaper.rb +63 -0
  74. data/test/spec_urlmap.rb +23 -0
  75. data/test/spec_utils.rb +60 -10
  76. data/test/spec_webrick.rb +41 -0
  77. metadata +12 -9
  78. data/test/cgi/lighttpd.errors +0 -1
  79. data/test/multipart/three_files_three_fields +0 -31
@@ -11,7 +11,7 @@ module Rack
11
11
  raise "#{path} file does not exist" unless ::File.exist?(path)
12
12
  @content_type = content_type
13
13
  @original_filename = ::File.basename(path)
14
- @tempfile = Tempfile.new(@original_filename)
14
+ @tempfile = Tempfile.new([@original_filename, ::File.extname(path)])
15
15
  @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
16
16
  @tempfile.binmode if binary
17
17
  FileUtils.copy_file(path, @tempfile.path)
@@ -31,4 +31,4 @@ module Rack
31
31
  end
32
32
  end
33
33
  end
34
- end
34
+ end
@@ -9,10 +9,29 @@ module Rack
9
9
  @app.call(env)
10
10
  end
11
11
 
12
- def info(progname = nil, &block); end
12
+ def info(progname = nil, &block); end
13
13
  def debug(progname = nil, &block); end
14
- def warn(progname = nil, &block); end
14
+ def warn(progname = nil, &block); end
15
15
  def error(progname = nil, &block); end
16
16
  def fatal(progname = nil, &block); end
17
+ def unknown(progname = nil, &block); end
18
+ def info? ; end
19
+ def debug? ; end
20
+ def warn? ; end
21
+ def error? ; end
22
+ def fatal? ; end
23
+ def level ; end
24
+ def progname ; end
25
+ def datetime_format ; end
26
+ def formatter ; end
27
+ def sev_threshold ; end
28
+ def level=(level); end
29
+ def progname=(progname); end
30
+ def datetime_format=(datetime_format); end
31
+ def formatter=(formatter); end
32
+ def sev_threshold=(sev_threshold); end
33
+ def close ; end
34
+ def add(severity, message = nil, progname = nil, &block); end
35
+ def <<(msg); end
17
36
  end
18
37
  end
@@ -8,10 +8,6 @@ module Rack
8
8
  # req = Rack::Request.new(env)
9
9
  # req.post?
10
10
  # req.params["data"]
11
- #
12
- # The environment hash passed will store a reference to the Request object
13
- # instantiated so that it will only instantiate if an instance of the Request
14
- # object doesn't already exist.
15
11
 
16
12
  class Request
17
13
  # The environment of the request.
@@ -56,7 +52,7 @@ module Rack
56
52
  return {} if content_type.nil?
57
53
  Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
58
54
  collect { |s| s.split('=', 2) }.
59
- map { |k,v| [k.downcase, v] }.flatten]
55
+ map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
60
56
  end
61
57
 
62
58
  # The character set of the request body if a "charset" media type
@@ -101,7 +97,7 @@ module Rack
101
97
  elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
102
98
  DEFAULT_PORTS[scheme]
103
99
  elsif @env.has_key?("HTTP_X_FORWARDED_PROTO")
104
- DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO']]
100
+ DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]]
105
101
  else
106
102
  @env["SERVER_PORT"].to_i
107
103
  end
@@ -109,7 +105,7 @@ module Rack
109
105
 
110
106
  def host
111
107
  # Remove port number.
112
- host_with_port.to_s.gsub(/:\d+\z/, '')
108
+ host_with_port.to_s.sub(/:\d+\z/, '')
113
109
  end
114
110
 
115
111
  def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
@@ -128,6 +124,9 @@ module Rack
128
124
  # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
129
125
  def options?; request_method == "OPTIONS" end
130
126
 
127
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
128
+ def link?; request_method == "LINK" end
129
+
131
130
  # Checks the HTTP request method (or verb) to see if it was of type PATCH
132
131
  def patch?; request_method == "PATCH" end
133
132
 
@@ -140,6 +139,9 @@ module Rack
140
139
  # Checks the HTTP request method (or verb) to see if it was of type TRACE
141
140
  def trace?; request_method == "TRACE" end
142
141
 
142
+ # Checks the HTTP request method (or verb) to see if it was of type UNLINK
143
+ def unlink?; request_method == "UNLINK" end
144
+
143
145
 
144
146
  # The set of form-data media-types. Requests that do not indicate
145
147
  # one of the media types presents in this list will not be eligible
@@ -186,8 +188,9 @@ module Rack
186
188
  if @env["rack.request.query_string"] == query_string
187
189
  @env["rack.request.query_hash"]
188
190
  else
191
+ p = parse_query(query_string)
189
192
  @env["rack.request.query_string"] = query_string
190
- @env["rack.request.query_hash"] = parse_query(query_string)
193
+ @env["rack.request.query_hash"] = p
191
194
  end
192
195
  end
193
196
 
@@ -201,7 +204,6 @@ module Rack
201
204
  elsif @env["rack.request.form_input"].equal? @env["rack.input"]
202
205
  @env["rack.request.form_hash"]
203
206
  elsif form_data? || parseable_data?
204
- @env["rack.request.form_input"] = @env["rack.input"]
205
207
  unless @env["rack.request.form_hash"] = parse_multipart(env)
206
208
  form_vars = @env["rack.input"].read
207
209
 
@@ -214,6 +216,7 @@ module Rack
214
216
 
215
217
  @env["rack.input"].rewind
216
218
  end
219
+ @env["rack.request.form_input"] = @env["rack.input"]
217
220
  @env["rack.request.form_hash"]
218
221
  else
219
222
  {}
@@ -331,14 +334,11 @@ module Rack
331
334
  end
332
335
 
333
336
  def accept_encoding
334
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/\s*,\s*/).map do |part|
335
- encoding, parameters = part.split(/\s*;\s*/, 2)
336
- quality = 1.0
337
- if parameters and /\Aq=([\d.]+)/ =~ parameters
338
- quality = $1.to_f
339
- end
340
- [encoding, quality]
341
- end
337
+ parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"])
338
+ end
339
+
340
+ def accept_language
341
+ parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"])
342
342
  end
343
343
 
344
344
  def trusted_proxy?(ip)
@@ -353,12 +353,6 @@ module Rack
353
353
 
354
354
  forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
355
355
 
356
- if client_ip = @env['HTTP_CLIENT_IP']
357
- # If forwarded_ips doesn't include the client_ip, it might be an
358
- # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
359
- return client_ip if forwarded_ips.include?(client_ip)
360
- end
361
-
362
356
  return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
363
357
  end
364
358
 
@@ -372,11 +366,31 @@ module Rack
372
366
  end
373
367
 
374
368
  def parse_query(qs)
375
- Utils.parse_nested_query(qs)
369
+ Utils.parse_nested_query(qs, '&')
376
370
  end
377
371
 
378
372
  def parse_multipart(env)
379
373
  Rack::Multipart.parse_multipart(env)
380
374
  end
375
+
376
+ def parse_http_accept_header(header)
377
+ header.to_s.split(/\s*,\s*/).map do |part|
378
+ attribute, parameters = part.split(/\s*;\s*/, 2)
379
+ quality = 1.0
380
+ if parameters and /\Aq=([\d.]+)/ =~ parameters
381
+ quality = $1.to_f
382
+ end
383
+ [attribute, quality]
384
+ end
385
+ end
386
+
387
+ private
388
+ def strip_doublequotes(s)
389
+ if s[0] == ?" && s[-1] == ?"
390
+ s[1..-2]
391
+ else
392
+ s
393
+ end
394
+ end
381
395
  end
382
396
  end
@@ -1,5 +1,6 @@
1
1
  require 'rack/request'
2
2
  require 'rack/utils'
3
+ require 'rack/body_proxy'
3
4
  require 'time'
4
5
 
5
6
  module Rack
@@ -121,10 +122,14 @@ module Rack
121
122
  def server_error?; status >= 500 && status < 600; end
122
123
 
123
124
  def ok?; status == 200; end
125
+ def created?; status == 201; end
126
+ def accepted?; status == 202; end
124
127
  def bad_request?; status == 400; end
128
+ def unauthorized?; status == 401; end
125
129
  def forbidden?; status == 403; end
126
130
  def not_found?; status == 404; end
127
131
  def method_not_allowed?; status == 405; end
132
+ def i_m_a_teapot?; status == 418; end
128
133
  def unprocessable?; status == 422; end
129
134
 
130
135
  def redirect?; [301, 302, 303, 307].include? status; end
@@ -1,4 +1,5 @@
1
1
  require 'rack/file'
2
+ require 'rack/body_proxy'
2
3
 
3
4
  module Rack
4
5
 
@@ -22,7 +23,7 @@ module Rack
22
23
  #
23
24
  # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
24
25
  # but requires parts of the filesystem to be mapped into a private URL
25
- # hierarachy.
26
+ # hierarchy.
26
27
  #
27
28
  # The following example shows the Nginx configuration required to create
28
29
  # a private "/files/" area, enable X-Accel-Redirect, and pass the special
@@ -117,8 +118,10 @@ module Rack
117
118
  if url = map_accel_path(env, path)
118
119
  headers['Content-Length'] = '0'
119
120
  headers[type] = url
120
- body.close if body.respond_to?(:close)
121
- body = []
121
+ obody = body
122
+ body = Rack::BodyProxy.new([]) do
123
+ obody.close if obody.respond_to?(:close)
124
+ end
122
125
  else
123
126
  env['rack.errors'].puts "X-Accel-Mapping header missing"
124
127
  end
@@ -126,8 +129,10 @@ module Rack
126
129
  path = F.expand_path(body.to_path)
127
130
  headers['Content-Length'] = '0'
128
131
  headers[type] = path
129
- body.close if body.respond_to?(:close)
130
- body = []
132
+ obody = body
133
+ body = Rack::BodyProxy.new([]) do
134
+ obody.close if obody.respond_to?(:close)
135
+ end
131
136
  when '', nil
132
137
  else
133
138
  env['rack.errors'].puts "Unknown x-sendfile variation: '#{type}'.\n"
@@ -1,7 +1,10 @@
1
1
  require 'optparse'
2
2
 
3
+
3
4
  module Rack
5
+
4
6
  class Server
7
+
5
8
  class Options
6
9
  def parse!(args)
7
10
  options = {}
@@ -27,6 +30,9 @@ module Rack
27
30
  opts.on("-w", "--warn", "turn warnings on for your script") {
28
31
  options[:warn] = true
29
32
  }
33
+ opts.on("-q", "--quiet", "turn off logging") {
34
+ options[:quiet] = true
35
+ }
30
36
 
31
37
  opts.on("-I", "--include PATH",
32
38
  "specify $LOAD_PATH (may be used more than once)") { |path|
@@ -66,7 +72,7 @@ module Rack
66
72
  options[:daemonize] = d ? true : false
67
73
  }
68
74
 
69
- opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
75
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
70
76
  options[:pid] = ::File.expand_path(f)
71
77
  }
72
78
 
@@ -166,7 +172,7 @@ module Rack
166
172
  # * :Port
167
173
  # the port to bind to (used by supporting Rack::Handler)
168
174
  # * :AccessLog
169
- # webrick acess log options (or supporting Rack::Handler)
175
+ # webrick access log options (or supporting Rack::Handler)
170
176
  # * :debug
171
177
  # turn on debug output ($DEBUG = true)
172
178
  # * :warn
@@ -185,11 +191,14 @@ module Rack
185
191
  end
186
192
 
187
193
  def default_options
194
+ environment = ENV['RACK_ENV'] || 'development'
195
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
196
+
188
197
  {
189
- :environment => ENV['RACK_ENV'] || "development",
198
+ :environment => environment,
190
199
  :pid => nil,
191
200
  :Port => 9292,
192
- :Host => "0.0.0.0",
201
+ :Host => default_host,
193
202
  :AccessLog => [],
194
203
  :config => "config.ru"
195
204
  }
@@ -199,29 +208,44 @@ module Rack
199
208
  @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
200
209
  end
201
210
 
202
- def self.logging_middleware
203
- lambda { |server|
204
- server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
205
- }
206
- end
211
+ class << self
212
+ def logging_middleware
213
+ lambda { |server|
214
+ server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
215
+ }
216
+ end
207
217
 
208
- def self.middleware
209
- @middleware ||= begin
218
+ def default_middleware_by_environment
210
219
  m = Hash.new {|h,k| h[k] = []}
211
- m["deployment"].concat [
220
+ m["deployment"] = [
212
221
  [Rack::ContentLength],
213
222
  [Rack::Chunked],
214
- logging_middleware
223
+ logging_middleware,
224
+ [Rack::TempfileReaper]
215
225
  ]
216
- m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
226
+ m["development"] = [
227
+ [Rack::ContentLength],
228
+ [Rack::Chunked],
229
+ logging_middleware,
230
+ [Rack::ShowExceptions],
231
+ [Rack::Lint],
232
+ [Rack::TempfileReaper]
233
+ ]
234
+
217
235
  m
218
236
  end
237
+
238
+ # Aliased for backwards-compatibility
239
+ alias :middleware :default_middleware_by_environment
219
240
  end
220
241
 
221
- def middleware
222
- self.class.middleware
242
+ def default_middleware_by_environment
243
+ self.class.default_middleware_by_environment
223
244
  end
224
245
 
246
+ # Aliased for backwards-compatibility
247
+ alias :middleware :default_middleware_by_environment
248
+
225
249
  def start &blk
226
250
  if options[:warn]
227
251
  $-w = true
@@ -301,7 +325,8 @@ module Rack
301
325
  end
302
326
 
303
327
  def build_app(app)
304
- middleware[options[:environment]].reverse_each do |middleware|
328
+ middlewares = default_middleware_by_environment[options[:environment]]
329
+ middlewares.reverse_each do |middleware|
305
330
  middleware = middleware.call(self) if middleware.respond_to?(:call)
306
331
  next unless middleware
307
332
  klass, *args = middleware
@@ -350,6 +375,8 @@ module Rack
350
375
  return :exited unless ::File.exist?(options[:pid])
351
376
 
352
377
  pid = ::File.read(options[:pid]).to_i
378
+ return :dead if pid == 0
379
+
353
380
  Process.kill(0, pid)
354
381
  :running
355
382
  rescue Errno::ESRCH
@@ -359,4 +386,5 @@ module Rack
359
386
  end
360
387
 
361
388
  end
389
+
362
390
  end
@@ -289,7 +289,7 @@ module Rack
289
289
  value && !value.empty?
290
290
  end
291
291
 
292
- # Session should be commited if it was loaded, any of specific options like :renew, :drop
292
+ # Session should be committed if it was loaded, any of specific options like :renew, :drop
293
293
  # or :expire_after was given and the security permissions match. Skips if skip is given.
294
294
 
295
295
  def commit_session?(env, session, options)
@@ -310,7 +310,7 @@ module Rack
310
310
  end
311
311
 
312
312
  def force_options?(options)
313
- options.values_at(:renew, :drop, :defer, :expire_after).any?
313
+ options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
314
314
  end
315
315
 
316
316
  def security_matches?(env, options)
@@ -342,11 +342,12 @@ module Rack
342
342
  if not data = set_session(env, session_id, session_data, options)
343
343
  env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
344
344
  elsif options[:defer] and not options[:renew]
345
- env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
345
+ env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
346
346
  else
347
347
  cookie = Hash.new
348
348
  cookie[:value] = data
349
349
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
350
+ cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
350
351
  set_cookie(env, headers, cookie.merge!(options))
351
352
  end
352
353
 
@@ -369,7 +370,7 @@ module Rack
369
370
  SessionHash
370
371
  end
371
372
 
372
- # All thread safety and session retrival proceedures should occur here.
373
+ # All thread safety and session retrieval procedures should occur here.
373
374
  # Should return [session_id, session].
374
375
  # If nil is provided as the session id, generation of a new valid id
375
376
  # should occur within.
@@ -378,7 +379,7 @@ module Rack
378
379
  raise '#get_session not implemented.'
379
380
  end
380
381
 
381
- # All thread safety and session storage proceedures should occur here.
382
+ # All thread safety and session storage procedures should occur here.
382
383
  # Must return the session id if the session was saved successfully, or
383
384
  # false if the session could not be saved.
384
385
 
@@ -386,7 +387,7 @@ module Rack
386
387
  raise '#set_session not implemented.'
387
388
  end
388
389
 
389
- # All thread safety and session destroy proceedures should occur here.
390
+ # All thread safety and session destroy procedures should occur here.
390
391
  # Should return a new session id or nil if options[:drop]
391
392
 
392
393
  def destroy_session(env, sid, options)
@@ -1,4 +1,5 @@
1
1
  require 'openssl'
2
+ require 'zlib'
2
3
  require 'rack/request'
3
4
  require 'rack/response'
4
5
  require 'rack/session/abstract/id'
@@ -78,6 +79,19 @@ module Rack
78
79
  ::Rack::Utils::OkJson.decode(super(str)) rescue nil
79
80
  end
80
81
  end
82
+
83
+ class ZipJSON < Base64
84
+ def encode(obj)
85
+ super(Zlib::Deflate.deflate(::Rack::Utils::OkJson.encode(obj)))
86
+ end
87
+
88
+ def decode(str)
89
+ return unless str
90
+ ::Rack::Utils::OkJson.decode(Zlib::Inflate.inflate(super(str)))
91
+ rescue
92
+ nil
93
+ end
94
+ end
81
95
  end
82
96
 
83
97
  # Use no encoding for session cookies
@@ -86,12 +100,6 @@ module Rack
86
100
  def decode(str); str; end
87
101
  end
88
102
 
89
- # Reverse string encoding. (trollface)
90
- class Reverse
91
- def encode(str); str.reverse; end
92
- def decode(str); str.reverse; end
93
- end
94
-
95
103
  attr_reader :coder
96
104
 
97
105
  def initialize(app, options={})
@@ -127,7 +135,9 @@ module Rack
127
135
  session_data = request.cookies[@key]
128
136
 
129
137
  if @secrets.size > 0 && session_data
130
- session_data, digest = session_data.split("--")
138
+ digest, session_data = session_data.reverse.split("--", 2)
139
+ digest.reverse! if digest
140
+ session_data.reverse! if session_data
131
141
  session_data = nil unless digest_match?(session_data, digest)
132
142
  end
133
143