sinatra 1.4.8 → 2.1.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 +5 -5
- data/AUTHORS.md +1 -0
- data/CHANGELOG.md +272 -54
- data/CONTRIBUTING.md +8 -8
- data/Gemfile +48 -47
- data/LICENSE +4 -1
- data/MAINTENANCE.md +42 -0
- data/README.de.md +651 -443
- data/README.es.md +738 -357
- data/README.fr.md +197 -100
- data/README.hu.md +40 -6
- data/README.ja.md +125 -67
- data/README.ko.md +15 -15
- data/README.malayalam.md +3141 -0
- data/README.md +592 -432
- data/README.pt-br.md +2362 -335
- data/README.pt-pt.md +5 -5
- data/README.ru.md +857 -608
- data/README.zh.md +91 -29
- data/Rakefile +77 -51
- data/SECURITY.md +35 -0
- data/VERSION +1 -0
- data/examples/chat.rb +2 -1
- data/examples/rainbows.conf +3 -0
- data/examples/rainbows.rb +20 -0
- data/examples/stream.ru +4 -4
- data/lib/sinatra/base.rb +243 -265
- data/lib/sinatra/indifferent_hash.rb +200 -0
- data/lib/sinatra/main.rb +30 -10
- data/lib/sinatra/show_exceptions.rb +67 -62
- data/lib/sinatra/version.rb +1 -1
- data/sinatra.gemspec +44 -8
- metadata +43 -167
- 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
|
@@ -37,12 +43,11 @@ module Sinatra
|
|
37
43
|
end
|
38
44
|
|
39
45
|
def preferred_type(*types)
|
40
|
-
|
41
|
-
return accepts.first if types.empty?
|
46
|
+
return accept.first if types.empty?
|
42
47
|
types.flatten!
|
43
|
-
return types.first if
|
44
|
-
|
45
|
-
type = types.detect { |t|
|
48
|
+
return types.first if accept.empty?
|
49
|
+
accept.detect do |accept_header|
|
50
|
+
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
|
46
51
|
return type if type
|
47
52
|
end
|
48
53
|
end
|
@@ -69,7 +74,11 @@ module Sinatra
|
|
69
74
|
request_method == "UNLINK"
|
70
75
|
end
|
71
76
|
|
72
|
-
|
77
|
+
def params
|
78
|
+
super
|
79
|
+
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
80
|
+
raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}"
|
81
|
+
end
|
73
82
|
|
74
83
|
class AcceptEntry
|
75
84
|
attr_accessor :params
|
@@ -113,6 +122,35 @@ module Sinatra
|
|
113
122
|
to_str.send(*args, &block)
|
114
123
|
end
|
115
124
|
end
|
125
|
+
|
126
|
+
class MimeTypeEntry
|
127
|
+
attr_reader :params
|
128
|
+
|
129
|
+
def initialize(entry)
|
130
|
+
params = entry.scan(HEADER_PARAM).map! do |s|
|
131
|
+
key, value = s.strip.split('=', 2)
|
132
|
+
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
|
133
|
+
[key, value]
|
134
|
+
end
|
135
|
+
|
136
|
+
@type = entry[/[^;]+/].delete(' ')
|
137
|
+
@params = Hash[params]
|
138
|
+
end
|
139
|
+
|
140
|
+
def accepts?(entry)
|
141
|
+
File.fnmatch(entry, self) && matches_params?(entry.params)
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_str
|
145
|
+
@type
|
146
|
+
end
|
147
|
+
|
148
|
+
def matches_params?(params)
|
149
|
+
return true if @params.empty?
|
150
|
+
|
151
|
+
params.all? { |k,v| !@params.has_key?(k) || @params[k] == v }
|
152
|
+
end
|
153
|
+
end
|
116
154
|
end
|
117
155
|
|
118
156
|
# The response object. See Rack::Response and Rack::Response::Helpers for
|
@@ -120,11 +158,7 @@ module Sinatra
|
|
120
158
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response
|
121
159
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
|
122
160
|
class Response < Rack::Response
|
123
|
-
DROP_BODY_RESPONSES = [204,
|
124
|
-
def initialize(*)
|
125
|
-
super
|
126
|
-
headers['Content-Type'] ||= 'text/html'
|
127
|
-
end
|
161
|
+
DROP_BODY_RESPONSES = [204, 304]
|
128
162
|
|
129
163
|
def body=(value)
|
130
164
|
value = value.body while Rack::Response === value
|
@@ -151,7 +185,7 @@ module Sinatra
|
|
151
185
|
if calculate_content_length?
|
152
186
|
# if some other code has already set Content-Length, don't muck with it
|
153
187
|
# currently, this would be the static file-handler
|
154
|
-
headers["Content-Length"] = body.
|
188
|
+
headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s
|
155
189
|
end
|
156
190
|
|
157
191
|
[status.to_i, headers, result]
|
@@ -172,7 +206,7 @@ module Sinatra
|
|
172
206
|
end
|
173
207
|
end
|
174
208
|
|
175
|
-
# Some Rack handlers (
|
209
|
+
# Some Rack handlers (Rainbows!) implement an extended body object protocol, however,
|
176
210
|
# some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
|
177
211
|
# This middleware will detect an extended body object and will make sure it reaches the
|
178
212
|
# handler directly. We do this here, so our middleware and middleware set up by the app will
|
@@ -221,6 +255,10 @@ module Sinatra
|
|
221
255
|
end
|
222
256
|
end
|
223
257
|
|
258
|
+
class BadRequest < TypeError #:nodoc:
|
259
|
+
def http_status; 400 end
|
260
|
+
end
|
261
|
+
|
224
262
|
class NotFound < NameError #:nodoc:
|
225
263
|
def http_status; 404 end
|
226
264
|
end
|
@@ -229,7 +267,7 @@ module Sinatra
|
|
229
267
|
module Helpers
|
230
268
|
# Set or retrieve the response status code.
|
231
269
|
def status(value = nil)
|
232
|
-
response.status = value if value
|
270
|
+
response.status = Rack::Utils.status_code(value) if value
|
233
271
|
response.status
|
234
272
|
end
|
235
273
|
|
@@ -240,7 +278,11 @@ module Sinatra
|
|
240
278
|
def block.each; yield(call) end
|
241
279
|
response.body = block
|
242
280
|
elsif value
|
243
|
-
|
281
|
+
# Rack 2.0 returns a Rack::File::Iterator here instead of
|
282
|
+
# Rack::File as it was in the previous API.
|
283
|
+
unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
|
284
|
+
headers.delete 'Content-Length'
|
285
|
+
end
|
244
286
|
response.body = value
|
245
287
|
else
|
246
288
|
response.body
|
@@ -264,8 +306,8 @@ module Sinatra
|
|
264
306
|
# Generates the absolute URI for a given path in the app.
|
265
307
|
# Takes Rack routers and reverse proxies into account.
|
266
308
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
267
|
-
return addr if addr =~ /\A[
|
268
|
-
uri = [host =
|
309
|
+
return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i
|
310
|
+
uri = [host = String.new]
|
269
311
|
if absolute
|
270
312
|
host << "http#{'s' if request.secure?}://"
|
271
313
|
if request.forwarded? or request.port != (request.secure? ? 443 : 80)
|
@@ -339,8 +381,8 @@ module Sinatra
|
|
339
381
|
|
340
382
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
341
383
|
# instructing the user agents to prompt to save.
|
342
|
-
def attachment(filename = nil, disposition =
|
343
|
-
response['Content-Disposition'] = disposition.to_s
|
384
|
+
def attachment(filename = nil, disposition = :attachment)
|
385
|
+
response['Content-Disposition'] = disposition.to_s.dup
|
344
386
|
if filename
|
345
387
|
params = '; filename="%s"' % File.basename(filename)
|
346
388
|
response['Content-Disposition'] << params
|
@@ -357,19 +399,19 @@ module Sinatra
|
|
357
399
|
|
358
400
|
disposition = opts[:disposition]
|
359
401
|
filename = opts[:filename]
|
360
|
-
disposition =
|
361
|
-
filename = path
|
402
|
+
disposition = :attachment if disposition.nil? and filename
|
403
|
+
filename = path if filename.nil?
|
362
404
|
attachment(filename, disposition) if disposition
|
363
405
|
|
364
406
|
last_modified opts[:last_modified] if opts[:last_modified]
|
365
407
|
|
366
|
-
file
|
367
|
-
file.
|
368
|
-
|
408
|
+
file = Rack::File.new(File.dirname(settings.app_file))
|
409
|
+
result = file.serving(request, path)
|
410
|
+
|
369
411
|
result[1].each { |k,v| headers[k] ||= v }
|
370
412
|
headers['Content-Length'] = result[1]['Content-Length']
|
371
413
|
opts[:status] &&= Integer(opts[:status])
|
372
|
-
halt opts[:status] || result[0], result[2]
|
414
|
+
halt (opts[:status] || result[0]), result[2]
|
373
415
|
rescue Errno::ENOENT
|
374
416
|
not_found
|
375
417
|
end
|
@@ -394,7 +436,7 @@ module Sinatra
|
|
394
436
|
def close
|
395
437
|
return if closed?
|
396
438
|
@closed = true
|
397
|
-
@scheduler.schedule { @callbacks.each { |c| c.call }}
|
439
|
+
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
398
440
|
end
|
399
441
|
|
400
442
|
def each(&front)
|
@@ -431,7 +473,7 @@ module Sinatra
|
|
431
473
|
#
|
432
474
|
# The close parameter specifies whether Stream#close should be called
|
433
475
|
# after the block has been executed. This is only relevant for evented
|
434
|
-
# servers like
|
476
|
+
# servers like Rainbows.
|
435
477
|
def stream(keep_open = false)
|
436
478
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
437
479
|
current = @params.dup
|
@@ -441,7 +483,7 @@ module Sinatra
|
|
441
483
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
442
484
|
# Any number of non-value directives (:public, :private, :no_cache,
|
443
485
|
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
|
444
|
-
# a Hash of value directives (:max_age, :
|
486
|
+
# a Hash of value directives (:max_age, :s_maxage).
|
445
487
|
#
|
446
488
|
# cache_control :public, :must_revalidate, :max_age => 60
|
447
489
|
# => Cache-Control: public, must-revalidate, max-age=60
|
@@ -451,8 +493,8 @@ module Sinatra
|
|
451
493
|
def cache_control(*values)
|
452
494
|
if values.last.kind_of?(Hash)
|
453
495
|
hash = values.pop
|
454
|
-
hash.reject! { |k,v| v == false }
|
455
|
-
hash.reject! { |k,v| values << k if v == true }
|
496
|
+
hash.reject! { |k, v| v == false }
|
497
|
+
hash.reject! { |k, v| values << k if v == true }
|
456
498
|
else
|
457
499
|
hash = {}
|
458
500
|
end
|
@@ -460,7 +502,7 @@ module Sinatra
|
|
460
502
|
values.map! { |value| value.to_s.tr('_','-') }
|
461
503
|
hash.each do |key, value|
|
462
504
|
key = key.to_s.tr('_', '-')
|
463
|
-
value = value.to_i if
|
505
|
+
value = value.to_i if ['max-age', 's-maxage'].include? key
|
464
506
|
values << "#{key}=#{value}"
|
465
507
|
end
|
466
508
|
|
@@ -473,7 +515,7 @@ module Sinatra
|
|
473
515
|
# "values" arguments are passed to the #cache_control helper:
|
474
516
|
#
|
475
517
|
# expires 500, :public, :must_revalidate
|
476
|
-
# => Cache-Control: public, must-revalidate, max-age=
|
518
|
+
# => Cache-Control: public, must-revalidate, max-age=500
|
477
519
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
478
520
|
#
|
479
521
|
def expires(amount, *values)
|
@@ -590,25 +632,20 @@ module Sinatra
|
|
590
632
|
status == 404
|
591
633
|
end
|
592
634
|
|
635
|
+
# whether or not the status is set to 400
|
636
|
+
def bad_request?
|
637
|
+
status == 400
|
638
|
+
end
|
639
|
+
|
593
640
|
# Generates a Time object from the given value.
|
594
641
|
# Used by #expires and #last_modified.
|
595
642
|
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
|
643
|
+
if value.is_a? Numeric
|
609
644
|
Time.at value
|
610
|
-
|
645
|
+
elsif value.respond_to? :to_s
|
611
646
|
Time.parse value.to_s
|
647
|
+
else
|
648
|
+
value.to_time
|
612
649
|
end
|
613
650
|
rescue ArgumentError => boom
|
614
651
|
raise boom
|
@@ -632,8 +669,6 @@ module Sinatra
|
|
632
669
|
end
|
633
670
|
end
|
634
671
|
|
635
|
-
private
|
636
|
-
|
637
672
|
# Template rendering methods. Each method takes the name of a template
|
638
673
|
# to render as a Symbol and returns a String with the rendered output,
|
639
674
|
# as well as an optional hash with additional options.
|
@@ -692,7 +727,7 @@ module Sinatra
|
|
692
727
|
render :less, template, options, locals
|
693
728
|
end
|
694
729
|
|
695
|
-
def stylus(template, options={}, locals={})
|
730
|
+
def stylus(template, options = {}, locals = {})
|
696
731
|
options.merge! :layout => false, :default_content_type => :css
|
697
732
|
render :styl, template, options, locals
|
698
733
|
end
|
@@ -707,6 +742,7 @@ module Sinatra
|
|
707
742
|
end
|
708
743
|
|
709
744
|
def markdown(template, options = {}, locals = {})
|
745
|
+
options[:exclude_outvar] = true
|
710
746
|
render :markdown, template, options, locals
|
711
747
|
end
|
712
748
|
|
@@ -771,15 +807,8 @@ module Sinatra
|
|
771
807
|
def find_template(views, name, engine)
|
772
808
|
yield ::File.join(views, "#{name}.#{@preferred_extension}")
|
773
809
|
|
774
|
-
|
775
|
-
|
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
|
810
|
+
Tilt.default_mapping.extensions_for(engine).each do |ext|
|
811
|
+
yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension
|
783
812
|
end
|
784
813
|
end
|
785
814
|
|
@@ -794,7 +823,7 @@ module Sinatra
|
|
794
823
|
|
795
824
|
def render(engine, data, options = {}, locals = {}, &block)
|
796
825
|
# merge app-level options
|
797
|
-
engine_options
|
826
|
+
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
798
827
|
options.merge!(engine_options) { |key, v1, v2| v1 }
|
799
828
|
|
800
829
|
# extract generic options
|
@@ -806,13 +835,15 @@ module Sinatra
|
|
806
835
|
layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
|
807
836
|
layout = @default_layout if layout.nil? or layout == true
|
808
837
|
layout_options = options.delete(:layout_options) || {}
|
809
|
-
content_type = options.delete(:
|
838
|
+
content_type = options.delete(:default_content_type)
|
839
|
+
content_type = options.delete(:content_type) || content_type
|
810
840
|
layout_engine = options.delete(:layout_engine) || engine
|
811
841
|
scope = options.delete(:scope) || self
|
842
|
+
exclude_outvar = options.delete(:exclude_outvar)
|
812
843
|
options.delete(:layout)
|
813
844
|
|
814
845
|
# set some defaults
|
815
|
-
options[:outvar]
|
846
|
+
options[:outvar] ||= '@_out_buf' unless exclude_outvar
|
816
847
|
options[:default_encoding] ||= settings.default_encoding
|
817
848
|
|
818
849
|
# compile and render template
|
@@ -863,7 +894,9 @@ module Sinatra
|
|
863
894
|
end
|
864
895
|
when Proc, String
|
865
896
|
body = data.is_a?(String) ? Proc.new { data } : data
|
866
|
-
|
897
|
+
caller = settings.caller_locations.first
|
898
|
+
path = options[:path] || caller[0]
|
899
|
+
line = options[:line] || caller[1]
|
867
900
|
template.new(path, line.to_i, options, &body)
|
868
901
|
else
|
869
902
|
raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
|
@@ -878,7 +911,7 @@ module Sinatra
|
|
878
911
|
include Helpers
|
879
912
|
include Templates
|
880
913
|
|
881
|
-
URI_INSTANCE = URI
|
914
|
+
URI_INSTANCE = URI::Parser.new
|
882
915
|
|
883
916
|
attr_accessor :app, :env, :request, :response, :params
|
884
917
|
attr_reader :template_cache
|
@@ -887,6 +920,7 @@ module Sinatra
|
|
887
920
|
super()
|
888
921
|
@app = app
|
889
922
|
@template_cache = Tilt::Cache.new
|
923
|
+
@pinned_response = nil # whether a before! filter pinned the content-type
|
890
924
|
yield self if block_given?
|
891
925
|
end
|
892
926
|
|
@@ -897,21 +931,19 @@ module Sinatra
|
|
897
931
|
|
898
932
|
def call!(env) # :nodoc:
|
899
933
|
@env = env
|
934
|
+
@params = IndifferentHash.new
|
900
935
|
@request = Request.new(env)
|
901
936
|
@response = Response.new
|
902
|
-
@params = indifferent_params(@request.params)
|
903
937
|
template_cache.clear if settings.reload_templates
|
904
|
-
force_encoding(@params)
|
905
938
|
|
906
|
-
@response['Content-Type'] = nil
|
907
939
|
invoke { dispatch! }
|
908
940
|
invoke { error_block!(response.status) } unless @env['sinatra.error']
|
909
941
|
|
910
942
|
unless @response['Content-Type']
|
911
|
-
if Array === body
|
943
|
+
if Array === body && body[0].respond_to?(:content_type)
|
912
944
|
content_type body[0].content_type
|
913
|
-
|
914
|
-
content_type
|
945
|
+
elsif default = settings.default_content_type
|
946
|
+
content_type default
|
915
947
|
end
|
916
948
|
end
|
917
949
|
|
@@ -961,17 +993,23 @@ module Sinatra
|
|
961
993
|
private
|
962
994
|
|
963
995
|
# Run filters defined on the class and all superclasses.
|
996
|
+
# Accepts an optional block to call after each filter is applied.
|
964
997
|
def filter!(type, base = settings)
|
965
998
|
filter! type, base.superclass if base.superclass.respond_to?(:filters)
|
966
|
-
base.filters[type].each
|
999
|
+
base.filters[type].each do |args|
|
1000
|
+
result = process_route(*args)
|
1001
|
+
yield result if block_given?
|
1002
|
+
end
|
967
1003
|
end
|
968
1004
|
|
969
1005
|
# Run routes defined on the class and all superclasses.
|
970
1006
|
def route!(base = settings, pass_block = nil)
|
971
1007
|
if routes = base.routes[@request.request_method]
|
972
|
-
routes.each do |pattern,
|
973
|
-
|
974
|
-
|
1008
|
+
routes.each do |pattern, conditions, block|
|
1009
|
+
@response.delete_header('Content-Type') unless @pinned_response
|
1010
|
+
|
1011
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1012
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
975
1013
|
route_eval { block[*args] }
|
976
1014
|
end
|
977
1015
|
|
@@ -999,23 +1037,35 @@ module Sinatra
|
|
999
1037
|
# Revert params afterwards.
|
1000
1038
|
#
|
1001
1039
|
# Returns pass block.
|
1002
|
-
def process_route(pattern,
|
1040
|
+
def process_route(pattern, conditions, block = nil, values = [])
|
1003
1041
|
route = @request.path_info
|
1004
1042
|
route = '/' if route.empty? and not settings.empty_path_info?
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1043
|
+
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1044
|
+
return unless params = pattern.params(route)
|
1045
|
+
|
1046
|
+
params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1047
|
+
force_encoding(params)
|
1048
|
+
@params = @params.merge(params) if params.any?
|
1049
|
+
|
1050
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
|
1051
|
+
if regexp_exists
|
1052
|
+
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1053
|
+
values += captures
|
1054
|
+
@params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
|
1055
|
+
else
|
1056
|
+
values += params.values.flatten
|
1011
1057
|
end
|
1012
1058
|
|
1013
1059
|
catch(:pass) do
|
1014
1060
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1015
1061
|
block ? block[self, values] : yield(self, values)
|
1016
1062
|
end
|
1063
|
+
rescue
|
1064
|
+
@env['sinatra.error.params'] = @params
|
1065
|
+
raise
|
1017
1066
|
ensure
|
1018
|
-
|
1067
|
+
params ||= {}
|
1068
|
+
params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params']
|
1019
1069
|
end
|
1020
1070
|
|
1021
1071
|
# No matching route was found or all routes passed. The default
|
@@ -1027,7 +1077,7 @@ module Sinatra
|
|
1027
1077
|
if @app
|
1028
1078
|
forward
|
1029
1079
|
else
|
1030
|
-
raise NotFound
|
1080
|
+
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1031
1081
|
end
|
1032
1082
|
end
|
1033
1083
|
|
@@ -1035,7 +1085,10 @@ module Sinatra
|
|
1035
1085
|
# a matching file is found, returns nil otherwise.
|
1036
1086
|
def static!(options = {})
|
1037
1087
|
return if (public_dir = settings.public_folder).nil?
|
1038
|
-
path =
|
1088
|
+
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1089
|
+
return unless valid_path?(path)
|
1090
|
+
|
1091
|
+
path = File.expand_path(path)
|
1039
1092
|
return unless File.file?(path)
|
1040
1093
|
|
1041
1094
|
env['sinatra.static_file'] = path
|
@@ -1043,28 +1096,10 @@ module Sinatra
|
|
1043
1096
|
send_file path, options.merge(:disposition => nil)
|
1044
1097
|
end
|
1045
1098
|
|
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
1099
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1066
1100
|
def invoke
|
1067
1101
|
res = catch(:halt) { yield }
|
1102
|
+
|
1068
1103
|
res = [res] if Integer === res or String === res
|
1069
1104
|
if Array === res and Integer === res.first
|
1070
1105
|
res = res.dup
|
@@ -1079,9 +1114,18 @@ module Sinatra
|
|
1079
1114
|
|
1080
1115
|
# Dispatch a request with error handling.
|
1081
1116
|
def dispatch!
|
1117
|
+
# Avoid passing frozen string in force_encoding
|
1118
|
+
@params.merge!(@request.params).each do |key, val|
|
1119
|
+
next unless val.respond_to?(:force_encoding)
|
1120
|
+
val = val.dup if val.frozen?
|
1121
|
+
@params[key] = force_encoding(val)
|
1122
|
+
end
|
1123
|
+
|
1082
1124
|
invoke do
|
1083
1125
|
static! if settings.static? && (request.get? || request.head?)
|
1084
|
-
filter! :before
|
1126
|
+
filter! :before do
|
1127
|
+
@pinned_response = !@response['Content-Type'].nil?
|
1128
|
+
end
|
1085
1129
|
route!
|
1086
1130
|
end
|
1087
1131
|
rescue ::Exception => boom
|
@@ -1096,6 +1140,9 @@ module Sinatra
|
|
1096
1140
|
|
1097
1141
|
# Error handling during requests.
|
1098
1142
|
def handle_exception!(boom)
|
1143
|
+
if error_params = @env['sinatra.error.params']
|
1144
|
+
@params = @params.merge(error_params)
|
1145
|
+
end
|
1099
1146
|
@env['sinatra.error'] = boom
|
1100
1147
|
|
1101
1148
|
if boom.respond_to? :http_status
|
@@ -1111,15 +1158,24 @@ module Sinatra
|
|
1111
1158
|
if server_error?
|
1112
1159
|
dump_errors! boom if settings.dump_errors?
|
1113
1160
|
raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
|
1161
|
+
elsif not_found?
|
1162
|
+
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1114
1163
|
end
|
1115
1164
|
|
1116
|
-
if
|
1117
|
-
|
1118
|
-
|
1165
|
+
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1166
|
+
return res
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
if not_found? || bad_request?
|
1170
|
+
if boom.message && boom.message != boom.class.name
|
1171
|
+
body boom.message
|
1172
|
+
else
|
1173
|
+
content_type 'text/html'
|
1174
|
+
body '<h1>' + (not_found? ? 'Not Found' : 'Bad Request') + '</h1>'
|
1175
|
+
end
|
1119
1176
|
end
|
1120
1177
|
|
1121
|
-
|
1122
|
-
return res if res or not server_error?
|
1178
|
+
return unless server_error?
|
1123
1179
|
raise boom if settings.raise_errors? or settings.show_exceptions?
|
1124
1180
|
error_block! Exception, boom
|
1125
1181
|
end
|
@@ -1147,12 +1203,12 @@ module Sinatra
|
|
1147
1203
|
|
1148
1204
|
class << self
|
1149
1205
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1150
|
-
/\/sinatra(\/(base|main|show_exceptions))?\.rb$/,
|
1206
|
+
/\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
|
1151
1207
|
/lib\/tilt.*\.rb$/, # all tilt code
|
1152
1208
|
/^\(.*\)$/, # generated code
|
1153
1209
|
/rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
|
1154
1210
|
/active_support/, # active_support require hacks
|
1155
|
-
/bundler(\/runtime)?\.rb/,
|
1211
|
+
/bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks
|
1156
1212
|
/<internal:/, # internal in ruby >= 1.9.2
|
1157
1213
|
/src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
|
1158
1214
|
]
|
@@ -1177,7 +1233,7 @@ module Sinatra
|
|
1177
1233
|
@extensions = []
|
1178
1234
|
|
1179
1235
|
if superclass.respond_to?(:templates)
|
1180
|
-
@templates = Hash.new { |hash,key| superclass.templates[key] }
|
1236
|
+
@templates = Hash.new { |hash, key| superclass.templates[key] }
|
1181
1237
|
else
|
1182
1238
|
@templates = {}
|
1183
1239
|
end
|
@@ -1232,8 +1288,8 @@ module Sinatra
|
|
1232
1288
|
end
|
1233
1289
|
end
|
1234
1290
|
|
1235
|
-
define_singleton("#{option}=", setter)
|
1236
|
-
define_singleton(option, getter)
|
1291
|
+
define_singleton("#{option}=", setter)
|
1292
|
+
define_singleton(option, getter)
|
1237
1293
|
define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
|
1238
1294
|
self
|
1239
1295
|
end
|
@@ -1252,16 +1308,16 @@ module Sinatra
|
|
1252
1308
|
# class, or an HTTP status code to specify which errors should be
|
1253
1309
|
# handled.
|
1254
1310
|
def error(*codes, &block)
|
1255
|
-
args = compile! "ERROR",
|
1256
|
-
codes = codes.
|
1311
|
+
args = compile! "ERROR", /.*/, block
|
1312
|
+
codes = codes.flat_map(&method(:Array))
|
1257
1313
|
codes << Exception if codes.empty?
|
1314
|
+
codes << Sinatra::NotFound if codes.include?(404)
|
1258
1315
|
codes.each { |c| (@errors[c] ||= []) << args }
|
1259
1316
|
end
|
1260
1317
|
|
1261
1318
|
# Sugar for `error(404) { ... }`
|
1262
1319
|
def not_found(&block)
|
1263
1320
|
error(404, &block)
|
1264
|
-
error(Sinatra::NotFound, &block)
|
1265
1321
|
end
|
1266
1322
|
|
1267
1323
|
# Define a named template. The block must return the template source.
|
@@ -1299,7 +1355,7 @@ module Sinatra
|
|
1299
1355
|
data.each_line do |line|
|
1300
1356
|
lines += 1
|
1301
1357
|
if line =~ /^@@\s*(.*\S)\s*$/
|
1302
|
-
template = force_encoding(
|
1358
|
+
template = force_encoding(String.new, encoding)
|
1303
1359
|
templates[$1.to_sym] = [template, file, lines]
|
1304
1360
|
elsif template
|
1305
1361
|
template << line
|
@@ -1328,21 +1384,20 @@ module Sinatra
|
|
1328
1384
|
# Define a before filter; runs before all requests within the same
|
1329
1385
|
# context as route handlers and may access/modify the request and
|
1330
1386
|
# response.
|
1331
|
-
def before(path =
|
1332
|
-
add_filter(:before, path, options, &block)
|
1387
|
+
def before(path = /.*/, **options, &block)
|
1388
|
+
add_filter(:before, path, **options, &block)
|
1333
1389
|
end
|
1334
1390
|
|
1335
1391
|
# Define an after filter; runs after all requests within the same
|
1336
1392
|
# context as route handlers and may access/modify the request and
|
1337
1393
|
# response.
|
1338
|
-
def after(path =
|
1339
|
-
add_filter(:after, path, options, &block)
|
1394
|
+
def after(path = /.*/, **options, &block)
|
1395
|
+
add_filter(:after, path, **options, &block)
|
1340
1396
|
end
|
1341
1397
|
|
1342
1398
|
# add a filter
|
1343
|
-
def add_filter(type, path =
|
1344
|
-
|
1345
|
-
filters[type] << compile!(type, path || //, block, options)
|
1399
|
+
def add_filter(type, path = /.*/, **options, &block)
|
1400
|
+
filters[type] << compile!(type, path, block, **options)
|
1346
1401
|
end
|
1347
1402
|
|
1348
1403
|
# Add a route condition. The route is considered non-matching when the
|
@@ -1387,7 +1442,7 @@ module Sinatra
|
|
1387
1442
|
# in `extensions` available to the handlers and templates
|
1388
1443
|
def helpers(*extensions, &block)
|
1389
1444
|
class_eval(&block) if block_given?
|
1390
|
-
|
1445
|
+
prepend(*extensions) if extensions.any?
|
1391
1446
|
end
|
1392
1447
|
|
1393
1448
|
# Register an extension. Alternatively take a block from which an
|
@@ -1422,7 +1477,7 @@ module Sinatra
|
|
1422
1477
|
return unless running?
|
1423
1478
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1424
1479
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1425
|
-
$stderr.puts "== Sinatra has ended his set (crowd applauds)" unless
|
1480
|
+
$stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
|
1426
1481
|
set :running_server, nil
|
1427
1482
|
set :handler_name, nil
|
1428
1483
|
end
|
@@ -1430,7 +1485,7 @@ module Sinatra
|
|
1430
1485
|
alias_method :stop!, :quit!
|
1431
1486
|
|
1432
1487
|
# Run the Sinatra app as a self-hosted server using
|
1433
|
-
#
|
1488
|
+
# Puma, Mongrel, or WEBrick (in that order). If given a block, will call
|
1434
1489
|
# with the constructed handler once we have taken the stage.
|
1435
1490
|
def run!(options = {}, &block)
|
1436
1491
|
return if running?
|
@@ -1503,8 +1558,12 @@ module Sinatra
|
|
1503
1558
|
|
1504
1559
|
# Starts the server by running the Rack Handler.
|
1505
1560
|
def start_server(handler, server_settings, handler_name)
|
1506
|
-
|
1507
|
-
|
1561
|
+
# Ensure we initialize middleware before startup, to match standard Rack
|
1562
|
+
# behavior, by ensuring an instance exists:
|
1563
|
+
prototype
|
1564
|
+
# Run the instance we created:
|
1565
|
+
handler.run(self, **server_settings) do |server|
|
1566
|
+
unless suppress_messages?
|
1508
1567
|
$stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1509
1568
|
end
|
1510
1569
|
|
@@ -1517,6 +1576,10 @@ module Sinatra
|
|
1517
1576
|
end
|
1518
1577
|
end
|
1519
1578
|
|
1579
|
+
def suppress_messages?
|
1580
|
+
handler_name =~ /cgi/i || quiet
|
1581
|
+
end
|
1582
|
+
|
1520
1583
|
def setup_traps
|
1521
1584
|
if traps?
|
1522
1585
|
at_exit { quit! }
|
@@ -1534,8 +1597,7 @@ module Sinatra
|
|
1534
1597
|
|
1535
1598
|
# Dynamically defines a method on settings.
|
1536
1599
|
def define_singleton(name, content = Proc.new)
|
1537
|
-
|
1538
|
-
(class << self; self; end).class_eval do
|
1600
|
+
singleton_class.class_eval do
|
1539
1601
|
undef_method(name) if method_defined? name
|
1540
1602
|
String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
|
1541
1603
|
end
|
@@ -1578,10 +1640,8 @@ module Sinatra
|
|
1578
1640
|
end
|
1579
1641
|
|
1580
1642
|
def route(verb, path, options = {}, &block)
|
1581
|
-
# Because of self.options.host
|
1582
|
-
host_name(options.delete(:host)) if options.key?(:host)
|
1583
1643
|
enable :empty_path_info if path == "" and empty_path_info.nil?
|
1584
|
-
signature = compile!(verb, path, block, options)
|
1644
|
+
signature = compile!(verb, path, block, **options)
|
1585
1645
|
(@routes[verb] ||= []) << signature
|
1586
1646
|
invoke_hook(:route_added, verb, path, block)
|
1587
1647
|
signature
|
@@ -1592,115 +1652,33 @@ module Sinatra
|
|
1592
1652
|
end
|
1593
1653
|
|
1594
1654
|
def generate_method(method_name, &block)
|
1595
|
-
method_name = method_name.to_sym
|
1596
1655
|
define_method(method_name, &block)
|
1597
1656
|
method = instance_method method_name
|
1598
1657
|
remove_method method_name
|
1599
1658
|
method
|
1600
1659
|
end
|
1601
1660
|
|
1602
|
-
def compile!(verb, path, block, options
|
1661
|
+
def compile!(verb, path, block, **options)
|
1662
|
+
# Because of self.options.host
|
1663
|
+
host_name(options.delete(:host)) if options.key?(:host)
|
1664
|
+
# Pass Mustermann opts to compile()
|
1665
|
+
route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
|
1666
|
+
|
1603
1667
|
options.each_pair { |option, args| send(option, *args) }
|
1668
|
+
|
1669
|
+
pattern = compile(path, route_mustermann_opts)
|
1604
1670
|
method_name = "#{verb} #{path}"
|
1605
1671
|
unbound_method = generate_method(method_name, &block)
|
1606
|
-
pattern, keys = compile path
|
1607
1672
|
conditions, @conditions = @conditions, []
|
1608
|
-
|
1609
1673
|
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)
|
1674
|
+
proc { |a, p| unbound_method.bind(a).call(*p) } :
|
1675
|
+
proc { |a, p| unbound_method.bind(a).call }
|
1650
1676
|
|
1651
|
-
|
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
|
1677
|
+
[ pattern, conditions, wrapper ]
|
1673
1678
|
end
|
1674
1679
|
|
1675
|
-
def
|
1676
|
-
|
1677
|
-
enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
|
1678
|
-
enc = "(?:#{enc}|#{encoded('+')})" if char == " "
|
1679
|
-
enc
|
1680
|
-
end
|
1681
|
-
|
1682
|
-
def escaped(char, enc = URI_INSTANCE.escape(char))
|
1683
|
-
[Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
|
1684
|
-
end
|
1685
|
-
|
1686
|
-
def safe_ignore(ignore)
|
1687
|
-
unsafe_ignore = []
|
1688
|
-
ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
|
1689
|
-
unsafe_ignore << hex[1..2]
|
1690
|
-
''
|
1691
|
-
end
|
1692
|
-
unsafe_patterns = unsafe_ignore.map! do |unsafe|
|
1693
|
-
chars = unsafe.split(//).map! do |char|
|
1694
|
-
char == char.downcase ? char : char + char.downcase
|
1695
|
-
end
|
1696
|
-
|
1697
|
-
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
1698
|
-
end
|
1699
|
-
if unsafe_patterns.length > 0
|
1700
|
-
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
1701
|
-
else
|
1702
|
-
"([^#{ignore}/?#]+)"
|
1703
|
-
end
|
1680
|
+
def compile(path, route_mustermann_opts = {})
|
1681
|
+
Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts))
|
1704
1682
|
end
|
1705
1683
|
|
1706
1684
|
def setup_default_middleware(builder)
|
@@ -1745,10 +1723,16 @@ module Sinatra
|
|
1745
1723
|
def setup_protection(builder)
|
1746
1724
|
return unless protection?
|
1747
1725
|
options = Hash === protection ? protection.dup : {}
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1726
|
+
options = {
|
1727
|
+
img_src: "'self' data:",
|
1728
|
+
font_src: "'self'"
|
1729
|
+
}.merge options
|
1730
|
+
|
1731
|
+
protect_session = options.fetch(:session) { sessions? }
|
1732
|
+
options[:without_session] = !protect_session
|
1733
|
+
|
1751
1734
|
options[:reaction] ||= :drop_session
|
1735
|
+
|
1752
1736
|
builder.use Rack::Protection, options
|
1753
1737
|
end
|
1754
1738
|
|
@@ -1757,7 +1741,7 @@ module Sinatra
|
|
1757
1741
|
options = {}
|
1758
1742
|
options[:secret] = session_secret if session_secret?
|
1759
1743
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
1760
|
-
builder.use
|
1744
|
+
builder.use session_store, options
|
1761
1745
|
end
|
1762
1746
|
|
1763
1747
|
def detect_rack_handler
|
@@ -1766,8 +1750,6 @@ module Sinatra
|
|
1766
1750
|
begin
|
1767
1751
|
return Rack::Handler.get(server_name.to_s)
|
1768
1752
|
rescue LoadError, NameError
|
1769
|
-
rescue ArgumentError
|
1770
|
-
Sinatra::Ext.get_handler(server_name.to_s)
|
1771
1753
|
end
|
1772
1754
|
end
|
1773
1755
|
fail "Server handler (#{servers.join(',')}) not found."
|
@@ -1801,36 +1783,30 @@ module Sinatra
|
|
1801
1783
|
end
|
1802
1784
|
end
|
1803
1785
|
|
1804
|
-
#
|
1805
|
-
#
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
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
|
1786
|
+
# Force data to specified encoding. It defaults to settings.default_encoding
|
1787
|
+
# which is UTF-8 by default
|
1788
|
+
def self.force_encoding(data, encoding = default_encoding)
|
1789
|
+
return if data == settings || data.is_a?(Tempfile)
|
1790
|
+
if data.respond_to? :force_encoding
|
1791
|
+
data.force_encoding(encoding).encode!
|
1792
|
+
elsif data.respond_to? :each_value
|
1793
|
+
data.each_value { |v| force_encoding(v, encoding) }
|
1794
|
+
elsif data.respond_to? :each
|
1795
|
+
data.each { |v| force_encoding(v, encoding) }
|
1822
1796
|
end
|
1823
|
-
|
1824
|
-
def self.force_encoding(data, *) data end
|
1797
|
+
data
|
1825
1798
|
end
|
1826
1799
|
|
1800
|
+
def force_encoding(*args) settings.force_encoding(*args) end
|
1801
|
+
|
1827
1802
|
reset!
|
1828
1803
|
|
1829
|
-
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
1804
|
+
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1830
1805
|
set :raise_errors, Proc.new { test? }
|
1831
1806
|
set :dump_errors, Proc.new { !test? }
|
1832
1807
|
set :show_exceptions, Proc.new { development? }
|
1833
1808
|
set :sessions, false
|
1809
|
+
set :session_store, Rack::Session::Cookie
|
1834
1810
|
set :logging, false
|
1835
1811
|
set :protection, true
|
1836
1812
|
set :method_override, false
|
@@ -1839,6 +1815,8 @@ module Sinatra
|
|
1839
1815
|
set :x_cascade, true
|
1840
1816
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1841
1817
|
settings.add_charset << /^text\//
|
1818
|
+
set :mustermann_opts, {}
|
1819
|
+
set :default_content_type, 'text/html'
|
1842
1820
|
|
1843
1821
|
# explicitly generating a session secret eagerly to play nice with preforking
|
1844
1822
|
begin
|
@@ -1861,6 +1839,7 @@ module Sinatra
|
|
1861
1839
|
set :server, %w[HTTP webrick]
|
1862
1840
|
set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
|
1863
1841
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1842
|
+
set :quiet, false
|
1864
1843
|
|
1865
1844
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1866
1845
|
|
@@ -1868,16 +1847,16 @@ module Sinatra
|
|
1868
1847
|
server.unshift 'control_tower'
|
1869
1848
|
else
|
1870
1849
|
server.unshift 'reel'
|
1850
|
+
server.unshift 'puma'
|
1871
1851
|
server.unshift 'mongrel' if ruby_engine.nil?
|
1872
|
-
server.unshift 'puma' if ruby_engine != 'rbx'
|
1873
1852
|
server.unshift 'thin' if ruby_engine != 'jruby'
|
1874
|
-
server.unshift 'puma' if ruby_engine == 'rbx'
|
1875
1853
|
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1876
1854
|
end
|
1877
1855
|
|
1878
1856
|
set :absolute_redirects, true
|
1879
1857
|
set :prefixed_redirects, false
|
1880
1858
|
set :empty_path_info, nil
|
1859
|
+
set :strict_paths, true
|
1881
1860
|
|
1882
1861
|
set :app_file, nil
|
1883
1862
|
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
@@ -1898,7 +1877,7 @@ module Sinatra
|
|
1898
1877
|
|
1899
1878
|
configure :development do
|
1900
1879
|
get '/__sinatra__/:image.png' do
|
1901
|
-
filename =
|
1880
|
+
filename = __dir__ + "/images/#{params[:image].to_i}.png"
|
1902
1881
|
content_type :png
|
1903
1882
|
send_file filename
|
1904
1883
|
end
|
@@ -1936,7 +1915,7 @@ module Sinatra
|
|
1936
1915
|
</style>
|
1937
1916
|
</head>
|
1938
1917
|
<body>
|
1939
|
-
<h2>Sinatra doesn
|
1918
|
+
<h2>Sinatra doesn’t know this ditty.</h2>
|
1940
1919
|
<img src='#{uri "/__sinatra__/404.png"}'>
|
1941
1920
|
<div id="c">
|
1942
1921
|
Try this:
|
@@ -1957,14 +1936,13 @@ module Sinatra
|
|
1957
1936
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1958
1937
|
# modular applications.
|
1959
1938
|
class Application < Base
|
1960
|
-
set :logging, Proc.new { !
|
1939
|
+
set :logging, Proc.new { !test? }
|
1961
1940
|
set :method_override, true
|
1962
|
-
set :run, Proc.new { !
|
1963
|
-
set :session_secret, Proc.new { super() unless development? }
|
1941
|
+
set :run, Proc.new { !test? }
|
1964
1942
|
set :app_file, nil
|
1965
1943
|
|
1966
1944
|
def self.register(*extensions, &block) #:nodoc:
|
1967
|
-
added_methods = extensions.
|
1945
|
+
added_methods = extensions.flat_map(&:public_instance_methods)
|
1968
1946
|
Delegator.delegate(*added_methods)
|
1969
1947
|
super(*extensions, &block)
|
1970
1948
|
end
|