sinatra 2.1.0 → 4.1.1

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