sinatra 2.0.7 → 3.0.6

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