sinatra 2.2.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/AUTHORS.md +14 -8
- data/CHANGELOG.md +157 -16
- data/CONTRIBUTING.md +11 -11
- data/Gemfile +52 -61
- data/MAINTENANCE.md +2 -2
- data/README.md +204 -487
- data/Rakefile +82 -79
- data/SECURITY.md +1 -1
- data/VERSION +1 -1
- data/examples/chat.rb +25 -12
- data/examples/lifecycle_events.rb +20 -0
- data/examples/simple.rb +2 -0
- data/examples/stream.ru +2 -2
- data/lib/sinatra/base.rb +435 -341
- data/lib/sinatra/indifferent_hash.rb +35 -35
- 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 +36 -33
- metadata +23 -29
- 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/examples/rainbows.conf +0 -3
- data/examples/rainbows.rb +0 -20
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
|
|
@@ -21,21 +19,22 @@ require 'sinatra/version'
|
|
21
19
|
|
22
20
|
module Sinatra
|
23
21
|
# The request object. See Rack::Request for more info:
|
24
|
-
#
|
22
|
+
# https://rubydoc.info/github/rack/rack/main/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,17 +151,17 @@ 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
|
157
158
|
|
158
159
|
# The response object. See Rack::Response and Rack::Response::Helpers for
|
159
160
|
# more info:
|
160
|
-
#
|
161
|
-
#
|
161
|
+
# https://rubydoc.info/github/rack/rack/main/Rack/Response
|
162
|
+
# https://rubydoc.info/github/rack/rack/main/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?
|
@@ -208,15 +209,17 @@ module Sinatra
|
|
208
209
|
end
|
209
210
|
end
|
210
211
|
|
211
|
-
# Some Rack handlers
|
212
|
+
# Some Rack handlers implement an extended body object protocol, however,
|
212
213
|
# some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
|
213
214
|
# This middleware will detect an extended body object and will make sure it reaches the
|
214
215
|
# handler directly. We do this here, so our middleware and middleware set up by the app will
|
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
|
|
@@ -282,7 +291,7 @@ module Sinatra
|
|
282
291
|
elsif value
|
283
292
|
# Rack 2.0 returns a Rack::File::Iterator here instead of
|
284
293
|
# Rack::File as it was in the previous API.
|
285
|
-
unless request.head? || value.is_a?(Rack::
|
294
|
+
unless request.head? || value.is_a?(Rack::Files::Iterator) || value.is_a?(Stream)
|
286
295
|
headers.delete 'Content-Length'
|
287
296
|
end
|
288
297
|
response.body = value
|
@@ -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.to_s =~ /\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'
|
@@ -381,36 +396,43 @@ module Sinatra
|
|
381
396
|
response['Content-Type'] = mime_type
|
382
397
|
end
|
383
398
|
|
399
|
+
# https://html.spec.whatwg.org/#multipart-form-data
|
400
|
+
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
401
|
+
'"' => '%22',
|
402
|
+
"\r" => '%0D',
|
403
|
+
"\n" => '%0A'
|
404
|
+
}.freeze
|
405
|
+
|
384
406
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
385
407
|
# instructing the user agents to prompt to save.
|
386
408
|
def attachment(filename = nil, disposition = :attachment)
|
387
409
|
response['Content-Disposition'] = disposition.to_s.dup
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
410
|
+
return unless filename
|
411
|
+
|
412
|
+
params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
|
413
|
+
response['Content-Disposition'] << params
|
414
|
+
ext = File.extname(filename)
|
415
|
+
content_type(ext) unless response['Content-Type'] || ext.empty?
|
394
416
|
end
|
395
417
|
|
396
418
|
# Use the contents of the file at +path+ as the response body.
|
397
419
|
def send_file(path, opts = {})
|
398
|
-
if opts[:type]
|
399
|
-
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'
|
400
422
|
end
|
401
423
|
|
402
424
|
disposition = opts[:disposition]
|
403
425
|
filename = opts[:filename]
|
404
|
-
disposition = :attachment if disposition.nil?
|
426
|
+
disposition = :attachment if disposition.nil? && filename
|
405
427
|
filename = path if filename.nil?
|
406
428
|
attachment(filename, disposition) if disposition
|
407
429
|
|
408
430
|
last_modified opts[:last_modified] if opts[:last_modified]
|
409
431
|
|
410
|
-
file = Rack::
|
432
|
+
file = Rack::Files.new(File.dirname(settings.app_file))
|
411
433
|
result = file.serving(request, path)
|
412
434
|
|
413
|
-
result[1].each { |k,v| headers[k] ||= v }
|
435
|
+
result[1].each { |k, v| headers[k] ||= v }
|
414
436
|
headers['Content-Length'] = result[1]['Content-Length']
|
415
437
|
opts[:status] &&= Integer(opts[:status])
|
416
438
|
halt (opts[:status] || result[0]), result[2]
|
@@ -431,12 +453,16 @@ module Sinatra
|
|
431
453
|
def self.defer(*) yield end
|
432
454
|
|
433
455
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
434
|
-
@back
|
435
|
-
@
|
456
|
+
@back = back.to_proc
|
457
|
+
@scheduler = scheduler
|
458
|
+
@keep_open = keep_open
|
459
|
+
@callbacks = []
|
460
|
+
@closed = false
|
436
461
|
end
|
437
462
|
|
438
463
|
def close
|
439
464
|
return if closed?
|
465
|
+
|
440
466
|
@closed = true
|
441
467
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
442
468
|
end
|
@@ -448,8 +474,9 @@ module Sinatra
|
|
448
474
|
@back.call(self)
|
449
475
|
rescue Exception => e
|
450
476
|
@scheduler.schedule { raise e }
|
477
|
+
ensure
|
478
|
+
close unless @keep_open
|
451
479
|
end
|
452
|
-
close unless @keep_open
|
453
480
|
end
|
454
481
|
end
|
455
482
|
|
@@ -460,6 +487,7 @@ module Sinatra
|
|
460
487
|
|
461
488
|
def callback(&block)
|
462
489
|
return yield if closed?
|
490
|
+
|
463
491
|
@callbacks << block
|
464
492
|
end
|
465
493
|
|
@@ -474,12 +502,20 @@ module Sinatra
|
|
474
502
|
# the response body have not yet been generated.
|
475
503
|
#
|
476
504
|
# The close parameter specifies whether Stream#close should be called
|
477
|
-
# after the block has been executed.
|
478
|
-
# servers like Rainbows.
|
505
|
+
# after the block has been executed.
|
479
506
|
def stream(keep_open = false)
|
480
507
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
481
508
|
current = @params.dup
|
482
|
-
|
509
|
+
stream = if scheduler == Stream && keep_open
|
510
|
+
Stream.new(scheduler, false) do |out|
|
511
|
+
until out.closed?
|
512
|
+
with_params(current) { yield(out) }
|
513
|
+
end
|
514
|
+
end
|
515
|
+
else
|
516
|
+
Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
|
517
|
+
end
|
518
|
+
body stream
|
483
519
|
end
|
484
520
|
|
485
521
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
@@ -493,18 +529,18 @@ module Sinatra
|
|
493
529
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
494
530
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
495
531
|
def cache_control(*values)
|
496
|
-
if values.last.
|
532
|
+
if values.last.is_a?(Hash)
|
497
533
|
hash = values.pop
|
498
|
-
hash.reject! { |
|
534
|
+
hash.reject! { |_k, v| v == false }
|
499
535
|
hash.reject! { |k, v| values << k if v == true }
|
500
536
|
else
|
501
537
|
hash = {}
|
502
538
|
end
|
503
539
|
|
504
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
540
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
505
541
|
hash.each do |key, value|
|
506
542
|
key = key.to_s.tr('_', '-')
|
507
|
-
value = value.to_i if [
|
543
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
508
544
|
values << "#{key}=#{value}"
|
509
545
|
end
|
510
546
|
|
@@ -521,7 +557,7 @@ module Sinatra
|
|
521
557
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
522
558
|
#
|
523
559
|
def expires(amount, *values)
|
524
|
-
values << {} unless values.last.
|
560
|
+
values << {} unless values.last.is_a?(Hash)
|
525
561
|
|
526
562
|
if amount.is_a? Integer
|
527
563
|
time = Time.now + amount.to_i
|
@@ -531,7 +567,7 @@ module Sinatra
|
|
531
567
|
max_age = time - Time.now
|
532
568
|
end
|
533
569
|
|
534
|
-
values.last.merge!(:max_age
|
570
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
535
571
|
cache_control(*values)
|
536
572
|
|
537
573
|
response['Expires'] = time.httpdate
|
@@ -546,17 +582,18 @@ module Sinatra
|
|
546
582
|
# with a '304 Not Modified' response.
|
547
583
|
def last_modified(time)
|
548
584
|
return unless time
|
585
|
+
|
549
586
|
time = time_for time
|
550
587
|
response['Last-Modified'] = time.httpdate
|
551
588
|
return if env['HTTP_IF_NONE_MATCH']
|
552
589
|
|
553
|
-
if status == 200
|
590
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
554
591
|
# compare based on seconds since epoch
|
555
592
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
556
593
|
halt 304 if since >= time.to_i
|
557
594
|
end
|
558
595
|
|
559
|
-
if (success?
|
596
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
560
597
|
# compare based on seconds since epoch
|
561
598
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
562
599
|
halt 412 if since < time.to_i
|
@@ -564,7 +601,7 @@ module Sinatra
|
|
564
601
|
rescue ArgumentError
|
565
602
|
end
|
566
603
|
|
567
|
-
ETAG_KINDS = [
|
604
|
+
ETAG_KINDS = %i[strong weak].freeze
|
568
605
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
569
606
|
# GET matches. The +value+ argument is an identifier that uniquely
|
570
607
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -576,27 +613,31 @@ module Sinatra
|
|
576
613
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
577
614
|
def etag(value, options = {})
|
578
615
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
579
|
-
options = {:
|
616
|
+
options = { kind: options } unless Hash === options
|
580
617
|
kind = options[:kind] || :strong
|
581
618
|
new_resource = options.fetch(:new_resource) { request.post? }
|
582
619
|
|
583
620
|
unless ETAG_KINDS.include?(kind)
|
584
|
-
raise ArgumentError,
|
621
|
+
raise ArgumentError, ':strong or :weak expected'
|
585
622
|
end
|
586
623
|
|
587
|
-
value = '"%s"'
|
624
|
+
value = format('"%s"', value)
|
588
625
|
value = "W/#{value}" if kind == :weak
|
589
626
|
response['ETag'] = value
|
590
627
|
|
591
|
-
|
592
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
593
|
-
halt(request.safe? ? 304 : 412)
|
594
|
-
end
|
628
|
+
return unless success? || status == 304
|
595
629
|
|
596
|
-
|
597
|
-
|
598
|
-
|
630
|
+
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
|
631
|
+
halt(request.safe? ? 304 : 412)
|
632
|
+
end
|
633
|
+
|
634
|
+
if env['HTTP_IF_MATCH']
|
635
|
+
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
|
636
|
+
|
637
|
+
halt 412
|
599
638
|
end
|
639
|
+
|
640
|
+
nil
|
600
641
|
end
|
601
642
|
|
602
643
|
# Sugar for redirect (example: redirect back)
|
@@ -649,8 +690,8 @@ module Sinatra
|
|
649
690
|
else
|
650
691
|
value.to_time
|
651
692
|
end
|
652
|
-
rescue ArgumentError =>
|
653
|
-
raise
|
693
|
+
rescue ArgumentError => e
|
694
|
+
raise e
|
654
695
|
rescue Exception
|
655
696
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
656
697
|
end
|
@@ -660,11 +701,13 @@ module Sinatra
|
|
660
701
|
# Helper method checking if a ETag value list includes the current ETag.
|
661
702
|
def etag_matches?(list, new_resource = request.post?)
|
662
703
|
return !new_resource if list == '*'
|
704
|
+
|
663
705
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
664
706
|
end
|
665
707
|
|
666
708
|
def with_params(temp_params)
|
667
|
-
original
|
709
|
+
original = @params
|
710
|
+
@params = temp_params
|
668
711
|
yield
|
669
712
|
ensure
|
670
713
|
@params = original if original
|
@@ -682,7 +725,7 @@ module Sinatra
|
|
682
725
|
# Possible options are:
|
683
726
|
# :content_type The content type to use, same arguments as content_type.
|
684
727
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
685
|
-
# the specified layout is used (Ignored for `sass`
|
728
|
+
# the specified layout is used (Ignored for `sass`)
|
686
729
|
# :layout_engine Engine to use for rendering the layout.
|
687
730
|
# :locals A hash with local variables that should be available
|
688
731
|
# in the template
|
@@ -704,36 +747,24 @@ module Sinatra
|
|
704
747
|
render(:erb, template, options, locals, &block)
|
705
748
|
end
|
706
749
|
|
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
750
|
def haml(template, options = {}, locals = {}, &block)
|
714
751
|
render(:haml, template, options, locals, &block)
|
715
752
|
end
|
716
753
|
|
717
754
|
def sass(template, options = {}, locals = {})
|
718
|
-
options
|
755
|
+
options[:default_content_type] = :css
|
756
|
+
options[:exclude_outvar] = true
|
757
|
+
options[:layout] = nil
|
719
758
|
render :sass, template, options, locals
|
720
759
|
end
|
721
760
|
|
722
761
|
def scss(template, options = {}, locals = {})
|
723
|
-
options
|
762
|
+
options[:default_content_type] = :css
|
763
|
+
options[:exclude_outvar] = true
|
764
|
+
options[:layout] = nil
|
724
765
|
render :scss, template, options, locals
|
725
766
|
end
|
726
767
|
|
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
768
|
def builder(template = nil, options = {}, locals = {}, &block)
|
738
769
|
options[:default_content_type] = :xml
|
739
770
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -748,10 +779,6 @@ module Sinatra
|
|
748
779
|
render :markdown, template, options, locals
|
749
780
|
end
|
750
781
|
|
751
|
-
def textile(template, options = {}, locals = {})
|
752
|
-
render :textile, template, options, locals
|
753
|
-
end
|
754
|
-
|
755
782
|
def rdoc(template, options = {}, locals = {})
|
756
783
|
render :rdoc, template, options, locals
|
757
784
|
end
|
@@ -760,19 +787,10 @@ module Sinatra
|
|
760
787
|
render :asciidoc, template, options, locals
|
761
788
|
end
|
762
789
|
|
763
|
-
def radius(template, options = {}, locals = {})
|
764
|
-
render :radius, template, options, locals
|
765
|
-
end
|
766
|
-
|
767
790
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
768
791
|
render_ruby(:mab, template, options, locals, &block)
|
769
792
|
end
|
770
793
|
|
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
794
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
777
795
|
options[:default_content_type] = :xml
|
778
796
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -782,18 +800,6 @@ module Sinatra
|
|
782
800
|
render(:slim, template, options, locals, &block)
|
783
801
|
end
|
784
802
|
|
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
803
|
def yajl(template, options = {}, locals = {})
|
798
804
|
options[:default_content_type] = :json
|
799
805
|
render :yajl, template, options, locals
|
@@ -818,24 +824,27 @@ module Sinatra
|
|
818
824
|
|
819
825
|
# logic shared between builder and nokogiri
|
820
826
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
821
|
-
|
822
|
-
|
827
|
+
if template.is_a?(Hash)
|
828
|
+
options = template
|
829
|
+
template = nil
|
830
|
+
end
|
831
|
+
template = proc { block } if template.nil?
|
823
832
|
render engine, template, options, locals
|
824
833
|
end
|
825
834
|
|
826
835
|
def render(engine, data, options = {}, locals = {}, &block)
|
827
836
|
# merge app-level options
|
828
837
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
829
|
-
options.merge!(engine_options) { |
|
838
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
830
839
|
|
831
840
|
# extract generic options
|
832
841
|
locals = options.delete(:locals) || locals || {}
|
833
|
-
views = options.delete(:views) || settings.views ||
|
842
|
+
views = options.delete(:views) || settings.views || './views'
|
834
843
|
layout = options[:layout]
|
835
844
|
layout = false if layout.nil? && options.include?(:layout)
|
836
845
|
eat_errors = layout.nil?
|
837
|
-
layout = engine_options[:layout] if layout.nil?
|
838
|
-
layout = @default_layout if layout.nil?
|
846
|
+
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
|
847
|
+
layout = @default_layout if layout.nil? || (layout == true)
|
839
848
|
layout_options = options.delete(:layout_options) || {}
|
840
849
|
content_type = options.delete(:default_content_type)
|
841
850
|
content_type = options.delete(:content_type) || content_type
|
@@ -860,12 +869,17 @@ module Sinatra
|
|
860
869
|
|
861
870
|
# render layout
|
862
871
|
if layout
|
863
|
-
|
864
|
-
|
872
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
873
|
+
options = options.merge(extra_options).merge!(layout_options)
|
874
|
+
|
865
875
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
866
876
|
end
|
867
877
|
|
868
|
-
|
878
|
+
if content_type
|
879
|
+
# sass-embedded returns a frozen string
|
880
|
+
output = +output
|
881
|
+
output.extend(ContentTyped).content_type = content_type
|
882
|
+
end
|
869
883
|
output
|
870
884
|
end
|
871
885
|
|
@@ -886,12 +900,13 @@ module Sinatra
|
|
886
900
|
@preferred_extension = engine.to_s
|
887
901
|
find_template(views, data, template) do |file|
|
888
902
|
path ||= file # keep the initial path rather than the last one
|
889
|
-
|
903
|
+
found = File.exist?(file)
|
904
|
+
if found
|
890
905
|
path = file
|
891
906
|
break
|
892
907
|
end
|
893
908
|
end
|
894
|
-
throw :layout_missing if eat_errors
|
909
|
+
throw :layout_missing if eat_errors && !found
|
895
910
|
template.new(path, 1, options)
|
896
911
|
end
|
897
912
|
end
|
@@ -907,13 +922,44 @@ module Sinatra
|
|
907
922
|
end
|
908
923
|
|
909
924
|
def compile_block_template(template, options, &body)
|
910
|
-
|
911
|
-
path =
|
912
|
-
line =
|
925
|
+
first_location = caller_locations.first
|
926
|
+
path = first_location.path
|
927
|
+
line = first_location.lineno
|
928
|
+
path = options[:path] || path
|
929
|
+
line = options[:line] || line
|
913
930
|
template.new(path, line.to_i, options, &body)
|
914
931
|
end
|
915
932
|
end
|
916
933
|
|
934
|
+
# Extremely simple template cache implementation.
|
935
|
+
# * Not thread-safe.
|
936
|
+
# * Size is unbounded.
|
937
|
+
# * Keys are not copied defensively, and should not be modified after
|
938
|
+
# being passed to #fetch. More specifically, the values returned by
|
939
|
+
# key#hash and key#eql? should not change.
|
940
|
+
#
|
941
|
+
# Implementation copied from Tilt::Cache.
|
942
|
+
class TemplateCache
|
943
|
+
def initialize
|
944
|
+
@cache = {}
|
945
|
+
end
|
946
|
+
|
947
|
+
# Caches a value for key, or returns the previously cached value.
|
948
|
+
# If a value has been previously cached for key then it is
|
949
|
+
# returned. Otherwise, block is yielded to and its return value
|
950
|
+
# which may be nil, is cached under key and returned.
|
951
|
+
def fetch(*key)
|
952
|
+
@cache.fetch(key) do
|
953
|
+
@cache[key] = yield
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
# Clears the cache.
|
958
|
+
def clear
|
959
|
+
@cache = {}
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
917
963
|
# Base class for all Sinatra applications and middleware.
|
918
964
|
class Base
|
919
965
|
include Rack::Utils
|
@@ -925,10 +971,10 @@ module Sinatra
|
|
925
971
|
attr_accessor :app, :env, :request, :response, :params
|
926
972
|
attr_reader :template_cache
|
927
973
|
|
928
|
-
def initialize(app = nil, **
|
974
|
+
def initialize(app = nil, **_kwargs)
|
929
975
|
super()
|
930
976
|
@app = app
|
931
|
-
@template_cache =
|
977
|
+
@template_cache = TemplateCache.new
|
932
978
|
@pinned_response = nil # whether a before! filter pinned the content-type
|
933
979
|
yield self if block_given?
|
934
980
|
end
|
@@ -952,7 +998,7 @@ module Sinatra
|
|
952
998
|
unless @response['Content-Type']
|
953
999
|
if Array === body && body[0].respond_to?(:content_type)
|
954
1000
|
content_type body[0].content_type
|
955
|
-
elsif default = settings.default_content_type
|
1001
|
+
elsif (default = settings.default_content_type)
|
956
1002
|
content_type default
|
957
1003
|
end
|
958
1004
|
end
|
@@ -970,12 +1016,6 @@ module Sinatra
|
|
970
1016
|
self.class.settings
|
971
1017
|
end
|
972
1018
|
|
973
|
-
def options
|
974
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
975
|
-
"use #settings instead."
|
976
|
-
settings
|
977
|
-
end
|
978
|
-
|
979
1019
|
# Exit the current block, halts any further processing
|
980
1020
|
# of the request, and returns the specified response.
|
981
1021
|
def halt(*response)
|
@@ -992,7 +1032,8 @@ module Sinatra
|
|
992
1032
|
|
993
1033
|
# Forward the request to the downstream app -- middleware only.
|
994
1034
|
def forward
|
995
|
-
|
1035
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
1036
|
+
|
996
1037
|
status, headers, body = @app.call env
|
997
1038
|
@response.status = status
|
998
1039
|
@response.body = body
|
@@ -1014,18 +1055,18 @@ module Sinatra
|
|
1014
1055
|
|
1015
1056
|
# Run routes defined on the class and all superclasses.
|
1016
1057
|
def route!(base = settings, pass_block = nil)
|
1017
|
-
|
1018
|
-
routes.each do |pattern, conditions, block|
|
1019
|
-
response.delete_header('Content-Type') unless @pinned_response
|
1058
|
+
routes = base.routes[@request.request_method]
|
1020
1059
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
route_eval { block[*args] }
|
1024
|
-
end
|
1060
|
+
routes&.each do |pattern, conditions, block|
|
1061
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1025
1062
|
|
1026
|
-
|
1027
|
-
|
1063
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1064
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1065
|
+
route_eval { block[*args] }
|
1028
1066
|
end
|
1067
|
+
|
1068
|
+
# don't wipe out pass_block in superclass
|
1069
|
+
pass_block = returned_pass_block if returned_pass_block
|
1029
1070
|
end
|
1030
1071
|
|
1031
1072
|
# Run routes defined in superclass.
|
@@ -1049,15 +1090,17 @@ module Sinatra
|
|
1049
1090
|
# Returns pass block.
|
1050
1091
|
def process_route(pattern, conditions, block = nil, values = [])
|
1051
1092
|
route = @request.path_info
|
1052
|
-
route = '/' if route.empty?
|
1093
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1053
1094
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1054
|
-
return unless params = pattern.params(route)
|
1055
1095
|
|
1056
|
-
params.
|
1096
|
+
params = pattern.params(route)
|
1097
|
+
return unless params
|
1098
|
+
|
1099
|
+
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
|
1057
1100
|
force_encoding(params)
|
1058
|
-
@params = @params.merge(params) if params.any?
|
1101
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1059
1102
|
|
1060
|
-
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)}
|
1103
|
+
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
|
1061
1104
|
if regexp_exists
|
1062
1105
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1063
1106
|
values += captures
|
@@ -1070,7 +1113,7 @@ module Sinatra
|
|
1070
1113
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1071
1114
|
block ? block[self, values] : yield(self, values)
|
1072
1115
|
end
|
1073
|
-
rescue
|
1116
|
+
rescue StandardError
|
1074
1117
|
@env['sinatra.error.params'] = @params
|
1075
1118
|
raise
|
1076
1119
|
ensure
|
@@ -1084,35 +1127,35 @@ module Sinatra
|
|
1084
1127
|
# a NotFound exception. Subclasses can override this method to perform
|
1085
1128
|
# custom route miss logic.
|
1086
1129
|
def route_missing
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1091
|
-
end
|
1130
|
+
raise NotFound unless @app
|
1131
|
+
|
1132
|
+
forward
|
1092
1133
|
end
|
1093
1134
|
|
1094
1135
|
# Attempt to serve static files from public directory. Throws :halt when
|
1095
1136
|
# a matching file is found, returns nil otherwise.
|
1096
1137
|
def static!(options = {})
|
1097
1138
|
return if (public_dir = settings.public_folder).nil?
|
1139
|
+
|
1098
1140
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1099
1141
|
return unless valid_path?(path)
|
1100
1142
|
|
1101
1143
|
path = File.expand_path(path)
|
1102
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1144
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1145
|
+
|
1103
1146
|
return unless File.file?(path)
|
1104
1147
|
|
1105
1148
|
env['sinatra.static_file'] = path
|
1106
1149
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1107
|
-
send_file path, options.merge(:
|
1150
|
+
send_file path, options.merge(disposition: nil)
|
1108
1151
|
end
|
1109
1152
|
|
1110
1153
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1111
|
-
def invoke
|
1112
|
-
res = catch(:halt)
|
1154
|
+
def invoke(&block)
|
1155
|
+
res = catch(:halt, &block)
|
1113
1156
|
|
1114
|
-
res = [res] if Integer === res
|
1115
|
-
if Array === res
|
1157
|
+
res = [res] if (Integer === res) || (String === res)
|
1158
|
+
if (Array === res) && (Integer === res.first)
|
1116
1159
|
res = res.dup
|
1117
1160
|
status(res.shift)
|
1118
1161
|
body(res.pop)
|
@@ -1128,6 +1171,7 @@ module Sinatra
|
|
1128
1171
|
# Avoid passing frozen string in force_encoding
|
1129
1172
|
@params.merge!(@request.params).each do |key, val|
|
1130
1173
|
next unless val.respond_to?(:force_encoding)
|
1174
|
+
|
1131
1175
|
val = val.dup if val.frozen?
|
1132
1176
|
@params[key] = force_encoding(val)
|
1133
1177
|
end
|
@@ -1139,39 +1183,43 @@ module Sinatra
|
|
1139
1183
|
end
|
1140
1184
|
route!
|
1141
1185
|
end
|
1142
|
-
rescue ::Exception =>
|
1143
|
-
invoke { handle_exception!(
|
1186
|
+
rescue ::Exception => e
|
1187
|
+
invoke { handle_exception!(e) }
|
1144
1188
|
ensure
|
1145
1189
|
begin
|
1146
1190
|
filter! :after unless env['sinatra.static_file']
|
1147
|
-
rescue ::Exception =>
|
1148
|
-
invoke { handle_exception!(
|
1191
|
+
rescue ::Exception => e
|
1192
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1149
1193
|
end
|
1150
1194
|
end
|
1151
1195
|
|
1152
1196
|
# Error handling during requests.
|
1153
1197
|
def handle_exception!(boom)
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1198
|
+
error_params = @env['sinatra.error.params']
|
1199
|
+
|
1200
|
+
@params = @params.merge(error_params) if error_params
|
1201
|
+
|
1157
1202
|
@env['sinatra.error'] = boom
|
1158
1203
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1204
|
+
http_status = if boom.is_a? Sinatra::Error
|
1205
|
+
if boom.respond_to? :http_status
|
1206
|
+
boom.http_status
|
1207
|
+
elsif settings.use_code? && boom.respond_to?(:code)
|
1208
|
+
boom.code
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
http_status = 500 unless http_status&.between?(400, 599)
|
1213
|
+
status(http_status)
|
1166
1214
|
|
1167
1215
|
if server_error?
|
1168
1216
|
dump_errors! boom if settings.dump_errors?
|
1169
|
-
raise boom if settings.show_exceptions?
|
1217
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1170
1218
|
elsif not_found?
|
1171
1219
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1172
1220
|
end
|
1173
1221
|
|
1174
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1222
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1175
1223
|
return res
|
1176
1224
|
end
|
1177
1225
|
|
@@ -1180,12 +1228,14 @@ module Sinatra
|
|
1180
1228
|
body Rack::Utils.escape_html(boom.message)
|
1181
1229
|
else
|
1182
1230
|
content_type 'text/html'
|
1183
|
-
body
|
1231
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1184
1232
|
end
|
1185
1233
|
end
|
1186
1234
|
|
1187
1235
|
return unless server_error?
|
1188
|
-
|
1236
|
+
|
1237
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1238
|
+
|
1189
1239
|
error_block! Exception, boom
|
1190
1240
|
end
|
1191
1241
|
|
@@ -1193,7 +1243,10 @@ module Sinatra
|
|
1193
1243
|
def error_block!(key, *block_params)
|
1194
1244
|
base = settings
|
1195
1245
|
while base.respond_to?(:errors)
|
1196
|
-
|
1246
|
+
args_array = base.errors[key]
|
1247
|
+
|
1248
|
+
next base = base.superclass unless args_array
|
1249
|
+
|
1197
1250
|
args_array.reverse_each do |args|
|
1198
1251
|
first = args == args_array.first
|
1199
1252
|
args += [block_params]
|
@@ -1201,51 +1254,63 @@ module Sinatra
|
|
1201
1254
|
return resp unless resp.nil? && !first
|
1202
1255
|
end
|
1203
1256
|
end
|
1204
|
-
return false unless key.respond_to?
|
1257
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1258
|
+
|
1205
1259
|
error_block!(key.superclass, *block_params)
|
1206
1260
|
end
|
1207
1261
|
|
1208
1262
|
def dump_errors!(boom)
|
1209
|
-
|
1263
|
+
if boom.respond_to?(:detailed_message)
|
1264
|
+
msg = boom.detailed_message(highlight: false)
|
1265
|
+
if msg =~ /\A(.*?)(?: \(#{ Regexp.quote(boom.class.to_s) }\))?\n/
|
1266
|
+
msg = $1
|
1267
|
+
additional_msg = $'.lines(chomp: true)
|
1268
|
+
else
|
1269
|
+
additional_msg = []
|
1270
|
+
end
|
1271
|
+
else
|
1272
|
+
msg = boom.message
|
1273
|
+
additional_msg = []
|
1274
|
+
end
|
1275
|
+
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{msg}:", *additional_msg, *boom.backtrace].join("\n\t")
|
1210
1276
|
@env['rack.errors'].puts(msg)
|
1211
1277
|
end
|
1212
1278
|
|
1213
1279
|
class << self
|
1214
1280
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1215
|
-
|
1216
|
-
/
|
1281
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1282
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1217
1283
|
/^\(.*\)$/, # generated code
|
1218
|
-
|
1284
|
+
/\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
|
1285
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1219
1286
|
/active_support/, # active_support require hacks
|
1220
|
-
|
1287
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1221
1288
|
/<internal:/, # internal in ruby >= 1.9.2
|
1222
|
-
/
|
1223
|
-
]
|
1289
|
+
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
1290
|
+
].freeze
|
1224
1291
|
|
1225
|
-
|
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
|
1292
|
+
attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
|
1230
1293
|
|
1231
|
-
|
1294
|
+
def callers_to_ignore
|
1295
|
+
CALLERS_TO_IGNORE
|
1296
|
+
end
|
1232
1297
|
|
1233
1298
|
# Removes all routes, filters, middleware and extension hooks from the
|
1234
1299
|
# current class (not routes/filters/... defined by its superclass).
|
1235
1300
|
def reset!
|
1236
1301
|
@conditions = []
|
1237
1302
|
@routes = {}
|
1238
|
-
@filters = {:
|
1303
|
+
@filters = { before: [], after: [] }
|
1239
1304
|
@errors = {}
|
1240
1305
|
@middleware = []
|
1241
1306
|
@prototype = nil
|
1242
1307
|
@extensions = []
|
1243
1308
|
|
1244
|
-
if superclass.respond_to?(:templates)
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1309
|
+
@templates = if superclass.respond_to?(:templates)
|
1310
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1311
|
+
else
|
1312
|
+
{}
|
1313
|
+
end
|
1249
1314
|
end
|
1250
1315
|
|
1251
1316
|
# Extension modules registered on this class and all superclasses.
|
@@ -1269,16 +1334,21 @@ module Sinatra
|
|
1269
1334
|
# Sets an option to the given value. If the value is a proc,
|
1270
1335
|
# the proc will be called every time the option is accessed.
|
1271
1336
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1272
|
-
raise ArgumentError if block
|
1273
|
-
|
1337
|
+
raise ArgumentError if block && !not_set
|
1338
|
+
|
1339
|
+
if block
|
1340
|
+
value = block
|
1341
|
+
not_set = false
|
1342
|
+
end
|
1274
1343
|
|
1275
1344
|
if not_set
|
1276
1345
|
raise ArgumentError unless option.respond_to?(:each)
|
1277
|
-
|
1346
|
+
|
1347
|
+
option.each { |k, v| set(k, v) }
|
1278
1348
|
return self
|
1279
1349
|
end
|
1280
1350
|
|
1281
|
-
if respond_to?("#{option}=")
|
1351
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1282
1352
|
return __send__("#{option}=", value)
|
1283
1353
|
end
|
1284
1354
|
|
@@ -1317,7 +1387,7 @@ module Sinatra
|
|
1317
1387
|
# class, or an HTTP status code to specify which errors should be
|
1318
1388
|
# handled.
|
1319
1389
|
def error(*codes, &block)
|
1320
|
-
args = compile!
|
1390
|
+
args = compile! 'ERROR', /.*/, block
|
1321
1391
|
codes = codes.flat_map(&method(:Array))
|
1322
1392
|
codes << Exception if codes.empty?
|
1323
1393
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1343,7 +1413,7 @@ module Sinatra
|
|
1343
1413
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1344
1414
|
# when no file is specified.
|
1345
1415
|
def inline_templates=(file = nil)
|
1346
|
-
file = (
|
1416
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1347
1417
|
|
1348
1418
|
begin
|
1349
1419
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1352,23 +1422,24 @@ module Sinatra
|
|
1352
1422
|
app, data = nil
|
1353
1423
|
end
|
1354
1424
|
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1425
|
+
return unless data
|
1426
|
+
|
1427
|
+
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1428
|
+
$2
|
1429
|
+
else
|
1430
|
+
settings.default_encoding
|
1431
|
+
end
|
1432
|
+
|
1433
|
+
lines = app.count("\n") + 1
|
1434
|
+
template = nil
|
1435
|
+
force_encoding data, encoding
|
1436
|
+
data.each_line do |line|
|
1437
|
+
lines += 1
|
1438
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1439
|
+
template = force_encoding(String.new, encoding)
|
1440
|
+
templates[$1.to_sym] = [template, file, lines]
|
1441
|
+
elsif template
|
1442
|
+
template << line
|
1372
1443
|
end
|
1373
1444
|
end
|
1374
1445
|
end
|
@@ -1377,8 +1448,10 @@ module Sinatra
|
|
1377
1448
|
def mime_type(type, value = nil)
|
1378
1449
|
return type if type.nil?
|
1379
1450
|
return type.to_s if type.to_s.include?('/')
|
1380
|
-
|
1451
|
+
|
1452
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1381
1453
|
return Rack::Mime.mime_type(type, nil) unless value
|
1454
|
+
|
1382
1455
|
Rack::Mime::MIME_TYPES[type] = value
|
1383
1456
|
end
|
1384
1457
|
|
@@ -1387,7 +1460,7 @@ module Sinatra
|
|
1387
1460
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1388
1461
|
def mime_types(type)
|
1389
1462
|
type = mime_type type
|
1390
|
-
type =~
|
1463
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1391
1464
|
end
|
1392
1465
|
|
1393
1466
|
# Define a before filter; runs before all requests within the same
|
@@ -1409,6 +1482,14 @@ module Sinatra
|
|
1409
1482
|
filters[type] << compile!(type, path, block, **options)
|
1410
1483
|
end
|
1411
1484
|
|
1485
|
+
def on_start(&on_start_callback)
|
1486
|
+
@on_start_callback = on_start_callback
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
def on_stop(&on_stop_callback)
|
1490
|
+
@on_stop_callback = on_stop_callback
|
1491
|
+
end
|
1492
|
+
|
1412
1493
|
# Add a route condition. The route is considered non-matching when the
|
1413
1494
|
# block returns false.
|
1414
1495
|
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
|
@@ -1416,7 +1497,7 @@ module Sinatra
|
|
1416
1497
|
end
|
1417
1498
|
|
1418
1499
|
def public=(value)
|
1419
|
-
|
1500
|
+
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1420
1501
|
set(:public_folder, value)
|
1421
1502
|
end
|
1422
1503
|
|
@@ -1438,14 +1519,21 @@ module Sinatra
|
|
1438
1519
|
route('HEAD', path, opts, &block)
|
1439
1520
|
end
|
1440
1521
|
|
1441
|
-
def put(path, opts = {}, &
|
1442
|
-
|
1443
|
-
def
|
1444
|
-
|
1445
|
-
def
|
1446
|
-
|
1447
|
-
def
|
1448
|
-
|
1522
|
+
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
|
1523
|
+
|
1524
|
+
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
|
1525
|
+
|
1526
|
+
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
|
1527
|
+
|
1528
|
+
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
|
1529
|
+
|
1530
|
+
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
|
1531
|
+
|
1532
|
+
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
|
1533
|
+
|
1534
|
+
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
|
1535
|
+
|
1536
|
+
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
|
1449
1537
|
|
1450
1538
|
# Makes the methods defined in the block and in the Modules given
|
1451
1539
|
# in `extensions` available to the handlers and templates
|
@@ -1485,37 +1573,41 @@ module Sinatra
|
|
1485
1573
|
# Stop the self-hosted server if running.
|
1486
1574
|
def quit!
|
1487
1575
|
return unless running?
|
1576
|
+
|
1488
1577
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1489
1578
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1490
|
-
|
1579
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1491
1580
|
set :running_server, nil
|
1492
1581
|
set :handler_name, nil
|
1582
|
+
|
1583
|
+
on_stop_callback.call unless on_stop_callback.nil?
|
1493
1584
|
end
|
1494
1585
|
|
1495
|
-
|
1586
|
+
alias stop! quit!
|
1496
1587
|
|
1497
1588
|
# Run the Sinatra app as a self-hosted server using
|
1498
|
-
# Puma,
|
1589
|
+
# Puma, Falcon, or WEBrick (in that order). If given a block, will call
|
1499
1590
|
# with the constructed handler once we have taken the stage.
|
1500
1591
|
def run!(options = {}, &block)
|
1501
1592
|
return if running?
|
1593
|
+
|
1502
1594
|
set options
|
1503
1595
|
handler = Rack::Handler.pick(server)
|
1504
1596
|
handler_name = handler.name.gsub(/.*::/, '')
|
1505
1597
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1506
|
-
server_settings.merge!(:
|
1598
|
+
server_settings.merge!(Port: port, Host: bind)
|
1507
1599
|
|
1508
1600
|
begin
|
1509
1601
|
start_server(handler, server_settings, handler_name, &block)
|
1510
1602
|
rescue Errno::EADDRINUSE
|
1511
|
-
|
1603
|
+
warn "== Someone is already performing on port #{port}!"
|
1512
1604
|
raise
|
1513
1605
|
ensure
|
1514
1606
|
quit!
|
1515
1607
|
end
|
1516
1608
|
end
|
1517
1609
|
|
1518
|
-
|
1610
|
+
alias start! run!
|
1519
1611
|
|
1520
1612
|
# Check whether the self-hosted server is running or not.
|
1521
1613
|
def running?
|
@@ -1533,10 +1625,11 @@ module Sinatra
|
|
1533
1625
|
# Create a new instance of the class fronted by its middleware
|
1534
1626
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1535
1627
|
# an instance of the class new was called on.
|
1536
|
-
def new(*args,
|
1537
|
-
instance = new!(*args,
|
1628
|
+
def new(*args, &block)
|
1629
|
+
instance = new!(*args, &block)
|
1538
1630
|
Wrapper.new(build(instance).to_app, instance)
|
1539
1631
|
end
|
1632
|
+
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
1540
1633
|
|
1541
1634
|
# Creates a Rack::Builder instance with all the middleware set up and
|
1542
1635
|
# the given +app+ as end point.
|
@@ -1558,12 +1651,6 @@ module Sinatra
|
|
1558
1651
|
cleaned_caller(1).flatten
|
1559
1652
|
end
|
1560
1653
|
|
1561
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1562
|
-
# first element being the file, and the second being the line.
|
1563
|
-
def caller_locations
|
1564
|
-
cleaned_caller 2
|
1565
|
-
end
|
1566
|
-
|
1567
1654
|
private
|
1568
1655
|
|
1569
1656
|
# Starts the server by running the Rack Handler.
|
@@ -1574,14 +1661,14 @@ module Sinatra
|
|
1574
1661
|
# Run the instance we created:
|
1575
1662
|
handler.run(self, **server_settings) do |server|
|
1576
1663
|
unless suppress_messages?
|
1577
|
-
|
1664
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1578
1665
|
end
|
1579
1666
|
|
1580
1667
|
setup_traps
|
1581
1668
|
set :running_server, server
|
1582
1669
|
set :handler_name, handler_name
|
1583
1670
|
server.threaded = settings.threaded if server.respond_to? :threaded=
|
1584
|
-
|
1671
|
+
on_start_callback.call unless on_start_callback.nil?
|
1585
1672
|
yield server if block_given?
|
1586
1673
|
end
|
1587
1674
|
end
|
@@ -1591,18 +1678,18 @@ module Sinatra
|
|
1591
1678
|
end
|
1592
1679
|
|
1593
1680
|
def setup_traps
|
1594
|
-
|
1595
|
-
at_exit { quit! }
|
1681
|
+
return unless traps?
|
1596
1682
|
|
1597
|
-
|
1598
|
-
old_handler = trap(signal) do
|
1599
|
-
quit!
|
1600
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1601
|
-
end
|
1602
|
-
end
|
1683
|
+
at_exit { quit! }
|
1603
1684
|
|
1604
|
-
|
1685
|
+
%i[INT TERM].each do |signal|
|
1686
|
+
old_handler = trap(signal) do
|
1687
|
+
quit!
|
1688
|
+
old_handler.call if old_handler.respond_to?(:call)
|
1689
|
+
end
|
1605
1690
|
end
|
1691
|
+
|
1692
|
+
set :traps, false
|
1606
1693
|
end
|
1607
1694
|
|
1608
1695
|
# Dynamically defines a method on settings.
|
@@ -1630,18 +1717,21 @@ module Sinatra
|
|
1630
1717
|
end
|
1631
1718
|
end
|
1632
1719
|
end
|
1633
|
-
|
1720
|
+
alias agent user_agent
|
1634
1721
|
|
1635
1722
|
# Condition for matching mimetypes. Accepts file extensions.
|
1636
1723
|
def provides(*types)
|
1637
1724
|
types.map! { |t| mime_types(t) }
|
1638
1725
|
types.flatten!
|
1639
1726
|
condition do
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1727
|
+
response_content_type = response['Content-Type']
|
1728
|
+
preferred_type = request.preferred_type(types)
|
1729
|
+
|
1730
|
+
if response_content_type
|
1731
|
+
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
|
1732
|
+
elsif preferred_type
|
1733
|
+
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
|
1734
|
+
content_type(preferred_type, params)
|
1645
1735
|
true
|
1646
1736
|
else
|
1647
1737
|
false
|
@@ -1650,7 +1740,7 @@ module Sinatra
|
|
1650
1740
|
end
|
1651
1741
|
|
1652
1742
|
def route(verb, path, options = {}, &block)
|
1653
|
-
enable :empty_path_info if path ==
|
1743
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1654
1744
|
signature = compile!(verb, path, block, **options)
|
1655
1745
|
(@routes[verb] ||= []) << signature
|
1656
1746
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1679,12 +1769,13 @@ module Sinatra
|
|
1679
1769
|
pattern = compile(path, route_mustermann_opts)
|
1680
1770
|
method_name = "#{verb} #{path}"
|
1681
1771
|
unbound_method = generate_method(method_name, &block)
|
1682
|
-
conditions
|
1683
|
-
|
1684
|
-
|
1685
|
-
proc { |a,
|
1772
|
+
conditions = @conditions
|
1773
|
+
@conditions = []
|
1774
|
+
wrapper = block.arity.zero? ?
|
1775
|
+
proc { |a, _p| unbound_method.bind(a).call } :
|
1776
|
+
proc { |a, p| unbound_method.bind(a).call(*p) }
|
1686
1777
|
|
1687
|
-
[
|
1778
|
+
[pattern, conditions, wrapper]
|
1688
1779
|
end
|
1689
1780
|
|
1690
1781
|
def compile(path, route_mustermann_opts = {})
|
@@ -1702,7 +1793,7 @@ module Sinatra
|
|
1702
1793
|
end
|
1703
1794
|
|
1704
1795
|
def setup_middleware(builder)
|
1705
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1796
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1706
1797
|
end
|
1707
1798
|
|
1708
1799
|
def setup_logging(builder)
|
@@ -1732,9 +1823,10 @@ module Sinatra
|
|
1732
1823
|
|
1733
1824
|
def setup_protection(builder)
|
1734
1825
|
return unless protection?
|
1826
|
+
|
1735
1827
|
options = Hash === protection ? protection.dup : {}
|
1736
1828
|
options = {
|
1737
|
-
img_src:
|
1829
|
+
img_src: "'self' data:",
|
1738
1830
|
font_src: "'self'"
|
1739
1831
|
}.merge options
|
1740
1832
|
|
@@ -1748,6 +1840,7 @@ module Sinatra
|
|
1748
1840
|
|
1749
1841
|
def setup_sessions(builder)
|
1750
1842
|
return unless sessions?
|
1843
|
+
|
1751
1844
|
options = {}
|
1752
1845
|
options[:secret] = session_secret if session_secret?
|
1753
1846
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1770,15 +1863,15 @@ module Sinatra
|
|
1770
1863
|
end
|
1771
1864
|
|
1772
1865
|
# used for deprecation warnings
|
1773
|
-
def
|
1774
|
-
|
1866
|
+
def warn_for_deprecation(message)
|
1867
|
+
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1775
1868
|
end
|
1776
1869
|
|
1777
1870
|
# Like Kernel#caller but excluding certain magic entries
|
1778
1871
|
def cleaned_caller(keep = 3)
|
1779
|
-
caller(1)
|
1780
|
-
map!
|
1781
|
-
reject { |file, *_|
|
1872
|
+
caller(1)
|
1873
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1874
|
+
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1782
1875
|
end
|
1783
1876
|
end
|
1784
1877
|
|
@@ -1786,6 +1879,7 @@ module Sinatra
|
|
1786
1879
|
# which is UTF-8 by default
|
1787
1880
|
def self.force_encoding(data, encoding = default_encoding)
|
1788
1881
|
return if data == settings || data.is_a?(Tempfile)
|
1882
|
+
|
1789
1883
|
if data.respond_to? :force_encoding
|
1790
1884
|
data.force_encoding(encoding).encode!
|
1791
1885
|
elsif data.respond_to? :each_value
|
@@ -1796,24 +1890,26 @@ module Sinatra
|
|
1796
1890
|
data
|
1797
1891
|
end
|
1798
1892
|
|
1799
|
-
def force_encoding(*args)
|
1893
|
+
def force_encoding(*args)
|
1894
|
+
settings.force_encoding(*args)
|
1895
|
+
end
|
1800
1896
|
|
1801
1897
|
reset!
|
1802
1898
|
|
1803
1899
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1804
|
-
set :raise_errors,
|
1805
|
-
set :dump_errors,
|
1806
|
-
set :show_exceptions,
|
1900
|
+
set :raise_errors, proc { test? }
|
1901
|
+
set :dump_errors, proc { !test? }
|
1902
|
+
set :show_exceptions, proc { development? }
|
1807
1903
|
set :sessions, false
|
1808
|
-
set :session_store, Rack::
|
1904
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1809
1905
|
set :logging, false
|
1810
1906
|
set :protection, true
|
1811
1907
|
set :method_override, false
|
1812
1908
|
set :use_code, false
|
1813
|
-
set :default_encoding,
|
1909
|
+
set :default_encoding, 'utf-8'
|
1814
1910
|
set :x_cascade, true
|
1815
1911
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1816
|
-
settings.add_charset <<
|
1912
|
+
settings.add_charset << %r{^text/}
|
1817
1913
|
set :mustermann_opts, {}
|
1818
1914
|
set :default_content_type, 'text/html'
|
1819
1915
|
|
@@ -1821,14 +1917,15 @@ module Sinatra
|
|
1821
1917
|
begin
|
1822
1918
|
require 'securerandom'
|
1823
1919
|
set :session_secret, SecureRandom.hex(64)
|
1824
|
-
rescue LoadError, NotImplementedError
|
1920
|
+
rescue LoadError, NotImplementedError, RuntimeError
|
1825
1921
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1826
|
-
|
1922
|
+
# RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
|
1923
|
+
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
|
1827
1924
|
end
|
1828
1925
|
|
1829
1926
|
class << self
|
1830
|
-
|
1831
|
-
|
1927
|
+
alias methodoverride? method_override?
|
1928
|
+
alias methodoverride= method_override=
|
1832
1929
|
end
|
1833
1930
|
|
1834
1931
|
set :run, false # start server via at-exit hook?
|
@@ -1836,21 +1933,16 @@ module Sinatra
|
|
1836
1933
|
set :handler_name, nil
|
1837
1934
|
set :traps, true
|
1838
1935
|
set :server, %w[HTTP webrick]
|
1839
|
-
set :bind,
|
1936
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1840
1937
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1841
1938
|
set :quiet, false
|
1842
1939
|
|
1843
1940
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1844
1941
|
|
1845
|
-
if ruby_engine
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
server.unshift 'puma'
|
1850
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1851
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1852
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1853
|
-
end
|
1942
|
+
server.unshift 'thin' if ruby_engine != 'jruby'
|
1943
|
+
server.unshift 'falcon' if ruby_engine != 'jruby'
|
1944
|
+
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1945
|
+
server.unshift 'puma'
|
1854
1946
|
|
1855
1947
|
set :absolute_redirects, true
|
1856
1948
|
set :prefixed_redirects, false
|
@@ -1858,14 +1950,14 @@ module Sinatra
|
|
1858
1950
|
set :strict_paths, true
|
1859
1951
|
|
1860
1952
|
set :app_file, nil
|
1861
|
-
set :root,
|
1862
|
-
set :views,
|
1863
|
-
set :reload_templates,
|
1953
|
+
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
|
1954
|
+
set :views, proc { root && File.join(root, 'views') }
|
1955
|
+
set :reload_templates, proc { development? }
|
1864
1956
|
set :lock, false
|
1865
1957
|
set :threaded, true
|
1866
1958
|
|
1867
|
-
set :public_folder,
|
1868
|
-
set :static,
|
1959
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1960
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1869
1961
|
set :static_cache_control, false
|
1870
1962
|
|
1871
1963
|
error ::Exception do
|
@@ -1884,7 +1976,7 @@ module Sinatra
|
|
1884
1976
|
error NotFound do
|
1885
1977
|
content_type 'text/html'
|
1886
1978
|
|
1887
|
-
if
|
1979
|
+
if instance_of?(Sinatra::Application)
|
1888
1980
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1889
1981
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1890
1982
|
"Hello World"
|
@@ -1899,11 +1991,11 @@ module Sinatra
|
|
1899
1991
|
end
|
1900
1992
|
RUBY
|
1901
1993
|
|
1902
|
-
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(
|
1994
|
+
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
|
1903
1995
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1904
1996
|
end
|
1905
1997
|
|
1906
|
-
|
1998
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1907
1999
|
<!DOCTYPE html>
|
1908
2000
|
<html>
|
1909
2001
|
<head>
|
@@ -1915,7 +2007,7 @@ module Sinatra
|
|
1915
2007
|
</head>
|
1916
2008
|
<body>
|
1917
2009
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1918
|
-
<img src='#{
|
2010
|
+
<img src='#{request.script_name}/__sinatra__/404.png'>
|
1919
2011
|
<div id="c">
|
1920
2012
|
Try this:
|
1921
2013
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1935,12 +2027,12 @@ module Sinatra
|
|
1935
2027
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1936
2028
|
# modular applications.
|
1937
2029
|
class Application < Base
|
1938
|
-
set :logging,
|
2030
|
+
set :logging, proc { !test? }
|
1939
2031
|
set :method_override, true
|
1940
|
-
set :run,
|
2032
|
+
set :run, proc { !test? }
|
1941
2033
|
set :app_file, nil
|
1942
2034
|
|
1943
|
-
def self.register(*extensions, &block)
|
2035
|
+
def self.register(*extensions, &block) # :nodoc:
|
1944
2036
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1945
2037
|
Delegator.delegate(*added_methods)
|
1946
2038
|
super(*extensions, &block)
|
@@ -1950,11 +2042,12 @@ module Sinatra
|
|
1950
2042
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1951
2043
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1952
2044
|
# at the top-level.
|
1953
|
-
module Delegator
|
2045
|
+
module Delegator # :nodoc:
|
1954
2046
|
def self.delegate(*methods)
|
1955
2047
|
methods.each do |method_name|
|
1956
2048
|
define_method(method_name) do |*args, &block|
|
1957
2049
|
return super(*args, &block) if respond_to? method_name
|
2050
|
+
|
1958
2051
|
Delegator.target.send(method_name, *args, &block)
|
1959
2052
|
end
|
1960
2053
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1966,7 +2059,7 @@ module Sinatra
|
|
1966
2059
|
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
|
1967
2060
|
:template, :layout, :before, :after, :error, :not_found, :configure,
|
1968
2061
|
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
|
1969
|
-
:production?, :helpers, :settings, :register
|
2062
|
+
:production?, :helpers, :settings, :register, :on_start, :on_stop
|
1970
2063
|
|
1971
2064
|
class << self
|
1972
2065
|
attr_accessor :target
|
@@ -1977,7 +2070,8 @@ module Sinatra
|
|
1977
2070
|
|
1978
2071
|
class Wrapper
|
1979
2072
|
def initialize(stack, instance)
|
1980
|
-
@stack
|
2073
|
+
@stack = stack
|
2074
|
+
@instance = instance
|
1981
2075
|
end
|
1982
2076
|
|
1983
2077
|
def settings
|