sinatra 2.2.4 → 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 +143 -17
- data/CONTRIBUTING.md +11 -11
- data/Gemfile +52 -60
- 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 +418 -336
- 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 +24 -31
- 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'
|
@@ -402,22 +417,22 @@ module Sinatra
|
|
402
417
|
|
403
418
|
# Use the contents of the file at +path+ as the response body.
|
404
419
|
def send_file(path, opts = {})
|
405
|
-
if opts[:type]
|
406
|
-
content_type opts[:type] || File.extname(path), :
|
420
|
+
if opts[:type] || !response['Content-Type']
|
421
|
+
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
|
407
422
|
end
|
408
423
|
|
409
424
|
disposition = opts[:disposition]
|
410
425
|
filename = opts[:filename]
|
411
|
-
disposition = :attachment if disposition.nil?
|
426
|
+
disposition = :attachment if disposition.nil? && filename
|
412
427
|
filename = path if filename.nil?
|
413
428
|
attachment(filename, disposition) if disposition
|
414
429
|
|
415
430
|
last_modified opts[:last_modified] if opts[:last_modified]
|
416
431
|
|
417
|
-
file = Rack::
|
432
|
+
file = Rack::Files.new(File.dirname(settings.app_file))
|
418
433
|
result = file.serving(request, path)
|
419
434
|
|
420
|
-
result[1].each { |k,v| headers[k] ||= v }
|
435
|
+
result[1].each { |k, v| headers[k] ||= v }
|
421
436
|
headers['Content-Length'] = result[1]['Content-Length']
|
422
437
|
opts[:status] &&= Integer(opts[:status])
|
423
438
|
halt (opts[:status] || result[0]), result[2]
|
@@ -438,12 +453,16 @@ module Sinatra
|
|
438
453
|
def self.defer(*) yield end
|
439
454
|
|
440
455
|
def initialize(scheduler = self.class, keep_open = false, &back)
|
441
|
-
@back
|
442
|
-
@
|
456
|
+
@back = back.to_proc
|
457
|
+
@scheduler = scheduler
|
458
|
+
@keep_open = keep_open
|
459
|
+
@callbacks = []
|
460
|
+
@closed = false
|
443
461
|
end
|
444
462
|
|
445
463
|
def close
|
446
464
|
return if closed?
|
465
|
+
|
447
466
|
@closed = true
|
448
467
|
@scheduler.schedule { @callbacks.each { |c| c.call } }
|
449
468
|
end
|
@@ -455,8 +474,9 @@ module Sinatra
|
|
455
474
|
@back.call(self)
|
456
475
|
rescue Exception => e
|
457
476
|
@scheduler.schedule { raise e }
|
477
|
+
ensure
|
478
|
+
close unless @keep_open
|
458
479
|
end
|
459
|
-
close unless @keep_open
|
460
480
|
end
|
461
481
|
end
|
462
482
|
|
@@ -467,6 +487,7 @@ module Sinatra
|
|
467
487
|
|
468
488
|
def callback(&block)
|
469
489
|
return yield if closed?
|
490
|
+
|
470
491
|
@callbacks << block
|
471
492
|
end
|
472
493
|
|
@@ -481,12 +502,20 @@ module Sinatra
|
|
481
502
|
# the response body have not yet been generated.
|
482
503
|
#
|
483
504
|
# The close parameter specifies whether Stream#close should be called
|
484
|
-
# after the block has been executed.
|
485
|
-
# servers like Rainbows.
|
505
|
+
# after the block has been executed.
|
486
506
|
def stream(keep_open = false)
|
487
507
|
scheduler = env['async.callback'] ? EventMachine : Stream
|
488
508
|
current = @params.dup
|
489
|
-
|
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
|
490
519
|
end
|
491
520
|
|
492
521
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
@@ -500,18 +529,18 @@ module Sinatra
|
|
500
529
|
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
501
530
|
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
502
531
|
def cache_control(*values)
|
503
|
-
if values.last.
|
532
|
+
if values.last.is_a?(Hash)
|
504
533
|
hash = values.pop
|
505
|
-
hash.reject! { |
|
534
|
+
hash.reject! { |_k, v| v == false }
|
506
535
|
hash.reject! { |k, v| values << k if v == true }
|
507
536
|
else
|
508
537
|
hash = {}
|
509
538
|
end
|
510
539
|
|
511
|
-
values.map! { |value| value.to_s.tr('_','-') }
|
540
|
+
values.map! { |value| value.to_s.tr('_', '-') }
|
512
541
|
hash.each do |key, value|
|
513
542
|
key = key.to_s.tr('_', '-')
|
514
|
-
value = value.to_i if [
|
543
|
+
value = value.to_i if %w[max-age s-maxage].include? key
|
515
544
|
values << "#{key}=#{value}"
|
516
545
|
end
|
517
546
|
|
@@ -528,7 +557,7 @@ module Sinatra
|
|
528
557
|
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
529
558
|
#
|
530
559
|
def expires(amount, *values)
|
531
|
-
values << {} unless values.last.
|
560
|
+
values << {} unless values.last.is_a?(Hash)
|
532
561
|
|
533
562
|
if amount.is_a? Integer
|
534
563
|
time = Time.now + amount.to_i
|
@@ -538,7 +567,7 @@ module Sinatra
|
|
538
567
|
max_age = time - Time.now
|
539
568
|
end
|
540
569
|
|
541
|
-
values.last.merge!(:max_age
|
570
|
+
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
|
542
571
|
cache_control(*values)
|
543
572
|
|
544
573
|
response['Expires'] = time.httpdate
|
@@ -553,17 +582,18 @@ module Sinatra
|
|
553
582
|
# with a '304 Not Modified' response.
|
554
583
|
def last_modified(time)
|
555
584
|
return unless time
|
585
|
+
|
556
586
|
time = time_for time
|
557
587
|
response['Last-Modified'] = time.httpdate
|
558
588
|
return if env['HTTP_IF_NONE_MATCH']
|
559
589
|
|
560
|
-
if status == 200
|
590
|
+
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
|
561
591
|
# compare based on seconds since epoch
|
562
592
|
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
563
593
|
halt 304 if since >= time.to_i
|
564
594
|
end
|
565
595
|
|
566
|
-
if (success?
|
596
|
+
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
|
567
597
|
# compare based on seconds since epoch
|
568
598
|
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
569
599
|
halt 412 if since < time.to_i
|
@@ -571,7 +601,7 @@ module Sinatra
|
|
571
601
|
rescue ArgumentError
|
572
602
|
end
|
573
603
|
|
574
|
-
ETAG_KINDS = [
|
604
|
+
ETAG_KINDS = %i[strong weak].freeze
|
575
605
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
576
606
|
# GET matches. The +value+ argument is an identifier that uniquely
|
577
607
|
# identifies the current version of the resource. The +kind+ argument
|
@@ -583,27 +613,31 @@ module Sinatra
|
|
583
613
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
584
614
|
def etag(value, options = {})
|
585
615
|
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
586
|
-
options = {:
|
616
|
+
options = { kind: options } unless Hash === options
|
587
617
|
kind = options[:kind] || :strong
|
588
618
|
new_resource = options.fetch(:new_resource) { request.post? }
|
589
619
|
|
590
620
|
unless ETAG_KINDS.include?(kind)
|
591
|
-
raise ArgumentError,
|
621
|
+
raise ArgumentError, ':strong or :weak expected'
|
592
622
|
end
|
593
623
|
|
594
|
-
value = '"%s"'
|
624
|
+
value = format('"%s"', value)
|
595
625
|
value = "W/#{value}" if kind == :weak
|
596
626
|
response['ETag'] = value
|
597
627
|
|
598
|
-
|
599
|
-
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
600
|
-
halt(request.safe? ? 304 : 412)
|
601
|
-
end
|
628
|
+
return unless success? || status == 304
|
602
629
|
|
603
|
-
|
604
|
-
|
605
|
-
|
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
|
606
638
|
end
|
639
|
+
|
640
|
+
nil
|
607
641
|
end
|
608
642
|
|
609
643
|
# Sugar for redirect (example: redirect back)
|
@@ -656,8 +690,8 @@ module Sinatra
|
|
656
690
|
else
|
657
691
|
value.to_time
|
658
692
|
end
|
659
|
-
rescue ArgumentError =>
|
660
|
-
raise
|
693
|
+
rescue ArgumentError => e
|
694
|
+
raise e
|
661
695
|
rescue Exception
|
662
696
|
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
663
697
|
end
|
@@ -667,11 +701,13 @@ module Sinatra
|
|
667
701
|
# Helper method checking if a ETag value list includes the current ETag.
|
668
702
|
def etag_matches?(list, new_resource = request.post?)
|
669
703
|
return !new_resource if list == '*'
|
704
|
+
|
670
705
|
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
671
706
|
end
|
672
707
|
|
673
708
|
def with_params(temp_params)
|
674
|
-
original
|
709
|
+
original = @params
|
710
|
+
@params = temp_params
|
675
711
|
yield
|
676
712
|
ensure
|
677
713
|
@params = original if original
|
@@ -689,7 +725,7 @@ module Sinatra
|
|
689
725
|
# Possible options are:
|
690
726
|
# :content_type The content type to use, same arguments as content_type.
|
691
727
|
# :layout If set to something falsy, no layout is rendered, otherwise
|
692
|
-
# the specified layout is used (Ignored for `sass`
|
728
|
+
# the specified layout is used (Ignored for `sass`)
|
693
729
|
# :layout_engine Engine to use for rendering the layout.
|
694
730
|
# :locals A hash with local variables that should be available
|
695
731
|
# in the template
|
@@ -711,36 +747,24 @@ module Sinatra
|
|
711
747
|
render(:erb, template, options, locals, &block)
|
712
748
|
end
|
713
749
|
|
714
|
-
def erubis(template, options = {}, locals = {})
|
715
|
-
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
716
|
-
"If you have Erubis installed, it will be used automatically."
|
717
|
-
render :erubis, template, options, locals
|
718
|
-
end
|
719
|
-
|
720
750
|
def haml(template, options = {}, locals = {}, &block)
|
721
751
|
render(:haml, template, options, locals, &block)
|
722
752
|
end
|
723
753
|
|
724
754
|
def sass(template, options = {}, locals = {})
|
725
|
-
options
|
755
|
+
options[:default_content_type] = :css
|
756
|
+
options[:exclude_outvar] = true
|
757
|
+
options[:layout] = nil
|
726
758
|
render :sass, template, options, locals
|
727
759
|
end
|
728
760
|
|
729
761
|
def scss(template, options = {}, locals = {})
|
730
|
-
options
|
762
|
+
options[:default_content_type] = :css
|
763
|
+
options[:exclude_outvar] = true
|
764
|
+
options[:layout] = nil
|
731
765
|
render :scss, template, options, locals
|
732
766
|
end
|
733
767
|
|
734
|
-
def less(template, options = {}, locals = {})
|
735
|
-
options.merge! :layout => false, :default_content_type => :css
|
736
|
-
render :less, template, options, locals
|
737
|
-
end
|
738
|
-
|
739
|
-
def stylus(template, options = {}, locals = {})
|
740
|
-
options.merge! :layout => false, :default_content_type => :css
|
741
|
-
render :styl, template, options, locals
|
742
|
-
end
|
743
|
-
|
744
768
|
def builder(template = nil, options = {}, locals = {}, &block)
|
745
769
|
options[:default_content_type] = :xml
|
746
770
|
render_ruby(:builder, template, options, locals, &block)
|
@@ -755,10 +779,6 @@ module Sinatra
|
|
755
779
|
render :markdown, template, options, locals
|
756
780
|
end
|
757
781
|
|
758
|
-
def textile(template, options = {}, locals = {})
|
759
|
-
render :textile, template, options, locals
|
760
|
-
end
|
761
|
-
|
762
782
|
def rdoc(template, options = {}, locals = {})
|
763
783
|
render :rdoc, template, options, locals
|
764
784
|
end
|
@@ -767,19 +787,10 @@ module Sinatra
|
|
767
787
|
render :asciidoc, template, options, locals
|
768
788
|
end
|
769
789
|
|
770
|
-
def radius(template, options = {}, locals = {})
|
771
|
-
render :radius, template, options, locals
|
772
|
-
end
|
773
|
-
|
774
790
|
def markaby(template = nil, options = {}, locals = {}, &block)
|
775
791
|
render_ruby(:mab, template, options, locals, &block)
|
776
792
|
end
|
777
793
|
|
778
|
-
def coffee(template, options = {}, locals = {})
|
779
|
-
options.merge! :layout => false, :default_content_type => :js
|
780
|
-
render :coffee, template, options, locals
|
781
|
-
end
|
782
|
-
|
783
794
|
def nokogiri(template = nil, options = {}, locals = {}, &block)
|
784
795
|
options[:default_content_type] = :xml
|
785
796
|
render_ruby(:nokogiri, template, options, locals, &block)
|
@@ -789,18 +800,6 @@ module Sinatra
|
|
789
800
|
render(:slim, template, options, locals, &block)
|
790
801
|
end
|
791
802
|
|
792
|
-
def creole(template, options = {}, locals = {})
|
793
|
-
render :creole, template, options, locals
|
794
|
-
end
|
795
|
-
|
796
|
-
def mediawiki(template, options = {}, locals = {})
|
797
|
-
render :mediawiki, template, options, locals
|
798
|
-
end
|
799
|
-
|
800
|
-
def wlang(template, options = {}, locals = {}, &block)
|
801
|
-
render(:wlang, template, options, locals, &block)
|
802
|
-
end
|
803
|
-
|
804
803
|
def yajl(template, options = {}, locals = {})
|
805
804
|
options[:default_content_type] = :json
|
806
805
|
render :yajl, template, options, locals
|
@@ -825,24 +824,27 @@ module Sinatra
|
|
825
824
|
|
826
825
|
# logic shared between builder and nokogiri
|
827
826
|
def render_ruby(engine, template, options = {}, locals = {}, &block)
|
828
|
-
|
829
|
-
|
827
|
+
if template.is_a?(Hash)
|
828
|
+
options = template
|
829
|
+
template = nil
|
830
|
+
end
|
831
|
+
template = proc { block } if template.nil?
|
830
832
|
render engine, template, options, locals
|
831
833
|
end
|
832
834
|
|
833
835
|
def render(engine, data, options = {}, locals = {}, &block)
|
834
836
|
# merge app-level options
|
835
837
|
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
|
836
|
-
options.merge!(engine_options) { |
|
838
|
+
options.merge!(engine_options) { |_key, v1, _v2| v1 }
|
837
839
|
|
838
840
|
# extract generic options
|
839
841
|
locals = options.delete(:locals) || locals || {}
|
840
|
-
views = options.delete(:views) || settings.views ||
|
842
|
+
views = options.delete(:views) || settings.views || './views'
|
841
843
|
layout = options[:layout]
|
842
844
|
layout = false if layout.nil? && options.include?(:layout)
|
843
845
|
eat_errors = layout.nil?
|
844
|
-
layout = engine_options[:layout] if layout.nil?
|
845
|
-
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)
|
846
848
|
layout_options = options.delete(:layout_options) || {}
|
847
849
|
content_type = options.delete(:default_content_type)
|
848
850
|
content_type = options.delete(:content_type) || content_type
|
@@ -867,12 +869,17 @@ module Sinatra
|
|
867
869
|
|
868
870
|
# render layout
|
869
871
|
if layout
|
870
|
-
|
871
|
-
|
872
|
+
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
|
873
|
+
options = options.merge(extra_options).merge!(layout_options)
|
874
|
+
|
872
875
|
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
873
876
|
end
|
874
877
|
|
875
|
-
|
878
|
+
if content_type
|
879
|
+
# sass-embedded returns a frozen string
|
880
|
+
output = +output
|
881
|
+
output.extend(ContentTyped).content_type = content_type
|
882
|
+
end
|
876
883
|
output
|
877
884
|
end
|
878
885
|
|
@@ -893,12 +900,13 @@ module Sinatra
|
|
893
900
|
@preferred_extension = engine.to_s
|
894
901
|
find_template(views, data, template) do |file|
|
895
902
|
path ||= file # keep the initial path rather than the last one
|
896
|
-
|
903
|
+
found = File.exist?(file)
|
904
|
+
if found
|
897
905
|
path = file
|
898
906
|
break
|
899
907
|
end
|
900
908
|
end
|
901
|
-
throw :layout_missing if eat_errors
|
909
|
+
throw :layout_missing if eat_errors && !found
|
902
910
|
template.new(path, 1, options)
|
903
911
|
end
|
904
912
|
end
|
@@ -914,13 +922,44 @@ module Sinatra
|
|
914
922
|
end
|
915
923
|
|
916
924
|
def compile_block_template(template, options, &body)
|
917
|
-
|
918
|
-
path =
|
919
|
-
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
|
920
930
|
template.new(path, line.to_i, options, &body)
|
921
931
|
end
|
922
932
|
end
|
923
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
|
+
|
924
963
|
# Base class for all Sinatra applications and middleware.
|
925
964
|
class Base
|
926
965
|
include Rack::Utils
|
@@ -932,10 +971,10 @@ module Sinatra
|
|
932
971
|
attr_accessor :app, :env, :request, :response, :params
|
933
972
|
attr_reader :template_cache
|
934
973
|
|
935
|
-
def initialize(app = nil, **
|
974
|
+
def initialize(app = nil, **_kwargs)
|
936
975
|
super()
|
937
976
|
@app = app
|
938
|
-
@template_cache =
|
977
|
+
@template_cache = TemplateCache.new
|
939
978
|
@pinned_response = nil # whether a before! filter pinned the content-type
|
940
979
|
yield self if block_given?
|
941
980
|
end
|
@@ -959,7 +998,7 @@ module Sinatra
|
|
959
998
|
unless @response['Content-Type']
|
960
999
|
if Array === body && body[0].respond_to?(:content_type)
|
961
1000
|
content_type body[0].content_type
|
962
|
-
elsif default = settings.default_content_type
|
1001
|
+
elsif (default = settings.default_content_type)
|
963
1002
|
content_type default
|
964
1003
|
end
|
965
1004
|
end
|
@@ -977,12 +1016,6 @@ module Sinatra
|
|
977
1016
|
self.class.settings
|
978
1017
|
end
|
979
1018
|
|
980
|
-
def options
|
981
|
-
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
982
|
-
"use #settings instead."
|
983
|
-
settings
|
984
|
-
end
|
985
|
-
|
986
1019
|
# Exit the current block, halts any further processing
|
987
1020
|
# of the request, and returns the specified response.
|
988
1021
|
def halt(*response)
|
@@ -999,7 +1032,8 @@ module Sinatra
|
|
999
1032
|
|
1000
1033
|
# Forward the request to the downstream app -- middleware only.
|
1001
1034
|
def forward
|
1002
|
-
|
1035
|
+
raise 'downstream app not set' unless @app.respond_to? :call
|
1036
|
+
|
1003
1037
|
status, headers, body = @app.call env
|
1004
1038
|
@response.status = status
|
1005
1039
|
@response.body = body
|
@@ -1021,18 +1055,18 @@ module Sinatra
|
|
1021
1055
|
|
1022
1056
|
# Run routes defined on the class and all superclasses.
|
1023
1057
|
def route!(base = settings, pass_block = nil)
|
1024
|
-
|
1025
|
-
routes.each do |pattern, conditions, block|
|
1026
|
-
response.delete_header('Content-Type') unless @pinned_response
|
1058
|
+
routes = base.routes[@request.request_method]
|
1027
1059
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
route_eval { block[*args] }
|
1031
|
-
end
|
1060
|
+
routes&.each do |pattern, conditions, block|
|
1061
|
+
response.delete_header('Content-Type') unless @pinned_response
|
1032
1062
|
|
1033
|
-
|
1034
|
-
|
1063
|
+
returned_pass_block = process_route(pattern, conditions) do |*args|
|
1064
|
+
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
|
1065
|
+
route_eval { block[*args] }
|
1035
1066
|
end
|
1067
|
+
|
1068
|
+
# don't wipe out pass_block in superclass
|
1069
|
+
pass_block = returned_pass_block if returned_pass_block
|
1036
1070
|
end
|
1037
1071
|
|
1038
1072
|
# Run routes defined in superclass.
|
@@ -1056,15 +1090,17 @@ module Sinatra
|
|
1056
1090
|
# Returns pass block.
|
1057
1091
|
def process_route(pattern, conditions, block = nil, values = [])
|
1058
1092
|
route = @request.path_info
|
1059
|
-
route = '/' if route.empty?
|
1093
|
+
route = '/' if route.empty? && !settings.empty_path_info?
|
1060
1094
|
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
|
1061
|
-
return unless params = pattern.params(route)
|
1062
1095
|
|
1063
|
-
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
|
1064
1100
|
force_encoding(params)
|
1065
|
-
@params = @params.merge(params) if params.any?
|
1101
|
+
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
|
1066
1102
|
|
1067
|
-
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) })
|
1068
1104
|
if regexp_exists
|
1069
1105
|
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
|
1070
1106
|
values += captures
|
@@ -1077,7 +1113,7 @@ module Sinatra
|
|
1077
1113
|
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
1078
1114
|
block ? block[self, values] : yield(self, values)
|
1079
1115
|
end
|
1080
|
-
rescue
|
1116
|
+
rescue StandardError
|
1081
1117
|
@env['sinatra.error.params'] = @params
|
1082
1118
|
raise
|
1083
1119
|
ensure
|
@@ -1091,35 +1127,35 @@ module Sinatra
|
|
1091
1127
|
# a NotFound exception. Subclasses can override this method to perform
|
1092
1128
|
# custom route miss logic.
|
1093
1129
|
def route_missing
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
raise NotFound, "#{request.request_method} #{request.path_info}"
|
1098
|
-
end
|
1130
|
+
raise NotFound unless @app
|
1131
|
+
|
1132
|
+
forward
|
1099
1133
|
end
|
1100
1134
|
|
1101
1135
|
# Attempt to serve static files from public directory. Throws :halt when
|
1102
1136
|
# a matching file is found, returns nil otherwise.
|
1103
1137
|
def static!(options = {})
|
1104
1138
|
return if (public_dir = settings.public_folder).nil?
|
1139
|
+
|
1105
1140
|
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
|
1106
1141
|
return unless valid_path?(path)
|
1107
1142
|
|
1108
1143
|
path = File.expand_path(path)
|
1109
|
-
return unless path.start_with?(File.expand_path(public_dir)
|
1144
|
+
return unless path.start_with?("#{File.expand_path(public_dir)}/")
|
1145
|
+
|
1110
1146
|
return unless File.file?(path)
|
1111
1147
|
|
1112
1148
|
env['sinatra.static_file'] = path
|
1113
1149
|
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
1114
|
-
send_file path, options.merge(:
|
1150
|
+
send_file path, options.merge(disposition: nil)
|
1115
1151
|
end
|
1116
1152
|
|
1117
1153
|
# Run the block with 'throw :halt' support and apply result to the response.
|
1118
|
-
def invoke
|
1119
|
-
res = catch(:halt)
|
1154
|
+
def invoke(&block)
|
1155
|
+
res = catch(:halt, &block)
|
1120
1156
|
|
1121
|
-
res = [res] if Integer === res
|
1122
|
-
if Array === res
|
1157
|
+
res = [res] if (Integer === res) || (String === res)
|
1158
|
+
if (Array === res) && (Integer === res.first)
|
1123
1159
|
res = res.dup
|
1124
1160
|
status(res.shift)
|
1125
1161
|
body(res.pop)
|
@@ -1135,6 +1171,7 @@ module Sinatra
|
|
1135
1171
|
# Avoid passing frozen string in force_encoding
|
1136
1172
|
@params.merge!(@request.params).each do |key, val|
|
1137
1173
|
next unless val.respond_to?(:force_encoding)
|
1174
|
+
|
1138
1175
|
val = val.dup if val.frozen?
|
1139
1176
|
@params[key] = force_encoding(val)
|
1140
1177
|
end
|
@@ -1146,39 +1183,43 @@ module Sinatra
|
|
1146
1183
|
end
|
1147
1184
|
route!
|
1148
1185
|
end
|
1149
|
-
rescue ::Exception =>
|
1150
|
-
invoke { handle_exception!(
|
1186
|
+
rescue ::Exception => e
|
1187
|
+
invoke { handle_exception!(e) }
|
1151
1188
|
ensure
|
1152
1189
|
begin
|
1153
1190
|
filter! :after unless env['sinatra.static_file']
|
1154
|
-
rescue ::Exception =>
|
1155
|
-
invoke { handle_exception!(
|
1191
|
+
rescue ::Exception => e
|
1192
|
+
invoke { handle_exception!(e) } unless @env['sinatra.error']
|
1156
1193
|
end
|
1157
1194
|
end
|
1158
1195
|
|
1159
1196
|
# Error handling during requests.
|
1160
1197
|
def handle_exception!(boom)
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1198
|
+
error_params = @env['sinatra.error.params']
|
1199
|
+
|
1200
|
+
@params = @params.merge(error_params) if error_params
|
1201
|
+
|
1164
1202
|
@env['sinatra.error'] = boom
|
1165
1203
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
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)
|
1173
1214
|
|
1174
1215
|
if server_error?
|
1175
1216
|
dump_errors! boom if settings.dump_errors?
|
1176
|
-
raise boom if settings.show_exceptions?
|
1217
|
+
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
|
1177
1218
|
elsif not_found?
|
1178
1219
|
headers['X-Cascade'] = 'pass' if settings.x_cascade?
|
1179
1220
|
end
|
1180
1221
|
|
1181
|
-
if res = error_block!(boom.class, boom) || error_block!(status, boom)
|
1222
|
+
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
|
1182
1223
|
return res
|
1183
1224
|
end
|
1184
1225
|
|
@@ -1187,12 +1228,14 @@ module Sinatra
|
|
1187
1228
|
body Rack::Utils.escape_html(boom.message)
|
1188
1229
|
else
|
1189
1230
|
content_type 'text/html'
|
1190
|
-
body
|
1231
|
+
body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
|
1191
1232
|
end
|
1192
1233
|
end
|
1193
1234
|
|
1194
1235
|
return unless server_error?
|
1195
|
-
|
1236
|
+
|
1237
|
+
raise boom if settings.raise_errors? || settings.show_exceptions?
|
1238
|
+
|
1196
1239
|
error_block! Exception, boom
|
1197
1240
|
end
|
1198
1241
|
|
@@ -1200,7 +1243,10 @@ module Sinatra
|
|
1200
1243
|
def error_block!(key, *block_params)
|
1201
1244
|
base = settings
|
1202
1245
|
while base.respond_to?(:errors)
|
1203
|
-
|
1246
|
+
args_array = base.errors[key]
|
1247
|
+
|
1248
|
+
next base = base.superclass unless args_array
|
1249
|
+
|
1204
1250
|
args_array.reverse_each do |args|
|
1205
1251
|
first = args == args_array.first
|
1206
1252
|
args += [block_params]
|
@@ -1208,34 +1254,42 @@ module Sinatra
|
|
1208
1254
|
return resp unless resp.nil? && !first
|
1209
1255
|
end
|
1210
1256
|
end
|
1211
|
-
return false unless key.respond_to?
|
1257
|
+
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
|
1258
|
+
|
1212
1259
|
error_block!(key.superclass, *block_params)
|
1213
1260
|
end
|
1214
1261
|
|
1215
1262
|
def dump_errors!(boom)
|
1216
|
-
|
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")
|
1217
1276
|
@env['rack.errors'].puts(msg)
|
1218
1277
|
end
|
1219
1278
|
|
1220
1279
|
class << self
|
1221
1280
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
1222
|
-
|
1223
|
-
/
|
1281
|
+
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
|
1282
|
+
%r{lib/tilt.*\.rb$}, # all tilt code
|
1224
1283
|
/^\(.*\)$/, # generated code
|
1225
|
-
|
1284
|
+
/\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
|
1285
|
+
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
|
1226
1286
|
/active_support/, # active_support require hacks
|
1227
|
-
|
1287
|
+
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
|
1228
1288
|
/<internal:/, # internal in ruby >= 1.9.2
|
1229
|
-
/
|
1230
|
-
]
|
1231
|
-
|
1232
|
-
# contrary to what the comment said previously, rubinius never supported this
|
1233
|
-
if defined?(RUBY_IGNORE_CALLERS)
|
1234
|
-
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1235
|
-
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1236
|
-
end
|
1289
|
+
%r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
|
1290
|
+
].freeze
|
1237
1291
|
|
1238
|
-
attr_reader :routes, :filters, :templates, :errors
|
1292
|
+
attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
|
1239
1293
|
|
1240
1294
|
def callers_to_ignore
|
1241
1295
|
CALLERS_TO_IGNORE
|
@@ -1246,17 +1300,17 @@ module Sinatra
|
|
1246
1300
|
def reset!
|
1247
1301
|
@conditions = []
|
1248
1302
|
@routes = {}
|
1249
|
-
@filters = {:
|
1303
|
+
@filters = { before: [], after: [] }
|
1250
1304
|
@errors = {}
|
1251
1305
|
@middleware = []
|
1252
1306
|
@prototype = nil
|
1253
1307
|
@extensions = []
|
1254
1308
|
|
1255
|
-
if superclass.respond_to?(:templates)
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1309
|
+
@templates = if superclass.respond_to?(:templates)
|
1310
|
+
Hash.new { |_hash, key| superclass.templates[key] }
|
1311
|
+
else
|
1312
|
+
{}
|
1313
|
+
end
|
1260
1314
|
end
|
1261
1315
|
|
1262
1316
|
# Extension modules registered on this class and all superclasses.
|
@@ -1280,16 +1334,21 @@ module Sinatra
|
|
1280
1334
|
# Sets an option to the given value. If the value is a proc,
|
1281
1335
|
# the proc will be called every time the option is accessed.
|
1282
1336
|
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1283
|
-
raise ArgumentError if block
|
1284
|
-
|
1337
|
+
raise ArgumentError if block && !not_set
|
1338
|
+
|
1339
|
+
if block
|
1340
|
+
value = block
|
1341
|
+
not_set = false
|
1342
|
+
end
|
1285
1343
|
|
1286
1344
|
if not_set
|
1287
1345
|
raise ArgumentError unless option.respond_to?(:each)
|
1288
|
-
|
1346
|
+
|
1347
|
+
option.each { |k, v| set(k, v) }
|
1289
1348
|
return self
|
1290
1349
|
end
|
1291
1350
|
|
1292
|
-
if respond_to?("#{option}=")
|
1351
|
+
if respond_to?("#{option}=") && !ignore_setter
|
1293
1352
|
return __send__("#{option}=", value)
|
1294
1353
|
end
|
1295
1354
|
|
@@ -1328,7 +1387,7 @@ module Sinatra
|
|
1328
1387
|
# class, or an HTTP status code to specify which errors should be
|
1329
1388
|
# handled.
|
1330
1389
|
def error(*codes, &block)
|
1331
|
-
args = compile!
|
1390
|
+
args = compile! 'ERROR', /.*/, block
|
1332
1391
|
codes = codes.flat_map(&method(:Array))
|
1333
1392
|
codes << Exception if codes.empty?
|
1334
1393
|
codes << Sinatra::NotFound if codes.include?(404)
|
@@ -1354,7 +1413,7 @@ module Sinatra
|
|
1354
1413
|
# Load embedded templates from the file; uses the caller's __FILE__
|
1355
1414
|
# when no file is specified.
|
1356
1415
|
def inline_templates=(file = nil)
|
1357
|
-
file = (
|
1416
|
+
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
|
1358
1417
|
|
1359
1418
|
begin
|
1360
1419
|
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
@@ -1363,23 +1422,24 @@ module Sinatra
|
|
1363
1422
|
app, data = nil
|
1364
1423
|
end
|
1365
1424
|
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
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
|
1383
1443
|
end
|
1384
1444
|
end
|
1385
1445
|
end
|
@@ -1388,8 +1448,10 @@ module Sinatra
|
|
1388
1448
|
def mime_type(type, value = nil)
|
1389
1449
|
return type if type.nil?
|
1390
1450
|
return type.to_s if type.to_s.include?('/')
|
1391
|
-
|
1451
|
+
|
1452
|
+
type = ".#{type}" unless type.to_s[0] == '.'
|
1392
1453
|
return Rack::Mime.mime_type(type, nil) unless value
|
1454
|
+
|
1393
1455
|
Rack::Mime::MIME_TYPES[type] = value
|
1394
1456
|
end
|
1395
1457
|
|
@@ -1398,7 +1460,7 @@ module Sinatra
|
|
1398
1460
|
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1399
1461
|
def mime_types(type)
|
1400
1462
|
type = mime_type type
|
1401
|
-
type =~
|
1463
|
+
type =~ %r{^application/(xml|javascript)$} ? [type, "text/#{$1}"] : [type]
|
1402
1464
|
end
|
1403
1465
|
|
1404
1466
|
# Define a before filter; runs before all requests within the same
|
@@ -1420,6 +1482,14 @@ module Sinatra
|
|
1420
1482
|
filters[type] << compile!(type, path, block, **options)
|
1421
1483
|
end
|
1422
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
|
+
|
1423
1493
|
# Add a route condition. The route is considered non-matching when the
|
1424
1494
|
# block returns false.
|
1425
1495
|
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
|
@@ -1427,7 +1497,7 @@ module Sinatra
|
|
1427
1497
|
end
|
1428
1498
|
|
1429
1499
|
def public=(value)
|
1430
|
-
|
1500
|
+
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
|
1431
1501
|
set(:public_folder, value)
|
1432
1502
|
end
|
1433
1503
|
|
@@ -1449,14 +1519,21 @@ module Sinatra
|
|
1449
1519
|
route('HEAD', path, opts, &block)
|
1450
1520
|
end
|
1451
1521
|
|
1452
|
-
def put(path, opts = {}, &
|
1453
|
-
|
1454
|
-
def
|
1455
|
-
|
1456
|
-
def
|
1457
|
-
|
1458
|
-
def
|
1459
|
-
|
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
|
1460
1537
|
|
1461
1538
|
# Makes the methods defined in the block and in the Modules given
|
1462
1539
|
# in `extensions` available to the handlers and templates
|
@@ -1496,37 +1573,41 @@ module Sinatra
|
|
1496
1573
|
# Stop the self-hosted server if running.
|
1497
1574
|
def quit!
|
1498
1575
|
return unless running?
|
1576
|
+
|
1499
1577
|
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1500
1578
|
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
|
1501
|
-
|
1579
|
+
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
|
1502
1580
|
set :running_server, nil
|
1503
1581
|
set :handler_name, nil
|
1582
|
+
|
1583
|
+
on_stop_callback.call unless on_stop_callback.nil?
|
1504
1584
|
end
|
1505
1585
|
|
1506
|
-
|
1586
|
+
alias stop! quit!
|
1507
1587
|
|
1508
1588
|
# Run the Sinatra app as a self-hosted server using
|
1509
|
-
# Puma,
|
1589
|
+
# Puma, Falcon, or WEBrick (in that order). If given a block, will call
|
1510
1590
|
# with the constructed handler once we have taken the stage.
|
1511
1591
|
def run!(options = {}, &block)
|
1512
1592
|
return if running?
|
1593
|
+
|
1513
1594
|
set options
|
1514
1595
|
handler = Rack::Handler.pick(server)
|
1515
1596
|
handler_name = handler.name.gsub(/.*::/, '')
|
1516
1597
|
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
|
1517
|
-
server_settings.merge!(:
|
1598
|
+
server_settings.merge!(Port: port, Host: bind)
|
1518
1599
|
|
1519
1600
|
begin
|
1520
1601
|
start_server(handler, server_settings, handler_name, &block)
|
1521
1602
|
rescue Errno::EADDRINUSE
|
1522
|
-
|
1603
|
+
warn "== Someone is already performing on port #{port}!"
|
1523
1604
|
raise
|
1524
1605
|
ensure
|
1525
1606
|
quit!
|
1526
1607
|
end
|
1527
1608
|
end
|
1528
1609
|
|
1529
|
-
|
1610
|
+
alias start! run!
|
1530
1611
|
|
1531
1612
|
# Check whether the self-hosted server is running or not.
|
1532
1613
|
def running?
|
@@ -1544,8 +1625,8 @@ module Sinatra
|
|
1544
1625
|
# Create a new instance of the class fronted by its middleware
|
1545
1626
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
1546
1627
|
# an instance of the class new was called on.
|
1547
|
-
def new(*args, &
|
1548
|
-
instance = new!(*args, &
|
1628
|
+
def new(*args, &block)
|
1629
|
+
instance = new!(*args, &block)
|
1549
1630
|
Wrapper.new(build(instance).to_app, instance)
|
1550
1631
|
end
|
1551
1632
|
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
|
@@ -1570,12 +1651,6 @@ module Sinatra
|
|
1570
1651
|
cleaned_caller(1).flatten
|
1571
1652
|
end
|
1572
1653
|
|
1573
|
-
# Like caller_files, but containing Arrays rather than strings with the
|
1574
|
-
# first element being the file, and the second being the line.
|
1575
|
-
def caller_locations
|
1576
|
-
cleaned_caller 2
|
1577
|
-
end
|
1578
|
-
|
1579
1654
|
private
|
1580
1655
|
|
1581
1656
|
# Starts the server by running the Rack Handler.
|
@@ -1586,14 +1661,14 @@ module Sinatra
|
|
1586
1661
|
# Run the instance we created:
|
1587
1662
|
handler.run(self, **server_settings) do |server|
|
1588
1663
|
unless suppress_messages?
|
1589
|
-
|
1664
|
+
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
|
1590
1665
|
end
|
1591
1666
|
|
1592
1667
|
setup_traps
|
1593
1668
|
set :running_server, server
|
1594
1669
|
set :handler_name, handler_name
|
1595
1670
|
server.threaded = settings.threaded if server.respond_to? :threaded=
|
1596
|
-
|
1671
|
+
on_start_callback.call unless on_start_callback.nil?
|
1597
1672
|
yield server if block_given?
|
1598
1673
|
end
|
1599
1674
|
end
|
@@ -1603,18 +1678,18 @@ module Sinatra
|
|
1603
1678
|
end
|
1604
1679
|
|
1605
1680
|
def setup_traps
|
1606
|
-
|
1607
|
-
at_exit { quit! }
|
1681
|
+
return unless traps?
|
1608
1682
|
|
1609
|
-
|
1610
|
-
old_handler = trap(signal) do
|
1611
|
-
quit!
|
1612
|
-
old_handler.call if old_handler.respond_to?(:call)
|
1613
|
-
end
|
1614
|
-
end
|
1683
|
+
at_exit { quit! }
|
1615
1684
|
|
1616
|
-
|
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
|
1617
1690
|
end
|
1691
|
+
|
1692
|
+
set :traps, false
|
1618
1693
|
end
|
1619
1694
|
|
1620
1695
|
# Dynamically defines a method on settings.
|
@@ -1642,18 +1717,21 @@ module Sinatra
|
|
1642
1717
|
end
|
1643
1718
|
end
|
1644
1719
|
end
|
1645
|
-
|
1720
|
+
alias agent user_agent
|
1646
1721
|
|
1647
1722
|
# Condition for matching mimetypes. Accepts file extensions.
|
1648
1723
|
def provides(*types)
|
1649
1724
|
types.map! { |t| mime_types(t) }
|
1650
1725
|
types.flatten!
|
1651
1726
|
condition do
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
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)
|
1657
1735
|
true
|
1658
1736
|
else
|
1659
1737
|
false
|
@@ -1662,7 +1740,7 @@ module Sinatra
|
|
1662
1740
|
end
|
1663
1741
|
|
1664
1742
|
def route(verb, path, options = {}, &block)
|
1665
|
-
enable :empty_path_info if path ==
|
1743
|
+
enable :empty_path_info if path == '' && empty_path_info.nil?
|
1666
1744
|
signature = compile!(verb, path, block, **options)
|
1667
1745
|
(@routes[verb] ||= []) << signature
|
1668
1746
|
invoke_hook(:route_added, verb, path, block)
|
@@ -1691,12 +1769,13 @@ module Sinatra
|
|
1691
1769
|
pattern = compile(path, route_mustermann_opts)
|
1692
1770
|
method_name = "#{verb} #{path}"
|
1693
1771
|
unbound_method = generate_method(method_name, &block)
|
1694
|
-
conditions
|
1695
|
-
|
1696
|
-
|
1697
|
-
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) }
|
1698
1777
|
|
1699
|
-
[
|
1778
|
+
[pattern, conditions, wrapper]
|
1700
1779
|
end
|
1701
1780
|
|
1702
1781
|
def compile(path, route_mustermann_opts = {})
|
@@ -1714,7 +1793,7 @@ module Sinatra
|
|
1714
1793
|
end
|
1715
1794
|
|
1716
1795
|
def setup_middleware(builder)
|
1717
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1796
|
+
middleware.each { |c, a, b| builder.use(c, *a, &b) }
|
1718
1797
|
end
|
1719
1798
|
|
1720
1799
|
def setup_logging(builder)
|
@@ -1744,9 +1823,10 @@ module Sinatra
|
|
1744
1823
|
|
1745
1824
|
def setup_protection(builder)
|
1746
1825
|
return unless protection?
|
1826
|
+
|
1747
1827
|
options = Hash === protection ? protection.dup : {}
|
1748
1828
|
options = {
|
1749
|
-
img_src:
|
1829
|
+
img_src: "'self' data:",
|
1750
1830
|
font_src: "'self'"
|
1751
1831
|
}.merge options
|
1752
1832
|
|
@@ -1760,6 +1840,7 @@ module Sinatra
|
|
1760
1840
|
|
1761
1841
|
def setup_sessions(builder)
|
1762
1842
|
return unless sessions?
|
1843
|
+
|
1763
1844
|
options = {}
|
1764
1845
|
options[:secret] = session_secret if session_secret?
|
1765
1846
|
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
@@ -1782,15 +1863,15 @@ module Sinatra
|
|
1782
1863
|
end
|
1783
1864
|
|
1784
1865
|
# used for deprecation warnings
|
1785
|
-
def
|
1786
|
-
|
1866
|
+
def warn_for_deprecation(message)
|
1867
|
+
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1787
1868
|
end
|
1788
1869
|
|
1789
1870
|
# Like Kernel#caller but excluding certain magic entries
|
1790
1871
|
def cleaned_caller(keep = 3)
|
1791
|
-
caller(1)
|
1792
|
-
map!
|
1793
|
-
reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1872
|
+
caller(1)
|
1873
|
+
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
|
1874
|
+
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
|
1794
1875
|
end
|
1795
1876
|
end
|
1796
1877
|
|
@@ -1798,6 +1879,7 @@ module Sinatra
|
|
1798
1879
|
# which is UTF-8 by default
|
1799
1880
|
def self.force_encoding(data, encoding = default_encoding)
|
1800
1881
|
return if data == settings || data.is_a?(Tempfile)
|
1882
|
+
|
1801
1883
|
if data.respond_to? :force_encoding
|
1802
1884
|
data.force_encoding(encoding).encode!
|
1803
1885
|
elsif data.respond_to? :each_value
|
@@ -1808,24 +1890,26 @@ module Sinatra
|
|
1808
1890
|
data
|
1809
1891
|
end
|
1810
1892
|
|
1811
|
-
def force_encoding(*args)
|
1893
|
+
def force_encoding(*args)
|
1894
|
+
settings.force_encoding(*args)
|
1895
|
+
end
|
1812
1896
|
|
1813
1897
|
reset!
|
1814
1898
|
|
1815
1899
|
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
1816
|
-
set :raise_errors,
|
1817
|
-
set :dump_errors,
|
1818
|
-
set :show_exceptions,
|
1900
|
+
set :raise_errors, proc { test? }
|
1901
|
+
set :dump_errors, proc { !test? }
|
1902
|
+
set :show_exceptions, proc { development? }
|
1819
1903
|
set :sessions, false
|
1820
|
-
set :session_store, Rack::
|
1904
|
+
set :session_store, Rack::Protection::EncryptedCookie
|
1821
1905
|
set :logging, false
|
1822
1906
|
set :protection, true
|
1823
1907
|
set :method_override, false
|
1824
1908
|
set :use_code, false
|
1825
|
-
set :default_encoding,
|
1909
|
+
set :default_encoding, 'utf-8'
|
1826
1910
|
set :x_cascade, true
|
1827
1911
|
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
|
1828
|
-
settings.add_charset <<
|
1912
|
+
settings.add_charset << %r{^text/}
|
1829
1913
|
set :mustermann_opts, {}
|
1830
1914
|
set :default_content_type, 'text/html'
|
1831
1915
|
|
@@ -1833,14 +1917,15 @@ module Sinatra
|
|
1833
1917
|
begin
|
1834
1918
|
require 'securerandom'
|
1835
1919
|
set :session_secret, SecureRandom.hex(64)
|
1836
|
-
rescue LoadError, NotImplementedError
|
1920
|
+
rescue LoadError, NotImplementedError, RuntimeError
|
1837
1921
|
# SecureRandom raises a NotImplementedError if no random device is available
|
1838
|
-
|
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))
|
1839
1924
|
end
|
1840
1925
|
|
1841
1926
|
class << self
|
1842
|
-
|
1843
|
-
|
1927
|
+
alias methodoverride? method_override?
|
1928
|
+
alias methodoverride= method_override=
|
1844
1929
|
end
|
1845
1930
|
|
1846
1931
|
set :run, false # start server via at-exit hook?
|
@@ -1848,21 +1933,16 @@ module Sinatra
|
|
1848
1933
|
set :handler_name, nil
|
1849
1934
|
set :traps, true
|
1850
1935
|
set :server, %w[HTTP webrick]
|
1851
|
-
set :bind,
|
1936
|
+
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
|
1852
1937
|
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
|
1853
1938
|
set :quiet, false
|
1854
1939
|
|
1855
1940
|
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
|
1856
1941
|
|
1857
|
-
if ruby_engine
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
server.unshift 'puma'
|
1862
|
-
server.unshift 'mongrel' if ruby_engine.nil?
|
1863
|
-
server.unshift 'thin' if ruby_engine != 'jruby'
|
1864
|
-
server.unshift 'trinidad' if ruby_engine == 'jruby'
|
1865
|
-
end
|
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'
|
1866
1946
|
|
1867
1947
|
set :absolute_redirects, true
|
1868
1948
|
set :prefixed_redirects, false
|
@@ -1870,14 +1950,14 @@ module Sinatra
|
|
1870
1950
|
set :strict_paths, true
|
1871
1951
|
|
1872
1952
|
set :app_file, nil
|
1873
|
-
set :root,
|
1874
|
-
set :views,
|
1875
|
-
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? }
|
1876
1956
|
set :lock, false
|
1877
1957
|
set :threaded, true
|
1878
1958
|
|
1879
|
-
set :public_folder,
|
1880
|
-
set :static,
|
1959
|
+
set :public_folder, proc { root && File.join(root, 'public') }
|
1960
|
+
set :static, proc { public_folder && File.exist?(public_folder) }
|
1881
1961
|
set :static_cache_control, false
|
1882
1962
|
|
1883
1963
|
error ::Exception do
|
@@ -1896,7 +1976,7 @@ module Sinatra
|
|
1896
1976
|
error NotFound do
|
1897
1977
|
content_type 'text/html'
|
1898
1978
|
|
1899
|
-
if
|
1979
|
+
if instance_of?(Sinatra::Application)
|
1900
1980
|
code = <<-RUBY.gsub(/^ {12}/, '')
|
1901
1981
|
#{request.request_method.downcase} '#{request.path_info}' do
|
1902
1982
|
"Hello World"
|
@@ -1911,11 +1991,11 @@ module Sinatra
|
|
1911
1991
|
end
|
1912
1992
|
RUBY
|
1913
1993
|
|
1914
|
-
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{^/}, '')
|
1915
1995
|
code = "# in #{file}\n#{code}" unless file.empty?
|
1916
1996
|
end
|
1917
1997
|
|
1918
|
-
|
1998
|
+
<<-HTML.gsub(/^ {10}/, '')
|
1919
1999
|
<!DOCTYPE html>
|
1920
2000
|
<html>
|
1921
2001
|
<head>
|
@@ -1927,7 +2007,7 @@ module Sinatra
|
|
1927
2007
|
</head>
|
1928
2008
|
<body>
|
1929
2009
|
<h2>Sinatra doesn’t know this ditty.</h2>
|
1930
|
-
<img src='#{
|
2010
|
+
<img src='#{request.script_name}/__sinatra__/404.png'>
|
1931
2011
|
<div id="c">
|
1932
2012
|
Try this:
|
1933
2013
|
<pre>#{Rack::Utils.escape_html(code)}</pre>
|
@@ -1947,12 +2027,12 @@ module Sinatra
|
|
1947
2027
|
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1948
2028
|
# modular applications.
|
1949
2029
|
class Application < Base
|
1950
|
-
set :logging,
|
2030
|
+
set :logging, proc { !test? }
|
1951
2031
|
set :method_override, true
|
1952
|
-
set :run,
|
2032
|
+
set :run, proc { !test? }
|
1953
2033
|
set :app_file, nil
|
1954
2034
|
|
1955
|
-
def self.register(*extensions, &block)
|
2035
|
+
def self.register(*extensions, &block) # :nodoc:
|
1956
2036
|
added_methods = extensions.flat_map(&:public_instance_methods)
|
1957
2037
|
Delegator.delegate(*added_methods)
|
1958
2038
|
super(*extensions, &block)
|
@@ -1962,11 +2042,12 @@ module Sinatra
|
|
1962
2042
|
# Sinatra delegation mixin. Mixing this module into an object causes all
|
1963
2043
|
# methods to be delegated to the Sinatra::Application class. Used primarily
|
1964
2044
|
# at the top-level.
|
1965
|
-
module Delegator
|
2045
|
+
module Delegator # :nodoc:
|
1966
2046
|
def self.delegate(*methods)
|
1967
2047
|
methods.each do |method_name|
|
1968
2048
|
define_method(method_name) do |*args, &block|
|
1969
2049
|
return super(*args, &block) if respond_to? method_name
|
2050
|
+
|
1970
2051
|
Delegator.target.send(method_name, *args, &block)
|
1971
2052
|
end
|
1972
2053
|
# ensure keyword argument passing is compatible with ruby >= 2.7
|
@@ -1978,7 +2059,7 @@ module Sinatra
|
|
1978
2059
|
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
|
1979
2060
|
:template, :layout, :before, :after, :error, :not_found, :configure,
|
1980
2061
|
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
|
1981
|
-
:production?, :helpers, :settings, :register
|
2062
|
+
:production?, :helpers, :settings, :register, :on_start, :on_stop
|
1982
2063
|
|
1983
2064
|
class << self
|
1984
2065
|
attr_accessor :target
|
@@ -1989,7 +2070,8 @@ module Sinatra
|
|
1989
2070
|
|
1990
2071
|
class Wrapper
|
1991
2072
|
def initialize(stack, instance)
|
1992
|
-
@stack
|
2073
|
+
@stack = stack
|
2074
|
+
@instance = instance
|
1993
2075
|
end
|
1994
2076
|
|
1995
2077
|
def settings
|