sinatra 1.4.6 → 2.0.0

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.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS.md +5 -2
  3. data/{CHANGES → CHANGELOG.md} +126 -46
  4. data/CONTRIBUTING.md +100 -0
  5. data/Gemfile +12 -17
  6. data/LICENSE +5 -2
  7. data/MAINTENANCE.md +42 -0
  8. data/README.de.md +711 -466
  9. data/README.es.md +206 -171
  10. data/README.fr.md +370 -344
  11. data/README.hu.md +44 -10
  12. data/README.ja.md +300 -210
  13. data/README.ko.md +230 -191
  14. data/README.md +675 -528
  15. data/README.pt-br.md +149 -115
  16. data/README.pt-pt.md +65 -65
  17. data/README.ru.md +198 -97
  18. data/README.zh.md +1943 -1237
  19. data/Rakefile +72 -49
  20. data/SECURITY.md +35 -0
  21. data/lib/sinatra/base.rb +141 -207
  22. data/lib/sinatra/indifferent_hash.rb +150 -0
  23. data/lib/sinatra/main.rb +1 -0
  24. data/lib/sinatra/show_exceptions.rb +70 -56
  25. data/lib/sinatra/version.rb +1 -1
  26. data/sinatra.gemspec +19 -7
  27. metadata +32 -163
  28. data/test/asciidoctor_test.rb +0 -72
  29. data/test/base_test.rb +0 -167
  30. data/test/builder_test.rb +0 -91
  31. data/test/coffee_test.rb +0 -96
  32. data/test/compile_test.rb +0 -183
  33. data/test/contest.rb +0 -91
  34. data/test/creole_test.rb +0 -65
  35. data/test/delegator_test.rb +0 -160
  36. data/test/encoding_test.rb +0 -20
  37. data/test/erb_test.rb +0 -116
  38. data/test/extensions_test.rb +0 -98
  39. data/test/filter_test.rb +0 -487
  40. data/test/haml_test.rb +0 -109
  41. data/test/helper.rb +0 -132
  42. data/test/helpers_test.rb +0 -1917
  43. data/test/integration/app.rb +0 -79
  44. data/test/integration_helper.rb +0 -236
  45. data/test/integration_test.rb +0 -104
  46. data/test/less_test.rb +0 -69
  47. data/test/liquid_test.rb +0 -77
  48. data/test/mapped_error_test.rb +0 -285
  49. data/test/markaby_test.rb +0 -80
  50. data/test/markdown_test.rb +0 -85
  51. data/test/mediawiki_test.rb +0 -68
  52. data/test/middleware_test.rb +0 -68
  53. data/test/nokogiri_test.rb +0 -67
  54. data/test/public/favicon.ico +0 -0
  55. data/test/rabl_test.rb +0 -89
  56. data/test/rack_test.rb +0 -45
  57. data/test/radius_test.rb +0 -59
  58. data/test/rdoc_test.rb +0 -66
  59. data/test/readme_test.rb +0 -130
  60. data/test/request_test.rb +0 -100
  61. data/test/response_test.rb +0 -63
  62. data/test/result_test.rb +0 -76
  63. data/test/route_added_hook_test.rb +0 -59
  64. data/test/routing_test.rb +0 -1412
  65. data/test/sass_test.rb +0 -115
  66. data/test/scss_test.rb +0 -88
  67. data/test/server_test.rb +0 -56
  68. data/test/settings_test.rb +0 -582
  69. data/test/sinatra_test.rb +0 -12
  70. data/test/slim_test.rb +0 -102
  71. data/test/static_test.rb +0 -236
  72. data/test/streaming_test.rb +0 -149
  73. data/test/stylus_test.rb +0 -90
  74. data/test/templates_test.rb +0 -382
  75. data/test/textile_test.rb +0 -65
  76. data/test/views/a/in_a.str +0 -1
  77. data/test/views/ascii.erb +0 -2
  78. data/test/views/b/in_b.str +0 -1
  79. data/test/views/calc.html.erb +0 -1
  80. data/test/views/error.builder +0 -3
  81. data/test/views/error.erb +0 -3
  82. data/test/views/error.haml +0 -3
  83. data/test/views/error.sass +0 -2
  84. data/test/views/explicitly_nested.str +0 -1
  85. data/test/views/foo/hello.test +0 -1
  86. data/test/views/hello.asciidoc +0 -1
  87. data/test/views/hello.builder +0 -1
  88. data/test/views/hello.coffee +0 -1
  89. data/test/views/hello.creole +0 -1
  90. data/test/views/hello.erb +0 -1
  91. data/test/views/hello.haml +0 -1
  92. data/test/views/hello.less +0 -5
  93. data/test/views/hello.liquid +0 -1
  94. data/test/views/hello.mab +0 -1
  95. data/test/views/hello.md +0 -1
  96. data/test/views/hello.mediawiki +0 -1
  97. data/test/views/hello.nokogiri +0 -1
  98. data/test/views/hello.rabl +0 -2
  99. data/test/views/hello.radius +0 -1
  100. data/test/views/hello.rdoc +0 -1
  101. data/test/views/hello.sass +0 -2
  102. data/test/views/hello.scss +0 -3
  103. data/test/views/hello.slim +0 -1
  104. data/test/views/hello.str +0 -1
  105. data/test/views/hello.styl +0 -2
  106. data/test/views/hello.test +0 -1
  107. data/test/views/hello.textile +0 -1
  108. data/test/views/hello.wlang +0 -1
  109. data/test/views/hello.yajl +0 -1
  110. data/test/views/layout2.builder +0 -3
  111. data/test/views/layout2.erb +0 -2
  112. data/test/views/layout2.haml +0 -2
  113. data/test/views/layout2.liquid +0 -2
  114. data/test/views/layout2.mab +0 -2
  115. data/test/views/layout2.nokogiri +0 -3
  116. data/test/views/layout2.rabl +0 -3
  117. data/test/views/layout2.radius +0 -2
  118. data/test/views/layout2.slim +0 -3
  119. data/test/views/layout2.str +0 -2
  120. data/test/views/layout2.test +0 -1
  121. data/test/views/layout2.wlang +0 -2
  122. data/test/views/nested.str +0 -1
  123. data/test/views/utf8.erb +0 -2
  124. data/test/wlang_test.rb +0 -87
  125. data/test/yajl_test.rb +0 -86
data/lib/sinatra/base.rb CHANGED
@@ -1,7 +1,13 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  # external dependencies
2
5
  require 'rack'
3
6
  require 'tilt'
4
7
  require 'rack/protection'
8
+ require 'mustermann'
9
+ require 'mustermann/sinatra'
10
+ require 'mustermann/regular'
5
11
 
6
12
  # stdlib dependencies
7
13
  require 'thread'
@@ -9,6 +15,7 @@ require 'time'
9
15
  require 'uri'
10
16
 
11
17
  # other files we need
18
+ require 'sinatra/indifferent_hash'
12
19
  require 'sinatra/show_exceptions'
13
20
  require 'sinatra/version'
14
21
 
@@ -68,6 +75,12 @@ module Sinatra
68
75
  request_method == "UNLINK"
69
76
  end
70
77
 
78
+ def params
79
+ super
80
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
81
+ raise BadRequest, "Invalid query parameters: #{e.message}"
82
+ end
83
+
71
84
  private
72
85
 
73
86
  class AcceptEntry
@@ -150,7 +163,7 @@ module Sinatra
150
163
  if calculate_content_length?
151
164
  # if some other code has already set Content-Length, don't muck with it
152
165
  # currently, this would be the static file-handler
153
- headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
166
+ headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
154
167
  end
155
168
 
156
169
  [status.to_i, headers, result]
@@ -220,6 +233,10 @@ module Sinatra
220
233
  end
221
234
  end
222
235
 
236
+ class BadRequest < TypeError #:nodoc:
237
+ def http_status; 400 end
238
+ end
239
+
223
240
  class NotFound < NameError #:nodoc:
224
241
  def http_status; 404 end
225
242
  end
@@ -228,7 +245,7 @@ module Sinatra
228
245
  module Helpers
229
246
  # Set or retrieve the response status code.
230
247
  def status(value = nil)
231
- response.status = value if value
248
+ response.status = Rack::Utils.status_code(value) if value
232
249
  response.status
233
250
  end
234
251
 
@@ -239,7 +256,11 @@ module Sinatra
239
256
  def block.each; yield(call) end
240
257
  response.body = block
241
258
  elsif value
242
- headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream)
259
+ # Rack 2.0 returns a Rack::File::Iterator here instead of
260
+ # Rack::File as it was in the previous API.
261
+ unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
262
+ headers.delete 'Content-Length'
263
+ end
243
264
  response.body = value
244
265
  else
245
266
  response.body
@@ -263,8 +284,8 @@ module Sinatra
263
284
  # Generates the absolute URI for a given path in the app.
264
285
  # Takes Rack routers and reverse proxies into account.
265
286
  def uri(addr = nil, absolute = true, add_script_name = true)
266
- return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
267
- uri = [host = ""]
287
+ return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i
288
+ uri = [host = String.new]
268
289
  if absolute
269
290
  host << "http#{'s' if request.secure?}://"
270
291
  if request.forwarded? or request.port != (request.secure? ? 443 : 80)
@@ -338,7 +359,7 @@ module Sinatra
338
359
 
339
360
  # Set the Content-Disposition to "attachment" with the specified filename,
340
361
  # instructing the user agents to prompt to save.
341
- def attachment(filename = nil, disposition = 'attachment')
362
+ def attachment(filename = nil, disposition = :attachment)
342
363
  response['Content-Disposition'] = disposition.to_s
343
364
  if filename
344
365
  params = '; filename="%s"' % File.basename(filename)
@@ -356,19 +377,19 @@ module Sinatra
356
377
 
357
378
  disposition = opts[:disposition]
358
379
  filename = opts[:filename]
359
- disposition = 'attachment' if disposition.nil? and filename
360
- filename = path if filename.nil?
380
+ disposition = :attachment if disposition.nil? and filename
381
+ filename = path if filename.nil?
361
382
  attachment(filename, disposition) if disposition
362
383
 
363
384
  last_modified opts[:last_modified] if opts[:last_modified]
364
385
 
365
- file = Rack::File.new nil
366
- file.path = path
367
- result = file.serving env
386
+ file = Rack::File.new(File.dirname(settings.app_file))
387
+ result = file.serving(request, path)
388
+
368
389
  result[1].each { |k,v| headers[k] ||= v }
369
390
  headers['Content-Length'] = result[1]['Content-Length']
370
391
  opts[:status] &&= Integer(opts[:status])
371
- halt opts[:status] || result[0], result[2]
392
+ halt (opts[:status] || result[0]), result[2]
372
393
  rescue Errno::ENOENT
373
394
  not_found
374
395
  end
@@ -393,7 +414,7 @@ module Sinatra
393
414
  def close
394
415
  return if closed?
395
416
  @closed = true
396
- @scheduler.schedule { @callbacks.each { |c| c.call }}
417
+ @scheduler.schedule { @callbacks.each { |c| c.call } }
397
418
  end
398
419
 
399
420
  def each(&front)
@@ -440,7 +461,7 @@ module Sinatra
440
461
  # Specify response freshness policy for HTTP caches (Cache-Control header).
441
462
  # Any number of non-value directives (:public, :private, :no_cache,
442
463
  # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
443
- # a Hash of value directives (:max_age, :min_stale, :s_max_age).
464
+ # a Hash of value directives (:max_age, :min_stale, :s_maxage).
444
465
  #
445
466
  # cache_control :public, :must_revalidate, :max_age => 60
446
467
  # => Cache-Control: public, must-revalidate, max-age=60
@@ -450,8 +471,8 @@ module Sinatra
450
471
  def cache_control(*values)
451
472
  if values.last.kind_of?(Hash)
452
473
  hash = values.pop
453
- hash.reject! { |k,v| v == false }
454
- hash.reject! { |k,v| values << k if v == true }
474
+ hash.reject! { |k, v| v == false }
475
+ hash.reject! { |k, v| values << k if v == true }
455
476
  else
456
477
  hash = {}
457
478
  end
@@ -459,7 +480,7 @@ module Sinatra
459
480
  values.map! { |value| value.to_s.tr('_','-') }
460
481
  hash.each do |key, value|
461
482
  key = key.to_s.tr('_', '-')
462
- value = value.to_i if key == "max-age"
483
+ value = value.to_i if ['max-age', 's-maxage'].include? key
463
484
  values << "#{key}=#{value}"
464
485
  end
465
486
 
@@ -472,7 +493,7 @@ module Sinatra
472
493
  # "values" arguments are passed to the #cache_control helper:
473
494
  #
474
495
  # expires 500, :public, :must_revalidate
475
- # => Cache-Control: public, must-revalidate, max-age=60
496
+ # => Cache-Control: public, must-revalidate, max-age=500
476
497
  # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
477
498
  #
478
499
  def expires(amount, *values)
@@ -589,25 +610,20 @@ module Sinatra
589
610
  status == 404
590
611
  end
591
612
 
613
+ # whether or not the status is set to 400
614
+ def bad_request?
615
+ status == 400
616
+ end
617
+
592
618
  # Generates a Time object from the given value.
593
619
  # Used by #expires and #last_modified.
594
620
  def time_for(value)
595
- if value.respond_to? :to_time
596
- value.to_time
597
- elsif value.is_a? Time
598
- value
599
- elsif value.respond_to? :new_offset
600
- # DateTime#to_time does the same on 1.9
601
- d = value.new_offset 0
602
- t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
603
- t.getlocal
604
- elsif value.respond_to? :mday
605
- # Date#to_time does the same on 1.9
606
- Time.local(value.year, value.mon, value.mday)
607
- elsif value.is_a? Numeric
621
+ if value.is_a? Numeric
608
622
  Time.at value
609
- else
623
+ elsif value.respond_to? :to_s
610
624
  Time.parse value.to_s
625
+ else
626
+ value.to_time
611
627
  end
612
628
  rescue ArgumentError => boom
613
629
  raise boom
@@ -691,7 +707,7 @@ module Sinatra
691
707
  render :less, template, options, locals
692
708
  end
693
709
 
694
- def stylus(template, options={}, locals={})
710
+ def stylus(template, options = {}, locals = {})
695
711
  options.merge! :layout => false, :default_content_type => :css
696
712
  render :styl, template, options, locals
697
713
  end
@@ -793,7 +809,7 @@ module Sinatra
793
809
 
794
810
  def render(engine, data, options = {}, locals = {}, &block)
795
811
  # merge app-level options
796
- engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
812
+ engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
797
813
  options.merge!(engine_options) { |key, v1, v2| v1 }
798
814
 
799
815
  # extract generic options
@@ -805,7 +821,8 @@ module Sinatra
805
821
  layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
806
822
  layout = @default_layout if layout.nil? or layout == true
807
823
  layout_options = options.delete(:layout_options) || {}
808
- content_type = options.delete(:content_type) || options.delete(:default_content_type)
824
+ content_type = options.delete(:default_content_type)
825
+ content_type = options.delete(:content_type) || content_type
809
826
  layout_engine = options.delete(:layout_engine) || engine
810
827
  scope = options.delete(:scope) || self
811
828
  options.delete(:layout)
@@ -862,7 +879,9 @@ module Sinatra
862
879
  end
863
880
  when Proc, String
864
881
  body = data.is_a?(String) ? Proc.new { data } : data
865
- path, line = settings.caller_locations.first
882
+ caller = settings.caller_locations.first
883
+ path = options[:path] || caller[0]
884
+ line = options[:line] || caller[1]
866
885
  template.new(path, line.to_i, options, &body)
867
886
  else
868
887
  raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
@@ -877,7 +896,7 @@ module Sinatra
877
896
  include Helpers
878
897
  include Templates
879
898
 
880
- URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
899
+ URI_INSTANCE = URI::Parser.new
881
900
 
882
901
  attr_accessor :app, :env, :request, :response, :params
883
902
  attr_reader :template_cache
@@ -898,9 +917,7 @@ module Sinatra
898
917
  @env = env
899
918
  @request = Request.new(env)
900
919
  @response = Response.new
901
- @params = indifferent_params(@request.params)
902
920
  template_cache.clear if settings.reload_templates
903
- force_encoding(@params)
904
921
 
905
922
  @response['Content-Type'] = nil
906
923
  invoke { dispatch! }
@@ -968,9 +985,9 @@ module Sinatra
968
985
  # Run routes defined on the class and all superclasses.
969
986
  def route!(base = settings, pass_block = nil)
970
987
  if routes = base.routes[@request.request_method]
971
- routes.each do |pattern, keys, conditions, block|
972
- returned_pass_block = process_route(pattern, keys, conditions) do |*args|
973
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
988
+ routes.each do |pattern, conditions, block|
989
+ returned_pass_block = process_route(pattern, conditions) do |*args|
990
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
974
991
  route_eval { block[*args] }
975
992
  end
976
993
 
@@ -998,21 +1015,30 @@ module Sinatra
998
1015
  # Revert params afterwards.
999
1016
  #
1000
1017
  # Returns pass block.
1001
- def process_route(pattern, keys, conditions, block = nil, values = [])
1018
+ def process_route(pattern, conditions, block = nil, values = [])
1002
1019
  route = @request.path_info
1003
1020
  route = '/' if route.empty? and not settings.empty_path_info?
1004
- return unless match = pattern.match(route)
1005
- values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
1021
+ route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
1022
+ return unless params = pattern.params(route)
1023
+
1024
+ params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1025
+ original, @params = @params, @params.merge(params) if params.any?
1006
1026
 
1007
- if values.any?
1008
- original, @params = params, params.merge('splat' => [], 'captures' => values)
1009
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
1027
+ if pattern.is_a? Mustermann::Regular
1028
+ captures = pattern.match(route).captures
1029
+ values += captures
1030
+ @params[:captures] = captures
1031
+ else
1032
+ values += params.values.flatten
1010
1033
  end
1011
1034
 
1012
1035
  catch(:pass) do
1013
1036
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1014
1037
  block ? block[self, values] : yield(self, values)
1015
1038
  end
1039
+ rescue
1040
+ @env['sinatra.error.params'] = @params
1041
+ raise
1016
1042
  ensure
1017
1043
  @params = original if original
1018
1044
  end
@@ -1034,7 +1060,7 @@ module Sinatra
1034
1060
  # a matching file is found, returns nil otherwise.
1035
1061
  def static!(options = {})
1036
1062
  return if (public_dir = settings.public_folder).nil?
1037
- path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
1063
+ path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" )
1038
1064
  return unless File.file?(path)
1039
1065
 
1040
1066
  env['sinatra.static_file'] = path
@@ -1042,30 +1068,12 @@ module Sinatra
1042
1068
  send_file path, options.merge(:disposition => nil)
1043
1069
  end
1044
1070
 
1045
- # Enable string or symbol key access to the nested params hash.
1046
- def indifferent_params(object)
1047
- case object
1048
- when Hash
1049
- new_hash = indifferent_hash
1050
- object.each { |key, value| new_hash[key] = indifferent_params(value) }
1051
- new_hash
1052
- when Array
1053
- object.map { |item| indifferent_params(item) }
1054
- else
1055
- object
1056
- end
1057
- end
1058
-
1059
- # Creates a Hash with indifferent access.
1060
- def indifferent_hash
1061
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
1062
- end
1063
-
1064
1071
  # Run the block with 'throw :halt' support and apply result to the response.
1065
1072
  def invoke
1066
1073
  res = catch(:halt) { yield }
1067
- res = [res] if Fixnum === res or String === res
1068
- if Array === res and Fixnum === res.first
1074
+
1075
+ res = [res] if Integer === res or String === res
1076
+ if Array === res and Integer === res.first
1069
1077
  res = res.dup
1070
1078
  status(res.shift)
1071
1079
  body(res.pop)
@@ -1078,6 +1086,8 @@ module Sinatra
1078
1086
 
1079
1087
  # Dispatch a request with error handling.
1080
1088
  def dispatch!
1089
+ force_encoding(@params = IndifferentHash[@request.params])
1090
+
1081
1091
  invoke do
1082
1092
  static! if settings.static? && (request.get? || request.head?)
1083
1093
  filter! :before
@@ -1095,6 +1105,9 @@ module Sinatra
1095
1105
 
1096
1106
  # Error handling during requests.
1097
1107
  def handle_exception!(boom)
1108
+ if error_params = @env['sinatra.error.params']
1109
+ @params = @params.merge(error_params)
1110
+ end
1098
1111
  @env['sinatra.error'] = boom
1099
1112
 
1100
1113
  if boom.respond_to? :http_status
@@ -1107,14 +1120,15 @@ module Sinatra
1107
1120
 
1108
1121
  status(500) unless status.between? 400, 599
1109
1122
 
1123
+ boom_message = boom.message if boom.message && boom.message != boom.class.name
1110
1124
  if server_error?
1111
1125
  dump_errors! boom if settings.dump_errors?
1112
1126
  raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1113
- end
1114
-
1115
- if not_found?
1127
+ elsif not_found?
1116
1128
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1117
- body '<h1>Not Found</h1>'
1129
+ body boom_message || '<h1>Not Found</h1>'
1130
+ elsif bad_request?
1131
+ body boom_message || '<h1>Bad Request</h1>'
1118
1132
  end
1119
1133
 
1120
1134
  res = error_block!(boom.class, boom) || error_block!(status, boom)
@@ -1176,7 +1190,7 @@ module Sinatra
1176
1190
  @extensions = []
1177
1191
 
1178
1192
  if superclass.respond_to?(:templates)
1179
- @templates = Hash.new { |hash,key| superclass.templates[key] }
1193
+ @templates = Hash.new { |hash, key| superclass.templates[key] }
1180
1194
  else
1181
1195
  @templates = {}
1182
1196
  end
@@ -1222,7 +1236,7 @@ module Sinatra
1222
1236
  case value
1223
1237
  when Proc
1224
1238
  getter = value
1225
- when Symbol, Fixnum, FalseClass, TrueClass, NilClass
1239
+ when Symbol, Integer, FalseClass, TrueClass, NilClass
1226
1240
  getter = value.inspect
1227
1241
  when Hash
1228
1242
  setter = proc do |val|
@@ -1251,16 +1265,16 @@ module Sinatra
1251
1265
  # class, or an HTTP status code to specify which errors should be
1252
1266
  # handled.
1253
1267
  def error(*codes, &block)
1254
- args = compile! "ERROR", //, block
1255
- codes = codes.map { |c| Array(c) }.flatten
1268
+ args = compile! "ERROR", /.*/, block
1269
+ codes = codes.flat_map(&method(:Array))
1256
1270
  codes << Exception if codes.empty?
1271
+ codes << Sinatra::NotFound if codes.include?(404)
1257
1272
  codes.each { |c| (@errors[c] ||= []) << args }
1258
1273
  end
1259
1274
 
1260
1275
  # Sugar for `error(404) { ... }`
1261
1276
  def not_found(&block)
1262
1277
  error(404, &block)
1263
- error(Sinatra::NotFound, &block)
1264
1278
  end
1265
1279
 
1266
1280
  # Define a named template. The block must return the template source.
@@ -1298,7 +1312,7 @@ module Sinatra
1298
1312
  data.each_line do |line|
1299
1313
  lines += 1
1300
1314
  if line =~ /^@@\s*(.*\S)\s*$/
1301
- template = force_encoding('', encoding)
1315
+ template = force_encoding(String.new, encoding)
1302
1316
  templates[$1.to_sym] = [template, file, lines]
1303
1317
  elsif template
1304
1318
  template << line
@@ -1327,21 +1341,20 @@ module Sinatra
1327
1341
  # Define a before filter; runs before all requests within the same
1328
1342
  # context as route handlers and may access/modify the request and
1329
1343
  # response.
1330
- def before(path = nil, options = {}, &block)
1344
+ def before(path = /.*/, **options, &block)
1331
1345
  add_filter(:before, path, options, &block)
1332
1346
  end
1333
1347
 
1334
1348
  # Define an after filter; runs after all requests within the same
1335
1349
  # context as route handlers and may access/modify the request and
1336
1350
  # response.
1337
- def after(path = nil, options = {}, &block)
1351
+ def after(path = /.*/, **options, &block)
1338
1352
  add_filter(:after, path, options, &block)
1339
1353
  end
1340
1354
 
1341
1355
  # add a filter
1342
- def add_filter(type, path = nil, options = {}, &block)
1343
- path, options = //, path if path.respond_to?(:each_pair)
1344
- filters[type] << compile!(type, path || //, block, options)
1356
+ def add_filter(type, path = /.*/, **options, &block)
1357
+ filters[type] << compile!(type, path, block, options)
1345
1358
  end
1346
1359
 
1347
1360
  # Add a route condition. The route is considered non-matching when the
@@ -1421,7 +1434,7 @@ module Sinatra
1421
1434
  return unless running?
1422
1435
  # Use Thin's hard #stop! if available, otherwise just #stop.
1423
1436
  running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1424
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1437
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless supress_messages?
1425
1438
  set :running_server, nil
1426
1439
  set :handler_name, nil
1427
1440
  end
@@ -1502,8 +1515,12 @@ module Sinatra
1502
1515
 
1503
1516
  # Starts the server by running the Rack Handler.
1504
1517
  def start_server(handler, server_settings, handler_name)
1518
+ # Ensure we initialize middleware before startup, to match standard Rack
1519
+ # behavior, by ensuring an instance exists:
1520
+ prototype
1521
+ # Run the instance we created:
1505
1522
  handler.run(self, server_settings) do |server|
1506
- unless handler_name =~ /cgi/i
1523
+ unless supress_messages?
1507
1524
  $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1508
1525
  end
1509
1526
 
@@ -1516,6 +1533,10 @@ module Sinatra
1516
1533
  end
1517
1534
  end
1518
1535
 
1536
+ def supress_messages?
1537
+ handler_name =~ /cgi/i || quiet
1538
+ end
1539
+
1519
1540
  def setup_traps
1520
1541
  if traps?
1521
1542
  at_exit { quit! }
@@ -1533,8 +1554,7 @@ module Sinatra
1533
1554
 
1534
1555
  # Dynamically defines a method on settings.
1535
1556
  def define_singleton(name, content = Proc.new)
1536
- # replace with call to singleton_class once we're 1.9 only
1537
- (class << self; self; end).class_eval do
1557
+ singleton_class.class_eval do
1538
1558
  undef_method(name) if method_defined? name
1539
1559
  String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
1540
1560
  end
@@ -1577,8 +1597,6 @@ module Sinatra
1577
1597
  end
1578
1598
 
1579
1599
  def route(verb, path, options = {}, &block)
1580
- # Because of self.options.host
1581
- host_name(options.delete(:host)) if options.key?(:host)
1582
1600
  enable :empty_path_info if path == "" and empty_path_info.nil?
1583
1601
  signature = compile!(verb, path, block, options)
1584
1602
  (@routes[verb] ||= []) << signature
@@ -1591,126 +1609,33 @@ module Sinatra
1591
1609
  end
1592
1610
 
1593
1611
  def generate_method(method_name, &block)
1594
- method_name = method_name.to_sym
1595
1612
  define_method(method_name, &block)
1596
1613
  method = instance_method method_name
1597
1614
  remove_method method_name
1598
1615
  method
1599
1616
  end
1600
1617
 
1601
- def compile!(verb, path, block, options = {})
1618
+ def compile!(verb, path, block, **options)
1619
+ # Because of self.options.host
1620
+ host_name(options.delete(:host)) if options.key?(:host)
1621
+ # Pass Mustermann opts to compile()
1622
+ route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
1623
+
1602
1624
  options.each_pair { |option, args| send(option, *args) }
1625
+
1626
+ pattern = compile(path, route_mustermann_opts)
1603
1627
  method_name = "#{verb} #{path}"
1604
1628
  unbound_method = generate_method(method_name, &block)
1605
- pattern, keys = compile path
1606
1629
  conditions, @conditions = @conditions, []
1607
-
1608
1630
  wrapper = block.arity != 0 ?
1609
- proc { |a,p| unbound_method.bind(a).call(*p) } :
1610
- proc { |a,p| unbound_method.bind(a).call }
1611
- wrapper.instance_variable_set(:@route_name, method_name)
1612
-
1613
- [ pattern, keys, conditions, wrapper ]
1614
- end
1615
-
1616
- def compile(path)
1617
- if path.respond_to? :to_str
1618
- keys = []
1619
-
1620
- # We append a / at the end if there was one.
1621
- # Reason: Splitting does not split off an empty
1622
- # string at the end if the split separator
1623
- # is at the end.
1624
- #
1625
- postfix = '/' if path =~ /\/\z/
1626
-
1627
- # Split the path into pieces in between forward slashes.
1628
- #
1629
- segments = path.split('/').map! do |segment|
1630
- ignore = []
1631
-
1632
- # Special character handling.
1633
- #
1634
- pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
1635
- ignore << escaped(c).join if c.match(/[\.@]/)
1636
- patt = encoded(c)
1637
- patt.gsub(/%[\da-fA-F]{2}/) do |match|
1638
- match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
1639
- end
1640
- end
1641
-
1642
- ignore = ignore.uniq.join
1631
+ proc { |a, p| unbound_method.bind(a).call(*p) } :
1632
+ proc { |a, p| unbound_method.bind(a).call }
1643
1633
 
1644
- # Key handling.
1645
- #
1646
- pattern.gsub(/((:\w+)|\*)/) do |match|
1647
- if match == "*"
1648
- keys << 'splat'
1649
- "(.*?)"
1650
- else
1651
- keys << $2[1..-1]
1652
- ignore_pattern = safe_ignore(ignore)
1653
-
1654
- ignore_pattern
1655
- end
1656
- end
1657
- end
1658
-
1659
- # Special case handling.
1660
- #
1661
- if segment = segments.pop
1662
- if segment.match(/\[\^\\\./)
1663
- parts = segment.rpartition(/\[\^\\\./)
1664
- parts[1] = '[^'
1665
- segments << parts.join
1666
- else
1667
- segments << segment
1668
- end
1669
- end
1670
- [/\A#{segments.join('/')}#{postfix}\z/, keys]
1671
- elsif path.respond_to?(:keys) && path.respond_to?(:match)
1672
- [path, path.keys]
1673
- elsif path.respond_to?(:names) && path.respond_to?(:match)
1674
- [path, path.names]
1675
- elsif path.respond_to? :match
1676
- [path, []]
1677
- else
1678
- raise TypeError, path
1679
- end
1680
- end
1681
-
1682
- def encoded(char)
1683
- enc = URI_INSTANCE.escape(char)
1684
- enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
1685
- enc = "(?:#{enc}|#{encoded('+')})" if char == " "
1686
- enc
1634
+ [ pattern, conditions, wrapper ]
1687
1635
  end
1688
1636
 
1689
- def escaped(char, enc = URI_INSTANCE.escape(char))
1690
- [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
1691
- end
1692
-
1693
- def safe_ignore(ignore)
1694
- unsafe_ignore = []
1695
- ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
1696
- unsafe_ignore << hex[1..2]
1697
- ''
1698
- end
1699
- unsafe_patterns = unsafe_ignore.map! do |unsafe|
1700
- chars = unsafe.split(//).map! do |char|
1701
- if char =~ /[A-Z]/
1702
- char <<= char.tr('A-Z', 'a-z')
1703
- end
1704
- char
1705
- end
1706
-
1707
- "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
1708
- end
1709
- if unsafe_patterns.length > 0
1710
- "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
1711
- else
1712
- "([^#{ignore}/?#]+)"
1713
- end
1637
+ def compile(path, route_mustermann_opts = {})
1638
+ Mustermann.new(path, mustermann_opts.merge(route_mustermann_opts))
1714
1639
  end
1715
1640
 
1716
1641
  def setup_default_middleware(builder)
@@ -1755,10 +1680,16 @@ module Sinatra
1755
1680
  def setup_protection(builder)
1756
1681
  return unless protection?
1757
1682
  options = Hash === protection ? protection.dup : {}
1758
- protect_session = options.fetch(:session) { sessions? }
1759
- options[:except] = Array options[:except]
1760
- options[:except] += [:session_hijacking, :remote_token] unless protect_session
1683
+ options = {
1684
+ img_src: "'self' data:",
1685
+ font_src: "'self'"
1686
+ }.merge options
1687
+
1688
+ protect_session = options.fetch(:session) { sessions? }
1689
+ options[:without_session] = !protect_session
1690
+
1761
1691
  options[:reaction] ||= :drop_session
1692
+
1762
1693
  builder.use Rack::Protection, options
1763
1694
  end
1764
1695
 
@@ -1767,7 +1698,7 @@ module Sinatra
1767
1698
  options = {}
1768
1699
  options[:secret] = session_secret if session_secret?
1769
1700
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1770
- builder.use Rack::Session::Cookie, options
1701
+ builder.use session_store, options
1771
1702
  end
1772
1703
 
1773
1704
  def detect_rack_handler
@@ -1834,11 +1765,12 @@ module Sinatra
1834
1765
 
1835
1766
  reset!
1836
1767
 
1837
- set :environment, (ENV['RACK_ENV'] || :development).to_sym
1768
+ set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1838
1769
  set :raise_errors, Proc.new { test? }
1839
1770
  set :dump_errors, Proc.new { !test? }
1840
1771
  set :show_exceptions, Proc.new { development? }
1841
1772
  set :sessions, false
1773
+ set :session_store, Rack::Session::Cookie
1842
1774
  set :logging, false
1843
1775
  set :protection, true
1844
1776
  set :method_override, false
@@ -1847,6 +1779,7 @@ module Sinatra
1847
1779
  set :x_cascade, true
1848
1780
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1849
1781
  settings.add_charset << /^text\//
1782
+ set :mustermann_opts, {}
1850
1783
 
1851
1784
  # explicitly generating a session secret eagerly to play nice with preforking
1852
1785
  begin
@@ -1869,6 +1802,7 @@ module Sinatra
1869
1802
  set :server, %w[HTTP webrick]
1870
1803
  set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1871
1804
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1805
+ set :quiet, false
1872
1806
 
1873
1807
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1874
1808
 
@@ -1886,6 +1820,7 @@ module Sinatra
1886
1820
  set :absolute_redirects, true
1887
1821
  set :prefixed_redirects, false
1888
1822
  set :empty_path_info, nil
1823
+ set :strict_paths, true
1889
1824
 
1890
1825
  set :app_file, nil
1891
1826
  set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
@@ -1944,7 +1879,7 @@ module Sinatra
1944
1879
  </style>
1945
1880
  </head>
1946
1881
  <body>
1947
- <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1882
+ <h2>Sinatra doesnt know this ditty.</h2>
1948
1883
  <img src='#{uri "/__sinatra__/404.png"}'>
1949
1884
  <div id="c">
1950
1885
  Try this:
@@ -1965,14 +1900,13 @@ module Sinatra
1965
1900
  # top-level. Subclassing Sinatra::Base is highly recommended for
1966
1901
  # modular applications.
1967
1902
  class Application < Base
1968
- set :logging, Proc.new { ! test? }
1903
+ set :logging, Proc.new { !test? }
1969
1904
  set :method_override, true
1970
- set :run, Proc.new { ! test? }
1971
- set :session_secret, Proc.new { super() unless development? }
1905
+ set :run, Proc.new { !test? }
1972
1906
  set :app_file, nil
1973
1907
 
1974
1908
  def self.register(*extensions, &block) #:nodoc:
1975
- added_methods = extensions.map {|m| m.public_instance_methods }.flatten
1909
+ added_methods = extensions.flat_map(&:public_instance_methods)
1976
1910
  Delegator.delegate(*added_methods)
1977
1911
  super(*extensions, &block)
1978
1912
  end