sinatra 1.4.8 → 2.0.0

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