sinatra 2.2.3 → 4.0.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.
data/lib/sinatra/base.rb CHANGED
@@ -1,16 +1,19 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  # external dependencies
5
4
  require 'rack'
5
+ begin
6
+ require 'rackup'
7
+ rescue LoadError
8
+ end
6
9
  require 'tilt'
7
10
  require 'rack/protection'
11
+ require 'rack/session'
8
12
  require 'mustermann'
9
13
  require 'mustermann/sinatra'
10
14
  require 'mustermann/regular'
11
15
 
12
16
  # stdlib dependencies
13
- require 'thread'
14
17
  require 'time'
15
18
  require 'uri'
16
19
 
@@ -21,21 +24,22 @@ require 'sinatra/version'
21
24
 
22
25
  module Sinatra
23
26
  # The request object. See Rack::Request for more info:
24
- # http://rubydoc.info/github/rack/rack/master/Rack/Request
27
+ # https://rubydoc.info/github/rack/rack/main/Rack/Request
25
28
  class Request < Rack::Request
26
- HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
27
- HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
29
+ HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
30
+ HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
28
31
 
29
32
  # Returns an array of acceptable media types for the response
30
33
  def accept
31
- @env['sinatra.accept'] ||= begin
32
- if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != ''
33
- @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS).
34
- map! { |e| AcceptEntry.new(e) }.sort
35
- else
36
- [AcceptEntry.new('*/*')]
37
- end
38
- end
34
+ @env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
35
+ @env['HTTP_ACCEPT']
36
+ .to_s
37
+ .scan(HEADER_VALUE_WITH_PARAMS)
38
+ .map! { |e| AcceptEntry.new(e) }
39
+ .sort
40
+ else
41
+ [AcceptEntry.new('*/*')]
42
+ end
39
43
  end
40
44
 
41
45
  def accept?(type)
@@ -44,8 +48,10 @@ module Sinatra
44
48
 
45
49
  def preferred_type(*types)
46
50
  return accept.first if types.empty?
51
+
47
52
  types.flatten!
48
53
  return types.first if accept.empty?
54
+
49
55
  accept.detect do |accept_header|
50
56
  type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
51
57
  return type if type
@@ -55,23 +61,23 @@ module Sinatra
55
61
  alias secure? ssl?
56
62
 
57
63
  def forwarded?
58
- @env.include? "HTTP_X_FORWARDED_HOST"
64
+ @env.include? 'HTTP_X_FORWARDED_HOST'
59
65
  end
60
66
 
61
67
  def safe?
62
- get? or head? or options? or trace?
68
+ get? || head? || options? || trace?
63
69
  end
64
70
 
65
71
  def idempotent?
66
- safe? or put? or delete? or link? or unlink?
72
+ safe? || put? || delete? || link? || unlink?
67
73
  end
68
74
 
69
75
  def link?
70
- request_method == "LINK"
76
+ request_method == 'LINK'
71
77
  end
72
78
 
73
79
  def unlink?
74
- request_method == "UNLINK"
80
+ request_method == 'UNLINK'
75
81
  end
76
82
 
77
83
  def params
@@ -95,17 +101,17 @@ module Sinatra
95
101
 
96
102
  @entry = entry
97
103
  @type = entry[/[^;]+/].delete(' ')
98
- @params = Hash[params]
104
+ @params = params.to_h
99
105
  @q = @params.delete('q') { 1.0 }.to_f
100
106
  end
101
107
 
102
108
  def <=>(other)
103
- other.priority <=> self.priority
109
+ other.priority <=> priority
104
110
  end
105
111
 
106
112
  def priority
107
113
  # We sort in descending order; better matches should be higher.
108
- [ @q, -@type.count('*'), @params.size ]
114
+ [@q, -@type.count('*'), @params.size]
109
115
  end
110
116
 
111
117
  def to_str
@@ -117,7 +123,7 @@ module Sinatra
117
123
  end
118
124
 
119
125
  def respond_to?(*args)
120
- super or to_str.respond_to?(*args)
126
+ super || to_str.respond_to?(*args)
121
127
  end
122
128
 
123
129
  def method_missing(*args, &block)
@@ -136,7 +142,7 @@ module Sinatra
136
142
  end
137
143
 
138
144
  @type = entry[/[^;]+/].delete(' ')
139
- @params = Hash[params]
145
+ @params = params.to_h
140
146
  end
141
147
 
142
148
  def accepts?(entry)
@@ -150,17 +156,17 @@ module Sinatra
150
156
  def matches_params?(params)
151
157
  return true if @params.empty?
152
158
 
153
- params.all? { |k,v| !@params.has_key?(k) || @params[k] == v }
159
+ params.all? { |k, v| !@params.key?(k) || @params[k] == v }
154
160
  end
155
161
  end
156
162
  end
157
163
 
158
164
  # The response object. See Rack::Response and Rack::Response::Helpers for
159
165
  # more info:
160
- # http://rubydoc.info/github/rack/rack/master/Rack/Response
161
- # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
166
+ # https://rubydoc.info/github/rack/rack/main/Rack/Response
167
+ # https://rubydoc.info/github/rack/rack/main/Rack/Response/Helpers
162
168
  class Response < Rack::Response
163
- DROP_BODY_RESPONSES = [204, 304]
169
+ DROP_BODY_RESPONSES = [204, 304].freeze
164
170
 
165
171
  def body=(value)
166
172
  value = value.body while Rack::Response === value
@@ -175,8 +181,8 @@ module Sinatra
175
181
  result = body
176
182
 
177
183
  if drop_content_info?
178
- headers.delete "Content-Length"
179
- headers.delete "Content-Type"
184
+ headers.delete 'content-length'
185
+ headers.delete 'content-type'
180
186
  end
181
187
 
182
188
  if drop_body?
@@ -185,9 +191,9 @@ module Sinatra
185
191
  end
186
192
 
187
193
  if calculate_content_length?
188
- # if some other code has already set Content-Length, don't muck with it
194
+ # if some other code has already set content-length, don't muck with it
189
195
  # currently, this would be the static file-handler
190
- headers["Content-Length"] = body.map(&:bytesize).reduce(0, :+).to_s
196
+ headers['content-length'] = body.map(&:bytesize).reduce(0, :+).to_s
191
197
  end
192
198
 
193
199
  [status, headers, result]
@@ -196,11 +202,11 @@ module Sinatra
196
202
  private
197
203
 
198
204
  def calculate_content_length?
199
- headers["Content-Type"] and not headers["Content-Length"] and Array === body
205
+ headers['content-type'] && !headers['content-length'] && (Array === body)
200
206
  end
201
207
 
202
208
  def drop_content_info?
203
- informational? or drop_body?
209
+ informational? || drop_body?
204
210
  end
205
211
 
206
212
  def drop_body?
@@ -208,15 +214,17 @@ module Sinatra
208
214
  end
209
215
  end
210
216
 
211
- # Some Rack handlers (Rainbows!) implement an extended body object protocol, however,
217
+ # Some Rack handlers implement an extended body object protocol, however,
212
218
  # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
213
219
  # This middleware will detect an extended body object and will make sure it reaches the
214
220
  # handler directly. We do this here, so our middleware and middleware set up by the app will
215
221
  # still be able to run.
216
222
  class ExtendedRack < Struct.new(:app)
217
223
  def call(env)
218
- result, callback = app.call(env), env['async.callback']
219
- return result unless callback and async?(*result)
224
+ result = app.call(env)
225
+ callback = env['async.callback']
226
+ return result unless callback && async?(*result)
227
+
220
228
  after_response { callback.call result }
221
229
  setup_close(env, *result)
222
230
  throw :async
@@ -224,20 +232,23 @@ module Sinatra
224
232
 
225
233
  private
226
234
 
227
- def setup_close(env, status, headers, body)
228
- return unless body.respond_to? :close and env.include? 'async.close'
235
+ def setup_close(env, _status, _headers, body)
236
+ return unless body.respond_to?(:close) && env.include?('async.close')
237
+
229
238
  env['async.close'].callback { body.close }
230
239
  env['async.close'].errback { body.close }
231
240
  end
232
241
 
233
242
  def after_response(&block)
234
- raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine
243
+ raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
244
+
235
245
  EventMachine.next_tick(&block)
236
246
  end
237
247
 
238
- def async?(status, headers, body)
248
+ def async?(status, _headers, body)
239
249
  return true if status == -1
240
- body.respond_to? :callback and body.respond_to? :errback
250
+
251
+ body.respond_to?(:callback) && body.respond_to?(:errback)
241
252
  end
242
253
  end
243
254
 
@@ -249,7 +260,7 @@ module Sinatra
249
260
  end
250
261
 
251
262
  superclass.class_eval do
252
- alias call_without_check call unless method_defined? :call_without_check
263
+ alias_method :call_without_check, :call unless method_defined? :call_without_check
253
264
  def call(env)
254
265
  env['sinatra.commonlogger'] = true
255
266
  call_without_check(env)
@@ -257,11 +268,14 @@ module Sinatra
257
268
  end
258
269
  end
259
270
 
260
- class BadRequest < TypeError #:nodoc:
271
+ class Error < StandardError # :nodoc:
272
+ end
273
+
274
+ class BadRequest < Error # :nodoc:
261
275
  def http_status; 400 end
262
276
  end
263
277
 
264
- class NotFound < NameError #:nodoc:
278
+ class NotFound < Error # :nodoc:
265
279
  def http_status; 404 end
266
280
  end
267
281
 
@@ -280,10 +294,8 @@ module Sinatra
280
294
  def block.each; yield(call) end
281
295
  response.body = block
282
296
  elsif value
283
- # Rack 2.0 returns a Rack::File::Iterator here instead of
284
- # Rack::File as it was in the previous API.
285
- unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
286
- headers.delete 'Content-Length'
297
+ unless request.head? || value.is_a?(Rack::Files::Iterator) || value.is_a?(Stream)
298
+ headers.delete 'content-length'
287
299
  end
288
300
  response.body = value
289
301
  else
@@ -293,7 +305,10 @@ module Sinatra
293
305
 
294
306
  # Halt processing and redirect to the URI provided.
295
307
  def redirect(uri, *args)
296
- if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
308
+ # SERVER_PROTOCOL is required in Rack 3, fall back to HTTP_VERSION
309
+ # for servers not updated for Rack 3 (like Puma 5)
310
+ http_version = env['SERVER_PROTOCOL'] || env['HTTP_VERSION']
311
+ if (http_version == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
297
312
  status 303
298
313
  else
299
314
  status 302
@@ -308,18 +323,19 @@ module Sinatra
308
323
  # Generates the absolute URI for a given path in the app.
309
324
  # Takes Rack routers and reverse proxies into account.
310
325
  def uri(addr = nil, absolute = true, add_script_name = true)
311
- return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i
326
+ return addr if addr.to_s =~ /\A[a-z][a-z0-9+.\-]*:/i
327
+
312
328
  uri = [host = String.new]
313
329
  if absolute
314
330
  host << "http#{'s' if request.secure?}://"
315
- if request.forwarded? or request.port != (request.secure? ? 443 : 80)
316
- host << request.host_with_port
317
- else
318
- host << request.host
319
- end
331
+ host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
332
+ request.host_with_port
333
+ else
334
+ request.host
335
+ end
320
336
  end
321
337
  uri << request.script_name.to_s if add_script_name
322
- uri << (addr ? addr : request.path_info).to_s
338
+ uri << (addr || request.path_info).to_s
323
339
  File.join uri
324
340
  end
325
341
 
@@ -328,7 +344,10 @@ module Sinatra
328
344
 
329
345
  # Halt processing and return the error status provided.
330
346
  def error(code, body = nil)
331
- code, body = 500, code.to_str if code.respond_to? :to_str
347
+ if code.respond_to? :to_str
348
+ body = code.to_str
349
+ code = 500
350
+ end
332
351
  response.body = body unless body.nil?
333
352
  halt code
334
353
  end
@@ -359,15 +378,17 @@ module Sinatra
359
378
  Base.mime_type(type)
360
379
  end
361
380
 
362
- # Set the Content-Type of the response body given a media type or file
381
+ # Set the content-type of the response body given a media type or file
363
382
  # extension.
364
383
  def content_type(type = nil, params = {})
365
- return response['Content-Type'] unless type
384
+ return response['content-type'] unless type
385
+
366
386
  default = params.delete :default
367
387
  mime_type = mime_type(type) || default
368
- fail "Unknown media type: %p" % type if mime_type.nil?
388
+ raise format('Unknown media type: %p', type) if mime_type.nil?
389
+
369
390
  mime_type = mime_type.dup
370
- unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
391
+ unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
371
392
  params[:charset] = params.delete('charset') || settings.default_encoding
372
393
  end
373
394
  params.delete :charset if mime_type.include? 'charset'
@@ -378,7 +399,7 @@ module Sinatra
378
399
  "#{key}=#{val}"
379
400
  end.join(', ')
380
401
  end
381
- response['Content-Type'] = mime_type
402
+ response['content-type'] = mime_type
382
403
  end
383
404
 
384
405
  # https://html.spec.whatwg.org/#multipart-form-data
@@ -397,28 +418,28 @@ module Sinatra
397
418
  params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
398
419
  response['Content-Disposition'] << params
399
420
  ext = File.extname(filename)
400
- content_type(ext) unless response['Content-Type'] || ext.empty?
421
+ content_type(ext) unless response['content-type'] || ext.empty?
401
422
  end
402
423
 
403
424
  # Use the contents of the file at +path+ as the response body.
404
425
  def send_file(path, opts = {})
405
- if opts[:type] or not response['Content-Type']
406
- content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
426
+ if opts[:type] || !response['content-type']
427
+ content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
407
428
  end
408
429
 
409
430
  disposition = opts[:disposition]
410
431
  filename = opts[:filename]
411
- disposition = :attachment if disposition.nil? and filename
432
+ disposition = :attachment if disposition.nil? && filename
412
433
  filename = path if filename.nil?
413
434
  attachment(filename, disposition) if disposition
414
435
 
415
436
  last_modified opts[:last_modified] if opts[:last_modified]
416
437
 
417
- file = Rack::File.new(File.dirname(settings.app_file))
438
+ file = Rack::Files.new(File.dirname(settings.app_file))
418
439
  result = file.serving(request, path)
419
440
 
420
- result[1].each { |k,v| headers[k] ||= v }
421
- headers['Content-Length'] = result[1]['Content-Length']
441
+ result[1].each { |k, v| headers[k] ||= v }
442
+ headers['content-length'] = result[1]['content-length']
422
443
  opts[:status] &&= Integer(opts[:status])
423
444
  halt (opts[:status] || result[0]), result[2]
424
445
  rescue Errno::ENOENT
@@ -438,12 +459,16 @@ module Sinatra
438
459
  def self.defer(*) yield end
439
460
 
440
461
  def initialize(scheduler = self.class, keep_open = false, &back)
441
- @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
442
- @callbacks, @closed = [], false
462
+ @back = back.to_proc
463
+ @scheduler = scheduler
464
+ @keep_open = keep_open
465
+ @callbacks = []
466
+ @closed = false
443
467
  end
444
468
 
445
469
  def close
446
470
  return if closed?
471
+
447
472
  @closed = true
448
473
  @scheduler.schedule { @callbacks.each { |c| c.call } }
449
474
  end
@@ -455,8 +480,9 @@ module Sinatra
455
480
  @back.call(self)
456
481
  rescue Exception => e
457
482
  @scheduler.schedule { raise e }
483
+ ensure
484
+ close unless @keep_open
458
485
  end
459
- close unless @keep_open
460
486
  end
461
487
  end
462
488
 
@@ -467,6 +493,7 @@ module Sinatra
467
493
 
468
494
  def callback(&block)
469
495
  return yield if closed?
496
+
470
497
  @callbacks << block
471
498
  end
472
499
 
@@ -481,12 +508,20 @@ module Sinatra
481
508
  # the response body have not yet been generated.
482
509
  #
483
510
  # The close parameter specifies whether Stream#close should be called
484
- # after the block has been executed. This is only relevant for evented
485
- # servers like Rainbows.
511
+ # after the block has been executed.
486
512
  def stream(keep_open = false)
487
513
  scheduler = env['async.callback'] ? EventMachine : Stream
488
514
  current = @params.dup
489
- body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
515
+ stream = if scheduler == Stream && keep_open
516
+ Stream.new(scheduler, false) do |out|
517
+ until out.closed?
518
+ with_params(current) { yield(out) }
519
+ end
520
+ end
521
+ else
522
+ Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
523
+ end
524
+ body stream
490
525
  end
491
526
 
492
527
  # Specify response freshness policy for HTTP caches (Cache-Control header).
@@ -500,18 +535,18 @@ module Sinatra
500
535
  # See RFC 2616 / 14.9 for more on standard cache control directives:
501
536
  # http://tools.ietf.org/html/rfc2616#section-14.9.1
502
537
  def cache_control(*values)
503
- if values.last.kind_of?(Hash)
538
+ if values.last.is_a?(Hash)
504
539
  hash = values.pop
505
- hash.reject! { |k, v| v == false }
540
+ hash.reject! { |_k, v| v == false }
506
541
  hash.reject! { |k, v| values << k if v == true }
507
542
  else
508
543
  hash = {}
509
544
  end
510
545
 
511
- values.map! { |value| value.to_s.tr('_','-') }
546
+ values.map! { |value| value.to_s.tr('_', '-') }
512
547
  hash.each do |key, value|
513
548
  key = key.to_s.tr('_', '-')
514
- value = value.to_i if ['max-age', 's-maxage'].include? key
549
+ value = value.to_i if %w[max-age s-maxage].include? key
515
550
  values << "#{key}=#{value}"
516
551
  end
517
552
 
@@ -528,7 +563,7 @@ module Sinatra
528
563
  # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
529
564
  #
530
565
  def expires(amount, *values)
531
- values << {} unless values.last.kind_of?(Hash)
566
+ values << {} unless values.last.is_a?(Hash)
532
567
 
533
568
  if amount.is_a? Integer
534
569
  time = Time.now + amount.to_i
@@ -538,7 +573,7 @@ module Sinatra
538
573
  max_age = time - Time.now
539
574
  end
540
575
 
541
- values.last.merge!(:max_age => max_age)
576
+ values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
542
577
  cache_control(*values)
543
578
 
544
579
  response['Expires'] = time.httpdate
@@ -553,17 +588,18 @@ module Sinatra
553
588
  # with a '304 Not Modified' response.
554
589
  def last_modified(time)
555
590
  return unless time
591
+
556
592
  time = time_for time
557
593
  response['Last-Modified'] = time.httpdate
558
594
  return if env['HTTP_IF_NONE_MATCH']
559
595
 
560
- if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
596
+ if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
561
597
  # compare based on seconds since epoch
562
598
  since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
563
599
  halt 304 if since >= time.to_i
564
600
  end
565
601
 
566
- if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
602
+ if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
567
603
  # compare based on seconds since epoch
568
604
  since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
569
605
  halt 412 if since < time.to_i
@@ -571,7 +607,7 @@ module Sinatra
571
607
  rescue ArgumentError
572
608
  end
573
609
 
574
- ETAG_KINDS = [:strong, :weak]
610
+ ETAG_KINDS = %i[strong weak].freeze
575
611
  # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
576
612
  # GET matches. The +value+ argument is an identifier that uniquely
577
613
  # identifies the current version of the resource. The +kind+ argument
@@ -583,27 +619,31 @@ module Sinatra
583
619
  # GET or HEAD, a '304 Not Modified' response is sent.
584
620
  def etag(value, options = {})
585
621
  # Before touching this code, please double check RFC 2616 14.24 and 14.26.
586
- options = {:kind => options} unless Hash === options
622
+ options = { kind: options } unless Hash === options
587
623
  kind = options[:kind] || :strong
588
624
  new_resource = options.fetch(:new_resource) { request.post? }
589
625
 
590
626
  unless ETAG_KINDS.include?(kind)
591
- raise ArgumentError, ":strong or :weak expected"
627
+ raise ArgumentError, ':strong or :weak expected'
592
628
  end
593
629
 
594
- value = '"%s"' % value
630
+ value = format('"%s"', value)
595
631
  value = "W/#{value}" if kind == :weak
596
632
  response['ETag'] = value
597
633
 
598
- if success? or status == 304
599
- if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
600
- halt(request.safe? ? 304 : 412)
601
- end
634
+ return unless success? || status == 304
602
635
 
603
- if env['HTTP_IF_MATCH']
604
- halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
605
- end
636
+ if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
637
+ halt(request.safe? ? 304 : 412)
606
638
  end
639
+
640
+ if env['HTTP_IF_MATCH']
641
+ return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
642
+
643
+ halt 412
644
+ end
645
+
646
+ nil
607
647
  end
608
648
 
609
649
  # Sugar for redirect (example: redirect back)
@@ -656,8 +696,8 @@ module Sinatra
656
696
  else
657
697
  value.to_time
658
698
  end
659
- rescue ArgumentError => boom
660
- raise boom
699
+ rescue ArgumentError => e
700
+ raise e
661
701
  rescue Exception
662
702
  raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
663
703
  end
@@ -667,11 +707,13 @@ module Sinatra
667
707
  # Helper method checking if a ETag value list includes the current ETag.
668
708
  def etag_matches?(list, new_resource = request.post?)
669
709
  return !new_resource if list == '*'
710
+
670
711
  list.to_s.split(/\s*,\s*/).include? response['ETag']
671
712
  end
672
713
 
673
714
  def with_params(temp_params)
674
- original, @params = @params, temp_params
715
+ original = @params
716
+ @params = temp_params
675
717
  yield
676
718
  ensure
677
719
  @params = original if original
@@ -689,7 +731,7 @@ module Sinatra
689
731
  # Possible options are:
690
732
  # :content_type The content type to use, same arguments as content_type.
691
733
  # :layout If set to something falsy, no layout is rendered, otherwise
692
- # the specified layout is used (Ignored for `sass` and `less`)
734
+ # the specified layout is used (Ignored for `sass`)
693
735
  # :layout_engine Engine to use for rendering the layout.
694
736
  # :locals A hash with local variables that should be available
695
737
  # in the template
@@ -711,36 +753,24 @@ module Sinatra
711
753
  render(:erb, template, options, locals, &block)
712
754
  end
713
755
 
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
756
  def haml(template, options = {}, locals = {}, &block)
721
757
  render(:haml, template, options, locals, &block)
722
758
  end
723
759
 
724
760
  def sass(template, options = {}, locals = {})
725
- options.merge! :layout => false, :default_content_type => :css
761
+ options[:default_content_type] = :css
762
+ options[:exclude_outvar] = true
763
+ options[:layout] = nil
726
764
  render :sass, template, options, locals
727
765
  end
728
766
 
729
767
  def scss(template, options = {}, locals = {})
730
- options.merge! :layout => false, :default_content_type => :css
768
+ options[:default_content_type] = :css
769
+ options[:exclude_outvar] = true
770
+ options[:layout] = nil
731
771
  render :scss, template, options, locals
732
772
  end
733
773
 
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
774
  def builder(template = nil, options = {}, locals = {}, &block)
745
775
  options[:default_content_type] = :xml
746
776
  render_ruby(:builder, template, options, locals, &block)
@@ -755,10 +785,6 @@ module Sinatra
755
785
  render :markdown, template, options, locals
756
786
  end
757
787
 
758
- def textile(template, options = {}, locals = {})
759
- render :textile, template, options, locals
760
- end
761
-
762
788
  def rdoc(template, options = {}, locals = {})
763
789
  render :rdoc, template, options, locals
764
790
  end
@@ -767,19 +793,10 @@ module Sinatra
767
793
  render :asciidoc, template, options, locals
768
794
  end
769
795
 
770
- def radius(template, options = {}, locals = {})
771
- render :radius, template, options, locals
772
- end
773
-
774
796
  def markaby(template = nil, options = {}, locals = {}, &block)
775
797
  render_ruby(:mab, template, options, locals, &block)
776
798
  end
777
799
 
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
800
  def nokogiri(template = nil, options = {}, locals = {}, &block)
784
801
  options[:default_content_type] = :xml
785
802
  render_ruby(:nokogiri, template, options, locals, &block)
@@ -789,18 +806,6 @@ module Sinatra
789
806
  render(:slim, template, options, locals, &block)
790
807
  end
791
808
 
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
809
  def yajl(template, options = {}, locals = {})
805
810
  options[:default_content_type] = :json
806
811
  render :yajl, template, options, locals
@@ -825,24 +830,27 @@ module Sinatra
825
830
 
826
831
  # logic shared between builder and nokogiri
827
832
  def render_ruby(engine, template, options = {}, locals = {}, &block)
828
- options, template = template, nil if template.is_a?(Hash)
829
- template = Proc.new { block } if template.nil?
833
+ if template.is_a?(Hash)
834
+ options = template
835
+ template = nil
836
+ end
837
+ template = proc { block } if template.nil?
830
838
  render engine, template, options, locals
831
839
  end
832
840
 
833
841
  def render(engine, data, options = {}, locals = {}, &block)
834
842
  # merge app-level options
835
843
  engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
836
- options.merge!(engine_options) { |key, v1, v2| v1 }
844
+ options.merge!(engine_options) { |_key, v1, _v2| v1 }
837
845
 
838
846
  # extract generic options
839
847
  locals = options.delete(:locals) || locals || {}
840
- views = options.delete(:views) || settings.views || "./views"
848
+ views = options.delete(:views) || settings.views || './views'
841
849
  layout = options[:layout]
842
850
  layout = false if layout.nil? && options.include?(:layout)
843
851
  eat_errors = layout.nil?
844
- layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
845
- layout = @default_layout if layout.nil? or layout == true
852
+ layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
853
+ layout = @default_layout if layout.nil? || (layout == true)
846
854
  layout_options = options.delete(:layout_options) || {}
847
855
  content_type = options.delete(:default_content_type)
848
856
  content_type = options.delete(:content_type) || content_type
@@ -867,12 +875,17 @@ module Sinatra
867
875
 
868
876
  # render layout
869
877
  if layout
870
- options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope).
871
- merge!(layout_options)
878
+ extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
879
+ options = options.merge(extra_options).merge!(layout_options)
880
+
872
881
  catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
873
882
  end
874
883
 
875
- output.extend(ContentTyped).content_type = content_type if content_type
884
+ if content_type
885
+ # sass-embedded returns a frozen string
886
+ output = +output
887
+ output.extend(ContentTyped).content_type = content_type
888
+ end
876
889
  output
877
890
  end
878
891
 
@@ -893,12 +906,13 @@ module Sinatra
893
906
  @preferred_extension = engine.to_s
894
907
  find_template(views, data, template) do |file|
895
908
  path ||= file # keep the initial path rather than the last one
896
- if found = File.exist?(file)
909
+ found = File.exist?(file)
910
+ if found
897
911
  path = file
898
912
  break
899
913
  end
900
914
  end
901
- throw :layout_missing if eat_errors and not found
915
+ throw :layout_missing if eat_errors && !found
902
916
  template.new(path, 1, options)
903
917
  end
904
918
  end
@@ -914,13 +928,44 @@ module Sinatra
914
928
  end
915
929
 
916
930
  def compile_block_template(template, options, &body)
917
- caller = settings.caller_locations.first
918
- path = options[:path] || caller[0]
919
- line = options[:line] || caller[1]
931
+ first_location = caller_locations.first
932
+ path = first_location.path
933
+ line = first_location.lineno
934
+ path = options[:path] || path
935
+ line = options[:line] || line
920
936
  template.new(path, line.to_i, options, &body)
921
937
  end
922
938
  end
923
939
 
940
+ # Extremely simple template cache implementation.
941
+ # * Not thread-safe.
942
+ # * Size is unbounded.
943
+ # * Keys are not copied defensively, and should not be modified after
944
+ # being passed to #fetch. More specifically, the values returned by
945
+ # key#hash and key#eql? should not change.
946
+ #
947
+ # Implementation copied from Tilt::Cache.
948
+ class TemplateCache
949
+ def initialize
950
+ @cache = {}
951
+ end
952
+
953
+ # Caches a value for key, or returns the previously cached value.
954
+ # If a value has been previously cached for key then it is
955
+ # returned. Otherwise, block is yielded to and its return value
956
+ # which may be nil, is cached under key and returned.
957
+ def fetch(*key)
958
+ @cache.fetch(key) do
959
+ @cache[key] = yield
960
+ end
961
+ end
962
+
963
+ # Clears the cache.
964
+ def clear
965
+ @cache = {}
966
+ end
967
+ end
968
+
924
969
  # Base class for all Sinatra applications and middleware.
925
970
  class Base
926
971
  include Rack::Utils
@@ -932,10 +977,10 @@ module Sinatra
932
977
  attr_accessor :app, :env, :request, :response, :params
933
978
  attr_reader :template_cache
934
979
 
935
- def initialize(app = nil, **kwargs)
980
+ def initialize(app = nil, **_kwargs)
936
981
  super()
937
982
  @app = app
938
- @template_cache = Tilt::Cache.new
983
+ @template_cache = TemplateCache.new
939
984
  @pinned_response = nil # whether a before! filter pinned the content-type
940
985
  yield self if block_given?
941
986
  end
@@ -956,10 +1001,10 @@ module Sinatra
956
1001
  invoke { dispatch! }
957
1002
  invoke { error_block!(response.status) } unless @env['sinatra.error']
958
1003
 
959
- unless @response['Content-Type']
1004
+ unless @response['content-type']
960
1005
  if Array === body && body[0].respond_to?(:content_type)
961
1006
  content_type body[0].content_type
962
- elsif default = settings.default_content_type
1007
+ elsif (default = settings.default_content_type)
963
1008
  content_type default
964
1009
  end
965
1010
  end
@@ -977,12 +1022,6 @@ module Sinatra
977
1022
  self.class.settings
978
1023
  end
979
1024
 
980
- def options
981
- warn "Sinatra::Base#options is deprecated and will be removed, " \
982
- "use #settings instead."
983
- settings
984
- end
985
-
986
1025
  # Exit the current block, halts any further processing
987
1026
  # of the request, and returns the specified response.
988
1027
  def halt(*response)
@@ -999,7 +1038,8 @@ module Sinatra
999
1038
 
1000
1039
  # Forward the request to the downstream app -- middleware only.
1001
1040
  def forward
1002
- fail "downstream app not set" unless @app.respond_to? :call
1041
+ raise 'downstream app not set' unless @app.respond_to? :call
1042
+
1003
1043
  status, headers, body = @app.call env
1004
1044
  @response.status = status
1005
1045
  @response.body = body
@@ -1021,18 +1061,18 @@ module Sinatra
1021
1061
 
1022
1062
  # Run routes defined on the class and all superclasses.
1023
1063
  def route!(base = settings, pass_block = nil)
1024
- if routes = base.routes[@request.request_method]
1025
- routes.each do |pattern, conditions, block|
1026
- response.delete_header('Content-Type') unless @pinned_response
1064
+ routes = base.routes[@request.request_method]
1027
1065
 
1028
- returned_pass_block = process_route(pattern, conditions) do |*args|
1029
- env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1030
- route_eval { block[*args] }
1031
- end
1066
+ routes&.each do |pattern, conditions, block|
1067
+ response.delete_header('content-type') unless @pinned_response
1032
1068
 
1033
- # don't wipe out pass_block in superclass
1034
- pass_block = returned_pass_block if returned_pass_block
1069
+ returned_pass_block = process_route(pattern, conditions) do |*args|
1070
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
1071
+ route_eval { block[*args] }
1035
1072
  end
1073
+
1074
+ # don't wipe out pass_block in superclass
1075
+ pass_block = returned_pass_block if returned_pass_block
1036
1076
  end
1037
1077
 
1038
1078
  # Run routes defined in superclass.
@@ -1056,15 +1096,17 @@ module Sinatra
1056
1096
  # Returns pass block.
1057
1097
  def process_route(pattern, conditions, block = nil, values = [])
1058
1098
  route = @request.path_info
1059
- route = '/' if route.empty? and not settings.empty_path_info?
1099
+ route = '/' if route.empty? && !settings.empty_path_info?
1060
1100
  route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
1061
- return unless params = pattern.params(route)
1062
1101
 
1063
- params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1102
+ params = pattern.params(route)
1103
+ return unless params
1104
+
1105
+ params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
1064
1106
  force_encoding(params)
1065
- @params = @params.merge(params) if params.any?
1107
+ @params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
1066
1108
 
1067
- regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
1109
+ regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
1068
1110
  if regexp_exists
1069
1111
  captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
1070
1112
  values += captures
@@ -1077,7 +1119,7 @@ module Sinatra
1077
1119
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1078
1120
  block ? block[self, values] : yield(self, values)
1079
1121
  end
1080
- rescue
1122
+ rescue StandardError
1081
1123
  @env['sinatra.error.params'] = @params
1082
1124
  raise
1083
1125
  ensure
@@ -1091,35 +1133,35 @@ module Sinatra
1091
1133
  # a NotFound exception. Subclasses can override this method to perform
1092
1134
  # custom route miss logic.
1093
1135
  def route_missing
1094
- if @app
1095
- forward
1096
- else
1097
- raise NotFound, "#{request.request_method} #{request.path_info}"
1098
- end
1136
+ raise NotFound unless @app
1137
+
1138
+ forward
1099
1139
  end
1100
1140
 
1101
1141
  # Attempt to serve static files from public directory. Throws :halt when
1102
1142
  # a matching file is found, returns nil otherwise.
1103
1143
  def static!(options = {})
1104
1144
  return if (public_dir = settings.public_folder).nil?
1145
+
1105
1146
  path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
1106
1147
  return unless valid_path?(path)
1107
1148
 
1108
1149
  path = File.expand_path(path)
1109
- return unless path.start_with?(File.expand_path(public_dir) + '/')
1150
+ return unless path.start_with?("#{File.expand_path(public_dir)}/")
1151
+
1110
1152
  return unless File.file?(path)
1111
1153
 
1112
1154
  env['sinatra.static_file'] = path
1113
1155
  cache_control(*settings.static_cache_control) if settings.static_cache_control?
1114
- send_file path, options.merge(:disposition => nil)
1156
+ send_file path, options.merge(disposition: nil)
1115
1157
  end
1116
1158
 
1117
1159
  # Run the block with 'throw :halt' support and apply result to the response.
1118
- def invoke
1119
- res = catch(:halt) { yield }
1160
+ def invoke(&block)
1161
+ res = catch(:halt, &block)
1120
1162
 
1121
- res = [res] if Integer === res or String === res
1122
- if Array === res and Integer === res.first
1163
+ res = [res] if (Integer === res) || (String === res)
1164
+ if (Array === res) && (Integer === res.first)
1123
1165
  res = res.dup
1124
1166
  status(res.shift)
1125
1167
  body(res.pop)
@@ -1135,6 +1177,7 @@ module Sinatra
1135
1177
  # Avoid passing frozen string in force_encoding
1136
1178
  @params.merge!(@request.params).each do |key, val|
1137
1179
  next unless val.respond_to?(:force_encoding)
1180
+
1138
1181
  val = val.dup if val.frozen?
1139
1182
  @params[key] = force_encoding(val)
1140
1183
  end
@@ -1142,43 +1185,47 @@ module Sinatra
1142
1185
  invoke do
1143
1186
  static! if settings.static? && (request.get? || request.head?)
1144
1187
  filter! :before do
1145
- @pinned_response = !response['Content-Type'].nil?
1188
+ @pinned_response = !response['content-type'].nil?
1146
1189
  end
1147
1190
  route!
1148
1191
  end
1149
- rescue ::Exception => boom
1150
- invoke { handle_exception!(boom) }
1192
+ rescue ::Exception => e
1193
+ invoke { handle_exception!(e) }
1151
1194
  ensure
1152
1195
  begin
1153
1196
  filter! :after unless env['sinatra.static_file']
1154
- rescue ::Exception => boom
1155
- invoke { handle_exception!(boom) } unless @env['sinatra.error']
1197
+ rescue ::Exception => e
1198
+ invoke { handle_exception!(e) } unless @env['sinatra.error']
1156
1199
  end
1157
1200
  end
1158
1201
 
1159
1202
  # Error handling during requests.
1160
1203
  def handle_exception!(boom)
1161
- if error_params = @env['sinatra.error.params']
1162
- @params = @params.merge(error_params)
1163
- end
1204
+ error_params = @env['sinatra.error.params']
1205
+
1206
+ @params = @params.merge(error_params) if error_params
1207
+
1164
1208
  @env['sinatra.error'] = boom
1165
1209
 
1166
- if boom.respond_to? :http_status and boom.http_status.between? 400, 599
1167
- status(boom.http_status)
1168
- elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1169
- status(boom.code)
1170
- else
1171
- status(500)
1172
- end
1210
+ http_status = if boom.is_a? Sinatra::Error
1211
+ if boom.respond_to? :http_status
1212
+ boom.http_status
1213
+ elsif settings.use_code? && boom.respond_to?(:code)
1214
+ boom.code
1215
+ end
1216
+ end
1217
+
1218
+ http_status = 500 unless http_status&.between?(400, 599)
1219
+ status(http_status)
1173
1220
 
1174
1221
  if server_error?
1175
1222
  dump_errors! boom if settings.dump_errors?
1176
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1223
+ raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
1177
1224
  elsif not_found?
1178
1225
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1179
1226
  end
1180
1227
 
1181
- if res = error_block!(boom.class, boom) || error_block!(status, boom)
1228
+ if (res = error_block!(boom.class, boom) || error_block!(status, boom))
1182
1229
  return res
1183
1230
  end
1184
1231
 
@@ -1187,12 +1234,14 @@ module Sinatra
1187
1234
  body Rack::Utils.escape_html(boom.message)
1188
1235
  else
1189
1236
  content_type 'text/html'
1190
- body '<h1>' + (not_found? ? 'Not Found' : 'Bad Request') + '</h1>'
1237
+ body "<h1>#{not_found? ? 'Not Found' : 'Bad Request'}</h1>"
1191
1238
  end
1192
1239
  end
1193
1240
 
1194
1241
  return unless server_error?
1195
- raise boom if settings.raise_errors? or settings.show_exceptions?
1242
+
1243
+ raise boom if settings.raise_errors? || settings.show_exceptions?
1244
+
1196
1245
  error_block! Exception, boom
1197
1246
  end
1198
1247
 
@@ -1200,7 +1249,10 @@ module Sinatra
1200
1249
  def error_block!(key, *block_params)
1201
1250
  base = settings
1202
1251
  while base.respond_to?(:errors)
1203
- next base = base.superclass unless args_array = base.errors[key]
1252
+ args_array = base.errors[key]
1253
+
1254
+ next base = base.superclass unless args_array
1255
+
1204
1256
  args_array.reverse_each do |args|
1205
1257
  first = args == args_array.first
1206
1258
  args += [block_params]
@@ -1208,51 +1260,63 @@ module Sinatra
1208
1260
  return resp unless resp.nil? && !first
1209
1261
  end
1210
1262
  end
1211
- return false unless key.respond_to? :superclass and key.superclass < Exception
1263
+ return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
1264
+
1212
1265
  error_block!(key.superclass, *block_params)
1213
1266
  end
1214
1267
 
1215
1268
  def dump_errors!(boom)
1216
- msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1269
+ if boom.respond_to?(:detailed_message)
1270
+ msg = boom.detailed_message(highlight: false)
1271
+ if msg =~ /\A(.*?)(?: \(#{ Regexp.quote(boom.class.to_s) }\))?\n/
1272
+ msg = $1
1273
+ additional_msg = $'.lines(chomp: true)
1274
+ else
1275
+ additional_msg = []
1276
+ end
1277
+ else
1278
+ msg = boom.message
1279
+ additional_msg = []
1280
+ end
1281
+ msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{msg}:", *additional_msg, *boom.backtrace].join("\n\t")
1217
1282
  @env['rack.errors'].puts(msg)
1218
1283
  end
1219
1284
 
1220
1285
  class << self
1221
1286
  CALLERS_TO_IGNORE = [ # :nodoc:
1222
- /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1223
- /lib\/tilt.*\.rb$/, # all tilt code
1287
+ %r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
1288
+ %r{lib/tilt.*\.rb$}, # all tilt code
1224
1289
  /^\(.*\)$/, # generated code
1225
- /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
1290
+ /\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
1291
+ %r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
1226
1292
  /active_support/, # active_support require hacks
1227
- /bundler(\/(?:runtime|inline))?\.rb/, # bundler require hacks
1293
+ %r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
1228
1294
  /<internal:/, # internal in ruby >= 1.9.2
1229
- /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1230
- ]
1295
+ %r{zeitwerk/kernel\.rb} # Zeitwerk kernel#require decorator
1296
+ ].freeze
1231
1297
 
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
1298
+ attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
1237
1299
 
1238
- attr_reader :routes, :filters, :templates, :errors
1300
+ def callers_to_ignore
1301
+ CALLERS_TO_IGNORE
1302
+ end
1239
1303
 
1240
1304
  # Removes all routes, filters, middleware and extension hooks from the
1241
1305
  # current class (not routes/filters/... defined by its superclass).
1242
1306
  def reset!
1243
1307
  @conditions = []
1244
1308
  @routes = {}
1245
- @filters = {:before => [], :after => []}
1309
+ @filters = { before: [], after: [] }
1246
1310
  @errors = {}
1247
1311
  @middleware = []
1248
1312
  @prototype = nil
1249
1313
  @extensions = []
1250
1314
 
1251
- if superclass.respond_to?(:templates)
1252
- @templates = Hash.new { |hash, key| superclass.templates[key] }
1253
- else
1254
- @templates = {}
1255
- end
1315
+ @templates = if superclass.respond_to?(:templates)
1316
+ Hash.new { |_hash, key| superclass.templates[key] }
1317
+ else
1318
+ {}
1319
+ end
1256
1320
  end
1257
1321
 
1258
1322
  # Extension modules registered on this class and all superclasses.
@@ -1276,16 +1340,21 @@ module Sinatra
1276
1340
  # Sets an option to the given value. If the value is a proc,
1277
1341
  # the proc will be called every time the option is accessed.
1278
1342
  def set(option, value = (not_set = true), ignore_setter = false, &block)
1279
- raise ArgumentError if block and !not_set
1280
- value, not_set = block, false if block
1343
+ raise ArgumentError if block && !not_set
1344
+
1345
+ if block
1346
+ value = block
1347
+ not_set = false
1348
+ end
1281
1349
 
1282
1350
  if not_set
1283
1351
  raise ArgumentError unless option.respond_to?(:each)
1284
- option.each { |k,v| set(k, v) }
1352
+
1353
+ option.each { |k, v| set(k, v) }
1285
1354
  return self
1286
1355
  end
1287
1356
 
1288
- if respond_to?("#{option}=") and not ignore_setter
1357
+ if respond_to?("#{option}=") && !ignore_setter
1289
1358
  return __send__("#{option}=", value)
1290
1359
  end
1291
1360
 
@@ -1324,7 +1393,7 @@ module Sinatra
1324
1393
  # class, or an HTTP status code to specify which errors should be
1325
1394
  # handled.
1326
1395
  def error(*codes, &block)
1327
- args = compile! "ERROR", /.*/, block
1396
+ args = compile! 'ERROR', /.*/, block
1328
1397
  codes = codes.flat_map(&method(:Array))
1329
1398
  codes << Exception if codes.empty?
1330
1399
  codes << Sinatra::NotFound if codes.include?(404)
@@ -1350,7 +1419,7 @@ module Sinatra
1350
1419
  # Load embedded templates from the file; uses the caller's __FILE__
1351
1420
  # when no file is specified.
1352
1421
  def inline_templates=(file = nil)
1353
- file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1422
+ file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
1354
1423
 
1355
1424
  begin
1356
1425
  io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
@@ -1359,23 +1428,24 @@ module Sinatra
1359
1428
  app, data = nil
1360
1429
  end
1361
1430
 
1362
- if data
1363
- if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1364
- encoding = $2
1365
- else
1366
- encoding = settings.default_encoding
1367
- end
1368
- lines = app.count("\n") + 1
1369
- template = nil
1370
- force_encoding data, encoding
1371
- data.each_line do |line|
1372
- lines += 1
1373
- if line =~ /^@@\s*(.*\S)\s*$/
1374
- template = force_encoding(String.new, encoding)
1375
- templates[$1.to_sym] = [template, file, lines]
1376
- elsif template
1377
- template << line
1378
- end
1431
+ return unless data
1432
+
1433
+ encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1434
+ $2
1435
+ else
1436
+ settings.default_encoding
1437
+ end
1438
+
1439
+ lines = app.count("\n") + 1
1440
+ template = nil
1441
+ force_encoding data, encoding
1442
+ data.each_line do |line|
1443
+ lines += 1
1444
+ if line =~ /^@@\s*(.*\S)\s*$/
1445
+ template = force_encoding(String.new, encoding)
1446
+ templates[$1.to_sym] = [template, file, lines]
1447
+ elsif template
1448
+ template << line
1379
1449
  end
1380
1450
  end
1381
1451
  end
@@ -1384,8 +1454,10 @@ module Sinatra
1384
1454
  def mime_type(type, value = nil)
1385
1455
  return type if type.nil?
1386
1456
  return type.to_s if type.to_s.include?('/')
1387
- type = ".#{type}" unless type.to_s[0] == ?.
1457
+
1458
+ type = ".#{type}" unless type.to_s[0] == '.'
1388
1459
  return Rack::Mime.mime_type(type, nil) unless value
1460
+
1389
1461
  Rack::Mime::MIME_TYPES[type] = value
1390
1462
  end
1391
1463
 
@@ -1394,7 +1466,13 @@ module Sinatra
1394
1466
  # mime_types :js # => ['application/javascript', 'text/javascript']
1395
1467
  def mime_types(type)
1396
1468
  type = mime_type type
1397
- type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
1469
+ if type =~ %r{^application/(xml|javascript)$}
1470
+ [type, "text/#{$1}"]
1471
+ elsif type =~ %r{^text/(xml|javascript)$}
1472
+ [type, "application/#{$1}"]
1473
+ else
1474
+ [type]
1475
+ end
1398
1476
  end
1399
1477
 
1400
1478
  # Define a before filter; runs before all requests within the same
@@ -1416,6 +1494,14 @@ module Sinatra
1416
1494
  filters[type] << compile!(type, path, block, **options)
1417
1495
  end
1418
1496
 
1497
+ def on_start(&on_start_callback)
1498
+ @on_start_callback = on_start_callback
1499
+ end
1500
+
1501
+ def on_stop(&on_stop_callback)
1502
+ @on_stop_callback = on_stop_callback
1503
+ end
1504
+
1419
1505
  # Add a route condition. The route is considered non-matching when the
1420
1506
  # block returns false.
1421
1507
  def condition(name = "#{caller.first[/`.*'/]} condition", &block)
@@ -1423,7 +1509,7 @@ module Sinatra
1423
1509
  end
1424
1510
 
1425
1511
  def public=(value)
1426
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
1512
+ warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
1427
1513
  set(:public_folder, value)
1428
1514
  end
1429
1515
 
@@ -1445,14 +1531,21 @@ module Sinatra
1445
1531
  route('HEAD', path, opts, &block)
1446
1532
  end
1447
1533
 
1448
- def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
1449
- def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
1450
- def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
1451
- def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
1452
- def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
1453
- def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
1454
- def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
1455
- def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
1534
+ def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
1535
+
1536
+ def post(path, opts = {}, &block) route 'POST', path, opts, &block end
1537
+
1538
+ def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
1539
+
1540
+ def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
1541
+
1542
+ def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
1543
+
1544
+ def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
1545
+
1546
+ def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
1547
+
1548
+ def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
1456
1549
 
1457
1550
  # Makes the methods defined in the block and in the Modules given
1458
1551
  # in `extensions` available to the handlers and templates
@@ -1492,37 +1585,58 @@ module Sinatra
1492
1585
  # Stop the self-hosted server if running.
1493
1586
  def quit!
1494
1587
  return unless running?
1588
+
1495
1589
  # Use Thin's hard #stop! if available, otherwise just #stop.
1496
1590
  running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1497
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
1591
+ warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
1498
1592
  set :running_server, nil
1499
1593
  set :handler_name, nil
1594
+
1595
+ on_stop_callback.call unless on_stop_callback.nil?
1500
1596
  end
1501
1597
 
1502
- alias_method :stop!, :quit!
1598
+ alias stop! quit!
1503
1599
 
1504
1600
  # Run the Sinatra app as a self-hosted server using
1505
- # Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1601
+ # Puma, Falcon, or WEBrick (in that order). If given a block, will call
1506
1602
  # with the constructed handler once we have taken the stage.
1507
1603
  def run!(options = {}, &block)
1604
+ unless defined?(Rackup::Handler)
1605
+ rackup_warning = <<~MISSING_RACKUP
1606
+ Sinatra could not start, the "rackup" gem was not found!
1607
+
1608
+ Add it to your bundle with:
1609
+
1610
+ bundle add rackup
1611
+
1612
+ or install it with:
1613
+
1614
+ gem install rackup
1615
+
1616
+ MISSING_RACKUP
1617
+ warn rackup_warning
1618
+ exit 1
1619
+ end
1620
+
1508
1621
  return if running?
1622
+
1509
1623
  set options
1510
- handler = Rack::Handler.pick(server)
1624
+ handler = Rackup::Handler.pick(server)
1511
1625
  handler_name = handler.name.gsub(/.*::/, '')
1512
1626
  server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1513
- server_settings.merge!(:Port => port, :Host => bind)
1627
+ server_settings.merge!(Port: port, Host: bind)
1514
1628
 
1515
1629
  begin
1516
1630
  start_server(handler, server_settings, handler_name, &block)
1517
1631
  rescue Errno::EADDRINUSE
1518
- $stderr.puts "== Someone is already performing on port #{port}!"
1632
+ warn "== Someone is already performing on port #{port}!"
1519
1633
  raise
1520
1634
  ensure
1521
1635
  quit!
1522
1636
  end
1523
1637
  end
1524
1638
 
1525
- alias_method :start!, :run!
1639
+ alias start! run!
1526
1640
 
1527
1641
  # Check whether the self-hosted server is running or not.
1528
1642
  def running?
@@ -1540,8 +1654,8 @@ module Sinatra
1540
1654
  # Create a new instance of the class fronted by its middleware
1541
1655
  # pipeline. The object is guaranteed to respond to #call but may not be
1542
1656
  # an instance of the class new was called on.
1543
- def new(*args, &bk)
1544
- instance = new!(*args, &bk)
1657
+ def new(*args, &block)
1658
+ instance = new!(*args, &block)
1545
1659
  Wrapper.new(build(instance).to_app, instance)
1546
1660
  end
1547
1661
  ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
@@ -1566,12 +1680,6 @@ module Sinatra
1566
1680
  cleaned_caller(1).flatten
1567
1681
  end
1568
1682
 
1569
- # Like caller_files, but containing Arrays rather than strings with the
1570
- # first element being the file, and the second being the line.
1571
- def caller_locations
1572
- cleaned_caller 2
1573
- end
1574
-
1575
1683
  private
1576
1684
 
1577
1685
  # Starts the server by running the Rack Handler.
@@ -1582,14 +1690,14 @@ module Sinatra
1582
1690
  # Run the instance we created:
1583
1691
  handler.run(self, **server_settings) do |server|
1584
1692
  unless suppress_messages?
1585
- $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1693
+ warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1586
1694
  end
1587
1695
 
1588
1696
  setup_traps
1589
1697
  set :running_server, server
1590
1698
  set :handler_name, handler_name
1591
1699
  server.threaded = settings.threaded if server.respond_to? :threaded=
1592
-
1700
+ on_start_callback.call unless on_start_callback.nil?
1593
1701
  yield server if block_given?
1594
1702
  end
1595
1703
  end
@@ -1599,18 +1707,18 @@ module Sinatra
1599
1707
  end
1600
1708
 
1601
1709
  def setup_traps
1602
- if traps?
1603
- at_exit { quit! }
1710
+ return unless traps?
1604
1711
 
1605
- [:INT, :TERM].each do |signal|
1606
- old_handler = trap(signal) do
1607
- quit!
1608
- old_handler.call if old_handler.respond_to?(:call)
1609
- end
1610
- end
1712
+ at_exit { quit! }
1611
1713
 
1612
- set :traps, false
1714
+ %i[INT TERM].each do |signal|
1715
+ old_handler = trap(signal) do
1716
+ quit!
1717
+ old_handler.call if old_handler.respond_to?(:call)
1718
+ end
1613
1719
  end
1720
+
1721
+ set :traps, false
1614
1722
  end
1615
1723
 
1616
1724
  # Dynamically defines a method on settings.
@@ -1638,18 +1746,21 @@ module Sinatra
1638
1746
  end
1639
1747
  end
1640
1748
  end
1641
- alias_method :agent, :user_agent
1749
+ alias agent user_agent
1642
1750
 
1643
1751
  # Condition for matching mimetypes. Accepts file extensions.
1644
1752
  def provides(*types)
1645
1753
  types.map! { |t| mime_types(t) }
1646
1754
  types.flatten!
1647
1755
  condition do
1648
- if type = response['Content-Type']
1649
- types.include? type or types.include? type[/^[^;]+/]
1650
- elsif type = request.preferred_type(types)
1651
- params = (type.respond_to?(:params) ? type.params : {})
1652
- content_type(type, params)
1756
+ response_content_type = response['content-type']
1757
+ preferred_type = request.preferred_type(types)
1758
+
1759
+ if response_content_type
1760
+ types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
1761
+ elsif preferred_type
1762
+ params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
1763
+ content_type(preferred_type, params)
1653
1764
  true
1654
1765
  else
1655
1766
  false
@@ -1658,7 +1769,7 @@ module Sinatra
1658
1769
  end
1659
1770
 
1660
1771
  def route(verb, path, options = {}, &block)
1661
- enable :empty_path_info if path == "" and empty_path_info.nil?
1772
+ enable :empty_path_info if path == '' && empty_path_info.nil?
1662
1773
  signature = compile!(verb, path, block, **options)
1663
1774
  (@routes[verb] ||= []) << signature
1664
1775
  invoke_hook(:route_added, verb, path, block)
@@ -1687,12 +1798,13 @@ module Sinatra
1687
1798
  pattern = compile(path, route_mustermann_opts)
1688
1799
  method_name = "#{verb} #{path}"
1689
1800
  unbound_method = generate_method(method_name, &block)
1690
- conditions, @conditions = @conditions, []
1691
- wrapper = block.arity != 0 ?
1692
- proc { |a, p| unbound_method.bind(a).call(*p) } :
1693
- proc { |a, p| unbound_method.bind(a).call }
1801
+ conditions = @conditions
1802
+ @conditions = []
1803
+ wrapper = block.arity.zero? ?
1804
+ proc { |a, _p| unbound_method.bind(a).call } :
1805
+ proc { |a, p| unbound_method.bind(a).call(*p) }
1694
1806
 
1695
- [ pattern, conditions, wrapper ]
1807
+ [pattern, conditions, wrapper]
1696
1808
  end
1697
1809
 
1698
1810
  def compile(path, route_mustermann_opts = {})
@@ -1710,7 +1822,7 @@ module Sinatra
1710
1822
  end
1711
1823
 
1712
1824
  def setup_middleware(builder)
1713
- middleware.each { |c,a,b| builder.use(c, *a, &b) }
1825
+ middleware.each { |c, a, b| builder.use(c, *a, &b) }
1714
1826
  end
1715
1827
 
1716
1828
  def setup_logging(builder)
@@ -1740,9 +1852,10 @@ module Sinatra
1740
1852
 
1741
1853
  def setup_protection(builder)
1742
1854
  return unless protection?
1855
+
1743
1856
  options = Hash === protection ? protection.dup : {}
1744
1857
  options = {
1745
- img_src: "'self' data:",
1858
+ img_src: "'self' data:",
1746
1859
  font_src: "'self'"
1747
1860
  }.merge options
1748
1861
 
@@ -1756,6 +1869,7 @@ module Sinatra
1756
1869
 
1757
1870
  def setup_sessions(builder)
1758
1871
  return unless sessions?
1872
+
1759
1873
  options = {}
1760
1874
  options[:secret] = session_secret if session_secret?
1761
1875
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
@@ -1778,15 +1892,15 @@ module Sinatra
1778
1892
  end
1779
1893
 
1780
1894
  # used for deprecation warnings
1781
- def warn(message)
1782
- super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1895
+ def warn_for_deprecation(message)
1896
+ warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1783
1897
  end
1784
1898
 
1785
1899
  # Like Kernel#caller but excluding certain magic entries
1786
1900
  def cleaned_caller(keep = 3)
1787
- caller(1).
1788
- map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1789
- reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1901
+ caller(1)
1902
+ .map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
1903
+ .reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
1790
1904
  end
1791
1905
  end
1792
1906
 
@@ -1794,6 +1908,7 @@ module Sinatra
1794
1908
  # which is UTF-8 by default
1795
1909
  def self.force_encoding(data, encoding = default_encoding)
1796
1910
  return if data == settings || data.is_a?(Tempfile)
1911
+
1797
1912
  if data.respond_to? :force_encoding
1798
1913
  data.force_encoding(encoding).encode!
1799
1914
  elsif data.respond_to? :each_value
@@ -1804,24 +1919,26 @@ module Sinatra
1804
1919
  data
1805
1920
  end
1806
1921
 
1807
- def force_encoding(*args) settings.force_encoding(*args) end
1922
+ def force_encoding(*args)
1923
+ settings.force_encoding(*args)
1924
+ end
1808
1925
 
1809
1926
  reset!
1810
1927
 
1811
1928
  set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1812
- set :raise_errors, Proc.new { test? }
1813
- set :dump_errors, Proc.new { !test? }
1814
- set :show_exceptions, Proc.new { development? }
1929
+ set :raise_errors, proc { test? }
1930
+ set :dump_errors, proc { !test? }
1931
+ set :show_exceptions, proc { development? }
1815
1932
  set :sessions, false
1816
1933
  set :session_store, Rack::Session::Cookie
1817
1934
  set :logging, false
1818
1935
  set :protection, true
1819
1936
  set :method_override, false
1820
1937
  set :use_code, false
1821
- set :default_encoding, "utf-8"
1938
+ set :default_encoding, 'utf-8'
1822
1939
  set :x_cascade, true
1823
1940
  set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1824
- settings.add_charset << /^text\//
1941
+ settings.add_charset << %r{^text/}
1825
1942
  set :mustermann_opts, {}
1826
1943
  set :default_content_type, 'text/html'
1827
1944
 
@@ -1829,14 +1946,15 @@ module Sinatra
1829
1946
  begin
1830
1947
  require 'securerandom'
1831
1948
  set :session_secret, SecureRandom.hex(64)
1832
- rescue LoadError, NotImplementedError
1949
+ rescue LoadError, NotImplementedError, RuntimeError
1833
1950
  # SecureRandom raises a NotImplementedError if no random device is available
1834
- set :session_secret, "%064x" % Kernel.rand(2**256-1)
1951
+ # RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
1952
+ set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
1835
1953
  end
1836
1954
 
1837
1955
  class << self
1838
- alias_method :methodoverride?, :method_override?
1839
- alias_method :methodoverride=, :method_override=
1956
+ alias methodoverride? method_override?
1957
+ alias methodoverride= method_override=
1840
1958
  end
1841
1959
 
1842
1960
  set :run, false # start server via at-exit hook?
@@ -1844,21 +1962,16 @@ module Sinatra
1844
1962
  set :handler_name, nil
1845
1963
  set :traps, true
1846
1964
  set :server, %w[HTTP webrick]
1847
- set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1965
+ set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
1848
1966
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1849
1967
  set :quiet, false
1850
1968
 
1851
1969
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1852
1970
 
1853
- if ruby_engine == 'macruby'
1854
- server.unshift 'control_tower'
1855
- else
1856
- server.unshift 'reel'
1857
- server.unshift 'puma'
1858
- server.unshift 'mongrel' if ruby_engine.nil?
1859
- server.unshift 'thin' if ruby_engine != 'jruby'
1860
- server.unshift 'trinidad' if ruby_engine == 'jruby'
1861
- end
1971
+ server.unshift 'thin' if ruby_engine != 'jruby'
1972
+ server.unshift 'falcon' if ruby_engine != 'jruby'
1973
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1974
+ server.unshift 'puma'
1862
1975
 
1863
1976
  set :absolute_redirects, true
1864
1977
  set :prefixed_redirects, false
@@ -1866,14 +1979,14 @@ module Sinatra
1866
1979
  set :strict_paths, true
1867
1980
 
1868
1981
  set :app_file, nil
1869
- set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1870
- set :views, Proc.new { root && File.join(root, 'views') }
1871
- set :reload_templates, Proc.new { development? }
1982
+ set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
1983
+ set :views, proc { root && File.join(root, 'views') }
1984
+ set :reload_templates, proc { development? }
1872
1985
  set :lock, false
1873
1986
  set :threaded, true
1874
1987
 
1875
- set :public_folder, Proc.new { root && File.join(root, 'public') }
1876
- set :static, Proc.new { public_folder && File.exist?(public_folder) }
1988
+ set :public_folder, proc { root && File.join(root, 'public') }
1989
+ set :static, proc { public_folder && File.exist?(public_folder) }
1877
1990
  set :static_cache_control, false
1878
1991
 
1879
1992
  error ::Exception do
@@ -1892,7 +2005,7 @@ module Sinatra
1892
2005
  error NotFound do
1893
2006
  content_type 'text/html'
1894
2007
 
1895
- if self.class == Sinatra::Application
2008
+ if instance_of?(Sinatra::Application)
1896
2009
  code = <<-RUBY.gsub(/^ {12}/, '')
1897
2010
  #{request.request_method.downcase} '#{request.path_info}' do
1898
2011
  "Hello World"
@@ -1907,11 +2020,11 @@ module Sinatra
1907
2020
  end
1908
2021
  RUBY
1909
2022
 
1910
- file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
2023
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
1911
2024
  code = "# in #{file}\n#{code}" unless file.empty?
1912
2025
  end
1913
2026
 
1914
- (<<-HTML).gsub(/^ {10}/, '')
2027
+ <<-HTML.gsub(/^ {10}/, '')
1915
2028
  <!DOCTYPE html>
1916
2029
  <html>
1917
2030
  <head>
@@ -1923,7 +2036,7 @@ module Sinatra
1923
2036
  </head>
1924
2037
  <body>
1925
2038
  <h2>Sinatra doesn’t know this ditty.</h2>
1926
- <img src='#{uri "/__sinatra__/404.png"}'>
2039
+ <img src='#{request.script_name}/__sinatra__/404.png'>
1927
2040
  <div id="c">
1928
2041
  Try this:
1929
2042
  <pre>#{Rack::Utils.escape_html(code)}</pre>
@@ -1943,12 +2056,12 @@ module Sinatra
1943
2056
  # top-level. Subclassing Sinatra::Base is highly recommended for
1944
2057
  # modular applications.
1945
2058
  class Application < Base
1946
- set :logging, Proc.new { !test? }
2059
+ set :logging, proc { !test? }
1947
2060
  set :method_override, true
1948
- set :run, Proc.new { !test? }
2061
+ set :run, proc { !test? }
1949
2062
  set :app_file, nil
1950
2063
 
1951
- def self.register(*extensions, &block) #:nodoc:
2064
+ def self.register(*extensions, &block) # :nodoc:
1952
2065
  added_methods = extensions.flat_map(&:public_instance_methods)
1953
2066
  Delegator.delegate(*added_methods)
1954
2067
  super(*extensions, &block)
@@ -1958,11 +2071,12 @@ module Sinatra
1958
2071
  # Sinatra delegation mixin. Mixing this module into an object causes all
1959
2072
  # methods to be delegated to the Sinatra::Application class. Used primarily
1960
2073
  # at the top-level.
1961
- module Delegator #:nodoc:
2074
+ module Delegator # :nodoc:
1962
2075
  def self.delegate(*methods)
1963
2076
  methods.each do |method_name|
1964
2077
  define_method(method_name) do |*args, &block|
1965
2078
  return super(*args, &block) if respond_to? method_name
2079
+
1966
2080
  Delegator.target.send(method_name, *args, &block)
1967
2081
  end
1968
2082
  # ensure keyword argument passing is compatible with ruby >= 2.7
@@ -1974,7 +2088,7 @@ module Sinatra
1974
2088
  delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
1975
2089
  :template, :layout, :before, :after, :error, :not_found, :configure,
1976
2090
  :set, :mime_type, :enable, :disable, :use, :development?, :test?,
1977
- :production?, :helpers, :settings, :register
2091
+ :production?, :helpers, :settings, :register, :on_start, :on_stop
1978
2092
 
1979
2093
  class << self
1980
2094
  attr_accessor :target
@@ -1985,7 +2099,8 @@ module Sinatra
1985
2099
 
1986
2100
  class Wrapper
1987
2101
  def initialize(stack, instance)
1988
- @stack, @instance = stack, instance
2102
+ @stack = stack
2103
+ @instance = instance
1989
2104
  end
1990
2105
 
1991
2106
  def settings