sinatra 2.2.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -17
- data/Gemfile +41 -66
- data/README.md +102 -399
- data/Rakefile +67 -67
- 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 +335 -333
- 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 +35 -26
- 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'
|
@@ -385,23 +400,23 @@ module Sinatra
|
|
385
400
|
# instructing the user agents to prompt to save.
|
386
401
|
def attachment(filename = nil, disposition = :attachment)
|
387
402
|
response['Content-Disposition'] = disposition.to_s.dup
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
403
|
+
return unless filename
|
404
|
+
|
405
|
+
params = format('; filename="%s"', File.basename(filename))
|
406
|
+
response['Content-Disposition'] << params
|
407
|
+
ext = File.extname(filename)
|
408
|
+
content_type(ext) unless response['Content-Type'] || ext.empty?
|
394
409
|
end
|
395
410
|
|
396
411
|
# Use the contents of the file at +path+ as the response body.
|
397
412
|
def send_file(path, opts = {})
|
398
|
-
if opts[:type]
|
399
|
-
content_type opts[:type] || File.extname(path), :
|
413
|
+
if opts[:type] || !response['Content-Type']
|
414
|
+
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
400
415
|
end
|
401
416
|
|
402
417
|
disposition = opts[:disposition]
|
403
418
|
filename = opts[:filename]
|
404
|
-
disposition = :attachment if disposition.nil?
|
419
|
+
disposition = :attachment if disposition.nil? && filename
|
405
420
|
filename = path if filename.nil?
|
406
421
|
attachment(filename, disposition) if disposition
|
407
422
|
|
@@ -410,7 +425,7 @@ module Sinatra
|
|
410
425
|
file = Rack::File.new(File.dirname(settings.app_file))
|
411
426
|
result = file.serving(request, path)
|
412
427
|
|
413
|
-
result[1].each { |k,v| headers[k] ||= v }
|
428
|
+
result[1].each { |k, v| headers[k] ||= v }
|
414
429
|
headers['Content-Length'] = result[1]['Content-Length']
|
415
430
|
opts[:status] &&= Integer(opts[:status])
|
416
431
|
halt (opts[:status] || result[0]), result[2]
|
@@ -431,12 +446,16 @@ module Sinatra
|
|
431
446
|
def self.defer(*) yield end
|
432
447
|
|
433
448
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
434
|
-
@back
|
435
|
-
@
|
449
|
+
@back = back.to_proc
|
450
|
+
@scheduler = scheduler
|
451
|
+
@keep_open = keep_open
|
452
|
+
@callbacks = []
|
453
|
+
@closed = false
|
436
454
|
end
|
437
455
|
|
438
456
|
def close
|
439
457
|
return if closed?
|
458
|
+
|
440
459
|
@closed = true
|
441
460
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
442
461
|
end
|
@@ -460,6 +479,7 @@ module Sinatra
|
|
460
479
|
|
461
480
|
def callback(&block)
|
462
481
|
return yield if closed?
|
482
|
+
|
463
483
|
@callbacks << block
|
464
484
|
end
|
465
485
|
|
@@ -493,18 +513,18 @@ module Sinatra
|
|
493
513
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
494
514
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
495
515
|
def cache_control(*values)
|
496
|
-
if values.last.
|
516
|
+
if values.last.is_a?(Hash)
|
497
517
|
hash = values.pop
|
498
|
-
hash.reject! { |
|
518
|
+
hash.reject! { |_k, v| v == false }
|
499
519
|
hash.reject! { |k, v| values << k if v == true }
|
500
520
|
else
|
501
521
|
hash = {}
|
502
522
|
end
|
503
523
|
|
504
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
524
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
505
525
|
hash.each do |key, value|
|
506
526
|
key = key.to_s.tr('_', '-')
|
507
|
-
value = value.to_i if [
|
527
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
508
528
|
values << "#{key}=#{value}"
|
509
529
|
end
|
510
530
|
|
@@ -521,7 +541,7 @@ module Sinatra
|
|
521
541
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
522
542
|
#
|
523
543
|
def expires(amount, *values)
|
524
|
-
values << {} unless values.last.
|
544
|
+
values << {} unless values.last.is_a?(Hash)
|
525
545
|
|
526
546
|
if amount.is_a? Integer
|
527
547
|
time = Time.now + amount.to_i
|
@@ -531,7 +551,7 @@ module Sinatra
|
|
531
551
|
max_age = time - Time.now
|
532
552
|
end
|
533
553
|
|
534
|
-
values.last.merge!(:max_age
|
554
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
535
555
|
cache_control(*values)
|
536
556
|
|
537
557
|
response['Expires'] = time.httpdate
|
@@ -546,17 +566,18 @@ module Sinatra
|
|
546
566
|
# with a '304 Not Modified' response.
|
547
567
|
def last_modified(time)
|
548
568
|
return unless time
|
569
|
+
|
549
570
|
time = time_for time
|
550
571
|
response['Last-Modified'] = time.httpdate
|
551
572
|
return if env['HTTP_IF_NONE_MATCH']
|
552
573
|
|
553
|
-
if status == 200
|
574
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
554
575
|
# compare based on seconds since epoch
|
555
576
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
556
577
|
halt 304 if since >= time.to_i
|
557
578
|
end
|
558
579
|
|
559
|
-
if (success?
|
580
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
560
581
|
# compare based on seconds since epoch
|
561
582
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
562
583
|
halt 412 if since < time.to_i
|
@@ -564,7 +585,7 @@ module Sinatra
|
|
564
585
|
rescue ArgumentError
|
565
586
|
end
|
566
587
|
|
567
|
-
ETAG_KINDS = [
|
588
|
+
ETAG_KINDS = %i[strong weak].freeze
|
568
589
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
569
590
|
# GET matches. The +value+ argument is an identifier that uniquely
|
570
591
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -576,27 +597,31 @@ module Sinatra
|
|
576
597
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
577
598
|
def etag(value, options = {})
|
578
599
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
579
|
-
options = {:
|
600
|
+
options = { kind: options } unless Hash === options
|
580
601
|
kind = options[:kind] || :strong
|
581
602
|
new_resource = options.fetch(:new_resource) { request.post? }
|
582
603
|
|
583
604
|
unless ETAG_KINDS.include?(kind)
|
584
|
-
raise ArgumentError,
|
605
|
+
raise ArgumentError, ':strong or :weak expected'
|
585
606
|
end
|
586
607
|
|
587
|
-
value = '"%s"'
|
608
|
+
value = format('"%s"', value)
|
588
609
|
value = "W/#{value}" if kind == :weak
|
589
610
|
response['ETag'] = value
|
590
611
|
|
591
|
-
|
592
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
593
|
-
halt(request.safe? ? 304 : 412)
|
594
|
-
end
|
612
|
+
return unless success? || status == 304
|
595
613
|
|
596
|
-
|
597
|
-
|
598
|
-
end
|
614
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
615
|
+
halt(request.safe? ? 304 : 412)
|
599
616
|
end
|
617
|
+
|
618
|
+
if env['HTTP_IF_MATCH']
|
619
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
620
|
+
|
621
|
+
halt 412
|
622
|
+
end
|
623
|
+
|
624
|
+
nil
|
600
625
|
end
|
601
626
|
|
602
627
|
# Sugar for redirect (example: redirect back)
|
@@ -649,8 +674,8 @@ module Sinatra
|
|
649
674
|
else
|
650
675
|
value.to_time
|
651
676
|
end
|
652
|
-
rescue ArgumentError =>
|
653
|
-
raise
|
677
|
+
rescue ArgumentError => e
|
678
|
+
raise e
|
654
679
|
rescue Exception
|
655
680
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
656
681
|
end
|
@@ -660,11 +685,13 @@ module Sinatra
|
|
660
685
|
# Helper method checking if a ETag value list includes the current ETag.
|
661
686
|
def etag_matches?(list, new_resource = request.post?)
|
662
687
|
return !new_resource if list == '*'
|
688
|
+
|
663
689
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
664
690
|
end
|
665
691
|
|
666
692
|
def with_params(temp_params)
|
667
|
-
original
|
693
|
+
original = @params
|
694
|
+
@params = temp_params
|
668
695
|
yield
|
669
696
|
ensure
|
670
697
|
@params = original if original
|
@@ -682,7 +709,7 @@ module Sinatra
|
|
682
709
|
# Possible options are:
|
683
710
|
# :content_type The content type to use, same arguments as content_type.
|
684
711
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
685
|
-
# the specified layout is used
|
712
|
+
# the specified layout is used
|
686
713
|
# :layout_engine Engine to use for rendering the layout.
|
687
714
|
# :locals A hash with local variables that should be available
|
688
715
|
# in the template
|
@@ -704,36 +731,10 @@ module Sinatra
|
|
704
731
|
render(:erb, template, options, locals, &block)
|
705
732
|
end
|
706
733
|
|
707
|
-
def erubis(template, options = {}, locals = {})
|
708
|
-
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
709
|
-
"If you have Erubis installed, it will be used automatically."
|
710
|
-
render :erubis, template, options, locals
|
711
|
-
end
|
712
|
-
|
713
734
|
def haml(template, options = {}, locals = {}, &block)
|
714
735
|
render(:haml, template, options, locals, &block)
|
715
736
|
end
|
716
737
|
|
717
|
-
def sass(template, options = {}, locals = {})
|
718
|
-
options.merge! :layout => false, :default_content_type => :css
|
719
|
-
render :sass, template, options, locals
|
720
|
-
end
|
721
|
-
|
722
|
-
def scss(template, options = {}, locals = {})
|
723
|
-
options.merge! :layout => false, :default_content_type => :css
|
724
|
-
render :scss, template, options, locals
|
725
|
-
end
|
726
|
-
|
727
|
-
def less(template, options = {}, locals = {})
|
728
|
-
options.merge! :layout => false, :default_content_type => :css
|
729
|
-
render :less, template, options, locals
|
730
|
-
end
|
731
|
-
|
732
|
-
def stylus(template, options = {}, locals = {})
|
733
|
-
options.merge! :layout => false, :default_content_type => :css
|
734
|
-
render :styl, template, options, locals
|
735
|
-
end
|
736
|
-
|
737
738
|
def builder(template = nil, options = {}, locals = {}, &block)
|
738
739
|
options[:default_content_type] = :xml
|
739
740
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -748,10 +749,6 @@ module Sinatra
|
|
748
749
|
render :markdown, template, options, locals
|
749
750
|
end
|
750
751
|
|
751
|
-
def textile(template, options = {}, locals = {})
|
752
|
-
render :textile, template, options, locals
|
753
|
-
end
|
754
|
-
|
755
752
|
def rdoc(template, options = {}, locals = {})
|
756
753
|
render :rdoc, template, options, locals
|
757
754
|
end
|
@@ -760,19 +757,10 @@ module Sinatra
|
|
760
757
|
render :asciidoc, template, options, locals
|
761
758
|
end
|
762
759
|
|
763
|
-
def radius(template, options = {}, locals = {})
|
764
|
-
render :radius, template, options, locals
|
765
|
-
end
|
766
|
-
|
767
760
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
768
761
|
render_ruby(:mab, template, options, locals, &block)
|
769
762
|
end
|
770
763
|
|
771
|
-
def coffee(template, options = {}, locals = {})
|
772
|
-
options.merge! :layout => false, :default_content_type => :js
|
773
|
-
render :coffee, template, options, locals
|
774
|
-
end
|
775
|
-
|
776
764
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
777
765
|
options[:default_content_type] = :xml
|
778
766
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -782,18 +770,6 @@ module Sinatra
|
|
782
770
|
render(:slim, template, options, locals, &block)
|
783
771
|
end
|
784
772
|
|
785
|
-
def creole(template, options = {}, locals = {})
|
786
|
-
render :creole, template, options, locals
|
787
|
-
end
|
788
|
-
|
789
|
-
def mediawiki(template, options = {}, locals = {})
|
790
|
-
render :mediawiki, template, options, locals
|
791
|
-
end
|
792
|
-
|
793
|
-
def wlang(template, options = {}, locals = {}, &block)
|
794
|
-
render(:wlang, template, options, locals, &block)
|
795
|
-
end
|
796
|
-
|
797
773
|
def yajl(template, options = {}, locals = {})
|
798
774
|
options[:default_content_type] = :json
|
799
775
|
render :yajl, template, options, locals
|
@@ -818,24 +794,27 @@ module Sinatra
|
|
818
794
|
|
819
795
|
# logic shared between builder and nokogiri
|
820
796
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
821
|
-
|
822
|
-
|
797
|
+
if template.is_a?(Hash)
|
798
|
+
options = template
|
799
|
+
template = nil
|
800
|
+
end
|
801
|
+
template = proc { block } if template.nil?
|
823
802
|
render engine, template, options, locals
|
824
803
|
end
|
825
804
|
|
826
805
|
def render(engine, data, options = {}, locals = {}, &block)
|
827
806
|
# merge app-level options
|
828
807
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
829
|
-
options.merge!(engine_options) { |
|
808
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
830
809
|
|
831
810
|
# extract generic options
|
832
811
|
locals = options.delete(:locals) || locals || {}
|
833
|
-
views = options.delete(:views) || settings.views ||
|
812
|
+
views = options.delete(:views) || settings.views || './views'
|
834
813
|
layout = options[:layout]
|
835
814
|
layout = false if layout.nil? && options.include?(:layout)
|
836
815
|
eat_errors = layout.nil?
|
837
|
-
layout = engine_options[:layout] if layout.nil?
|
838
|
-
layout = @default_layout if layout.nil?
|
816
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
817
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
839
818
|
layout_options = options.delete(:layout_options) || {}
|
840
819
|
content_type = options.delete(:default_content_type)
|
841
820
|
content_type = options.delete(:content_type) || content_type
|
@@ -860,8 +839,9 @@ module Sinatra
|
|
860
839
|
|
861
840
|
# render layout
|
862
841
|
if layout
|
863
|
-
|
864
|
-
|
842
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
843
|
+
options = options.merge(extra_options).merge!(layout_options)
|
844
|
+
|
865
845
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
866
846
|
end
|
867
847
|
|
@@ -886,12 +866,13 @@ module Sinatra
|
|
886
866
|
@preferred_extension = engine.to_s
|
887
867
|
find_template(views, data, template) do |file|
|
888
868
|
path ||= file # keep the initial path rather than the last one
|
889
|
-
|
869
|
+
found = File.exist?(file)
|
870
|
+
if found
|
890
871
|
path = file
|
891
872
|
break
|
892
873
|
end
|
893
874
|
end
|
894
|
-
throw :layout_missing if eat_errors
|
875
|
+
throw :layout_missing if eat_errors && !found
|
895
876
|
template.new(path, 1, options)
|
896
877
|
end
|
897
878
|
end
|
@@ -907,9 +888,11 @@ module Sinatra
|
|
907
888
|
end
|
908
889
|
|
909
890
|
def compile_block_template(template, options, &body)
|
910
|
-
|
911
|
-
path =
|
912
|
-
line =
|
891
|
+
first_location = caller_locations.first
|
892
|
+
path = first_location.path
|
893
|
+
line = first_location.lineno
|
894
|
+
path = options[:path] || path
|
895
|
+
line = options[:line] || line
|
913
896
|
template.new(path, line.to_i, options, &body)
|
914
897
|
end
|
915
898
|
end
|
@@ -925,7 +908,7 @@ module Sinatra
|
|
925
908
|
attr_accessor :app, :env, :request, :response, :params
|
926
909
|
attr_reader :template_cache
|
927
910
|
|
928
|
-
def initialize(app = nil, **
|
911
|
+
def initialize(app = nil, **_kwargs)
|
929
912
|
super()
|
930
913
|
@app = app
|
931
914
|
@template_cache = Tilt::Cache.new
|
@@ -952,7 +935,7 @@ module Sinatra
|
|
952
935
|
unless @response['Content-Type']
|
953
936
|
if Array === body && body[0].respond_to?(:content_type)
|
954
937
|
content_type body[0].content_type
|
955
|
-
elsif default = settings.default_content_type
|
938
|
+
elsif (default = settings.default_content_type)
|
956
939
|
content_type default
|
957
940
|
end
|
958
941
|
end
|
@@ -970,12 +953,6 @@ module Sinatra
|
|
970
953
|
self.class.settings
|
971
954
|
end
|
972
955
|
|
973
|
-
def options
|
974
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
975
|
-
"use #settings instead."
|
976
|
-
settings
|
977
|
-
end
|
978
|
-
|
979
956
|
# Exit the current block, halts any further processing
|
980
957
|
# of the request, and returns the specified response.
|
981
958
|
def halt(*response)
|
@@ -992,7 +969,8 @@ module Sinatra
|
|
992
969
|
|
993
970
|
# Forward the request to the downstream app -- middleware only.
|
994
971
|
def forward
|
995
|
-
|
972
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
973
|
+
|
996
974
|
status, headers, body = @app.call env
|
997
975
|
@response.status = status
|
998
976
|
@response.body = body
|
@@ -1014,18 +992,18 @@ module Sinatra
|
|
1014
992
|
|
1015
993
|
# Run routes defined on the class and all superclasses.
|
1016
994
|
def route!(base = settings, pass_block = nil)
|
1017
|
-
|
1018
|
-
routes.each do |pattern, conditions, block|
|
1019
|
-
response.delete_header('Content-Type') unless @pinned_response
|
995
|
+
routes = base.routes[@request.request_method]
|
1020
996
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
route_eval { block[*args] }
|
1024
|
-
end
|
997
|
+
routes&.each do |pattern, conditions, block|
|
998
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1025
999
|
|
1026
|
-
|
1027
|
-
|
1000
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1001
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1002
|
+
route_eval { block[*args] }
|
1028
1003
|
end
|
1004
|
+
|
1005
|
+
# don't wipe out pass_block in superclass
|
1006
|
+
pass_block = returned_pass_block if returned_pass_block
|
1029
1007
|
end
|
1030
1008
|
|
1031
1009
|
# Run routes defined in superclass.
|
@@ -1049,15 +1027,17 @@ module Sinatra
|
|
1049
1027
|
# Returns pass block.
|
1050
1028
|
def process_route(pattern, conditions, block = nil, values = [])
|
1051
1029
|
route = @request.path_info
|
1052
|
-
route = '/' if route.empty?
|
1030
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1053
1031
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1054
|
-
return unless params = pattern.params(route)
|
1055
1032
|
|
1056
|
-
params.
|
1033
|
+
params = pattern.params(route)
|
1034
|
+
return unless params
|
1035
|
+
|
1036
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1057
1037
|
force_encoding(params)
|
1058
|
-
@params = @params.merge(params) if params.any?
|
1038
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1059
1039
|
|
1060
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1040
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1061
1041
|
if regexp_exists
|
1062
1042
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1063
1043
|
values += captures
|
@@ -1070,7 +1050,7 @@ module Sinatra
|
|
1070
1050
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1071
1051
|
block ? block[self, values] : yield(self, values)
|
1072
1052
|
end
|
1073
|
-
rescue
|
1053
|
+
rescue StandardError
|
1074
1054
|
@env['sinatra.error.params'] = @params
|
1075
1055
|
raise
|
1076
1056
|
ensure
|
@@ -1084,35 +1064,35 @@ module Sinatra
|
|
1084
1064
|
# a NotFound exception. Subclasses can override this method to perform
|
1085
1065
|
# custom route miss logic.
|
1086
1066
|
def route_missing
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1091
|
-
end
|
1067
|
+
raise NotFound unless @app
|
1068
|
+
|
1069
|
+
forward
|
1092
1070
|
end
|
1093
1071
|
|
1094
1072
|
# Attempt to serve static files from public directory. Throws :halt when
|
1095
1073
|
# a matching file is found, returns nil otherwise.
|
1096
1074
|
def static!(options = {})
|
1097
1075
|
return if (public_dir = settings.public_folder).nil?
|
1076
|
+
|
1098
1077
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1099
1078
|
return unless valid_path?(path)
|
1100
1079
|
|
1101
1080
|
path = File.expand_path(path)
|
1102
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1081
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1082
|
+
|
1103
1083
|
return unless File.file?(path)
|
1104
1084
|
|
1105
1085
|
env['sinatra.static_file'] = path
|
1106
1086
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1107
|
-
send_file path, options.merge(:
|
1087
|
+
send_file path, options.merge(disposition: nil)
|
1108
1088
|
end
|
1109
1089
|
|
1110
1090
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1111
|
-
def invoke
|
1112
|
-
res = catch(:halt)
|
1091
|
+
def invoke(&block)
|
1092
|
+
res = catch(:halt, &block)
|
1113
1093
|
|
1114
|
-
res = [res] if Integer === res
|
1115
|
-
if Array === res
|
1094
|
+
res = [res] if (Integer === res) || (String === res)
|
1095
|
+
if (Array === res) && (Integer === res.first)
|
1116
1096
|
res = res.dup
|
1117
1097
|
status(res.shift)
|
1118
1098
|
body(res.pop)
|
@@ -1128,6 +1108,7 @@ module Sinatra
|
|
1128
1108
|
# Avoid passing frozen string in force_encoding
|
1129
1109
|
@params.merge!(@request.params).each do |key, val|
|
1130
1110
|
next unless val.respond_to?(:force_encoding)
|
1111
|
+
|
1131
1112
|
val = val.dup if val.frozen?
|
1132
1113
|
@params[key] = force_encoding(val)
|
1133
1114
|
end
|
@@ -1139,39 +1120,43 @@ module Sinatra
|
|
1139
1120
|
end
|
1140
1121
|
route!
|
1141
1122
|
end
|
1142
|
-
rescue ::Exception =>
|
1143
|
-
invoke { handle_exception!(
|
1123
|
+
rescue ::Exception => e
|
1124
|
+
invoke { handle_exception!(e) }
|
1144
1125
|
ensure
|
1145
1126
|
begin
|
1146
1127
|
filter! :after unless env['sinatra.static_file']
|
1147
|
-
rescue ::Exception =>
|
1148
|
-
invoke { handle_exception!(
|
1128
|
+
rescue ::Exception => e
|
1129
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1149
1130
|
end
|
1150
1131
|
end
|
1151
1132
|
|
1152
1133
|
# Error handling during requests.
|
1153
1134
|
def handle_exception!(boom)
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1135
|
+
error_params = @env['sinatra.error.params']
|
1136
|
+
|
1137
|
+
@params = @params.merge(error_params) if error_params
|
1138
|
+
|
1157
1139
|
@env['sinatra.error'] = boom
|
1158
1140
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1141
|
+
http_status = if boom.is_a? Sinatra::Error
|
1142
|
+
if boom.respond_to? :http_status
|
1143
|
+
boom.http_status
|
1144
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1145
|
+
boom.code
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1150
|
+
status(http_status)
|
1166
1151
|
|
1167
1152
|
if server_error?
|
1168
1153
|
dump_errors! boom if settings.dump_errors?
|
1169
|
-
raise boom if settings.show_exceptions?
|
1154
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1170
1155
|
elsif not_found?
|
1171
1156
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1172
1157
|
end
|
1173
1158
|
|
1174
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1159
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1175
1160
|
return res
|
1176
1161
|
end
|
1177
1162
|
|
@@ -1180,12 +1165,14 @@ module Sinatra
|
|
1180
1165
|
body Rack::Utils.escape_html(boom.message)
|
1181
1166
|
else
|
1182
1167
|
content_type 'text/html'
|
1183
|
-
body
|
1168
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1184
1169
|
end
|
1185
1170
|
end
|
1186
1171
|
|
1187
1172
|
return unless server_error?
|
1188
|
-
|
1173
|
+
|
1174
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1175
|
+
|
1189
1176
|
error_block! Exception, boom
|
1190
1177
|
end
|
1191
1178
|
|
@@ -1193,7 +1180,10 @@ module Sinatra
|
|
1193
1180
|
def error_block!(key, *block_params)
|
1194
1181
|
base = settings
|
1195
1182
|
while base.respond_to?(:errors)
|
1196
|
-
|
1183
|
+
args_array = base.errors[key]
|
1184
|
+
|
1185
|
+
next base = base.superclass unless args_array
|
1186
|
+
|
1197
1187
|
args_array.reverse_each do |args|
|
1198
1188
|
first = args == args_array.first
|
1199
1189
|
args += [block_params]
|
@@ -1201,32 +1191,26 @@ module Sinatra
|
|
1201
1191
|
return resp unless resp.nil? && !first
|
1202
1192
|
end
|
1203
1193
|
end
|
1204
|
-
return false unless key.respond_to?
|
1194
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1195
|
+
|
1205
1196
|
error_block!(key.superclass, *block_params)
|
1206
1197
|
end
|
1207
1198
|
|
1208
1199
|
def dump_errors!(boom)
|
1209
|
-
msg = ["#{Time.now.strftime(
|
1200
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
1210
1201
|
@env['rack.errors'].puts(msg)
|
1211
1202
|
end
|
1212
1203
|
|
1213
1204
|
class << self
|
1214
1205
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1215
|
-
|
1216
|
-
/
|
1206
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1207
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1217
1208
|
/^\(.*\)$/, # generated code
|
1218
|
-
/
|
1209
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1219
1210
|
/active_support/, # active_support require hacks
|
1220
|
-
|
1221
|
-
/<internal
|
1222
|
-
|
1223
|
-
]
|
1224
|
-
|
1225
|
-
# contrary to what the comment said previously, rubinius never supported this
|
1226
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1227
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1228
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1229
|
-
end
|
1211
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1212
|
+
/<internal:/ # internal in ruby >= 1.9.2
|
1213
|
+
].freeze
|
1230
1214
|
|
1231
1215
|
attr_reader :routes, :filters, :templates, :errors
|
1232
1216
|
|
@@ -1235,17 +1219,17 @@ module Sinatra
|
|
1235
1219
|
def reset!
|
1236
1220
|
@conditions = []
|
1237
1221
|
@routes = {}
|
1238
|
-
@filters = {:
|
1222
|
+
@filters = { before: [], after: [] }
|
1239
1223
|
@errors = {}
|
1240
1224
|
@middleware = []
|
1241
1225
|
@prototype = nil
|
1242
1226
|
@extensions = []
|
1243
1227
|
|
1244
|
-
if superclass.respond_to?(:templates)
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1228
|
+
@templates = if superclass.respond_to?(:templates)
|
1229
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1230
|
+
else
|
1231
|
+
{}
|
1232
|
+
end
|
1249
1233
|
end
|
1250
1234
|
|
1251
1235
|
# Extension modules registered on this class and all superclasses.
|
@@ -1269,16 +1253,21 @@ module Sinatra
|
|
1269
1253
|
# Sets an option to the given value. If the value is a proc,
|
1270
1254
|
# the proc will be called every time the option is accessed.
|
1271
1255
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1272
|
-
raise ArgumentError if block
|
1273
|
-
|
1256
|
+
raise ArgumentError if block && !not_set
|
1257
|
+
|
1258
|
+
if block
|
1259
|
+
value = block
|
1260
|
+
not_set = false
|
1261
|
+
end
|
1274
1262
|
|
1275
1263
|
if not_set
|
1276
1264
|
raise ArgumentError unless option.respond_to?(:each)
|
1277
|
-
|
1265
|
+
|
1266
|
+
option.each { |k, v| set(k, v) }
|
1278
1267
|
return self
|
1279
1268
|
end
|
1280
1269
|
|
1281
|
-
if respond_to?("#{option}=")
|
1270
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1282
1271
|
return __send__("#{option}=", value)
|
1283
1272
|
end
|
1284
1273
|
|
@@ -1317,7 +1306,7 @@ module Sinatra
|
|
1317
1306
|
# class, or an HTTP status code to specify which errors should be
|
1318
1307
|
# handled.
|
1319
1308
|
def error(*codes, &block)
|
1320
|
-
args = compile!
|
1309
|
+
args = compile! 'ERROR', /.*/, block
|
1321
1310
|
codes = codes.flat_map(&method(:Array))
|
1322
1311
|
codes << Exception if codes.empty?
|
1323
1312
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1343,7 +1332,7 @@ module Sinatra
|
|
1343
1332
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1344
1333
|
# when no file is specified.
|
1345
1334
|
def inline_templates=(file = nil)
|
1346
|
-
file = (
|
1335
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1347
1336
|
|
1348
1337
|
begin
|
1349
1338
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1352,23 +1341,24 @@ module Sinatra
|
|
1352
1341
|
app, data = nil
|
1353
1342
|
end
|
1354
1343
|
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1344
|
+
return unless data
|
1345
|
+
|
1346
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1347
|
+
$2
|
1348
|
+
else
|
1349
|
+
settings.default_encoding
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
lines = app.count("\n") + 1
|
1353
|
+
template = nil
|
1354
|
+
force_encoding data, encoding
|
1355
|
+
data.each_line do |line|
|
1356
|
+
lines += 1
|
1357
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1358
|
+
template = force_encoding(String.new, encoding)
|
1359
|
+
templates[$1.to_sym] = [template, file, lines]
|
1360
|
+
elsif template
|
1361
|
+
template << line
|
1372
1362
|
end
|
1373
1363
|
end
|
1374
1364
|
end
|
@@ -1377,8 +1367,10 @@ module Sinatra
|
|
1377
1367
|
def mime_type(type, value = nil)
|
1378
1368
|
return type if type.nil?
|
1379
1369
|
return type.to_s if type.to_s.include?('/')
|
1380
|
-
|
1370
|
+
|
1371
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1381
1372
|
return Rack::Mime.mime_type(type, nil) unless value
|
1373
|
+
|
1382
1374
|
Rack::Mime::MIME_TYPES[type] = value
|
1383
1375
|
end
|
1384
1376
|
|
@@ -1387,7 +1379,7 @@ module Sinatra
|
|
1387
1379
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1388
1380
|
def mime_types(type)
|
1389
1381
|
type = mime_type type
|
1390
|
-
type =~
|
1382
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1391
1383
|
end
|
1392
1384
|
|
1393
1385
|
# Define a before filter; runs before all requests within the same
|
@@ -1416,7 +1408,7 @@ module Sinatra
|
|
1416
1408
|
end
|
1417
1409
|
|
1418
1410
|
def public=(value)
|
1419
|
-
warn
|
1411
|
+
warn ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1420
1412
|
set(:public_folder, value)
|
1421
1413
|
end
|
1422
1414
|
|
@@ -1438,14 +1430,21 @@ module Sinatra
|
|
1438
1430
|
route('HEAD', path, opts, &block)
|
1439
1431
|
end
|
1440
1432
|
|
1441
|
-
def put(path, opts = {}, &
|
1442
|
-
|
1443
|
-
def
|
1444
|
-
|
1445
|
-
def
|
1446
|
-
|
1447
|
-
def
|
1448
|
-
|
1433
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1434
|
+
|
1435
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1436
|
+
|
1437
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1438
|
+
|
1439
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1440
|
+
|
1441
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1442
|
+
|
1443
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1444
|
+
|
1445
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1446
|
+
|
1447
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1449
1448
|
|
1450
1449
|
# Makes the methods defined in the block and in the Modules given
|
1451
1450
|
# in `extensions` available to the handlers and templates
|
@@ -1485,37 +1484,39 @@ module Sinatra
|
|
1485
1484
|
# Stop the self-hosted server if running.
|
1486
1485
|
def quit!
|
1487
1486
|
return unless running?
|
1487
|
+
|
1488
1488
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1489
1489
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1490
|
-
|
1490
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1491
1491
|
set :running_server, nil
|
1492
1492
|
set :handler_name, nil
|
1493
1493
|
end
|
1494
1494
|
|
1495
|
-
|
1495
|
+
alias stop! quit!
|
1496
1496
|
|
1497
1497
|
# Run the Sinatra app as a self-hosted server using
|
1498
|
-
# Puma, Mongrel, or WEBrick (in that order). If given a block, will call
|
1498
|
+
# Puma, Falcon, Mongrel, or WEBrick (in that order). If given a block, will call
|
1499
1499
|
# with the constructed handler once we have taken the stage.
|
1500
1500
|
def run!(options = {}, &block)
|
1501
1501
|
return if running?
|
1502
|
+
|
1502
1503
|
set options
|
1503
1504
|
handler = Rack::Handler.pick(server)
|
1504
1505
|
handler_name = handler.name.gsub(/.*::/, '')
|
1505
1506
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1506
|
-
server_settings.merge!(:
|
1507
|
+
server_settings.merge!(Port: port, Host: bind)
|
1507
1508
|
|
1508
1509
|
begin
|
1509
1510
|
start_server(handler, server_settings, handler_name, &block)
|
1510
1511
|
rescue Errno::EADDRINUSE
|
1511
|
-
|
1512
|
+
warn "== Someone is already performing on port #{port}!"
|
1512
1513
|
raise
|
1513
1514
|
ensure
|
1514
1515
|
quit!
|
1515
1516
|
end
|
1516
1517
|
end
|
1517
1518
|
|
1518
|
-
|
1519
|
+
alias start! run!
|
1519
1520
|
|
1520
1521
|
# Check whether the self-hosted server is running or not.
|
1521
1522
|
def running?
|
@@ -1533,8 +1534,8 @@ module Sinatra
|
|
1533
1534
|
# Create a new instance of the class fronted by its middleware
|
1534
1535
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1535
1536
|
# an instance of the class new was called on.
|
1536
|
-
def new(*args, &
|
1537
|
-
instance = new!(*args, &
|
1537
|
+
def new(*args, &block)
|
1538
|
+
instance = new!(*args, &block)
|
1538
1539
|
Wrapper.new(build(instance).to_app, instance)
|
1539
1540
|
end
|
1540
1541
|
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
@@ -1559,12 +1560,6 @@ module Sinatra
|
|
1559
1560
|
cleaned_caller(1).flatten
|
1560
1561
|
end
|
1561
1562
|
|
1562
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1563
|
-
# first element being the file, and the second being the line.
|
1564
|
-
def caller_locations
|
1565
|
-
cleaned_caller 2
|
1566
|
-
end
|
1567
|
-
|
1568
1563
|
private
|
1569
1564
|
|
1570
1565
|
# Starts the server by running the Rack Handler.
|
@@ -1575,7 +1570,7 @@ module Sinatra
|
|
1575
1570
|
# Run the instance we created:
|
1576
1571
|
handler.run(self, **server_settings) do |server|
|
1577
1572
|
unless suppress_messages?
|
1578
|
-
|
1573
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1579
1574
|
end
|
1580
1575
|
|
1581
1576
|
setup_traps
|
@@ -1592,18 +1587,18 @@ module Sinatra
|
|
1592
1587
|
end
|
1593
1588
|
|
1594
1589
|
def setup_traps
|
1595
|
-
|
1596
|
-
at_exit { quit! }
|
1590
|
+
return unless traps?
|
1597
1591
|
|
1598
|
-
|
1599
|
-
old_handler = trap(signal) do
|
1600
|
-
quit!
|
1601
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1602
|
-
end
|
1603
|
-
end
|
1592
|
+
at_exit { quit! }
|
1604
1593
|
|
1605
|
-
|
1594
|
+
%i[INT TERM].each do |signal|
|
1595
|
+
old_handler = trap(signal) do
|
1596
|
+
quit!
|
1597
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1598
|
+
end
|
1606
1599
|
end
|
1600
|
+
|
1601
|
+
set :traps, false
|
1607
1602
|
end
|
1608
1603
|
|
1609
1604
|
# Dynamically defines a method on settings.
|
@@ -1631,18 +1626,21 @@ module Sinatra
|
|
1631
1626
|
end
|
1632
1627
|
end
|
1633
1628
|
end
|
1634
|
-
|
1629
|
+
alias agent user_agent
|
1635
1630
|
|
1636
1631
|
# Condition for matching mimetypes. Accepts file extensions.
|
1637
1632
|
def provides(*types)
|
1638
1633
|
types.map! { |t| mime_types(t) }
|
1639
1634
|
types.flatten!
|
1640
1635
|
condition do
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1636
|
+
response_content_type = response['Content-Type']
|
1637
|
+
preferred_type = request.preferred_type(types)
|
1638
|
+
|
1639
|
+
if response_content_type
|
1640
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1641
|
+
elsif preferred_type
|
1642
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1643
|
+
content_type(preferred_type, params)
|
1646
1644
|
true
|
1647
1645
|
else
|
1648
1646
|
false
|
@@ -1651,7 +1649,7 @@ module Sinatra
|
|
1651
1649
|
end
|
1652
1650
|
|
1653
1651
|
def route(verb, path, options = {}, &block)
|
1654
|
-
enable :empty_path_info if path ==
|
1652
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1655
1653
|
signature = compile!(verb, path, block, **options)
|
1656
1654
|
(@routes[verb] ||= []) << signature
|
1657
1655
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1680,12 +1678,13 @@ module Sinatra
|
|
1680
1678
|
pattern = compile(path, route_mustermann_opts)
|
1681
1679
|
method_name = "#{verb} #{path}"
|
1682
1680
|
unbound_method = generate_method(method_name, &block)
|
1683
|
-
conditions
|
1684
|
-
|
1685
|
-
|
1686
|
-
proc { |a,
|
1681
|
+
conditions = @conditions
|
1682
|
+
@conditions = []
|
1683
|
+
wrapper = block.arity.zero? ?
|
1684
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1685
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1687
1686
|
|
1688
|
-
[
|
1687
|
+
[pattern, conditions, wrapper]
|
1689
1688
|
end
|
1690
1689
|
|
1691
1690
|
def compile(path, route_mustermann_opts = {})
|
@@ -1703,7 +1702,7 @@ module Sinatra
|
|
1703
1702
|
end
|
1704
1703
|
|
1705
1704
|
def setup_middleware(builder)
|
1706
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1705
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1707
1706
|
end
|
1708
1707
|
|
1709
1708
|
def setup_logging(builder)
|
@@ -1733,9 +1732,10 @@ module Sinatra
|
|
1733
1732
|
|
1734
1733
|
def setup_protection(builder)
|
1735
1734
|
return unless protection?
|
1735
|
+
|
1736
1736
|
options = Hash === protection ? protection.dup : {}
|
1737
1737
|
options = {
|
1738
|
-
img_src:
|
1738
|
+
img_src: "'self' data:",
|
1739
1739
|
font_src: "'self'"
|
1740
1740
|
}.merge options
|
1741
1741
|
|
@@ -1749,6 +1749,7 @@ module Sinatra
|
|
1749
1749
|
|
1750
1750
|
def setup_sessions(builder)
|
1751
1751
|
return unless sessions?
|
1752
|
+
|
1752
1753
|
options = {}
|
1753
1754
|
options[:secret] = session_secret if session_secret?
|
1754
1755
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1777,9 +1778,9 @@ module Sinatra
|
|
1777
1778
|
|
1778
1779
|
# Like Kernel#caller but excluding certain magic entries
|
1779
1780
|
def cleaned_caller(keep = 3)
|
1780
|
-
caller(1)
|
1781
|
-
map!
|
1782
|
-
reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
1781
|
+
caller(1)
|
1782
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1783
|
+
.reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
1783
1784
|
end
|
1784
1785
|
end
|
1785
1786
|
|
@@ -1787,6 +1788,7 @@ module Sinatra
|
|
1787
1788
|
# which is UTF-8 by default
|
1788
1789
|
def self.force_encoding(data, encoding = default_encoding)
|
1789
1790
|
return if data == settings || data.is_a?(Tempfile)
|
1791
|
+
|
1790
1792
|
if data.respond_to? :force_encoding
|
1791
1793
|
data.force_encoding(encoding).encode!
|
1792
1794
|
elsif data.respond_to? :each_value
|
@@ -1797,24 +1799,26 @@ module Sinatra
|
|
1797
1799
|
data
|
1798
1800
|
end
|
1799
1801
|
|
1800
|
-
def force_encoding(*args)
|
1802
|
+
def force_encoding(*args)
|
1803
|
+
settings.force_encoding(*args)
|
1804
|
+
end
|
1801
1805
|
|
1802
1806
|
reset!
|
1803
1807
|
|
1804
1808
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1805
|
-
set :raise_errors,
|
1806
|
-
set :dump_errors,
|
1807
|
-
set :show_exceptions,
|
1809
|
+
set :raise_errors, proc { test? }
|
1810
|
+
set :dump_errors, proc { !test? }
|
1811
|
+
set :show_exceptions, proc { development? }
|
1808
1812
|
set :sessions, false
|
1809
|
-
set :session_store, Rack::
|
1813
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1810
1814
|
set :logging, false
|
1811
1815
|
set :protection, true
|
1812
1816
|
set :method_override, false
|
1813
1817
|
set :use_code, false
|
1814
|
-
set :default_encoding,
|
1818
|
+
set :default_encoding, 'utf-8'
|
1815
1819
|
set :x_cascade, true
|
1816
1820
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1817
|
-
settings.add_charset <<
|
1821
|
+
settings.add_charset << %r{^text/}
|
1818
1822
|
set :mustermann_opts, {}
|
1819
1823
|
set :default_content_type, 'text/html'
|
1820
1824
|
|
@@ -1824,12 +1828,12 @@ module Sinatra
|
|
1824
1828
|
set :session_secret, SecureRandom.hex(64)
|
1825
1829
|
rescue LoadError, NotImplementedError
|
1826
1830
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1827
|
-
set :session_secret,
|
1831
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1828
1832
|
end
|
1829
1833
|
|
1830
1834
|
class << self
|
1831
|
-
|
1832
|
-
|
1835
|
+
alias methodoverride? method_override?
|
1836
|
+
alias methodoverride= method_override=
|
1833
1837
|
end
|
1834
1838
|
|
1835
1839
|
set :run, false # start server via at-exit hook?
|
@@ -1837,21 +1841,17 @@ module Sinatra
|
|
1837
1841
|
set :handler_name, nil
|
1838
1842
|
set :traps, true
|
1839
1843
|
set :server, %w[HTTP webrick]
|
1840
|
-
set :bind,
|
1844
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1841
1845
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1842
1846
|
set :quiet, false
|
1843
1847
|
|
1844
1848
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1845
1849
|
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1852
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1853
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1854
|
-
end
|
1850
|
+
server.unshift 'puma'
|
1851
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1852
|
+
server.unshift 'mongrel' if ruby_engine.nil?
|
1853
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1854
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1855
1855
|
|
1856
1856
|
set :absolute_redirects, true
|
1857
1857
|
set :prefixed_redirects, false
|
@@ -1859,14 +1859,14 @@ module Sinatra
|
|
1859
1859
|
set :strict_paths, true
|
1860
1860
|
|
1861
1861
|
set :app_file, nil
|
1862
|
-
set :root,
|
1863
|
-
set :views,
|
1864
|
-
set :reload_templates,
|
1862
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1863
|
+
set :views, proc { root && File.join(root, 'views') }
|
1864
|
+
set :reload_templates, proc { development? }
|
1865
1865
|
set :lock, false
|
1866
1866
|
set :threaded, true
|
1867
1867
|
|
1868
|
-
set :public_folder,
|
1869
|
-
set :static,
|
1868
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1869
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1870
1870
|
set :static_cache_control, false
|
1871
1871
|
|
1872
1872
|
error ::Exception do
|
@@ -1885,7 +1885,7 @@ module Sinatra
|
|
1885
1885
|
error NotFound do
|
1886
1886
|
content_type 'text/html'
|
1887
1887
|
|
1888
|
-
if
|
1888
|
+
if instance_of?(Sinatra::Application)
|
1889
1889
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1890
1890
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1891
1891
|
"Hello World"
|
@@ -1900,11 +1900,11 @@ module Sinatra
|
|
1900
1900
|
end
|
1901
1901
|
RUBY
|
1902
1902
|
|
1903
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1903
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1904
1904
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1905
1905
|
end
|
1906
1906
|
|
1907
|
-
|
1907
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1908
1908
|
<!DOCTYPE html>
|
1909
1909
|
<html>
|
1910
1910
|
<head>
|
@@ -1916,7 +1916,7 @@ module Sinatra
|
|
1916
1916
|
</head>
|
1917
1917
|
<body>
|
1918
1918
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1919
|
-
<img src='#{uri
|
1919
|
+
<img src='#{uri '/__sinatra__/404.png'}'>
|
1920
1920
|
<div id="c">
|
1921
1921
|
Try this:
|
1922
1922
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1936,12 +1936,12 @@ module Sinatra
|
|
1936
1936
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1937
1937
|
# modular applications.
|
1938
1938
|
class Application < Base
|
1939
|
-
set :logging,
|
1939
|
+
set :logging, proc { !test? }
|
1940
1940
|
set :method_override, true
|
1941
|
-
set :run,
|
1941
|
+
set :run, proc { !test? }
|
1942
1942
|
set :app_file, nil
|
1943
1943
|
|
1944
|
-
def self.register(*extensions, &block)
|
1944
|
+
def self.register(*extensions, &block) # :nodoc:
|
1945
1945
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1946
1946
|
Delegator.delegate(*added_methods)
|
1947
1947
|
super(*extensions, &block)
|
@@ -1951,11 +1951,12 @@ module Sinatra
|
|
1951
1951
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1952
1952
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1953
1953
|
# at the top-level.
|
1954
|
-
module Delegator
|
1954
|
+
module Delegator # :nodoc:
|
1955
1955
|
def self.delegate(*methods)
|
1956
1956
|
methods.each do |method_name|
|
1957
1957
|
define_method(method_name) do |*args, &block|
|
1958
1958
|
return super(*args, &block) if respond_to? method_name
|
1959
|
+
|
1959
1960
|
Delegator.target.send(method_name, *args, &block)
|
1960
1961
|
end
|
1961
1962
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1978,7 +1979,8 @@ module Sinatra
|
|
1978
1979
|
|
1979
1980
|
class Wrapper
|
1980
1981
|
def initialize(stack, instance)
|
1981
|
-
@stack
|
1982
|
+
@stack = stack
|
1983
|
+
@instance = instance
|
1982
1984
|
end
|
1983
1985
|
|
1984
1986
|
def settings
|