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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -47
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +41 -49
- data/LICENSE +4 -1
- data/MAINTENANCE.md +42 -0
- data/README.de.md +644 -436
- data/README.es.md +6 -6
- data/README.fr.md +9 -9
- data/README.hu.md +37 -3
- data/README.ja.md +103 -45
- data/README.ko.md +8 -8
- data/README.md +471 -363
- data/README.pt-br.md +3 -3
- data/README.pt-pt.md +2 -2
- data/README.ru.md +42 -64
- data/README.zh.md +8 -8
- data/Rakefile +72 -49
- data/SECURITY.md +35 -0
- data/lib/sinatra/base.rb +137 -195
- data/lib/sinatra/indifferent_hash.rb +150 -0
- data/lib/sinatra/main.rb +1 -0
- data/lib/sinatra/show_exceptions.rb +63 -55
- data/lib/sinatra/version.rb +1 -1
- data/sinatra.gemspec +19 -7
- metadata +30 -164
- data/lib/sinatra/ext.rb +0 -17
- 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/public/hello+world.txt +0 -1
- 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 -1456
- 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 -266
- 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,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 +
|
|
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
|
-
|
|
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[
|
|
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 =
|
|
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 =
|
|
361
|
-
filename = path
|
|
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
|
|
367
|
-
file.
|
|
368
|
-
|
|
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, :
|
|
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
|
|
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=
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(:
|
|
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
|
-
|
|
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
|
|
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,
|
|
973
|
-
returned_pass_block = process_route(pattern,
|
|
974
|
-
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}"
|
|
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,
|
|
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
|
-
|
|
1006
|
-
|
|
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
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
-
|
|
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",
|
|
1256
|
-
codes = codes.
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1344
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1683
|
-
|
|
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
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
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
|
|
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
|
|
1882
|
+
<h2>Sinatra doesn’t 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 { !
|
|
1903
|
+
set :logging, Proc.new { !test? }
|
|
1961
1904
|
set :method_override, true
|
|
1962
|
-
set :run, Proc.new { !
|
|
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.
|
|
1909
|
+
added_methods = extensions.flat_map(&:public_instance_methods)
|
|
1968
1910
|
Delegator.delegate(*added_methods)
|
|
1969
1911
|
super(*extensions, &block)
|
|
1970
1912
|
end
|