sinatra 2.2.4 → 3.0.5
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.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -17
- data/Gemfile +44 -64
- data/README.md +105 -402
- data/Rakefile +65 -65
- data/VERSION +1 -1
- data/examples/chat.rb +5 -3
- data/examples/rainbows.rb +3 -1
- data/examples/simple.rb +2 -0
- data/examples/stream.ru +2 -0
- data/lib/sinatra/base.rb +331 -328
- data/lib/sinatra/indifferent_hash.rb +25 -33
- data/lib/sinatra/main.rb +18 -16
- data/lib/sinatra/show_exceptions.rb +17 -15
- data/lib/sinatra/version.rb +3 -1
- data/lib/sinatra.rb +2 -0
- data/sinatra.gemspec +38 -33
- metadata +32 -24
- data/README.de.md +0 -3239
- data/README.es.md +0 -3231
- data/README.fr.md +0 -3111
- data/README.hu.md +0 -728
- data/README.ja.md +0 -2844
- data/README.ko.md +0 -2967
- data/README.malayalam.md +0 -3141
- data/README.pt-br.md +0 -3787
- data/README.pt-pt.md +0 -791
- data/README.ru.md +0 -3207
- data/README.zh.md +0 -2934
data/lib/sinatra/base.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
# external dependencies
|
@@ -10,7 +9,6 @@ require 'mustermann/sinatra'
|
|
10
9
|
require 'mustermann/regular'
|
11
10
|
|
12
11
|
# stdlib dependencies
|
13
|
-
require 'thread'
|
14
12
|
require 'time'
|
15
13
|
require 'uri'
|
16
14
|
|
@@ -23,19 +21,20 @@ module Sinatra
|
|
23
21
|
# The request object. See Rack::Request for more info:
|
24
22
|
# http://rubydoc.info/github/rack/rack/master/Rack/Request
|
25
23
|
class Request < Rack::Request
|
26
|
-
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s
|
27
|
-
HEADER_VALUE_WITH_PARAMS =
|
24
|
+
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
25
|
+
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
|
28
26
|
|
29
27
|
# Returns an array of acceptable media types for the response
|
30
28
|
def accept
|
31
|
-
@env['sinatra.accept'] ||=
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
@env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
|
30
|
+
@env['HTTP_ACCEPT']
|
31
|
+
.to_s
|
32
|
+
.scan(HEADER_VALUE_WITH_PARAMS)
|
33
|
+
.map! { |e| AcceptEntry.new(e) }
|
34
|
+
.sort
|
35
|
+
else
|
36
|
+
[AcceptEntry.new('*/*')]
|
37
|
+
end
|
39
38
|
end
|
40
39
|
|
41
40
|
def accept?(type)
|
@@ -44,8 +43,10 @@ module Sinatra
|
|
44
43
|
|
45
44
|
def preferred_type(*types)
|
46
45
|
return accept.first if types.empty?
|
46
|
+
|
47
47
|
types.flatten!
|
48
48
|
return types.first if accept.empty?
|
49
|
+
|
49
50
|
accept.detect do |accept_header|
|
50
51
|
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
|
51
52
|
return type if type
|
@@ -55,23 +56,23 @@ module Sinatra
|
|
55
56
|
alias secure? ssl?
|
56
57
|
|
57
58
|
def forwarded?
|
58
|
-
@env.include?
|
59
|
+
@env.include? 'HTTP_X_FORWARDED_HOST'
|
59
60
|
end
|
60
61
|
|
61
62
|
def safe?
|
62
|
-
get?
|
63
|
+
get? || head? || options? || trace?
|
63
64
|
end
|
64
65
|
|
65
66
|
def idempotent?
|
66
|
-
safe?
|
67
|
+
safe? || put? || delete? || link? || unlink?
|
67
68
|
end
|
68
69
|
|
69
70
|
def link?
|
70
|
-
request_method ==
|
71
|
+
request_method == 'LINK'
|
71
72
|
end
|
72
73
|
|
73
74
|
def unlink?
|
74
|
-
request_method ==
|
75
|
+
request_method == 'UNLINK'
|
75
76
|
end
|
76
77
|
|
77
78
|
def params
|
@@ -95,17 +96,17 @@ module Sinatra
|
|
95
96
|
|
96
97
|
@entry = entry
|
97
98
|
@type = entry[/[^;]+/].delete(' ')
|
98
|
-
@params =
|
99
|
+
@params = params.to_h
|
99
100
|
@q = @params.delete('q') { 1.0 }.to_f
|
100
101
|
end
|
101
102
|
|
102
103
|
def <=>(other)
|
103
|
-
other.priority <=>
|
104
|
+
other.priority <=> priority
|
104
105
|
end
|
105
106
|
|
106
107
|
def priority
|
107
108
|
# We sort in descending order; better matches should be higher.
|
108
|
-
[
|
109
|
+
[@q, -@type.count('*'), @params.size]
|
109
110
|
end
|
110
111
|
|
111
112
|
def to_str
|
@@ -117,7 +118,7 @@ module Sinatra
|
|
117
118
|
end
|
118
119
|
|
119
120
|
def respond_to?(*args)
|
120
|
-
super
|
121
|
+
super || to_str.respond_to?(*args)
|
121
122
|
end
|
122
123
|
|
123
124
|
def method_missing(*args, &block)
|
@@ -136,7 +137,7 @@ module Sinatra
|
|
136
137
|
end
|
137
138
|
|
138
139
|
@type = entry[/[^;]+/].delete(' ')
|
139
|
-
@params =
|
140
|
+
@params = params.to_h
|
140
141
|
end
|
141
142
|
|
142
143
|
def accepts?(entry)
|
@@ -150,7 +151,7 @@ module Sinatra
|
|
150
151
|
def matches_params?(params)
|
151
152
|
return true if @params.empty?
|
152
153
|
|
153
|
-
params.all? { |k,v| !@params.
|
154
|
+
params.all? { |k, v| !@params.key?(k) || @params[k] == v }
|
154
155
|
end
|
155
156
|
end
|
156
157
|
end
|
@@ -160,7 +161,7 @@ module Sinatra
|
|
160
161
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response
|
161
162
|
# http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
|
162
163
|
class Response < Rack::Response
|
163
|
-
DROP_BODY_RESPONSES = [204, 304]
|
164
|
+
DROP_BODY_RESPONSES = [204, 304].freeze
|
164
165
|
|
165
166
|
def body=(value)
|
166
167
|
value = value.body while Rack::Response === value
|
@@ -175,8 +176,8 @@ module Sinatra
|
|
175
176
|
result = body
|
176
177
|
|
177
178
|
if drop_content_info?
|
178
|
-
headers.delete
|
179
|
-
headers.delete
|
179
|
+
headers.delete 'Content-Length'
|
180
|
+
headers.delete 'Content-Type'
|
180
181
|
end
|
181
182
|
|
182
183
|
if drop_body?
|
@@ -187,7 +188,7 @@ module Sinatra
|
|
187
188
|
if calculate_content_length?
|
188
189
|
# if some other code has already set Content-Length, don't muck with it
|
189
190
|
# currently, this would be the static file-handler
|
190
|
-
headers[
|
191
|
+
headers['Content-Length'] = body.map(&:bytesize).reduce(0, :+).to_s
|
191
192
|
end
|
192
193
|
|
193
194
|
[status, headers, result]
|
@@ -196,11 +197,11 @@ module Sinatra
|
|
196
197
|
private
|
197
198
|
|
198
199
|
def calculate_content_length?
|
199
|
-
headers[
|
200
|
+
headers['Content-Type'] && !headers['Content-Length'] && (Array === body)
|
200
201
|
end
|
201
202
|
|
202
203
|
def drop_content_info?
|
203
|
-
informational?
|
204
|
+
informational? || drop_body?
|
204
205
|
end
|
205
206
|
|
206
207
|
def drop_body?
|
@@ -215,8 +216,10 @@ module Sinatra
|
|
215
216
|
# still be able to run.
|
216
217
|
class ExtendedRack < Struct.new(:app)
|
217
218
|
def call(env)
|
218
|
-
result
|
219
|
-
|
219
|
+
result = app.call(env)
|
220
|
+
callback = env['async.callback']
|
221
|
+
return result unless callback && async?(*result)
|
222
|
+
|
220
223
|
after_response { callback.call result }
|
221
224
|
setup_close(env, *result)
|
222
225
|
throw :async
|
@@ -224,20 +227,23 @@ module Sinatra
|
|
224
227
|
|
225
228
|
private
|
226
229
|
|
227
|
-
def setup_close(env,
|
228
|
-
return unless body.respond_to?
|
230
|
+
def setup_close(env, _status, _headers, body)
|
231
|
+
return unless body.respond_to?(:close) && env.include?('async.close')
|
232
|
+
|
229
233
|
env['async.close'].callback { body.close }
|
230
234
|
env['async.close'].errback { body.close }
|
231
235
|
end
|
232
236
|
|
233
237
|
def after_response(&block)
|
234
|
-
raise NotImplementedError,
|
238
|
+
raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
|
239
|
+
|
235
240
|
EventMachine.next_tick(&block)
|
236
241
|
end
|
237
242
|
|
238
|
-
def async?(status,
|
243
|
+
def async?(status, _headers, body)
|
239
244
|
return true if status == -1
|
240
|
-
|
245
|
+
|
246
|
+
body.respond_to?(:callback) && body.respond_to?(:errback)
|
241
247
|
end
|
242
248
|
end
|
243
249
|
|
@@ -249,7 +255,7 @@ module Sinatra
|
|
249
255
|
end
|
250
256
|
|
251
257
|
superclass.class_eval do
|
252
|
-
|
258
|
+
alias_method :call_without_check, :call unless method_defined? :call_without_check
|
253
259
|
def call(env)
|
254
260
|
env['sinatra.commonlogger'] = true
|
255
261
|
call_without_check(env)
|
@@ -257,11 +263,14 @@ module Sinatra
|
|
257
263
|
end
|
258
264
|
end
|
259
265
|
|
260
|
-
class
|
266
|
+
class Error < StandardError # :nodoc:
|
267
|
+
end
|
268
|
+
|
269
|
+
class BadRequest < Error # :nodoc:
|
261
270
|
def http_status; 400 end
|
262
271
|
end
|
263
272
|
|
264
|
-
class NotFound <
|
273
|
+
class NotFound < Error # :nodoc:
|
265
274
|
def http_status; 404 end
|
266
275
|
end
|
267
276
|
|
@@ -293,7 +302,7 @@ module Sinatra
|
|
293
302
|
|
294
303
|
# Halt processing and redirect to the URI provided.
|
295
304
|
def redirect(uri, *args)
|
296
|
-
if env['HTTP_VERSION'] == 'HTTP/1.1'
|
305
|
+
if (env['HTTP_VERSION'] == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
|
297
306
|
status 303
|
298
307
|
else
|
299
308
|
status 302
|
@@ -308,18 +317,19 @@ module Sinatra
|
|
308
317
|
# Generates the absolute URI for a given path in the app.
|
309
318
|
# Takes Rack routers and reverse proxies into account.
|
310
319
|
def uri(addr = nil, absolute = true, add_script_name = true)
|
311
|
-
return addr if addr =~ /\A[a-z][a-z0-9
|
320
|
+
return addr if addr =~ /\A[a-z][a-z0-9+.\-]*:/i
|
321
|
+
|
312
322
|
uri = [host = String.new]
|
313
323
|
if absolute
|
314
324
|
host << "http#{'s' if request.secure?}://"
|
315
|
-
if request.forwarded?
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
325
|
+
host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
|
326
|
+
request.host_with_port
|
327
|
+
else
|
328
|
+
request.host
|
329
|
+
end
|
320
330
|
end
|
321
331
|
uri << request.script_name.to_s if add_script_name
|
322
|
-
uri << (addr
|
332
|
+
uri << (addr || request.path_info).to_s
|
323
333
|
File.join uri
|
324
334
|
end
|
325
335
|
|
@@ -328,7 +338,10 @@ module Sinatra
|
|
328
338
|
|
329
339
|
# Halt processing and return the error status provided.
|
330
340
|
def error(code, body = nil)
|
331
|
-
|
341
|
+
if code.respond_to? :to_str
|
342
|
+
body = code.to_str
|
343
|
+
code = 500
|
344
|
+
end
|
332
345
|
response.body = body unless body.nil?
|
333
346
|
halt code
|
334
347
|
end
|
@@ -363,11 +376,13 @@ module Sinatra
|
|
363
376
|
# extension.
|
364
377
|
def content_type(type = nil, params = {})
|
365
378
|
return response['Content-Type'] unless type
|
379
|
+
|
366
380
|
default = params.delete :default
|
367
381
|
mime_type = mime_type(type) || default
|
368
|
-
|
382
|
+
raise format('Unknown media type: %p', type) if mime_type.nil?
|
383
|
+
|
369
384
|
mime_type = mime_type.dup
|
370
|
-
unless params.include?
|
385
|
+
unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
|
371
386
|
params[:charset] = params.delete('charset') || settings.default_encoding
|
372
387
|
end
|
373
388
|
params.delete :charset if mime_type.include? 'charset'
|
@@ -402,13 +417,13 @@ module Sinatra
|
|
402
417
|
|
403
418
|
# Use the contents of the file at +path+ as the response body.
|
404
419
|
def send_file(path, opts = {})
|
405
|
-
if opts[:type]
|
406
|
-
content_type opts[:type] || File.extname(path), :
|
420
|
+
if opts[:type] || !response['Content-Type']
|
421
|
+
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
407
422
|
end
|
408
423
|
|
409
424
|
disposition = opts[:disposition]
|
410
425
|
filename = opts[:filename]
|
411
|
-
disposition = :attachment if disposition.nil?
|
426
|
+
disposition = :attachment if disposition.nil? && filename
|
412
427
|
filename = path if filename.nil?
|
413
428
|
attachment(filename, disposition) if disposition
|
414
429
|
|
@@ -417,7 +432,7 @@ module Sinatra
|
|
417
432
|
file = Rack::File.new(File.dirname(settings.app_file))
|
418
433
|
result = file.serving(request, path)
|
419
434
|
|
420
|
-
result[1].each { |k,v| headers[k] ||= v }
|
435
|
+
result[1].each { |k, v| headers[k] ||= v }
|
421
436
|
headers['Content-Length'] = result[1]['Content-Length']
|
422
437
|
opts[:status] &&= Integer(opts[:status])
|
423
438
|
halt (opts[:status] || result[0]), result[2]
|
@@ -438,12 +453,16 @@ module Sinatra
|
|
438
453
|
def self.defer(*) yield end
|
439
454
|
|
440
455
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
441
|
-
@back
|
442
|
-
@
|
456
|
+
@back = back.to_proc
|
457
|
+
@scheduler = scheduler
|
458
|
+
@keep_open = keep_open
|
459
|
+
@callbacks = []
|
460
|
+
@closed = false
|
443
461
|
end
|
444
462
|
|
445
463
|
def close
|
446
464
|
return if closed?
|
465
|
+
|
447
466
|
@closed = true
|
448
467
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
449
468
|
end
|
@@ -467,6 +486,7 @@ module Sinatra
|
|
467
486
|
|
468
487
|
def callback(&block)
|
469
488
|
return yield if closed?
|
489
|
+
|
470
490
|
@callbacks << block
|
471
491
|
end
|
472
492
|
|
@@ -500,18 +520,18 @@ module Sinatra
|
|
500
520
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
501
521
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
502
522
|
def cache_control(*values)
|
503
|
-
if values.last.
|
523
|
+
if values.last.is_a?(Hash)
|
504
524
|
hash = values.pop
|
505
|
-
hash.reject! { |
|
525
|
+
hash.reject! { |_k, v| v == false }
|
506
526
|
hash.reject! { |k, v| values << k if v == true }
|
507
527
|
else
|
508
528
|
hash = {}
|
509
529
|
end
|
510
530
|
|
511
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
531
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
512
532
|
hash.each do |key, value|
|
513
533
|
key = key.to_s.tr('_', '-')
|
514
|
-
value = value.to_i if [
|
534
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
515
535
|
values << "#{key}=#{value}"
|
516
536
|
end
|
517
537
|
|
@@ -528,7 +548,7 @@ module Sinatra
|
|
528
548
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
529
549
|
#
|
530
550
|
def expires(amount, *values)
|
531
|
-
values << {} unless values.last.
|
551
|
+
values << {} unless values.last.is_a?(Hash)
|
532
552
|
|
533
553
|
if amount.is_a? Integer
|
534
554
|
time = Time.now + amount.to_i
|
@@ -538,7 +558,7 @@ module Sinatra
|
|
538
558
|
max_age = time - Time.now
|
539
559
|
end
|
540
560
|
|
541
|
-
values.last.merge!(:max_age
|
561
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
542
562
|
cache_control(*values)
|
543
563
|
|
544
564
|
response['Expires'] = time.httpdate
|
@@ -553,17 +573,18 @@ module Sinatra
|
|
553
573
|
# with a '304 Not Modified' response.
|
554
574
|
def last_modified(time)
|
555
575
|
return unless time
|
576
|
+
|
556
577
|
time = time_for time
|
557
578
|
response['Last-Modified'] = time.httpdate
|
558
579
|
return if env['HTTP_IF_NONE_MATCH']
|
559
580
|
|
560
|
-
if status == 200
|
581
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
561
582
|
# compare based on seconds since epoch
|
562
583
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
563
584
|
halt 304 if since >= time.to_i
|
564
585
|
end
|
565
586
|
|
566
|
-
if (success?
|
587
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
567
588
|
# compare based on seconds since epoch
|
568
589
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
569
590
|
halt 412 if since < time.to_i
|
@@ -571,7 +592,7 @@ module Sinatra
|
|
571
592
|
rescue ArgumentError
|
572
593
|
end
|
573
594
|
|
574
|
-
ETAG_KINDS = [
|
595
|
+
ETAG_KINDS = %i[strong weak].freeze
|
575
596
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
576
597
|
# GET matches. The +value+ argument is an identifier that uniquely
|
577
598
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -583,27 +604,31 @@ module Sinatra
|
|
583
604
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
584
605
|
def etag(value, options = {})
|
585
606
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
586
|
-
options = {:
|
607
|
+
options = { kind: options } unless Hash === options
|
587
608
|
kind = options[:kind] || :strong
|
588
609
|
new_resource = options.fetch(:new_resource) { request.post? }
|
589
610
|
|
590
611
|
unless ETAG_KINDS.include?(kind)
|
591
|
-
raise ArgumentError,
|
612
|
+
raise ArgumentError, ':strong or :weak expected'
|
592
613
|
end
|
593
614
|
|
594
|
-
value = '"%s"'
|
615
|
+
value = format('"%s"', value)
|
595
616
|
value = "W/#{value}" if kind == :weak
|
596
617
|
response['ETag'] = value
|
597
618
|
|
598
|
-
|
599
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
600
|
-
halt(request.safe? ? 304 : 412)
|
601
|
-
end
|
619
|
+
return unless success? || status == 304
|
602
620
|
|
603
|
-
|
604
|
-
|
605
|
-
|
621
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
622
|
+
halt(request.safe? ? 304 : 412)
|
623
|
+
end
|
624
|
+
|
625
|
+
if env['HTTP_IF_MATCH']
|
626
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
627
|
+
|
628
|
+
halt 412
|
606
629
|
end
|
630
|
+
|
631
|
+
nil
|
607
632
|
end
|
608
633
|
|
609
634
|
# Sugar for redirect (example: redirect back)
|
@@ -656,8 +681,8 @@ module Sinatra
|
|
656
681
|
else
|
657
682
|
value.to_time
|
658
683
|
end
|
659
|
-
rescue ArgumentError =>
|
660
|
-
raise
|
684
|
+
rescue ArgumentError => e
|
685
|
+
raise e
|
661
686
|
rescue Exception
|
662
687
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
663
688
|
end
|
@@ -667,11 +692,13 @@ module Sinatra
|
|
667
692
|
# Helper method checking if a ETag value list includes the current ETag.
|
668
693
|
def etag_matches?(list, new_resource = request.post?)
|
669
694
|
return !new_resource if list == '*'
|
695
|
+
|
670
696
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
671
697
|
end
|
672
698
|
|
673
699
|
def with_params(temp_params)
|
674
|
-
original
|
700
|
+
original = @params
|
701
|
+
@params = temp_params
|
675
702
|
yield
|
676
703
|
ensure
|
677
704
|
@params = original if original
|
@@ -689,7 +716,7 @@ module Sinatra
|
|
689
716
|
# Possible options are:
|
690
717
|
# :content_type The content type to use, same arguments as content_type.
|
691
718
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
692
|
-
# the specified layout is used
|
719
|
+
# the specified layout is used
|
693
720
|
# :layout_engine Engine to use for rendering the layout.
|
694
721
|
# :locals A hash with local variables that should be available
|
695
722
|
# in the template
|
@@ -711,36 +738,10 @@ module Sinatra
|
|
711
738
|
render(:erb, template, options, locals, &block)
|
712
739
|
end
|
713
740
|
|
714
|
-
def erubis(template, options = {}, locals = {})
|
715
|
-
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
716
|
-
"If you have Erubis installed, it will be used automatically."
|
717
|
-
render :erubis, template, options, locals
|
718
|
-
end
|
719
|
-
|
720
741
|
def haml(template, options = {}, locals = {}, &block)
|
721
742
|
render(:haml, template, options, locals, &block)
|
722
743
|
end
|
723
744
|
|
724
|
-
def sass(template, options = {}, locals = {})
|
725
|
-
options.merge! :layout => false, :default_content_type => :css
|
726
|
-
render :sass, template, options, locals
|
727
|
-
end
|
728
|
-
|
729
|
-
def scss(template, options = {}, locals = {})
|
730
|
-
options.merge! :layout => false, :default_content_type => :css
|
731
|
-
render :scss, template, options, locals
|
732
|
-
end
|
733
|
-
|
734
|
-
def less(template, options = {}, locals = {})
|
735
|
-
options.merge! :layout => false, :default_content_type => :css
|
736
|
-
render :less, template, options, locals
|
737
|
-
end
|
738
|
-
|
739
|
-
def stylus(template, options = {}, locals = {})
|
740
|
-
options.merge! :layout => false, :default_content_type => :css
|
741
|
-
render :styl, template, options, locals
|
742
|
-
end
|
743
|
-
|
744
745
|
def builder(template = nil, options = {}, locals = {}, &block)
|
745
746
|
options[:default_content_type] = :xml
|
746
747
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -755,10 +756,6 @@ module Sinatra
|
|
755
756
|
render :markdown, template, options, locals
|
756
757
|
end
|
757
758
|
|
758
|
-
def textile(template, options = {}, locals = {})
|
759
|
-
render :textile, template, options, locals
|
760
|
-
end
|
761
|
-
|
762
759
|
def rdoc(template, options = {}, locals = {})
|
763
760
|
render :rdoc, template, options, locals
|
764
761
|
end
|
@@ -767,19 +764,10 @@ module Sinatra
|
|
767
764
|
render :asciidoc, template, options, locals
|
768
765
|
end
|
769
766
|
|
770
|
-
def radius(template, options = {}, locals = {})
|
771
|
-
render :radius, template, options, locals
|
772
|
-
end
|
773
|
-
|
774
767
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
775
768
|
render_ruby(:mab, template, options, locals, &block)
|
776
769
|
end
|
777
770
|
|
778
|
-
def coffee(template, options = {}, locals = {})
|
779
|
-
options.merge! :layout => false, :default_content_type => :js
|
780
|
-
render :coffee, template, options, locals
|
781
|
-
end
|
782
|
-
|
783
771
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
784
772
|
options[:default_content_type] = :xml
|
785
773
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -789,18 +777,6 @@ module Sinatra
|
|
789
777
|
render(:slim, template, options, locals, &block)
|
790
778
|
end
|
791
779
|
|
792
|
-
def creole(template, options = {}, locals = {})
|
793
|
-
render :creole, template, options, locals
|
794
|
-
end
|
795
|
-
|
796
|
-
def mediawiki(template, options = {}, locals = {})
|
797
|
-
render :mediawiki, template, options, locals
|
798
|
-
end
|
799
|
-
|
800
|
-
def wlang(template, options = {}, locals = {}, &block)
|
801
|
-
render(:wlang, template, options, locals, &block)
|
802
|
-
end
|
803
|
-
|
804
780
|
def yajl(template, options = {}, locals = {})
|
805
781
|
options[:default_content_type] = :json
|
806
782
|
render :yajl, template, options, locals
|
@@ -825,24 +801,27 @@ module Sinatra
|
|
825
801
|
|
826
802
|
# logic shared between builder and nokogiri
|
827
803
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
828
|
-
|
829
|
-
|
804
|
+
if template.is_a?(Hash)
|
805
|
+
options = template
|
806
|
+
template = nil
|
807
|
+
end
|
808
|
+
template = proc { block } if template.nil?
|
830
809
|
render engine, template, options, locals
|
831
810
|
end
|
832
811
|
|
833
812
|
def render(engine, data, options = {}, locals = {}, &block)
|
834
813
|
# merge app-level options
|
835
814
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
836
|
-
options.merge!(engine_options) { |
|
815
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
837
816
|
|
838
817
|
# extract generic options
|
839
818
|
locals = options.delete(:locals) || locals || {}
|
840
|
-
views = options.delete(:views) || settings.views ||
|
819
|
+
views = options.delete(:views) || settings.views || './views'
|
841
820
|
layout = options[:layout]
|
842
821
|
layout = false if layout.nil? && options.include?(:layout)
|
843
822
|
eat_errors = layout.nil?
|
844
|
-
layout = engine_options[:layout] if layout.nil?
|
845
|
-
layout = @default_layout if layout.nil?
|
823
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
824
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
846
825
|
layout_options = options.delete(:layout_options) || {}
|
847
826
|
content_type = options.delete(:default_content_type)
|
848
827
|
content_type = options.delete(:content_type) || content_type
|
@@ -867,8 +846,9 @@ module Sinatra
|
|
867
846
|
|
868
847
|
# render layout
|
869
848
|
if layout
|
870
|
-
|
871
|
-
|
849
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
850
|
+
options = options.merge(extra_options).merge!(layout_options)
|
851
|
+
|
872
852
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
873
853
|
end
|
874
854
|
|
@@ -893,12 +873,13 @@ module Sinatra
|
|
893
873
|
@preferred_extension = engine.to_s
|
894
874
|
find_template(views, data, template) do |file|
|
895
875
|
path ||= file # keep the initial path rather than the last one
|
896
|
-
|
876
|
+
found = File.exist?(file)
|
877
|
+
if found
|
897
878
|
path = file
|
898
879
|
break
|
899
880
|
end
|
900
881
|
end
|
901
|
-
throw :layout_missing if eat_errors
|
882
|
+
throw :layout_missing if eat_errors && !found
|
902
883
|
template.new(path, 1, options)
|
903
884
|
end
|
904
885
|
end
|
@@ -914,9 +895,11 @@ module Sinatra
|
|
914
895
|
end
|
915
896
|
|
916
897
|
def compile_block_template(template, options, &body)
|
917
|
-
|
918
|
-
path =
|
919
|
-
line =
|
898
|
+
first_location = caller_locations.first
|
899
|
+
path = first_location.path
|
900
|
+
line = first_location.lineno
|
901
|
+
path = options[:path] || path
|
902
|
+
line = options[:line] || line
|
920
903
|
template.new(path, line.to_i, options, &body)
|
921
904
|
end
|
922
905
|
end
|
@@ -932,7 +915,7 @@ module Sinatra
|
|
932
915
|
attr_accessor :app, :env, :request, :response, :params
|
933
916
|
attr_reader :template_cache
|
934
917
|
|
935
|
-
def initialize(app = nil, **
|
918
|
+
def initialize(app = nil, **_kwargs)
|
936
919
|
super()
|
937
920
|
@app = app
|
938
921
|
@template_cache = Tilt::Cache.new
|
@@ -959,7 +942,7 @@ module Sinatra
|
|
959
942
|
unless @response['Content-Type']
|
960
943
|
if Array === body && body[0].respond_to?(:content_type)
|
961
944
|
content_type body[0].content_type
|
962
|
-
elsif default = settings.default_content_type
|
945
|
+
elsif (default = settings.default_content_type)
|
963
946
|
content_type default
|
964
947
|
end
|
965
948
|
end
|
@@ -977,12 +960,6 @@ module Sinatra
|
|
977
960
|
self.class.settings
|
978
961
|
end
|
979
962
|
|
980
|
-
def options
|
981
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
982
|
-
"use #settings instead."
|
983
|
-
settings
|
984
|
-
end
|
985
|
-
|
986
963
|
# Exit the current block, halts any further processing
|
987
964
|
# of the request, and returns the specified response.
|
988
965
|
def halt(*response)
|
@@ -999,7 +976,8 @@ module Sinatra
|
|
999
976
|
|
1000
977
|
# Forward the request to the downstream app -- middleware only.
|
1001
978
|
def forward
|
1002
|
-
|
979
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
980
|
+
|
1003
981
|
status, headers, body = @app.call env
|
1004
982
|
@response.status = status
|
1005
983
|
@response.body = body
|
@@ -1021,18 +999,18 @@ module Sinatra
|
|
1021
999
|
|
1022
1000
|
# Run routes defined on the class and all superclasses.
|
1023
1001
|
def route!(base = settings, pass_block = nil)
|
1024
|
-
|
1025
|
-
routes.each do |pattern, conditions, block|
|
1026
|
-
response.delete_header('Content-Type') unless @pinned_response
|
1002
|
+
routes = base.routes[@request.request_method]
|
1027
1003
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
route_eval { block[*args] }
|
1031
|
-
end
|
1004
|
+
routes&.each do |pattern, conditions, block|
|
1005
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1032
1006
|
|
1033
|
-
|
1034
|
-
|
1007
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1008
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1009
|
+
route_eval { block[*args] }
|
1035
1010
|
end
|
1011
|
+
|
1012
|
+
# don't wipe out pass_block in superclass
|
1013
|
+
pass_block = returned_pass_block if returned_pass_block
|
1036
1014
|
end
|
1037
1015
|
|
1038
1016
|
# Run routes defined in superclass.
|
@@ -1056,15 +1034,17 @@ module Sinatra
|
|
1056
1034
|
# Returns pass block.
|
1057
1035
|
def process_route(pattern, conditions, block = nil, values = [])
|
1058
1036
|
route = @request.path_info
|
1059
|
-
route = '/' if route.empty?
|
1037
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1060
1038
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1061
|
-
return unless params = pattern.params(route)
|
1062
1039
|
|
1063
|
-
params.
|
1040
|
+
params = pattern.params(route)
|
1041
|
+
return unless params
|
1042
|
+
|
1043
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1064
1044
|
force_encoding(params)
|
1065
|
-
@params = @params.merge(params) if params.any?
|
1045
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1066
1046
|
|
1067
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1047
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1068
1048
|
if regexp_exists
|
1069
1049
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1070
1050
|
values += captures
|
@@ -1077,7 +1057,7 @@ module Sinatra
|
|
1077
1057
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1078
1058
|
block ? block[self, values] : yield(self, values)
|
1079
1059
|
end
|
1080
|
-
rescue
|
1060
|
+
rescue StandardError
|
1081
1061
|
@env['sinatra.error.params'] = @params
|
1082
1062
|
raise
|
1083
1063
|
ensure
|
@@ -1091,35 +1071,35 @@ module Sinatra
|
|
1091
1071
|
# a NotFound exception. Subclasses can override this method to perform
|
1092
1072
|
# custom route miss logic.
|
1093
1073
|
def route_missing
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1098
|
-
end
|
1074
|
+
raise NotFound unless @app
|
1075
|
+
|
1076
|
+
forward
|
1099
1077
|
end
|
1100
1078
|
|
1101
1079
|
# Attempt to serve static files from public directory. Throws :halt when
|
1102
1080
|
# a matching file is found, returns nil otherwise.
|
1103
1081
|
def static!(options = {})
|
1104
1082
|
return if (public_dir = settings.public_folder).nil?
|
1083
|
+
|
1105
1084
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1106
1085
|
return unless valid_path?(path)
|
1107
1086
|
|
1108
1087
|
path = File.expand_path(path)
|
1109
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1088
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1089
|
+
|
1110
1090
|
return unless File.file?(path)
|
1111
1091
|
|
1112
1092
|
env['sinatra.static_file'] = path
|
1113
1093
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1114
|
-
send_file path, options.merge(:
|
1094
|
+
send_file path, options.merge(disposition: nil)
|
1115
1095
|
end
|
1116
1096
|
|
1117
1097
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1118
|
-
def invoke
|
1119
|
-
res = catch(:halt)
|
1098
|
+
def invoke(&block)
|
1099
|
+
res = catch(:halt, &block)
|
1120
1100
|
|
1121
|
-
res = [res] if Integer === res
|
1122
|
-
if Array === res
|
1101
|
+
res = [res] if (Integer === res) || (String === res)
|
1102
|
+
if (Array === res) && (Integer === res.first)
|
1123
1103
|
res = res.dup
|
1124
1104
|
status(res.shift)
|
1125
1105
|
body(res.pop)
|
@@ -1135,6 +1115,7 @@ module Sinatra
|
|
1135
1115
|
# Avoid passing frozen string in force_encoding
|
1136
1116
|
@params.merge!(@request.params).each do |key, val|
|
1137
1117
|
next unless val.respond_to?(:force_encoding)
|
1118
|
+
|
1138
1119
|
val = val.dup if val.frozen?
|
1139
1120
|
@params[key] = force_encoding(val)
|
1140
1121
|
end
|
@@ -1146,39 +1127,43 @@ module Sinatra
|
|
1146
1127
|
end
|
1147
1128
|
route!
|
1148
1129
|
end
|
1149
|
-
rescue ::Exception =>
|
1150
|
-
invoke { handle_exception!(
|
1130
|
+
rescue ::Exception => e
|
1131
|
+
invoke { handle_exception!(e) }
|
1151
1132
|
ensure
|
1152
1133
|
begin
|
1153
1134
|
filter! :after unless env['sinatra.static_file']
|
1154
|
-
rescue ::Exception =>
|
1155
|
-
invoke { handle_exception!(
|
1135
|
+
rescue ::Exception => e
|
1136
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1156
1137
|
end
|
1157
1138
|
end
|
1158
1139
|
|
1159
1140
|
# Error handling during requests.
|
1160
1141
|
def handle_exception!(boom)
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1142
|
+
error_params = @env['sinatra.error.params']
|
1143
|
+
|
1144
|
+
@params = @params.merge(error_params) if error_params
|
1145
|
+
|
1164
1146
|
@env['sinatra.error'] = boom
|
1165
1147
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1148
|
+
http_status = if boom.is_a? Sinatra::Error
|
1149
|
+
if boom.respond_to? :http_status
|
1150
|
+
boom.http_status
|
1151
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1152
|
+
boom.code
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1157
|
+
status(http_status)
|
1173
1158
|
|
1174
1159
|
if server_error?
|
1175
1160
|
dump_errors! boom if settings.dump_errors?
|
1176
|
-
raise boom if settings.show_exceptions?
|
1161
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1177
1162
|
elsif not_found?
|
1178
1163
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1179
1164
|
end
|
1180
1165
|
|
1181
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1166
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1182
1167
|
return res
|
1183
1168
|
end
|
1184
1169
|
|
@@ -1187,12 +1172,14 @@ module Sinatra
|
|
1187
1172
|
body Rack::Utils.escape_html(boom.message)
|
1188
1173
|
else
|
1189
1174
|
content_type 'text/html'
|
1190
|
-
body
|
1175
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1191
1176
|
end
|
1192
1177
|
end
|
1193
1178
|
|
1194
1179
|
return unless server_error?
|
1195
|
-
|
1180
|
+
|
1181
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1182
|
+
|
1196
1183
|
error_block! Exception, boom
|
1197
1184
|
end
|
1198
1185
|
|
@@ -1200,7 +1187,10 @@ module Sinatra
|
|
1200
1187
|
def error_block!(key, *block_params)
|
1201
1188
|
base = settings
|
1202
1189
|
while base.respond_to?(:errors)
|
1203
|
-
|
1190
|
+
args_array = base.errors[key]
|
1191
|
+
|
1192
|
+
next base = base.superclass unless args_array
|
1193
|
+
|
1204
1194
|
args_array.reverse_each do |args|
|
1205
1195
|
first = args == args_array.first
|
1206
1196
|
args += [block_params]
|
@@ -1208,32 +1198,27 @@ module Sinatra
|
|
1208
1198
|
return resp unless resp.nil? && !first
|
1209
1199
|
end
|
1210
1200
|
end
|
1211
|
-
return false unless key.respond_to?
|
1201
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1202
|
+
|
1212
1203
|
error_block!(key.superclass, *block_params)
|
1213
1204
|
end
|
1214
1205
|
|
1215
1206
|
def dump_errors!(boom)
|
1216
|
-
msg = ["#{Time.now.strftime(
|
1207
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
1217
1208
|
@env['rack.errors'].puts(msg)
|
1218
1209
|
end
|
1219
1210
|
|
1220
1211
|
class << self
|
1221
1212
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1222
|
-
|
1223
|
-
/
|
1213
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1214
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1224
1215
|
/^\(.*\)$/, # generated code
|
1225
|
-
/
|
1216
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1226
1217
|
/active_support/, # active_support require hacks
|
1227
|
-
|
1218
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1228
1219
|
/<internal:/, # internal in ruby >= 1.9.2
|
1229
|
-
/
|
1230
|
-
]
|
1231
|
-
|
1232
|
-
# contrary to what the comment said previously, rubinius never supported this
|
1233
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1234
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1235
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1236
|
-
end
|
1220
|
+
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
1221
|
+
].freeze
|
1237
1222
|
|
1238
1223
|
attr_reader :routes, :filters, :templates, :errors
|
1239
1224
|
|
@@ -1246,17 +1231,17 @@ module Sinatra
|
|
1246
1231
|
def reset!
|
1247
1232
|
@conditions = []
|
1248
1233
|
@routes = {}
|
1249
|
-
@filters = {:
|
1234
|
+
@filters = { before: [], after: [] }
|
1250
1235
|
@errors = {}
|
1251
1236
|
@middleware = []
|
1252
1237
|
@prototype = nil
|
1253
1238
|
@extensions = []
|
1254
1239
|
|
1255
|
-
if superclass.respond_to?(:templates)
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1240
|
+
@templates = if superclass.respond_to?(:templates)
|
1241
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1242
|
+
else
|
1243
|
+
{}
|
1244
|
+
end
|
1260
1245
|
end
|
1261
1246
|
|
1262
1247
|
# Extension modules registered on this class and all superclasses.
|
@@ -1280,16 +1265,21 @@ module Sinatra
|
|
1280
1265
|
# Sets an option to the given value. If the value is a proc,
|
1281
1266
|
# the proc will be called every time the option is accessed.
|
1282
1267
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1283
|
-
raise ArgumentError if block
|
1284
|
-
|
1268
|
+
raise ArgumentError if block && !not_set
|
1269
|
+
|
1270
|
+
if block
|
1271
|
+
value = block
|
1272
|
+
not_set = false
|
1273
|
+
end
|
1285
1274
|
|
1286
1275
|
if not_set
|
1287
1276
|
raise ArgumentError unless option.respond_to?(:each)
|
1288
|
-
|
1277
|
+
|
1278
|
+
option.each { |k, v| set(k, v) }
|
1289
1279
|
return self
|
1290
1280
|
end
|
1291
1281
|
|
1292
|
-
if respond_to?("#{option}=")
|
1282
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1293
1283
|
return __send__("#{option}=", value)
|
1294
1284
|
end
|
1295
1285
|
|
@@ -1328,7 +1318,7 @@ module Sinatra
|
|
1328
1318
|
# class, or an HTTP status code to specify which errors should be
|
1329
1319
|
# handled.
|
1330
1320
|
def error(*codes, &block)
|
1331
|
-
args = compile!
|
1321
|
+
args = compile! 'ERROR', /.*/, block
|
1332
1322
|
codes = codes.flat_map(&method(:Array))
|
1333
1323
|
codes << Exception if codes.empty?
|
1334
1324
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1354,7 +1344,7 @@ module Sinatra
|
|
1354
1344
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1355
1345
|
# when no file is specified.
|
1356
1346
|
def inline_templates=(file = nil)
|
1357
|
-
file = (
|
1347
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1358
1348
|
|
1359
1349
|
begin
|
1360
1350
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1363,23 +1353,24 @@ module Sinatra
|
|
1363
1353
|
app, data = nil
|
1364
1354
|
end
|
1365
1355
|
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1356
|
+
return unless data
|
1357
|
+
|
1358
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1359
|
+
$2
|
1360
|
+
else
|
1361
|
+
settings.default_encoding
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
lines = app.count("\n") + 1
|
1365
|
+
template = nil
|
1366
|
+
force_encoding data, encoding
|
1367
|
+
data.each_line do |line|
|
1368
|
+
lines += 1
|
1369
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1370
|
+
template = force_encoding(String.new, encoding)
|
1371
|
+
templates[$1.to_sym] = [template, file, lines]
|
1372
|
+
elsif template
|
1373
|
+
template << line
|
1383
1374
|
end
|
1384
1375
|
end
|
1385
1376
|
end
|
@@ -1388,8 +1379,10 @@ module Sinatra
|
|
1388
1379
|
def mime_type(type, value = nil)
|
1389
1380
|
return type if type.nil?
|
1390
1381
|
return type.to_s if type.to_s.include?('/')
|
1391
|
-
|
1382
|
+
|
1383
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1392
1384
|
return Rack::Mime.mime_type(type, nil) unless value
|
1385
|
+
|
1393
1386
|
Rack::Mime::MIME_TYPES[type] = value
|
1394
1387
|
end
|
1395
1388
|
|
@@ -1398,7 +1391,7 @@ module Sinatra
|
|
1398
1391
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1399
1392
|
def mime_types(type)
|
1400
1393
|
type = mime_type type
|
1401
|
-
type =~
|
1394
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1402
1395
|
end
|
1403
1396
|
|
1404
1397
|
# Define a before filter; runs before all requests within the same
|
@@ -1427,7 +1420,7 @@ module Sinatra
|
|
1427
1420
|
end
|
1428
1421
|
|
1429
1422
|
def public=(value)
|
1430
|
-
|
1423
|
+
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1431
1424
|
set(:public_folder, value)
|
1432
1425
|
end
|
1433
1426
|
|
@@ -1449,14 +1442,21 @@ module Sinatra
|
|
1449
1442
|
route('HEAD', path, opts, &block)
|
1450
1443
|
end
|
1451
1444
|
|
1452
|
-
def put(path, opts = {}, &
|
1453
|
-
|
1454
|
-
def
|
1455
|
-
|
1456
|
-
def
|
1457
|
-
|
1458
|
-
def
|
1459
|
-
|
1445
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1446
|
+
|
1447
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1448
|
+
|
1449
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1450
|
+
|
1451
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1452
|
+
|
1453
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1454
|
+
|
1455
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1456
|
+
|
1457
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1458
|
+
|
1459
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1460
1460
|
|
1461
1461
|
# Makes the methods defined in the block and in the Modules given
|
1462
1462
|
# in `extensions` available to the handlers and templates
|
@@ -1496,37 +1496,39 @@ module Sinatra
|
|
1496
1496
|
# Stop the self-hosted server if running.
|
1497
1497
|
def quit!
|
1498
1498
|
return unless running?
|
1499
|
+
|
1499
1500
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1500
1501
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1501
|
-
|
1502
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1502
1503
|
set :running_server, nil
|
1503
1504
|
set :handler_name, nil
|
1504
1505
|
end
|
1505
1506
|
|
1506
|
-
|
1507
|
+
alias stop! quit!
|
1507
1508
|
|
1508
1509
|
# Run the Sinatra app as a self-hosted server using
|
1509
|
-
# Puma, Mongrel, or WEBrick (in that order). If given a block, will call
|
1510
|
+
# Puma, Falcon, Mongrel, or WEBrick (in that order). If given a block, will call
|
1510
1511
|
# with the constructed handler once we have taken the stage.
|
1511
1512
|
def run!(options = {}, &block)
|
1512
1513
|
return if running?
|
1514
|
+
|
1513
1515
|
set options
|
1514
1516
|
handler = Rack::Handler.pick(server)
|
1515
1517
|
handler_name = handler.name.gsub(/.*::/, '')
|
1516
1518
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1517
|
-
server_settings.merge!(:
|
1519
|
+
server_settings.merge!(Port: port, Host: bind)
|
1518
1520
|
|
1519
1521
|
begin
|
1520
1522
|
start_server(handler, server_settings, handler_name, &block)
|
1521
1523
|
rescue Errno::EADDRINUSE
|
1522
|
-
|
1524
|
+
warn "== Someone is already performing on port #{port}!"
|
1523
1525
|
raise
|
1524
1526
|
ensure
|
1525
1527
|
quit!
|
1526
1528
|
end
|
1527
1529
|
end
|
1528
1530
|
|
1529
|
-
|
1531
|
+
alias start! run!
|
1530
1532
|
|
1531
1533
|
# Check whether the self-hosted server is running or not.
|
1532
1534
|
def running?
|
@@ -1544,8 +1546,8 @@ module Sinatra
|
|
1544
1546
|
# Create a new instance of the class fronted by its middleware
|
1545
1547
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1546
1548
|
# an instance of the class new was called on.
|
1547
|
-
def new(*args, &
|
1548
|
-
instance = new!(*args, &
|
1549
|
+
def new(*args, &block)
|
1550
|
+
instance = new!(*args, &block)
|
1549
1551
|
Wrapper.new(build(instance).to_app, instance)
|
1550
1552
|
end
|
1551
1553
|
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
@@ -1570,12 +1572,6 @@ module Sinatra
|
|
1570
1572
|
cleaned_caller(1).flatten
|
1571
1573
|
end
|
1572
1574
|
|
1573
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1574
|
-
# first element being the file, and the second being the line.
|
1575
|
-
def caller_locations
|
1576
|
-
cleaned_caller 2
|
1577
|
-
end
|
1578
|
-
|
1579
1575
|
private
|
1580
1576
|
|
1581
1577
|
# Starts the server by running the Rack Handler.
|
@@ -1586,7 +1582,7 @@ module Sinatra
|
|
1586
1582
|
# Run the instance we created:
|
1587
1583
|
handler.run(self, **server_settings) do |server|
|
1588
1584
|
unless suppress_messages?
|
1589
|
-
|
1585
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1590
1586
|
end
|
1591
1587
|
|
1592
1588
|
setup_traps
|
@@ -1603,18 +1599,18 @@ module Sinatra
|
|
1603
1599
|
end
|
1604
1600
|
|
1605
1601
|
def setup_traps
|
1606
|
-
|
1607
|
-
at_exit { quit! }
|
1602
|
+
return unless traps?
|
1608
1603
|
|
1609
|
-
|
1610
|
-
old_handler = trap(signal) do
|
1611
|
-
quit!
|
1612
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1613
|
-
end
|
1614
|
-
end
|
1604
|
+
at_exit { quit! }
|
1615
1605
|
|
1616
|
-
|
1606
|
+
%i[INT TERM].each do |signal|
|
1607
|
+
old_handler = trap(signal) do
|
1608
|
+
quit!
|
1609
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1610
|
+
end
|
1617
1611
|
end
|
1612
|
+
|
1613
|
+
set :traps, false
|
1618
1614
|
end
|
1619
1615
|
|
1620
1616
|
# Dynamically defines a method on settings.
|
@@ -1642,18 +1638,21 @@ module Sinatra
|
|
1642
1638
|
end
|
1643
1639
|
end
|
1644
1640
|
end
|
1645
|
-
|
1641
|
+
alias agent user_agent
|
1646
1642
|
|
1647
1643
|
# Condition for matching mimetypes. Accepts file extensions.
|
1648
1644
|
def provides(*types)
|
1649
1645
|
types.map! { |t| mime_types(t) }
|
1650
1646
|
types.flatten!
|
1651
1647
|
condition do
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1648
|
+
response_content_type = response['Content-Type']
|
1649
|
+
preferred_type = request.preferred_type(types)
|
1650
|
+
|
1651
|
+
if response_content_type
|
1652
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1653
|
+
elsif preferred_type
|
1654
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1655
|
+
content_type(preferred_type, params)
|
1657
1656
|
true
|
1658
1657
|
else
|
1659
1658
|
false
|
@@ -1662,7 +1661,7 @@ module Sinatra
|
|
1662
1661
|
end
|
1663
1662
|
|
1664
1663
|
def route(verb, path, options = {}, &block)
|
1665
|
-
enable :empty_path_info if path ==
|
1664
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1666
1665
|
signature = compile!(verb, path, block, **options)
|
1667
1666
|
(@routes[verb] ||= []) << signature
|
1668
1667
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1691,12 +1690,13 @@ module Sinatra
|
|
1691
1690
|
pattern = compile(path, route_mustermann_opts)
|
1692
1691
|
method_name = "#{verb} #{path}"
|
1693
1692
|
unbound_method = generate_method(method_name, &block)
|
1694
|
-
conditions
|
1695
|
-
|
1696
|
-
|
1697
|
-
proc { |a,
|
1693
|
+
conditions = @conditions
|
1694
|
+
@conditions = []
|
1695
|
+
wrapper = block.arity.zero? ?
|
1696
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1697
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1698
1698
|
|
1699
|
-
[
|
1699
|
+
[pattern, conditions, wrapper]
|
1700
1700
|
end
|
1701
1701
|
|
1702
1702
|
def compile(path, route_mustermann_opts = {})
|
@@ -1714,7 +1714,7 @@ module Sinatra
|
|
1714
1714
|
end
|
1715
1715
|
|
1716
1716
|
def setup_middleware(builder)
|
1717
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1717
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1718
1718
|
end
|
1719
1719
|
|
1720
1720
|
def setup_logging(builder)
|
@@ -1744,9 +1744,10 @@ module Sinatra
|
|
1744
1744
|
|
1745
1745
|
def setup_protection(builder)
|
1746
1746
|
return unless protection?
|
1747
|
+
|
1747
1748
|
options = Hash === protection ? protection.dup : {}
|
1748
1749
|
options = {
|
1749
|
-
img_src:
|
1750
|
+
img_src: "'self' data:",
|
1750
1751
|
font_src: "'self'"
|
1751
1752
|
}.merge options
|
1752
1753
|
|
@@ -1760,6 +1761,7 @@ module Sinatra
|
|
1760
1761
|
|
1761
1762
|
def setup_sessions(builder)
|
1762
1763
|
return unless sessions?
|
1764
|
+
|
1763
1765
|
options = {}
|
1764
1766
|
options[:secret] = session_secret if session_secret?
|
1765
1767
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1782,15 +1784,15 @@ module Sinatra
|
|
1782
1784
|
end
|
1783
1785
|
|
1784
1786
|
# used for deprecation warnings
|
1785
|
-
def
|
1786
|
-
|
1787
|
+
def warn_for_deprecation(message)
|
1788
|
+
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1787
1789
|
end
|
1788
1790
|
|
1789
1791
|
# Like Kernel#caller but excluding certain magic entries
|
1790
1792
|
def cleaned_caller(keep = 3)
|
1791
|
-
caller(1)
|
1792
|
-
map!
|
1793
|
-
reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1793
|
+
caller(1)
|
1794
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1795
|
+
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1794
1796
|
end
|
1795
1797
|
end
|
1796
1798
|
|
@@ -1798,6 +1800,7 @@ module Sinatra
|
|
1798
1800
|
# which is UTF-8 by default
|
1799
1801
|
def self.force_encoding(data, encoding = default_encoding)
|
1800
1802
|
return if data == settings || data.is_a?(Tempfile)
|
1803
|
+
|
1801
1804
|
if data.respond_to? :force_encoding
|
1802
1805
|
data.force_encoding(encoding).encode!
|
1803
1806
|
elsif data.respond_to? :each_value
|
@@ -1808,24 +1811,26 @@ module Sinatra
|
|
1808
1811
|
data
|
1809
1812
|
end
|
1810
1813
|
|
1811
|
-
def force_encoding(*args)
|
1814
|
+
def force_encoding(*args)
|
1815
|
+
settings.force_encoding(*args)
|
1816
|
+
end
|
1812
1817
|
|
1813
1818
|
reset!
|
1814
1819
|
|
1815
1820
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1816
|
-
set :raise_errors,
|
1817
|
-
set :dump_errors,
|
1818
|
-
set :show_exceptions,
|
1821
|
+
set :raise_errors, proc { test? }
|
1822
|
+
set :dump_errors, proc { !test? }
|
1823
|
+
set :show_exceptions, proc { development? }
|
1819
1824
|
set :sessions, false
|
1820
|
-
set :session_store, Rack::
|
1825
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1821
1826
|
set :logging, false
|
1822
1827
|
set :protection, true
|
1823
1828
|
set :method_override, false
|
1824
1829
|
set :use_code, false
|
1825
|
-
set :default_encoding,
|
1830
|
+
set :default_encoding, 'utf-8'
|
1826
1831
|
set :x_cascade, true
|
1827
1832
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1828
|
-
settings.add_charset <<
|
1833
|
+
settings.add_charset << %r{^text/}
|
1829
1834
|
set :mustermann_opts, {}
|
1830
1835
|
set :default_content_type, 'text/html'
|
1831
1836
|
|
@@ -1835,12 +1840,12 @@ module Sinatra
|
|
1835
1840
|
set :session_secret, SecureRandom.hex(64)
|
1836
1841
|
rescue LoadError, NotImplementedError
|
1837
1842
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1838
|
-
set :session_secret,
|
1843
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1839
1844
|
end
|
1840
1845
|
|
1841
1846
|
class << self
|
1842
|
-
|
1843
|
-
|
1847
|
+
alias methodoverride? method_override?
|
1848
|
+
alias methodoverride= method_override=
|
1844
1849
|
end
|
1845
1850
|
|
1846
1851
|
set :run, false # start server via at-exit hook?
|
@@ -1848,21 +1853,17 @@ module Sinatra
|
|
1848
1853
|
set :handler_name, nil
|
1849
1854
|
set :traps, true
|
1850
1855
|
set :server, %w[HTTP webrick]
|
1851
|
-
set :bind,
|
1856
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1852
1857
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1853
1858
|
set :quiet, false
|
1854
1859
|
|
1855
1860
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1856
1861
|
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1863
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1864
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1865
|
-
end
|
1862
|
+
server.unshift 'puma'
|
1863
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1864
|
+
server.unshift 'mongrel' if ruby_engine.nil?
|
1865
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1866
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1866
1867
|
|
1867
1868
|
set :absolute_redirects, true
|
1868
1869
|
set :prefixed_redirects, false
|
@@ -1870,14 +1871,14 @@ module Sinatra
|
|
1870
1871
|
set :strict_paths, true
|
1871
1872
|
|
1872
1873
|
set :app_file, nil
|
1873
|
-
set :root,
|
1874
|
-
set :views,
|
1875
|
-
set :reload_templates,
|
1874
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1875
|
+
set :views, proc { root && File.join(root, 'views') }
|
1876
|
+
set :reload_templates, proc { development? }
|
1876
1877
|
set :lock, false
|
1877
1878
|
set :threaded, true
|
1878
1879
|
|
1879
|
-
set :public_folder,
|
1880
|
-
set :static,
|
1880
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1881
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1881
1882
|
set :static_cache_control, false
|
1882
1883
|
|
1883
1884
|
error ::Exception do
|
@@ -1896,7 +1897,7 @@ module Sinatra
|
|
1896
1897
|
error NotFound do
|
1897
1898
|
content_type 'text/html'
|
1898
1899
|
|
1899
|
-
if
|
1900
|
+
if instance_of?(Sinatra::Application)
|
1900
1901
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1901
1902
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1902
1903
|
"Hello World"
|
@@ -1911,11 +1912,11 @@ module Sinatra
|
|
1911
1912
|
end
|
1912
1913
|
RUBY
|
1913
1914
|
|
1914
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1915
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1915
1916
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1916
1917
|
end
|
1917
1918
|
|
1918
|
-
|
1919
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1919
1920
|
<!DOCTYPE html>
|
1920
1921
|
<html>
|
1921
1922
|
<head>
|
@@ -1927,7 +1928,7 @@ module Sinatra
|
|
1927
1928
|
</head>
|
1928
1929
|
<body>
|
1929
1930
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1930
|
-
<img src='#{uri
|
1931
|
+
<img src='#{uri '/__sinatra__/404.png'}'>
|
1931
1932
|
<div id="c">
|
1932
1933
|
Try this:
|
1933
1934
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1947,12 +1948,12 @@ module Sinatra
|
|
1947
1948
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1948
1949
|
# modular applications.
|
1949
1950
|
class Application < Base
|
1950
|
-
set :logging,
|
1951
|
+
set :logging, proc { !test? }
|
1951
1952
|
set :method_override, true
|
1952
|
-
set :run,
|
1953
|
+
set :run, proc { !test? }
|
1953
1954
|
set :app_file, nil
|
1954
1955
|
|
1955
|
-
def self.register(*extensions, &block)
|
1956
|
+
def self.register(*extensions, &block) # :nodoc:
|
1956
1957
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1957
1958
|
Delegator.delegate(*added_methods)
|
1958
1959
|
super(*extensions, &block)
|
@@ -1962,11 +1963,12 @@ module Sinatra
|
|
1962
1963
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1963
1964
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1964
1965
|
# at the top-level.
|
1965
|
-
module Delegator
|
1966
|
+
module Delegator # :nodoc:
|
1966
1967
|
def self.delegate(*methods)
|
1967
1968
|
methods.each do |method_name|
|
1968
1969
|
define_method(method_name) do |*args, &block|
|
1969
1970
|
return super(*args, &block) if respond_to? method_name
|
1971
|
+
|
1970
1972
|
Delegator.target.send(method_name, *args, &block)
|
1971
1973
|
end
|
1972
1974
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1989,7 +1991,8 @@ module Sinatra
|
|
1989
1991
|
|
1990
1992
|
class Wrapper
|
1991
1993
|
def initialize(stack, instance)
|
1992
|
-
@stack
|
1994
|
+
@stack = stack
|
1995
|
+
@instance = instance
|
1993
1996
|
end
|
1994
1997
|
|
1995
1998
|
def settings
|