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