sinatra 1.3.0.e → 1.3.0.f

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

Potentially problematic release.


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

data/README.zh.rdoc CHANGED
@@ -194,10 +194,10 @@ Rack body对象或者HTTP状态码:
194
194
 
195
195
  == 静态文件
196
196
 
197
- 静态文件是从 <tt>./public</tt> 目录提供服务。你可以通过设置<tt>:public</tt>
197
+ 静态文件是从 <tt>./public_folder</tt> 目录提供服务。你可以通过设置<tt>:public</tt>
198
198
  选项设定一个不同的位置:
199
199
 
200
- set :public, File.dirname(__FILE__) + '/static'
200
+ set :public_folder, File.dirname(__FILE__) + '/static'
201
201
 
202
202
  请注意public目录名并没有被包含在URL之中。文件
203
203
  <tt>./public/css/style.css</tt>是通过
@@ -897,7 +897,7 @@ Session被用来在请求之间保持状态。如果被激活,每一个用户
897
897
  get '/foo' do
898
898
  status 418
899
899
  headers \
900
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
900
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
901
901
  "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
902
902
  body "I'm a tea pot!"
903
903
  end
@@ -961,7 +961,7 @@ Sinatra并不理解。使用 +mime_type+ 通过文件扩展名来注册它们:
961
961
 
962
962
  或者使用session:
963
963
 
964
- enable :session
964
+ enable :sessions
965
965
 
966
966
  get '/foo' do
967
967
  session[:secret] = 'foo'
@@ -1268,7 +1268,7 @@ Sinatra会自动处理range请求。
1268
1268
  <tt>redirect '/foo'</tt> 会和
1269
1269
  <tt>redirect to('/foo')</tt>起相同作用。默认禁用。
1270
1270
 
1271
- [public] public文件夹的位置。
1271
+ [public_folder] public文件夹的位置。
1272
1272
 
1273
1273
  [reload_templates] 是否每个请求都重新载入模板。
1274
1274
  在development mode和 Ruby 1.8.6 中被企业(用来
data/Rakefile CHANGED
@@ -3,6 +3,12 @@ require 'rake/testtask'
3
3
  require 'fileutils'
4
4
  require 'date'
5
5
 
6
+ # CI Reporter is only needed for the CI
7
+ begin
8
+ require 'ci/reporter/rake/test_unit'
9
+ rescue LoadError
10
+ end
11
+
6
12
  task :default => :test
7
13
  task :spec => :test
8
14
 
@@ -77,7 +83,7 @@ task :add_template, [:name] do |t, args|
77
83
  puts "Liquid not found in #{file}"
78
84
  else
79
85
  puts "Adding section to #{file}"
80
- template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
86
+ template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
81
87
  code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1"
82
88
  File.open(file, "w") { |f| f << code }
83
89
  end
@@ -102,7 +108,7 @@ end
102
108
 
103
109
  desc "list of authors"
104
110
  task :authors, [:commit_range, :format, :sep] do |t, a|
105
- a.with_defaults :format => "%s (%d)", :sep => ", "
111
+ a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all'
106
112
  authors = Hash.new { |h,k| h[k] = 0 }
107
113
  blake = "Blake Mizerany"
108
114
  overall = 0
data/lib/sinatra/base.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # external dependencies
2
2
  require 'rack'
3
3
  require 'tilt'
4
+ require "rack/protection"
4
5
 
5
6
  # stdlib dependencies
6
7
  require 'thread'
@@ -39,6 +40,14 @@ module Sinatra
39
40
  @env.include? "HTTP_X_FORWARDED_HOST"
40
41
  end
41
42
 
43
+ def safe?
44
+ get? or head? or options? or trace?
45
+ end
46
+
47
+ def idempotent?
48
+ safe? or put? or delete?
49
+ end
50
+
42
51
  private
43
52
 
44
53
  def accept_entry(entry)
@@ -55,8 +64,8 @@ module Sinatra
55
64
  # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
56
65
  class Response < Rack::Response
57
66
  def body=(value)
58
- value = value.body while value.respond_to? :body and value.body != value
59
- @body = value.respond_to?(:to_str) ? [value.to_str] : value
67
+ value = value.body while Rack::Response === value
68
+ @body = String === value ? [value.to_str] : value
60
69
  end
61
70
 
62
71
  def each
@@ -64,10 +73,17 @@ module Sinatra
64
73
  end
65
74
 
66
75
  def finish
67
- if body.respond_to? :to_ary and not [204, 304].include?(status.to_i)
76
+ if status.to_i / 100 == 1
77
+ headers.delete "Content-Length"
78
+ headers.delete "Content-Type"
79
+ elsif Array === body and not [204, 304].include?(status.to_i)
68
80
  headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
69
81
  end
70
- super
82
+
83
+ # Rack::Response#finish sometimes returns self as response body. We don't want that.
84
+ status, headers, result = super
85
+ result = body if result == self
86
+ [status, headers, result]
71
87
  end
72
88
  end
73
89
 
@@ -98,7 +114,11 @@ module Sinatra
98
114
 
99
115
  # Halt processing and redirect to the URI provided.
100
116
  def redirect(uri, *args)
101
- status 302
117
+ if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
118
+ status 303
119
+ else
120
+ status 302
121
+ end
102
122
 
103
123
  # According to RFC 2616 section 14.30, "the field value consists of a
104
124
  # single absolute URI"
@@ -186,6 +206,8 @@ module Sinatra
186
206
  if filename
187
207
  params = '; filename="%s"' % File.basename(filename)
188
208
  response['Content-Disposition'] << params
209
+ ext = File.extname(filename)
210
+ content_type(ext) unless response['Content-Type'] or ext.empty?
189
211
  end
190
212
  end
191
213
 
@@ -212,6 +234,61 @@ module Sinatra
212
234
  not_found
213
235
  end
214
236
 
237
+ # Class of the response body in case you use #stream.
238
+ #
239
+ # Three things really matter: The front and back block (back being the
240
+ # blog generating content, front the one sending it to the client) and
241
+ # the scheduler, integrating with whatever concurrency feature the Rack
242
+ # handler is using.
243
+ #
244
+ # Scheduler has to respond to defer and schedule.
245
+ class Stream
246
+ def self.schedule(*) yield end
247
+ def self.defer(*) yield end
248
+
249
+ def initialize(scheduler = self.class, keep_open = false, &back)
250
+ @back, @scheduler, @callback, @keep_open = back.to_proc, scheduler, nil, keep_open
251
+ end
252
+
253
+ def close
254
+ @scheduler.schedule { @callback.call if @callback }
255
+ end
256
+
257
+ def each(&front)
258
+ @front = front
259
+ @scheduler.defer do
260
+ begin
261
+ @back.call(self)
262
+ rescue Exception => e
263
+ @scheduler.schedule { raise e }
264
+ end
265
+ close unless @keep_open
266
+ end
267
+ end
268
+
269
+ def <<(data)
270
+ @scheduler.schedule { @front.call(data.to_s) }
271
+ self
272
+ end
273
+
274
+ def callback(&block)
275
+ @callback = block
276
+ end
277
+
278
+ alias errback callback
279
+ end
280
+
281
+ # Allows to start sending data to the client even though later parts of
282
+ # the response body have not yet been generated.
283
+ #
284
+ # The close parameter specifies whether Stream#close should be called
285
+ # after the block has been executed. This is only relevant for evented
286
+ # servers like Thin or Rainbows.
287
+ def stream(keep_open = false, &block)
288
+ scheduler = env['async.callback'] ? EventMachine : Stream
289
+ body Stream.new(scheduler, keep_open, &block)
290
+ end
291
+
215
292
  # Specify response freshness policy for HTTP caches (Cache-Control header).
216
293
  # Any number of non-value directives (:public, :private, :no_cache,
217
294
  # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
@@ -298,10 +375,13 @@ module Sinatra
298
375
  value = 'W/' + value if kind == :weak
299
376
  response['ETag'] = value
300
377
 
301
- # Conditional GET check
302
378
  if etags = env['HTTP_IF_NONE_MATCH']
303
379
  etags = etags.split(/\s*,\s*/)
304
- halt 304 if etags.include?(value) || etags.include?('*')
380
+ if etags.include?(value) or etags.include?('*')
381
+ halt 304 if request.safe?
382
+ else
383
+ halt 412 unless request.safe?
384
+ end
305
385
  end
306
386
  end
307
387
 
@@ -310,11 +390,38 @@ module Sinatra
310
390
  request.referer
311
391
  end
312
392
 
313
- private
393
+ # whether or not the status is set to 1xx
394
+ def informational?
395
+ status.between? 100, 199
396
+ end
397
+
398
+ # whether or not the status is set to 2xx
399
+ def success?
400
+ status.between? 200, 299
401
+ end
402
+
403
+ # whether or not the status is set to 3xx
404
+ def redirect?
405
+ status.between? 300, 399
406
+ end
407
+
408
+ # whether or not the status is set to 4xx
409
+ def client_error?
410
+ status.between? 400, 499
411
+ end
412
+
413
+ # whether or not the status is set to 5xx
414
+ def server_error?
415
+ status.between? 500, 599
416
+ end
417
+
418
+ # whether or not the status is set to 404
419
+ def not_found?
420
+ status == 404
421
+ end
314
422
 
315
- # Ruby 1.8 has no #to_time method.
316
- # This can be removed and calls to it replaced with to_time,
317
- # if 1.8 support is dropped.
423
+ # Generates a Time object from the given value.
424
+ # Used by #expires and #last_modified.
318
425
  def time_for(value)
319
426
  if value.respond_to? :to_time
320
427
  value.to_time
@@ -340,6 +447,8 @@ module Sinatra
340
447
  end
341
448
  end
342
449
 
450
+ private
451
+
343
452
  # Template rendering methods. Each method takes the name of a template
344
453
  # to render as a Symbol and returns a String with the rendered output,
345
454
  # as well as an optional hash with additional options.
@@ -374,7 +483,7 @@ module Sinatra
374
483
 
375
484
  def erubis(template, options={}, locals={})
376
485
  warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
377
- "If you have Erubis installed, it will be used automatically.\n\tfrom #{caller.first}"
486
+ "If you have Erubis installed, it will be used automatically."
378
487
  render :erubis, template, options, locals
379
488
  end
380
489
 
@@ -567,7 +676,7 @@ module Sinatra
567
676
  invoke { error_block!(response.status) }
568
677
 
569
678
  unless @response['Content-Type']
570
- if body.respond_to? :to_ary and body[0].respond_to? :content_type
679
+ if Array === body and body[0].respond_to? :content_type
571
680
  content_type body[0].content_type
572
681
  else
573
682
  content_type :html
@@ -589,7 +698,7 @@ module Sinatra
589
698
 
590
699
  def options
591
700
  warn "Sinatra::Base#options is deprecated and will be removed, " \
592
- "use #settings instead.\n\tfrom #{caller.first}"
701
+ "use #settings instead."
593
702
  settings
594
703
  end
595
704
 
@@ -653,12 +762,12 @@ module Sinatra
653
762
  # Revert params afterwards.
654
763
  #
655
764
  # Returns pass block.
656
- def process_route(pattern, keys, conditions, block = nil)
765
+ def process_route(pattern, keys, conditions, block = nil, values = [])
657
766
  @original_params ||= @params
658
767
  route = @request.path_info
659
768
  route = '/' if route.empty? and not settings.empty_path_info?
660
769
  if match = pattern.match(route)
661
- values = match.captures.to_a.map { |v| force_encoding URI.decode(v) if v }
770
+ values += match.captures.to_a.map { |v| force_encoding URI.decode(v) if v }
662
771
  params =
663
772
  if keys.any?
664
773
  keys.zip(values).inject({}) do |hash,(k,v)|
@@ -701,7 +810,7 @@ module Sinatra
701
810
  # Attempt to serve static files from public directory. Throws :halt when
702
811
  # a matching file is found, returns nil otherwise.
703
812
  def static!
704
- return if (public_dir = settings.public).nil?
813
+ return if (public_dir = settings.public_folder).nil?
705
814
  public_dir = File.expand_path(public_dir)
706
815
 
707
816
  path = File.expand_path(public_dir + unescape(request.path_info))
@@ -753,32 +862,34 @@ module Sinatra
753
862
  # Error handling during requests.
754
863
  def handle_exception!(boom)
755
864
  @env['sinatra.error'] = boom
756
- dump_errors!(boom) if settings.dump_errors?
757
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
758
- @response.status = boom.respond_to?(:code) ? Integer(boom.code) : 500
865
+ status boom.respond_to?(:code) ? Integer(boom.code) : 500
759
866
 
760
- if @response.status == 404
761
- @response.headers['X-Cascade'] = 'pass'
762
- @response.body = ['<h1>Not Found</h1>']
867
+ if server_error?
868
+ dump_errors! boom if settings.dump_errors?
869
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
763
870
  end
764
871
 
765
- if res = error_block!(boom.class)
766
- res
767
- elsif @response.status >= 500
768
- raise boom if settings.raise_errors? or settings.show_exceptions?
769
- error_block! Exception
872
+ if not_found?
873
+ headers['X-Cascade'] = 'pass'
874
+ body '<h1>Not Found</h1>'
770
875
  end
876
+
877
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
878
+ return res if res or not server_error?
879
+ raise boom if settings.raise_errors? or settings.show_exceptions?
880
+ error_block! Exception, boom
771
881
  end
772
882
 
773
883
  # Find an custom error block for the key(s) specified.
774
- def error_block!(key)
884
+ def error_block!(key, *block_params)
775
885
  base = settings
776
886
  while base.respond_to?(:errors)
777
887
  next base = base.superclass unless args = base.errors[key]
888
+ args += [block_params]
778
889
  return process_route(*args)
779
890
  end
780
891
  return false unless key.respond_to? :superclass and key.superclass < Exception
781
- error_block! key.superclass
892
+ error_block!(key.superclass, *block_params)
782
893
  end
783
894
 
784
895
  def dump_errors!(boom)
@@ -979,6 +1090,11 @@ module Sinatra
979
1090
  @conditions << generate_method(name, &block)
980
1091
  end
981
1092
 
1093
+ def public=(value)
1094
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder instead"
1095
+ set(:public_folder, value)
1096
+ end
1097
+
982
1098
  private
983
1099
  # Condition for matching host name. Parameter might be String or Regexp.
984
1100
  def host_name(pattern)
@@ -1036,8 +1152,10 @@ module Sinatra
1036
1152
  # Because of self.options.host
1037
1153
  host_name(options.delete(:host)) if options.key?(:host)
1038
1154
  enable :empty_path_info if path == "" and empty_path_info.nil?
1039
- (@routes[verb] ||= []) << compile!(verb, path, block, options)
1155
+ signature = compile!(verb, path, block, options)
1156
+ (@routes[verb] ||= []) << signature
1040
1157
  invoke_hook(:route_added, verb, path, block)
1158
+ signature
1041
1159
  end
1042
1160
 
1043
1161
  def invoke_hook(name, *args)
@@ -1134,23 +1252,28 @@ module Sinatra
1134
1252
  def quit!(server, handler_name)
1135
1253
  # Use Thin's hard #stop! if available, otherwise just #stop.
1136
1254
  server.respond_to?(:stop!) ? server.stop! : server.stop
1137
- STDERR.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1255
+ $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1138
1256
  end
1139
1257
 
1140
1258
  # Run the Sinatra app as a self-hosted server using
1141
- # Thin, Mongrel or WEBrick (in that order)
1259
+ # Thin, Mongrel or WEBrick (in that order). If given a block, will call
1260
+ # with the constructed handler once we have taken the stage.
1142
1261
  def run!(options={})
1143
1262
  set options
1144
1263
  handler = detect_rack_handler
1145
1264
  handler_name = handler.name.gsub(/.*::/, '')
1146
- STDERR.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
1147
- "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
1148
1265
  handler.run self, :Host => bind, :Port => port do |server|
1266
+ unless handler_name =~ /cgi/i
1267
+ $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
1268
+ "on #{port} for #{environment} with backup from #{handler_name}"
1269
+ end
1149
1270
  [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
1271
+ server.threaded = settings.threaded if server.respond_to? :threaded=
1150
1272
  set :running, true
1273
+ yield server if block_given?
1151
1274
  end
1152
1275
  rescue Errno::EADDRINUSE => e
1153
- STDERR.puts "== Someone is already performing on port #{port}!"
1276
+ $stderr.puts "== Someone is already performing on port #{port}!"
1154
1277
  end
1155
1278
 
1156
1279
  # The prototype instance used to process requests.
@@ -1186,8 +1309,9 @@ module Sinatra
1186
1309
  builder.use ShowExceptions if show_exceptions?
1187
1310
  builder.use Rack::MethodOverride if method_override?
1188
1311
  builder.use Rack::Head
1189
- setup_logging builder
1190
- setup_sessions builder
1312
+ setup_logging builder
1313
+ setup_sessions builder
1314
+ setup_protection builder
1191
1315
  end
1192
1316
 
1193
1317
  def setup_middleware(builder)
@@ -1207,6 +1331,14 @@ module Sinatra
1207
1331
  end
1208
1332
  end
1209
1333
 
1334
+ def setup_protection(builder)
1335
+ return unless protection?
1336
+ options = Hash === protection ? protection.dup : {}
1337
+ options[:except] = Array options[:except]
1338
+ options[:except] += [:session_hijacking, :remote_token] unless sessions?
1339
+ builder.use Rack::Protection, options
1340
+ end
1341
+
1210
1342
  def setup_sessions(builder)
1211
1343
  return unless sessions?
1212
1344
  options = {}
@@ -1219,7 +1351,7 @@ module Sinatra
1219
1351
  servers = Array(server)
1220
1352
  servers.each do |server_name|
1221
1353
  begin
1222
- return Rack::Handler.get(server_name)
1354
+ return Rack::Handler.get(server_name.to_s)
1223
1355
  rescue LoadError
1224
1356
  rescue NameError
1225
1357
  end
@@ -1250,7 +1382,8 @@ module Sinatra
1250
1382
  /rubygems\/custom_require\.rb$/, # rubygems require hacks
1251
1383
  /active_support/, # active_support require hacks
1252
1384
  /bundler(\/runtime)?\.rb/, # bundler require hacks
1253
- /<internal:/ # internal in ruby >= 1.9.2
1385
+ /<internal:/, # internal in ruby >= 1.9.2
1386
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1254
1387
  ]
1255
1388
 
1256
1389
  # add rubinius (and hopefully other VM impls) ignore patterns ...
@@ -1259,16 +1392,26 @@ module Sinatra
1259
1392
  # Like Kernel#caller but excluding certain magic entries and without
1260
1393
  # line / method information; the resulting array contains filenames only.
1261
1394
  def caller_files
1262
- caller_locations.
1263
- map { |file,line| file }
1395
+ cleaned_caller(1).flatten
1264
1396
  end
1265
1397
 
1266
1398
  # Like caller_files, but containing Arrays rather than strings with the
1267
1399
  # first element being the file, and the second being the line.
1268
1400
  def caller_locations
1401
+ cleaned_caller 2
1402
+ end
1403
+
1404
+ private
1405
+ # used for deprecation warnings
1406
+ def warn(message)
1407
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1408
+ end
1409
+
1410
+ # Like Kernel#caller but excluding certain magic entries
1411
+ def cleaned_caller(keep = 3)
1269
1412
  caller(1).
1270
- map { |line| line.split(/:(?=\d|in )/)[0,2] }.
1271
- reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1413
+ map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1414
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1272
1415
  end
1273
1416
  end
1274
1417
 
@@ -1303,6 +1446,7 @@ module Sinatra
1303
1446
  set :show_exceptions, Proc.new { development? }
1304
1447
  set :sessions, false
1305
1448
  set :logging, false
1449
+ set :protection, true
1306
1450
  set :method_override, false
1307
1451
  set :default_encoding, "utf-8"
1308
1452
  set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
@@ -1337,9 +1481,10 @@ module Sinatra
1337
1481
  set :views, Proc.new { root && File.join(root, 'views') }
1338
1482
  set :reload_templates, Proc.new { development? }
1339
1483
  set :lock, false
1484
+ set :threaded, true
1340
1485
 
1341
- set :public, Proc.new { root && File.join(root, 'public') }
1342
- set :static, Proc.new { public && File.exist?(public) }
1486
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1487
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1343
1488
  set :static_cache_control, false
1344
1489
 
1345
1490
  error ::Exception do