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.
- checksums.yaml +4 -4
- data/AUTHORS.md +5 -2
- data/{CHANGES → CHANGELOG.md} +126 -46
- data/CONTRIBUTING.md +100 -0
- data/Gemfile +12 -17
- data/LICENSE +5 -2
- data/MAINTENANCE.md +42 -0
- data/README.de.md +711 -466
- data/README.es.md +206 -171
- data/README.fr.md +370 -344
- data/README.hu.md +44 -10
- data/README.ja.md +300 -210
- data/README.ko.md +230 -191
- data/README.md +675 -528
- data/README.pt-br.md +149 -115
- data/README.pt-pt.md +65 -65
- data/README.ru.md +198 -97
- data/README.zh.md +1943 -1237
- data/Rakefile +72 -49
- data/SECURITY.md +35 -0
- data/lib/sinatra/base.rb +141 -207
- data/lib/sinatra/indifferent_hash.rb +150 -0
- data/lib/sinatra/main.rb +1 -0
- data/lib/sinatra/show_exceptions.rb +70 -56
- data/lib/sinatra/version.rb +1 -1
- data/sinatra.gemspec +19 -7
- metadata +32 -163
- data/test/asciidoctor_test.rb +0 -72
- data/test/base_test.rb +0 -167
- data/test/builder_test.rb +0 -91
- data/test/coffee_test.rb +0 -96
- data/test/compile_test.rb +0 -183
- data/test/contest.rb +0 -91
- data/test/creole_test.rb +0 -65
- data/test/delegator_test.rb +0 -160
- data/test/encoding_test.rb +0 -20
- data/test/erb_test.rb +0 -116
- data/test/extensions_test.rb +0 -98
- data/test/filter_test.rb +0 -487
- data/test/haml_test.rb +0 -109
- data/test/helper.rb +0 -132
- data/test/helpers_test.rb +0 -1917
- data/test/integration/app.rb +0 -79
- data/test/integration_helper.rb +0 -236
- data/test/integration_test.rb +0 -104
- data/test/less_test.rb +0 -69
- data/test/liquid_test.rb +0 -77
- data/test/mapped_error_test.rb +0 -285
- data/test/markaby_test.rb +0 -80
- data/test/markdown_test.rb +0 -85
- data/test/mediawiki_test.rb +0 -68
- data/test/middleware_test.rb +0 -68
- data/test/nokogiri_test.rb +0 -67
- data/test/public/favicon.ico +0 -0
- data/test/rabl_test.rb +0 -89
- data/test/rack_test.rb +0 -45
- data/test/radius_test.rb +0 -59
- data/test/rdoc_test.rb +0 -66
- data/test/readme_test.rb +0 -130
- data/test/request_test.rb +0 -100
- data/test/response_test.rb +0 -63
- data/test/result_test.rb +0 -76
- data/test/route_added_hook_test.rb +0 -59
- data/test/routing_test.rb +0 -1412
- data/test/sass_test.rb +0 -115
- data/test/scss_test.rb +0 -88
- data/test/server_test.rb +0 -56
- data/test/settings_test.rb +0 -582
- data/test/sinatra_test.rb +0 -12
- data/test/slim_test.rb +0 -102
- data/test/static_test.rb +0 -236
- data/test/streaming_test.rb +0 -149
- data/test/stylus_test.rb +0 -90
- data/test/templates_test.rb +0 -382
- data/test/textile_test.rb +0 -65
- data/test/views/a/in_a.str +0 -1
- data/test/views/ascii.erb +0 -2
- data/test/views/b/in_b.str +0 -1
- data/test/views/calc.html.erb +0 -1
- data/test/views/error.builder +0 -3
- data/test/views/error.erb +0 -3
- data/test/views/error.haml +0 -3
- data/test/views/error.sass +0 -2
- data/test/views/explicitly_nested.str +0 -1
- data/test/views/foo/hello.test +0 -1
- data/test/views/hello.asciidoc +0 -1
- data/test/views/hello.builder +0 -1
- data/test/views/hello.coffee +0 -1
- data/test/views/hello.creole +0 -1
- data/test/views/hello.erb +0 -1
- data/test/views/hello.haml +0 -1
- data/test/views/hello.less +0 -5
- data/test/views/hello.liquid +0 -1
- data/test/views/hello.mab +0 -1
- data/test/views/hello.md +0 -1
- data/test/views/hello.mediawiki +0 -1
- data/test/views/hello.nokogiri +0 -1
- data/test/views/hello.rabl +0 -2
- data/test/views/hello.radius +0 -1
- data/test/views/hello.rdoc +0 -1
- data/test/views/hello.sass +0 -2
- data/test/views/hello.scss +0 -3
- data/test/views/hello.slim +0 -1
- data/test/views/hello.str +0 -1
- data/test/views/hello.styl +0 -2
- data/test/views/hello.test +0 -1
- data/test/views/hello.textile +0 -1
- data/test/views/hello.wlang +0 -1
- data/test/views/hello.yajl +0 -1
- data/test/views/layout2.builder +0 -3
- data/test/views/layout2.erb +0 -2
- data/test/views/layout2.haml +0 -2
- data/test/views/layout2.liquid +0 -2
- data/test/views/layout2.mab +0 -2
- data/test/views/layout2.nokogiri +0 -3
- data/test/views/layout2.rabl +0 -3
- data/test/views/layout2.radius +0 -2
- data/test/views/layout2.slim +0 -3
- data/test/views/layout2.str +0 -2
- data/test/views/layout2.test +0 -1
- data/test/views/layout2.wlang +0 -2
- data/test/views/nested.str +0 -1
- data/test/views/utf8.erb +0 -2
- data/test/wlang_test.rb +0 -87
- 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 +
|
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
|
-
|
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[
|
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 =
|
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 =
|
360
|
-
filename = path
|
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
|
366
|
-
file.
|
367
|
-
|
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, :
|
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
|
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=
|
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.
|
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
|
-
|
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
|
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(:
|
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
|
-
|
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
|
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,
|
972
|
-
returned_pass_block = process_route(pattern,
|
973
|
-
env['sinatra.route'] =
|
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,
|
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
|
-
|
1005
|
-
|
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
|
1008
|
-
|
1009
|
-
|
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
|
-
|
1068
|
-
if
|
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
|
-
|
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,
|
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",
|
1255
|
-
codes = codes.
|
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(
|
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 =
|
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 =
|
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 =
|
1343
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
1690
|
-
|
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
|
-
|
1759
|
-
|
1760
|
-
|
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
|
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
|
1882
|
+
<h2>Sinatra doesn’t 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 { !
|
1903
|
+
set :logging, Proc.new { !test? }
|
1969
1904
|
set :method_override, true
|
1970
|
-
set :run, Proc.new { !
|
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.
|
1909
|
+
added_methods = extensions.flat_map(&:public_instance_methods)
|
1976
1910
|
Delegator.delegate(*added_methods)
|
1977
1911
|
super(*extensions, &block)
|
1978
1912
|
end
|