sinatra 1.4.8 → 2.0.8.1

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 (129) hide show
  1. checksums.yaml +5 -5
  2. data/AUTHORS.md +1 -0
  3. data/CHANGELOG.md +238 -54
  4. data/CONTRIBUTING.md +8 -8
  5. data/Gemfile +47 -47
  6. data/LICENSE +4 -1
  7. data/MAINTENANCE.md +42 -0
  8. data/README.de.md +650 -442
  9. data/README.es.md +738 -357
  10. data/README.fr.md +15 -15
  11. data/README.hu.md +37 -3
  12. data/README.ja.md +124 -66
  13. data/README.ko.md +14 -14
  14. data/README.malayalam.md +3141 -0
  15. data/README.md +530 -403
  16. data/README.pt-br.md +2361 -334
  17. data/README.pt-pt.md +2 -2
  18. data/README.ru.md +856 -607
  19. data/README.zh.md +90 -28
  20. data/Rakefile +77 -51
  21. data/SECURITY.md +35 -0
  22. data/VERSION +1 -0
  23. data/lib/sinatra/base.rb +177 -239
  24. data/lib/sinatra/indifferent_hash.rb +200 -0
  25. data/lib/sinatra/main.rb +30 -10
  26. data/lib/sinatra/show_exceptions.rb +102 -62
  27. data/lib/sinatra/version.rb +1 -1
  28. data/sinatra.gemspec +44 -8
  29. metadata +41 -166
  30. data/lib/sinatra/ext.rb +0 -17
  31. data/test/asciidoctor_test.rb +0 -72
  32. data/test/base_test.rb +0 -167
  33. data/test/builder_test.rb +0 -91
  34. data/test/coffee_test.rb +0 -96
  35. data/test/compile_test.rb +0 -183
  36. data/test/contest.rb +0 -91
  37. data/test/creole_test.rb +0 -65
  38. data/test/delegator_test.rb +0 -160
  39. data/test/encoding_test.rb +0 -20
  40. data/test/erb_test.rb +0 -116
  41. data/test/extensions_test.rb +0 -98
  42. data/test/filter_test.rb +0 -487
  43. data/test/haml_test.rb +0 -109
  44. data/test/helper.rb +0 -132
  45. data/test/helpers_test.rb +0 -1917
  46. data/test/integration/app.rb +0 -79
  47. data/test/integration_helper.rb +0 -236
  48. data/test/integration_test.rb +0 -104
  49. data/test/less_test.rb +0 -69
  50. data/test/liquid_test.rb +0 -77
  51. data/test/mapped_error_test.rb +0 -285
  52. data/test/markaby_test.rb +0 -80
  53. data/test/markdown_test.rb +0 -85
  54. data/test/mediawiki_test.rb +0 -68
  55. data/test/middleware_test.rb +0 -68
  56. data/test/nokogiri_test.rb +0 -67
  57. data/test/public/favicon.ico +0 -0
  58. data/test/public/hello+world.txt +0 -1
  59. data/test/rabl_test.rb +0 -89
  60. data/test/rack_test.rb +0 -45
  61. data/test/radius_test.rb +0 -59
  62. data/test/rdoc_test.rb +0 -66
  63. data/test/readme_test.rb +0 -130
  64. data/test/request_test.rb +0 -100
  65. data/test/response_test.rb +0 -63
  66. data/test/result_test.rb +0 -76
  67. data/test/route_added_hook_test.rb +0 -59
  68. data/test/routing_test.rb +0 -1456
  69. data/test/sass_test.rb +0 -115
  70. data/test/scss_test.rb +0 -88
  71. data/test/server_test.rb +0 -56
  72. data/test/settings_test.rb +0 -582
  73. data/test/sinatra_test.rb +0 -12
  74. data/test/slim_test.rb +0 -102
  75. data/test/static_test.rb +0 -266
  76. data/test/streaming_test.rb +0 -149
  77. data/test/stylus_test.rb +0 -90
  78. data/test/templates_test.rb +0 -382
  79. data/test/textile_test.rb +0 -65
  80. data/test/views/a/in_a.str +0 -1
  81. data/test/views/ascii.erb +0 -2
  82. data/test/views/b/in_b.str +0 -1
  83. data/test/views/calc.html.erb +0 -1
  84. data/test/views/error.builder +0 -3
  85. data/test/views/error.erb +0 -3
  86. data/test/views/error.haml +0 -3
  87. data/test/views/error.sass +0 -2
  88. data/test/views/explicitly_nested.str +0 -1
  89. data/test/views/foo/hello.test +0 -1
  90. data/test/views/hello.asciidoc +0 -1
  91. data/test/views/hello.builder +0 -1
  92. data/test/views/hello.coffee +0 -1
  93. data/test/views/hello.creole +0 -1
  94. data/test/views/hello.erb +0 -1
  95. data/test/views/hello.haml +0 -1
  96. data/test/views/hello.less +0 -5
  97. data/test/views/hello.liquid +0 -1
  98. data/test/views/hello.mab +0 -1
  99. data/test/views/hello.md +0 -1
  100. data/test/views/hello.mediawiki +0 -1
  101. data/test/views/hello.nokogiri +0 -1
  102. data/test/views/hello.rabl +0 -2
  103. data/test/views/hello.radius +0 -1
  104. data/test/views/hello.rdoc +0 -1
  105. data/test/views/hello.sass +0 -2
  106. data/test/views/hello.scss +0 -3
  107. data/test/views/hello.slim +0 -1
  108. data/test/views/hello.str +0 -1
  109. data/test/views/hello.styl +0 -2
  110. data/test/views/hello.test +0 -1
  111. data/test/views/hello.textile +0 -1
  112. data/test/views/hello.wlang +0 -1
  113. data/test/views/hello.yajl +0 -1
  114. data/test/views/layout2.builder +0 -3
  115. data/test/views/layout2.erb +0 -2
  116. data/test/views/layout2.haml +0 -2
  117. data/test/views/layout2.liquid +0 -2
  118. data/test/views/layout2.mab +0 -2
  119. data/test/views/layout2.nokogiri +0 -3
  120. data/test/views/layout2.rabl +0 -3
  121. data/test/views/layout2.radius +0 -2
  122. data/test/views/layout2.slim +0 -3
  123. data/test/views/layout2.str +0 -2
  124. data/test/views/layout2.test +0 -1
  125. data/test/views/layout2.wlang +0 -2
  126. data/test/views/nested.str +0 -1
  127. data/test/views/utf8.erb +0 -2
  128. data/test/wlang_test.rb +0 -87
  129. 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: #{Rack::Utils.escape_html(e.message)}"
82
+ end
83
+
72
84
  private
73
85
 
74
86
  class AcceptEntry
@@ -120,7 +132,7 @@ module Sinatra
120
132
  # http://rubydoc.info/github/rack/rack/master/Rack/Response
121
133
  # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
122
134
  class Response < Rack::Response
123
- DROP_BODY_RESPONSES = [204, 205, 304]
135
+ DROP_BODY_RESPONSES = [204, 304]
124
136
  def initialize(*)
125
137
  super
126
138
  headers['Content-Type'] ||= 'text/html'
@@ -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,8 +359,8 @@ 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')
343
- response['Content-Disposition'] = disposition.to_s
362
+ def attachment(filename = nil, disposition = :attachment)
363
+ response['Content-Disposition'] = disposition.to_s.dup
344
364
  if filename
345
365
  params = '; filename="%s"' % File.basename(filename)
346
366
  response['Content-Disposition'] << params
@@ -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, :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
@@ -707,6 +722,7 @@ module Sinatra
707
722
  end
708
723
 
709
724
  def markdown(template, options = {}, locals = {})
725
+ options[:exclude_outvar] = true
710
726
  render :markdown, template, options, locals
711
727
  end
712
728
 
@@ -771,15 +787,8 @@ module Sinatra
771
787
  def find_template(views, name, engine)
772
788
  yield ::File.join(views, "#{name}.#{@preferred_extension}")
773
789
 
774
- if Tilt.respond_to?(:mappings)
775
- Tilt.mappings.each do |ext, engines|
776
- next unless ext != @preferred_extension and engines.include? engine
777
- yield ::File.join(views, "#{name}.#{ext}")
778
- end
779
- else
780
- Tilt.default_mapping.extensions_for(engine).each do |ext|
781
- yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension
782
- end
790
+ Tilt.default_mapping.extensions_for(engine).each do |ext|
791
+ yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension
783
792
  end
784
793
  end
785
794
 
@@ -794,7 +803,7 @@ module Sinatra
794
803
 
795
804
  def render(engine, data, options = {}, locals = {}, &block)
796
805
  # merge app-level options
797
- engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
806
+ engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
798
807
  options.merge!(engine_options) { |key, v1, v2| v1 }
799
808
 
800
809
  # extract generic options
@@ -806,13 +815,15 @@ module Sinatra
806
815
  layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
807
816
  layout = @default_layout if layout.nil? or layout == true
808
817
  layout_options = options.delete(:layout_options) || {}
809
- content_type = options.delete(:content_type) || options.delete(:default_content_type)
818
+ content_type = options.delete(:default_content_type)
819
+ content_type = options.delete(:content_type) || content_type
810
820
  layout_engine = options.delete(:layout_engine) || engine
811
821
  scope = options.delete(:scope) || self
822
+ exclude_outvar = options.delete(:exclude_outvar)
812
823
  options.delete(:layout)
813
824
 
814
825
  # set some defaults
815
- options[:outvar] ||= '@_out_buf'
826
+ options[:outvar] ||= '@_out_buf' unless exclude_outvar
816
827
  options[:default_encoding] ||= settings.default_encoding
817
828
 
818
829
  # compile and render template
@@ -863,7 +874,9 @@ module Sinatra
863
874
  end
864
875
  when Proc, String
865
876
  body = data.is_a?(String) ? Proc.new { data } : data
866
- path, line = settings.caller_locations.first
877
+ caller = settings.caller_locations.first
878
+ path = options[:path] || caller[0]
879
+ line = options[:line] || caller[1]
867
880
  template.new(path, line.to_i, options, &body)
868
881
  else
869
882
  raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
@@ -878,7 +891,7 @@ module Sinatra
878
891
  include Helpers
879
892
  include Templates
880
893
 
881
- URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
894
+ URI_INSTANCE = URI::Parser.new
882
895
 
883
896
  attr_accessor :app, :env, :request, :response, :params
884
897
  attr_reader :template_cache
@@ -897,11 +910,10 @@ module Sinatra
897
910
 
898
911
  def call!(env) # :nodoc:
899
912
  @env = env
913
+ @params = IndifferentHash.new
900
914
  @request = Request.new(env)
901
915
  @response = Response.new
902
- @params = indifferent_params(@request.params)
903
916
  template_cache.clear if settings.reload_templates
904
- force_encoding(@params)
905
917
 
906
918
  @response['Content-Type'] = nil
907
919
  invoke { dispatch! }
@@ -969,9 +981,9 @@ module Sinatra
969
981
  # Run routes defined on the class and all superclasses.
970
982
  def route!(base = settings, pass_block = nil)
971
983
  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)
984
+ routes.each do |pattern, conditions, block|
985
+ returned_pass_block = process_route(pattern, conditions) do |*args|
986
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
975
987
  route_eval { block[*args] }
976
988
  end
977
989
 
@@ -999,23 +1011,35 @@ module Sinatra
999
1011
  # Revert params afterwards.
1000
1012
  #
1001
1013
  # Returns pass block.
1002
- def process_route(pattern, keys, conditions, block = nil, values = [])
1014
+ def process_route(pattern, conditions, block = nil, values = [])
1003
1015
  route = @request.path_info
1004
1016
  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 }
1007
-
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 }
1017
+ route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
1018
+ return unless params = pattern.params(route)
1019
+
1020
+ params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1021
+ force_encoding(params)
1022
+ @params = @params.merge(params) if params.any?
1023
+
1024
+ regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
1025
+ if regexp_exists
1026
+ captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
1027
+ values += captures
1028
+ @params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
1029
+ else
1030
+ values += params.values.flatten
1011
1031
  end
1012
1032
 
1013
1033
  catch(:pass) do
1014
1034
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1015
1035
  block ? block[self, values] : yield(self, values)
1016
1036
  end
1037
+ rescue
1038
+ @env['sinatra.error.params'] = @params
1039
+ raise
1017
1040
  ensure
1018
- @params = original if original
1041
+ params ||= {}
1042
+ params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params']
1019
1043
  end
1020
1044
 
1021
1045
  # No matching route was found or all routes passed. The default
@@ -1027,7 +1051,7 @@ module Sinatra
1027
1051
  if @app
1028
1052
  forward
1029
1053
  else
1030
- raise NotFound
1054
+ raise NotFound, "#{request.request_method} #{request.path_info}"
1031
1055
  end
1032
1056
  end
1033
1057
 
@@ -1043,28 +1067,10 @@ module Sinatra
1043
1067
  send_file path, options.merge(:disposition => nil)
1044
1068
  end
1045
1069
 
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
1070
  # Run the block with 'throw :halt' support and apply result to the response.
1066
1071
  def invoke
1067
1072
  res = catch(:halt) { yield }
1073
+
1068
1074
  res = [res] if Integer === res or String === res
1069
1075
  if Array === res and Integer === res.first
1070
1076
  res = res.dup
@@ -1079,6 +1085,13 @@ module Sinatra
1079
1085
 
1080
1086
  # Dispatch a request with error handling.
1081
1087
  def dispatch!
1088
+ # Avoid passing frozen string in force_encoding
1089
+ @params.merge!(@request.params).each do |key, val|
1090
+ next unless val.respond_to?(:force_encoding)
1091
+ val = val.dup if val.frozen?
1092
+ @params[key] = force_encoding(val)
1093
+ end
1094
+
1082
1095
  invoke do
1083
1096
  static! if settings.static? && (request.get? || request.head?)
1084
1097
  filter! :before
@@ -1096,6 +1109,9 @@ module Sinatra
1096
1109
 
1097
1110
  # Error handling during requests.
1098
1111
  def handle_exception!(boom)
1112
+ if error_params = @env['sinatra.error.params']
1113
+ @params = @params.merge(error_params)
1114
+ end
1099
1115
  @env['sinatra.error'] = boom
1100
1116
 
1101
1117
  if boom.respond_to? :http_status
@@ -1108,14 +1124,15 @@ module Sinatra
1108
1124
 
1109
1125
  status(500) unless status.between? 400, 599
1110
1126
 
1127
+ boom_message = boom.message if boom.message && boom.message != boom.class.name
1111
1128
  if server_error?
1112
1129
  dump_errors! boom if settings.dump_errors?
1113
1130
  raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1114
- end
1115
-
1116
- if not_found?
1131
+ elsif not_found?
1117
1132
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1118
- body '<h1>Not Found</h1>'
1133
+ body boom_message || '<h1>Not Found</h1>'
1134
+ elsif bad_request?
1135
+ body boom_message || '<h1>Bad Request</h1>'
1119
1136
  end
1120
1137
 
1121
1138
  res = error_block!(boom.class, boom) || error_block!(status, boom)
@@ -1147,12 +1164,12 @@ module Sinatra
1147
1164
 
1148
1165
  class << self
1149
1166
  CALLERS_TO_IGNORE = [ # :nodoc:
1150
- /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1167
+ /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1151
1168
  /lib\/tilt.*\.rb$/, # all tilt code
1152
1169
  /^\(.*\)$/, # generated code
1153
1170
  /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
1154
1171
  /active_support/, # active_support require hacks
1155
- /bundler(\/runtime)?\.rb/, # bundler require hacks
1172
+ /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks
1156
1173
  /<internal:/, # internal in ruby >= 1.9.2
1157
1174
  /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1158
1175
  ]
@@ -1177,7 +1194,7 @@ module Sinatra
1177
1194
  @extensions = []
1178
1195
 
1179
1196
  if superclass.respond_to?(:templates)
1180
- @templates = Hash.new { |hash,key| superclass.templates[key] }
1197
+ @templates = Hash.new { |hash, key| superclass.templates[key] }
1181
1198
  else
1182
1199
  @templates = {}
1183
1200
  end
@@ -1232,8 +1249,8 @@ module Sinatra
1232
1249
  end
1233
1250
  end
1234
1251
 
1235
- define_singleton("#{option}=", setter) if setter
1236
- define_singleton(option, getter) if getter
1252
+ define_singleton("#{option}=", setter)
1253
+ define_singleton(option, getter)
1237
1254
  define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
1238
1255
  self
1239
1256
  end
@@ -1252,16 +1269,16 @@ module Sinatra
1252
1269
  # class, or an HTTP status code to specify which errors should be
1253
1270
  # handled.
1254
1271
  def error(*codes, &block)
1255
- args = compile! "ERROR", //, block
1256
- codes = codes.map { |c| Array(c) }.flatten
1272
+ args = compile! "ERROR", /.*/, block
1273
+ codes = codes.flat_map(&method(:Array))
1257
1274
  codes << Exception if codes.empty?
1275
+ codes << Sinatra::NotFound if codes.include?(404)
1258
1276
  codes.each { |c| (@errors[c] ||= []) << args }
1259
1277
  end
1260
1278
 
1261
1279
  # Sugar for `error(404) { ... }`
1262
1280
  def not_found(&block)
1263
1281
  error(404, &block)
1264
- error(Sinatra::NotFound, &block)
1265
1282
  end
1266
1283
 
1267
1284
  # Define a named template. The block must return the template source.
@@ -1299,7 +1316,7 @@ module Sinatra
1299
1316
  data.each_line do |line|
1300
1317
  lines += 1
1301
1318
  if line =~ /^@@\s*(.*\S)\s*$/
1302
- template = force_encoding('', encoding)
1319
+ template = force_encoding(String.new, encoding)
1303
1320
  templates[$1.to_sym] = [template, file, lines]
1304
1321
  elsif template
1305
1322
  template << line
@@ -1328,21 +1345,20 @@ module Sinatra
1328
1345
  # Define a before filter; runs before all requests within the same
1329
1346
  # context as route handlers and may access/modify the request and
1330
1347
  # response.
1331
- def before(path = nil, options = {}, &block)
1332
- add_filter(:before, path, options, &block)
1348
+ def before(path = /.*/, **options, &block)
1349
+ add_filter(:before, path, **options, &block)
1333
1350
  end
1334
1351
 
1335
1352
  # Define an after filter; runs after all requests within the same
1336
1353
  # context as route handlers and may access/modify the request and
1337
1354
  # response.
1338
- def after(path = nil, options = {}, &block)
1339
- add_filter(:after, path, options, &block)
1355
+ def after(path = /.*/, **options, &block)
1356
+ add_filter(:after, path, **options, &block)
1340
1357
  end
1341
1358
 
1342
1359
  # 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)
1360
+ def add_filter(type, path = /.*/, **options, &block)
1361
+ filters[type] << compile!(type, path, block, **options)
1346
1362
  end
1347
1363
 
1348
1364
  # Add a route condition. The route is considered non-matching when the
@@ -1422,7 +1438,7 @@ module Sinatra
1422
1438
  return unless running?
1423
1439
  # Use Thin's hard #stop! if available, otherwise just #stop.
1424
1440
  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
1441
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
1426
1442
  set :running_server, nil
1427
1443
  set :handler_name, nil
1428
1444
  end
@@ -1503,8 +1519,12 @@ module Sinatra
1503
1519
 
1504
1520
  # Starts the server by running the Rack Handler.
1505
1521
  def start_server(handler, server_settings, handler_name)
1522
+ # Ensure we initialize middleware before startup, to match standard Rack
1523
+ # behavior, by ensuring an instance exists:
1524
+ prototype
1525
+ # Run the instance we created:
1506
1526
  handler.run(self, server_settings) do |server|
1507
- unless handler_name =~ /cgi/i
1527
+ unless suppress_messages?
1508
1528
  $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1509
1529
  end
1510
1530
 
@@ -1517,6 +1537,10 @@ module Sinatra
1517
1537
  end
1518
1538
  end
1519
1539
 
1540
+ def suppress_messages?
1541
+ handler_name =~ /cgi/i || quiet
1542
+ end
1543
+
1520
1544
  def setup_traps
1521
1545
  if traps?
1522
1546
  at_exit { quit! }
@@ -1534,8 +1558,7 @@ module Sinatra
1534
1558
 
1535
1559
  # Dynamically defines a method on settings.
1536
1560
  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
1561
+ singleton_class.class_eval do
1539
1562
  undef_method(name) if method_defined? name
1540
1563
  String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
1541
1564
  end
@@ -1578,10 +1601,8 @@ module Sinatra
1578
1601
  end
1579
1602
 
1580
1603
  def route(verb, path, options = {}, &block)
1581
- # Because of self.options.host
1582
- host_name(options.delete(:host)) if options.key?(:host)
1583
1604
  enable :empty_path_info if path == "" and empty_path_info.nil?
1584
- signature = compile!(verb, path, block, options)
1605
+ signature = compile!(verb, path, block, **options)
1585
1606
  (@routes[verb] ||= []) << signature
1586
1607
  invoke_hook(:route_added, verb, path, block)
1587
1608
  signature
@@ -1592,115 +1613,33 @@ module Sinatra
1592
1613
  end
1593
1614
 
1594
1615
  def generate_method(method_name, &block)
1595
- method_name = method_name.to_sym
1596
1616
  define_method(method_name, &block)
1597
1617
  method = instance_method method_name
1598
1618
  remove_method method_name
1599
1619
  method
1600
1620
  end
1601
1621
 
1602
- def compile!(verb, path, block, options = {})
1622
+ def compile!(verb, path, block, **options)
1623
+ # Because of self.options.host
1624
+ host_name(options.delete(:host)) if options.key?(:host)
1625
+ # Pass Mustermann opts to compile()
1626
+ route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
1627
+
1603
1628
  options.each_pair { |option, args| send(option, *args) }
1629
+
1630
+ pattern = compile(path, route_mustermann_opts)
1604
1631
  method_name = "#{verb} #{path}"
1605
1632
  unbound_method = generate_method(method_name, &block)
1606
- pattern, keys = compile path
1607
1633
  conditions, @conditions = @conditions, []
1608
-
1609
1634
  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
1674
-
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
1680
- end
1635
+ proc { |a, p| unbound_method.bind(a).call(*p) } :
1636
+ proc { |a, p| unbound_method.bind(a).call }
1681
1637
 
1682
- def escaped(char, enc = URI_INSTANCE.escape(char))
1683
- [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
1638
+ [ pattern, conditions, wrapper ]
1684
1639
  end
1685
1640
 
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
1641
+ def compile(path, route_mustermann_opts = {})
1642
+ Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts))
1704
1643
  end
1705
1644
 
1706
1645
  def setup_default_middleware(builder)
@@ -1745,10 +1684,16 @@ module Sinatra
1745
1684
  def setup_protection(builder)
1746
1685
  return unless protection?
1747
1686
  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
1687
+ options = {
1688
+ img_src: "'self' data:",
1689
+ font_src: "'self'"
1690
+ }.merge options
1691
+
1692
+ protect_session = options.fetch(:session) { sessions? }
1693
+ options[:without_session] = !protect_session
1694
+
1751
1695
  options[:reaction] ||= :drop_session
1696
+
1752
1697
  builder.use Rack::Protection, options
1753
1698
  end
1754
1699
 
@@ -1757,7 +1702,7 @@ module Sinatra
1757
1702
  options = {}
1758
1703
  options[:secret] = session_secret if session_secret?
1759
1704
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1760
- builder.use Rack::Session::Cookie, options
1705
+ builder.use session_store, options
1761
1706
  end
1762
1707
 
1763
1708
  def detect_rack_handler
@@ -1766,8 +1711,6 @@ module Sinatra
1766
1711
  begin
1767
1712
  return Rack::Handler.get(server_name.to_s)
1768
1713
  rescue LoadError, NameError
1769
- rescue ArgumentError
1770
- Sinatra::Ext.get_handler(server_name.to_s)
1771
1714
  end
1772
1715
  end
1773
1716
  fail "Server handler (#{servers.join(',')}) not found."
@@ -1801,36 +1744,30 @@ module Sinatra
1801
1744
  end
1802
1745
  end
1803
1746
 
1804
- # Fixes encoding issues by
1805
- # * defaulting to UTF-8
1806
- # * casting params to Encoding.default_external
1807
- #
1808
- # The latter might not be necessary if Rack handles it one day.
1809
- # Keep an eye on Rack's LH #100.
1810
- def force_encoding(*args) settings.force_encoding(*args) end
1811
- if defined? Encoding
1812
- def self.force_encoding(data, encoding = default_encoding)
1813
- return if data == settings || data.is_a?(Tempfile)
1814
- if data.respond_to? :force_encoding
1815
- data.force_encoding(encoding).encode!
1816
- elsif data.respond_to? :each_value
1817
- data.each_value { |v| force_encoding(v, encoding) }
1818
- elsif data.respond_to? :each
1819
- data.each { |v| force_encoding(v, encoding) }
1820
- end
1821
- data
1747
+ # Force data to specified encoding. It defaults to settings.default_encoding
1748
+ # which is UTF-8 by default
1749
+ def self.force_encoding(data, encoding = default_encoding)
1750
+ return if data == settings || data.is_a?(Tempfile)
1751
+ if data.respond_to? :force_encoding
1752
+ data.force_encoding(encoding).encode!
1753
+ elsif data.respond_to? :each_value
1754
+ data.each_value { |v| force_encoding(v, encoding) }
1755
+ elsif data.respond_to? :each
1756
+ data.each { |v| force_encoding(v, encoding) }
1822
1757
  end
1823
- else
1824
- def self.force_encoding(data, *) data end
1758
+ data
1825
1759
  end
1826
1760
 
1761
+ def force_encoding(*args) settings.force_encoding(*args) end
1762
+
1827
1763
  reset!
1828
1764
 
1829
- set :environment, (ENV['RACK_ENV'] || :development).to_sym
1765
+ set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1830
1766
  set :raise_errors, Proc.new { test? }
1831
1767
  set :dump_errors, Proc.new { !test? }
1832
1768
  set :show_exceptions, Proc.new { development? }
1833
1769
  set :sessions, false
1770
+ set :session_store, Rack::Session::Cookie
1834
1771
  set :logging, false
1835
1772
  set :protection, true
1836
1773
  set :method_override, false
@@ -1839,6 +1776,7 @@ module Sinatra
1839
1776
  set :x_cascade, true
1840
1777
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1841
1778
  settings.add_charset << /^text\//
1779
+ set :mustermann_opts, {}
1842
1780
 
1843
1781
  # explicitly generating a session secret eagerly to play nice with preforking
1844
1782
  begin
@@ -1861,6 +1799,7 @@ module Sinatra
1861
1799
  set :server, %w[HTTP webrick]
1862
1800
  set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1863
1801
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1802
+ set :quiet, false
1864
1803
 
1865
1804
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1866
1805
 
@@ -1868,16 +1807,16 @@ module Sinatra
1868
1807
  server.unshift 'control_tower'
1869
1808
  else
1870
1809
  server.unshift 'reel'
1810
+ server.unshift 'puma'
1871
1811
  server.unshift 'mongrel' if ruby_engine.nil?
1872
- server.unshift 'puma' if ruby_engine != 'rbx'
1873
1812
  server.unshift 'thin' if ruby_engine != 'jruby'
1874
- server.unshift 'puma' if ruby_engine == 'rbx'
1875
1813
  server.unshift 'trinidad' if ruby_engine == 'jruby'
1876
1814
  end
1877
1815
 
1878
1816
  set :absolute_redirects, true
1879
1817
  set :prefixed_redirects, false
1880
1818
  set :empty_path_info, nil
1819
+ set :strict_paths, true
1881
1820
 
1882
1821
  set :app_file, nil
1883
1822
  set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
@@ -1936,7 +1875,7 @@ module Sinatra
1936
1875
  </style>
1937
1876
  </head>
1938
1877
  <body>
1939
- <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1878
+ <h2>Sinatra doesnt know this ditty.</h2>
1940
1879
  <img src='#{uri "/__sinatra__/404.png"}'>
1941
1880
  <div id="c">
1942
1881
  Try this:
@@ -1957,14 +1896,13 @@ module Sinatra
1957
1896
  # top-level. Subclassing Sinatra::Base is highly recommended for
1958
1897
  # modular applications.
1959
1898
  class Application < Base
1960
- set :logging, Proc.new { ! test? }
1899
+ set :logging, Proc.new { !test? }
1961
1900
  set :method_override, true
1962
- set :run, Proc.new { ! test? }
1963
- set :session_secret, Proc.new { super() unless development? }
1901
+ set :run, Proc.new { !test? }
1964
1902
  set :app_file, nil
1965
1903
 
1966
1904
  def self.register(*extensions, &block) #:nodoc:
1967
- added_methods = extensions.map {|m| m.public_instance_methods }.flatten
1905
+ added_methods = extensions.flat_map(&:public_instance_methods)
1968
1906
  Delegator.delegate(*added_methods)
1969
1907
  super(*extensions, &block)
1970
1908
  end