Created
January 14, 2016 20:22
-
-
Save gjtorikian/6c879d8beb728c0cc259 to your computer and use it in GitHub Desktop.
sinatra.diff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb | |
index 3b07ab0..b76ef47 100644 | |
--- a/lib/sinatra/base.rb | |
+++ b/lib/sinatra/base.rb | |
@@ -2,6 +2,7 @@ | |
require 'rack' | |
require 'tilt' | |
require 'rack/protection' | |
+require 'forwardable' | |
# stdlib dependencies | |
require 'thread' | |
@@ -9,31 +10,43 @@ require 'time' | |
require 'uri' | |
# other files we need | |
-require 'sinatra/showexceptions' | |
+require 'sinatra/show_exceptions' | |
require 'sinatra/version' | |
module Sinatra | |
# The request object. See Rack::Request for more info: | |
- # http://rack.rubyforge.org/doc/classes/Rack/Request.html | |
+ # http://rubydoc.info/github/rack/rack/master/Rack/Request | |
class Request < Rack::Request | |
+ HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ | |
+ HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ | |
+ | |
# Returns an array of acceptable media types for the response | |
def accept | |
@env['sinatra.accept'] ||= begin | |
- entries = @env['HTTP_ACCEPT'].to_s.split(',') | |
- entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first) | |
+ if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != '' | |
+ @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS). | |
+ map! { |e| AcceptEntry.new(e) }.sort | |
+ else | |
+ [AcceptEntry.new('*/*')] | |
+ end | |
end | |
end | |
+ def accept?(type) | |
+ preferred_type(type).to_s.include?(type) | |
+ end | |
+ | |
def preferred_type(*types) | |
- return accept.first if types.empty? | |
+ accepts = accept # just evaluate once | |
+ return accepts.first if types.empty? | |
types.flatten! | |
- accept.detect do |pattern| | |
+ return types.first if accepts.empty? | |
+ accepts.detect do |pattern| | |
type = types.detect { |t| File.fnmatch(pattern, t) } | |
return type if type | |
end | |
end | |
- alias accept? preferred_type | |
alias secure? ssl? | |
def forwarded? | |
@@ -45,24 +58,69 @@ module Sinatra | |
end | |
def idempotent? | |
- safe? or put? or delete? | |
+ safe? or put? or delete? or link? or unlink? | |
+ end | |
+ | |
+ def link? | |
+ request_method == "LINK" | |
+ end | |
+ | |
+ def unlink? | |
+ request_method == "UNLINK" | |
end | |
private | |
- def accept_entry(entry) | |
- type, *options = entry.delete(' ').split(';') | |
- quality = 0 # we sort smallest first | |
- options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } | |
- [type, [quality, type.count('*'), 1 - options.size]] | |
+ class AcceptEntry | |
+ attr_accessor :params | |
+ attr_reader :entry | |
+ | |
+ def initialize(entry) | |
+ params = entry.scan(HEADER_PARAM).map! do |s| | |
+ key, value = s.strip.split('=', 2) | |
+ value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') | |
+ [key, value] | |
+ end | |
+ | |
+ @entry = entry | |
+ @type = entry[/[^;]+/].delete(' ') | |
+ @params = Hash[params] | |
+ @q = @params.delete('q') { 1.0 }.to_f | |
+ end | |
+ | |
+ def <=>(other) | |
+ other.priority <=> self.priority | |
+ end | |
+ | |
+ def priority | |
+ # We sort in descending order; better matches should be higher. | |
+ [ @q, -@type.count('*'), @params.size ] | |
+ end | |
+ | |
+ def to_str | |
+ @type | |
+ end | |
+ | |
+ def to_s(full = false) | |
+ full ? entry : to_str | |
+ end | |
+ | |
+ def respond_to?(*args) | |
+ super or to_str.respond_to?(*args) | |
+ end | |
+ | |
+ def method_missing(*args, &block) | |
+ to_str.send(*args, &block) | |
+ end | |
end | |
end | |
- # The response object. See Rack::Response and Rack::ResponseHelpers for | |
+ # The response object. See Rack::Response and Rack::Response::Helpers for | |
# more info: | |
- # http://rack.rubyforge.org/doc/classes/Rack/Response.html | |
- # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html | |
+ # http://rubydoc.info/github/rack/rack/master/Rack/Response | |
+ # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers | |
class Response < Rack::Response | |
+ DROP_BODY_RESPONSES = [204, 205, 304] | |
def initialize(*) | |
super | |
headers['Content-Type'] ||= 'text/html' | |
@@ -110,7 +168,7 @@ module Sinatra | |
end | |
def drop_body? | |
- [204, 205, 304].include?(status.to_i) | |
+ DROP_BODY_RESPONSES.include?(status.to_i) | |
end | |
end | |
@@ -164,24 +222,25 @@ module Sinatra | |
end | |
class NotFound < NameError #:nodoc: | |
- def code ; 404 ; end | |
+ def http_status; 404 end | |
end | |
# Methods available to routes, before/after filters, and views. | |
module Helpers | |
# Set or retrieve the response status code. | |
- def status(value=nil) | |
+ def status(value = nil) | |
response.status = value if value | |
response.status | |
end | |
# Set or retrieve the response body. When a block is given, | |
# evaluation is deferred until the body is read with #each. | |
- def body(value=nil, &block) | |
+ def body(value = nil, &block) | |
if block_given? | |
def block.each; yield(call) end | |
response.body = block | |
elsif value | |
+ headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream) | |
response.body = value | |
else | |
response.body | |
@@ -198,7 +257,7 @@ module Sinatra | |
# According to RFC 2616 section 14.30, "the field value consists of a | |
# single absolute URI" | |
- response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?) | |
+ response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) | |
halt(*args) | |
end | |
@@ -224,32 +283,27 @@ module Sinatra | |
alias to uri | |
# Halt processing and return the error status provided. | |
- def error(code, body=nil) | |
+ def error(code, body = nil) | |
code, body = 500, code.to_str if code.respond_to? :to_str | |
response.body = body unless body.nil? | |
halt code | |
end | |
# Halt processing and return a 404 Not Found. | |
- def not_found(body=nil) | |
+ def not_found(body = nil) | |
error 404, body | |
end | |
# Set multiple response headers with Hash. | |
- def headers(hash=nil) | |
+ def headers(hash = nil) | |
response.headers.merge! hash if hash | |
response.headers | |
end | |
- # Access the underlying Rack session. | |
- def session | |
- request.session | |
- end | |
- | |
- # Access shared logger object. | |
- def logger | |
- request.logger | |
- end | |
+ extend Forwardable | |
+ def_delegators :request, | |
+ :session, # Access the underlying Rack session. | |
+ :logger # Access shared logger object. | |
# Look up a media type by file extension in Rack's mime registry. | |
def mime_type(type) | |
@@ -258,7 +312,7 @@ module Sinatra | |
# Set the Content-Type of the response body given a media type or file | |
# extension. | |
- def content_type(type = nil, params={}) | |
+ def content_type(type = nil, params = {}) | |
return response['Content-Type'] unless type | |
default = params.delete :default | |
mime_type = mime_type(type) || default | |
@@ -270,15 +324,18 @@ module Sinatra | |
params.delete :charset if mime_type.include? 'charset' | |
unless params.empty? | |
mime_type << (mime_type.include?(';') ? ', ' : ';') | |
- mime_type << params.map { |kv| kv.join('=') }.join(', ') | |
+ mime_type << params.map do |key, val| | |
+ val = val.inspect if val =~ /[";,]/ | |
+ "#{key}=#{val}" | |
+ end.join(', ') | |
end | |
response['Content-Type'] = mime_type | |
end | |
# Set the Content-Disposition to "attachment" with the specified filename, | |
# instructing the user agents to prompt to save. | |
- def attachment(filename=nil) | |
- response['Content-Disposition'] = 'attachment' | |
+ def attachment(filename = nil, disposition = 'attachment') | |
+ response['Content-Disposition'] = disposition.to_s | |
if filename | |
params = '; filename="%s"' % File.basename(filename) | |
response['Content-Disposition'] << params | |
@@ -288,16 +345,16 @@ module Sinatra | |
end | |
# Use the contents of the file at +path+ as the response body. | |
- def send_file(path, opts={}) | |
+ def send_file(path, opts = {}) | |
if opts[:type] or not response['Content-Type'] | |
content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' | |
end | |
- if opts[:disposition] == 'attachment' || opts[:filename] | |
- attachment opts[:filename] || path | |
- elsif opts[:disposition] == 'inline' | |
- response['Content-Disposition'] = 'inline' | |
- end | |
+ disposition = opts[:disposition] | |
+ filename = opts[:filename] | |
+ disposition = 'attachment' if disposition.nil? and filename | |
+ filename = path if filename.nil? | |
+ attachment(filename, disposition) if disposition | |
last_modified opts[:last_modified] if opts[:last_modified] | |
@@ -305,7 +362,9 @@ module Sinatra | |
file.path = path | |
result = file.serving env | |
result[1].each { |k,v| headers[k] ||= v } | |
- halt result[0], result[2] | |
+ headers['Content-Length'] = result[1]['Content-Length'] | |
+ opts[:status] &&= Integer(opts[:status]) | |
+ halt opts[:status] || result[0], result[2] | |
rescue Errno::ENOENT | |
not_found | |
end | |
@@ -328,7 +387,7 @@ module Sinatra | |
end | |
def close | |
- return if @closed | |
+ return if closed? | |
@closed = true | |
@scheduler.schedule { @callbacks.each { |c| c.call }} | |
end | |
@@ -351,11 +410,15 @@ module Sinatra | |
end | |
def callback(&block) | |
- return yield if @closed | |
+ return yield if closed? | |
@callbacks << block | |
end | |
alias errback callback | |
+ | |
+ def closed? | |
+ @closed | |
+ end | |
end | |
# Allows to start sending data to the client even though later parts of | |
@@ -393,7 +456,7 @@ module Sinatra | |
hash.each do |key, value| | |
key = key.to_s.tr('_', '-') | |
value = value.to_i if key == "max-age" | |
- values << [key, value].join('=') | |
+ values << "#{key}=#{value}" | |
end | |
response['Cache-Control'] = values.join(', ') if values.any? | |
@@ -452,6 +515,7 @@ module Sinatra | |
rescue ArgumentError | |
end | |
+ ETAG_KINDS = [:strong, :weak] | |
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional | |
# GET matches. The +value+ argument is an identifier that uniquely | |
# identifies the current version of the resource. The +kind+ argument | |
@@ -467,12 +531,12 @@ module Sinatra | |
kind = options[:kind] || :strong | |
new_resource = options.fetch(:new_resource) { request.post? } | |
- unless [:strong, :weak].include?(kind) | |
+ unless ETAG_KINDS.include?(kind) | |
raise ArgumentError, ":strong or :weak expected" | |
end | |
value = '"%s"' % value | |
- value = 'W/' + value if kind == :weak | |
+ value = "W/#{value}" if kind == :weak | |
response['ETag'] = value | |
if success? or status == 304 | |
@@ -575,7 +639,7 @@ module Sinatra | |
# | |
# Possible options are: | |
# :content_type The content type to use, same arguments as content_type. | |
- # :layout If set to false, no layout is rendered, otherwise | |
+ # :layout If set to something falsy, no layout is rendered, otherwise | |
# the specified layout is used (Ignored for `sass` and `less`) | |
# :layout_engine Engine to use for rendering the layout. | |
# :locals A hash with local variables that should be available | |
@@ -591,117 +655,160 @@ module Sinatra | |
def initialize | |
super | |
@default_layout = :layout | |
+ @preferred_extension = nil | |
end | |
- def erb(template, options={}, locals={}) | |
- render :erb, template, options, locals | |
+ def erb(template, options = {}, locals = {}, &block) | |
+ render(:erb, template, options, locals, &block) | |
end | |
- def erubis(template, options={}, locals={}) | |
+ def erubis(template, options = {}, locals = {}) | |
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ | |
"If you have Erubis installed, it will be used automatically." | |
render :erubis, template, options, locals | |
end | |
- def haml(template, options={}, locals={}) | |
- render :haml, template, options, locals | |
+ def haml(template, options = {}, locals = {}, &block) | |
+ render(:haml, template, options, locals, &block) | |
end | |
- def sass(template, options={}, locals={}) | |
+ def sass(template, options = {}, locals = {}) | |
options.merge! :layout => false, :default_content_type => :css | |
render :sass, template, options, locals | |
end | |
- def scss(template, options={}, locals={}) | |
+ def scss(template, options = {}, locals = {}) | |
options.merge! :layout => false, :default_content_type => :css | |
render :scss, template, options, locals | |
end | |
- def less(template, options={}, locals={}) | |
+ def less(template, options = {}, locals = {}) | |
options.merge! :layout => false, :default_content_type => :css | |
render :less, template, options, locals | |
end | |
- def builder(template=nil, options={}, locals={}, &block) | |
+ def stylus(template, options={}, locals={}) | |
+ options.merge! :layout => false, :default_content_type => :css | |
+ render :styl, template, options, locals | |
+ end | |
+ | |
+ def builder(template = nil, options = {}, locals = {}, &block) | |
options[:default_content_type] = :xml | |
render_ruby(:builder, template, options, locals, &block) | |
end | |
- def liquid(template, options={}, locals={}) | |
- render :liquid, template, options, locals | |
+ def liquid(template, options = {}, locals = {}, &block) | |
+ render(:liquid, template, options, locals, &block) | |
end | |
- def markdown(template, options={}, locals={}) | |
+ def markdown(template, options = {}, locals = {}) | |
render :markdown, template, options, locals | |
end | |
- def textile(template, options={}, locals={}) | |
+ def textile(template, options = {}, locals = {}) | |
render :textile, template, options, locals | |
end | |
- def rdoc(template, options={}, locals={}) | |
+ def rdoc(template, options = {}, locals = {}) | |
render :rdoc, template, options, locals | |
end | |
- def radius(template, options={}, locals={}) | |
+ def asciidoc(template, options = {}, locals = {}) | |
+ render :asciidoc, template, options, locals | |
+ end | |
+ | |
+ def radius(template, options = {}, locals = {}) | |
render :radius, template, options, locals | |
end | |
- def markaby(template=nil, options={}, locals={}, &block) | |
+ def markaby(template = nil, options = {}, locals = {}, &block) | |
render_ruby(:mab, template, options, locals, &block) | |
end | |
- def coffee(template, options={}, locals={}) | |
+ def coffee(template, options = {}, locals = {}) | |
options.merge! :layout => false, :default_content_type => :js | |
render :coffee, template, options, locals | |
end | |
- def nokogiri(template=nil, options={}, locals={}, &block) | |
+ def nokogiri(template = nil, options = {}, locals = {}, &block) | |
options[:default_content_type] = :xml | |
render_ruby(:nokogiri, template, options, locals, &block) | |
end | |
- def slim(template, options={}, locals={}) | |
- render :slim, template, options, locals | |
+ def slim(template, options = {}, locals = {}, &block) | |
+ render(:slim, template, options, locals, &block) | |
end | |
- def creole(template, options={}, locals={}) | |
+ def creole(template, options = {}, locals = {}) | |
render :creole, template, options, locals | |
end | |
+ def mediawiki(template, options = {}, locals = {}) | |
+ render :mediawiki, template, options, locals | |
+ end | |
+ | |
+ def wlang(template, options = {}, locals = {}, &block) | |
+ render(:wlang, template, options, locals, &block) | |
+ end | |
+ | |
+ def yajl(template, options = {}, locals = {}) | |
+ options[:default_content_type] = :json | |
+ render :yajl, template, options, locals | |
+ end | |
+ | |
+ def rabl(template, options = {}, locals = {}) | |
+ Rabl.register! | |
+ render :rabl, template, options, locals | |
+ end | |
+ | |
# Calls the given block for every possible template file in views, | |
# named name.ext, where ext is registered on engine. | |
def find_template(views, name, engine) | |
yield ::File.join(views, "#{name}.#{@preferred_extension}") | |
- Tilt.mappings.each do |ext, engines| | |
- next unless ext != @preferred_extension and engines.include? engine | |
- yield ::File.join(views, "#{name}.#{ext}") | |
+ | |
+ if Tilt.respond_to?(:mappings) | |
+ Tilt.mappings.each do |ext, engines| | |
+ next unless ext != @preferred_extension and engines.include? engine | |
+ yield ::File.join(views, "#{name}.#{ext}") | |
+ end | |
+ else | |
+ Tilt.default_mapping.extensions_for(engine).each do |ext| | |
+ yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension | |
+ end | |
end | |
end | |
- private | |
+ private | |
+ | |
# logic shared between builder and nokogiri | |
- def render_ruby(engine, template, options={}, locals={}, &block) | |
+ def render_ruby(engine, template, options = {}, locals = {}, &block) | |
options, template = template, nil if template.is_a?(Hash) | |
template = Proc.new { block } if template.nil? | |
render engine, template, options, locals | |
end | |
- def render(engine, data, options={}, locals={}, &block) | |
+ def render(engine, data, options = {}, locals = {}, &block) | |
# merge app-level options | |
- options = settings.send(engine).merge(options) if settings.respond_to?(engine) | |
- options[:outvar] ||= '@_out_buf' | |
- options[:default_encoding] ||= settings.default_encoding | |
+ engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} | |
+ options.merge!(engine_options) { |key, v1, v2| v1 } | |
# extract generic options | |
locals = options.delete(:locals) || locals || {} | |
views = options.delete(:views) || settings.views || "./views" | |
- layout = options.delete(:layout) | |
+ layout = options[:layout] | |
+ layout = false if layout.nil? && options.include?(:layout) | |
eat_errors = layout.nil? | |
- layout = @default_layout if layout.nil? or layout == true | |
- content_type = options.delete(:content_type) || options.delete(:default_content_type) | |
- layout_engine = options.delete(:layout_engine) || engine | |
- scope = options.delete(:scope) || self | |
+ layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false) | |
+ layout = @default_layout if layout.nil? or layout == true | |
+ layout_options = options.delete(:layout_options) || {} | |
+ content_type = options.delete(:content_type) || options.delete(:default_content_type) | |
+ layout_engine = options.delete(:layout_engine) || engine | |
+ scope = options.delete(:scope) || self | |
+ options.delete(:layout) | |
+ | |
+ # set some defaults | |
+ options[:outvar] ||= '@_out_buf' | |
+ options[:default_encoding] ||= settings.default_encoding | |
# compile and render template | |
begin | |
@@ -715,7 +822,8 @@ module Sinatra | |
# render layout | |
if layout | |
- options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope) | |
+ options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). | |
+ merge!(layout_options) | |
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } | |
end | |
@@ -725,7 +833,7 @@ module Sinatra | |
def compile_template(engine, data, options, views) | |
eat_errors = options.delete :eat_errors | |
- template_cache.fetch engine, data, options do | |
+ template_cache.fetch engine, data, options, views do | |
template = Tilt[engine] | |
raise "Template engine not found: #{engine}" if template.nil? | |
@@ -740,7 +848,7 @@ module Sinatra | |
@preferred_extension = engine.to_s | |
find_template(views, data, template) do |file| | |
path ||= file # keep the initial path rather than the last one | |
- if found = File.exists?(file) | |
+ if found = File.exist?(file) | |
path = file | |
break | |
end | |
@@ -765,10 +873,12 @@ module Sinatra | |
include Helpers | |
include Templates | |
- attr_accessor :app | |
+ URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI | |
+ | |
+ attr_accessor :app, :env, :request, :response, :params | |
attr_reader :template_cache | |
- def initialize(app=nil) | |
+ def initialize(app = nil) | |
super() | |
@app = app | |
@template_cache = Tilt::Cache.new | |
@@ -780,8 +890,6 @@ module Sinatra | |
dup.call!(env) | |
end | |
- attr_accessor :env, :request, :response, :params | |
- | |
def call!(env) # :nodoc: | |
@env = env | |
@request = Request.new(env) | |
@@ -792,7 +900,7 @@ module Sinatra | |
@response['Content-Type'] = nil | |
invoke { dispatch! } | |
- invoke { error_block!(response.status) } | |
+ invoke { error_block!(response.status) } unless @env['sinatra.error'] | |
unless @response['Content-Type'] | |
if Array === body and body[0].respond_to? :content_type | |
@@ -845,7 +953,8 @@ module Sinatra | |
nil | |
end | |
- private | |
+ private | |
+ | |
# Run filters defined on the class and all superclasses. | |
def filter!(type, base = settings) | |
filter! type, base.superclass if base.superclass.respond_to?(:filters) | |
@@ -853,12 +962,16 @@ module Sinatra | |
end | |
# Run routes defined on the class and all superclasses. | |
- def route!(base = settings, pass_block=nil) | |
+ def route!(base = settings, pass_block = nil) | |
if routes = base.routes[@request.request_method] | |
routes.each do |pattern, keys, conditions, block| | |
- pass_block = process_route(pattern, keys, conditions) do |*args| | |
+ returned_pass_block = process_route(pattern, keys, conditions) do |*args| | |
+ env['sinatra.route'] = block.instance_variable_get(:@route_name) | |
route_eval { block[*args] } | |
end | |
+ | |
+ # don't wipe out pass_block in superclass | |
+ pass_block = returned_pass_block if returned_pass_block | |
end | |
end | |
@@ -885,7 +998,7 @@ module Sinatra | |
route = @request.path_info | |
route = '/' if route.empty? and not settings.empty_path_info? | |
return unless match = pattern.match(route) | |
- values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v } | |
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v } | |
if values.any? | |
original, @params = params, params.merge('splat' => [], 'captures' => values) | |
@@ -915,24 +1028,27 @@ module Sinatra | |
# Attempt to serve static files from public directory. Throws :halt when | |
# a matching file is found, returns nil otherwise. | |
- def static! | |
+ def static!(options = {}) | |
return if (public_dir = settings.public_folder).nil? | |
- public_dir = File.expand_path(public_dir) | |
- | |
- path = File.expand_path(public_dir + unescape(request.path_info)) | |
- return unless path.start_with?(public_dir) and File.file?(path) | |
+ path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" ) | |
+ return unless File.file?(path) | |
env['sinatra.static_file'] = path | |
cache_control(*settings.static_cache_control) if settings.static_cache_control? | |
- send_file path, :disposition => nil | |
+ send_file path, options.merge(:disposition => nil) | |
end | |
# Enable string or symbol key access to the nested params hash. | |
- def indifferent_params(params) | |
- params = indifferent_hash.merge(params) | |
- params.each do |key, value| | |
- next unless value.is_a?(Hash) | |
- params[key] = indifferent_params(value) | |
+ def indifferent_params(object) | |
+ case object | |
+ when Hash | |
+ new_hash = indifferent_hash | |
+ object.each { |key, value| new_hash[key] = indifferent_params(value) } | |
+ new_hash | |
+ when Array | |
+ object.map { |item| indifferent_params(item) } | |
+ else | |
+ object | |
end | |
end | |
@@ -946,6 +1062,7 @@ module Sinatra | |
res = catch(:halt) { yield } | |
res = [res] if Fixnum === res or String === res | |
if Array === res and Fixnum === res.first | |
+ res = res.dup | |
status(res.shift) | |
body(res.pop) | |
headers(*res) | |
@@ -965,13 +1082,26 @@ module Sinatra | |
rescue ::Exception => boom | |
invoke { handle_exception!(boom) } | |
ensure | |
- filter! :after unless env['sinatra.static_file'] | |
+ begin | |
+ filter! :after unless env['sinatra.static_file'] | |
+ rescue ::Exception => boom | |
+ invoke { handle_exception!(boom) } unless @env['sinatra.error'] | |
+ end | |
end | |
# Error handling during requests. | |
def handle_exception!(boom) | |
@env['sinatra.error'] = boom | |
- status boom.respond_to?(:code) ? Integer(boom.code) : 500 | |
+ | |
+ if boom.respond_to? :http_status | |
+ status(boom.http_status) | |
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 | |
+ status(boom.code) | |
+ else | |
+ status(500) | |
+ end | |
+ | |
+ status(500) unless status.between? 400, 599 | |
if server_error? | |
dump_errors! boom if settings.dump_errors? | |
@@ -979,7 +1109,7 @@ module Sinatra | |
end | |
if not_found? | |
- headers['X-Cascade'] = 'pass' | |
+ headers['X-Cascade'] = 'pass' if settings.x_cascade? | |
body '<h1>Not Found</h1>' | |
end | |
@@ -993,20 +1123,41 @@ module Sinatra | |
def error_block!(key, *block_params) | |
base = settings | |
while base.respond_to?(:errors) | |
- next base = base.superclass unless args = base.errors[key] | |
- args += [block_params] | |
- return process_route(*args) | |
+ next base = base.superclass unless args_array = base.errors[key] | |
+ args_array.reverse_each do |args| | |
+ first = args == args_array.first | |
+ args += [block_params] | |
+ resp = process_route(*args) | |
+ return resp unless resp.nil? && !first | |
+ end | |
end | |
return false unless key.respond_to? :superclass and key.superclass < Exception | |
error_block!(key.superclass, *block_params) | |
end | |
def dump_errors!(boom) | |
- msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") | |
+ msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") | |
@env['rack.errors'].puts(msg) | |
end | |
class << self | |
+ CALLERS_TO_IGNORE = [ # :nodoc: | |
+ /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code | |
+ /lib\/tilt.*\.rb$/, # all tilt code | |
+ /^\(.*\)$/, # generated code | |
+ /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks | |
+ /active_support/, # active_support require hacks | |
+ /bundler(\/runtime)?\.rb/, # bundler require hacks | |
+ /<internal:/, # internal in ruby >= 1.9.2 | |
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files | |
+ ] | |
+ | |
+ # contrary to what the comment said previously, rubinius never supported this | |
+ if defined?(RUBY_IGNORE_CALLERS) | |
+ warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" | |
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) | |
+ end | |
+ | |
attr_reader :routes, :filters, :templates, :errors | |
# Removes all routes, filters, middleware and extension hooks from the | |
@@ -1099,12 +1250,13 @@ module Sinatra | |
args = compile! "ERROR", //, block | |
codes = codes.map { |c| Array(c) }.flatten | |
codes << Exception if codes.empty? | |
- codes.each { |c| @errors[c] = args } | |
+ codes.each { |c| (@errors[c] ||= []) << args } | |
end | |
# Sugar for `error(404) { ... }` | |
def not_found(&block) | |
- error 404, &block | |
+ error(404, &block) | |
+ error(Sinatra::NotFound, &block) | |
end | |
# Define a named template. The block must return the template source. | |
@@ -1114,13 +1266,13 @@ module Sinatra | |
end | |
# Define the layout template. The block must return the template source. | |
- def layout(name=:layout, &block) | |
+ def layout(name = :layout, &block) | |
template name, &block | |
end | |
- # Load embeded templates from the file; uses the caller's __FILE__ | |
+ # Load embedded templates from the file; uses the caller's __FILE__ | |
# when no file is specified. | |
- def inline_templates=(file=nil) | |
+ def inline_templates=(file = nil) | |
file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file | |
begin | |
@@ -1152,8 +1304,9 @@ module Sinatra | |
end | |
# Lookup or register a mime type in Rack's mime registry. | |
- def mime_type(type, value=nil) | |
- return type if type.nil? || type.to_s.include?('/') | |
+ def mime_type(type, value = nil) | |
+ return type if type.nil? | |
+ return type.to_s if type.to_s.include?('/') | |
type = ".#{type}" unless type.to_s[0] == ?. | |
return Rack::Mime.mime_type(type, nil) unless value | |
Rack::Mime::MIME_TYPES[type] = value | |
@@ -1194,11 +1347,186 @@ module Sinatra | |
end | |
def public=(value) | |
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder instead" | |
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead" | |
set(:public_folder, value) | |
end | |
- private | |
+ def public_dir=(value) | |
+ self.public_folder = value | |
+ end | |
+ | |
+ def public_dir | |
+ public_folder | |
+ end | |
+ | |
+ # Defining a `GET` handler also automatically defines | |
+ # a `HEAD` handler. | |
+ def get(path, opts = {}, &block) | |
+ conditions = @conditions.dup | |
+ route('GET', path, opts, &block) | |
+ | |
+ @conditions = conditions | |
+ route('HEAD', path, opts, &block) | |
+ end | |
+ | |
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end | |
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end | |
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end | |
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end | |
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end | |
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end | |
+ def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end | |
+ def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end | |
+ | |
+ # Makes the methods defined in the block and in the Modules given | |
+ # in `extensions` available to the handlers and templates | |
+ def helpers(*extensions, &block) | |
+ class_eval(&block) if block_given? | |
+ include(*extensions) if extensions.any? | |
+ end | |
+ | |
+ # Register an extension. Alternatively take a block from which an | |
+ # extension will be created and registered on the fly. | |
+ def register(*extensions, &block) | |
+ extensions << Module.new(&block) if block_given? | |
+ @extensions += extensions | |
+ extensions.each do |extension| | |
+ extend extension | |
+ extension.registered(self) if extension.respond_to?(:registered) | |
+ end | |
+ end | |
+ | |
+ def development?; environment == :development end | |
+ def production?; environment == :production end | |
+ def test?; environment == :test end | |
+ | |
+ # Set configuration options for Sinatra and/or the app. | |
+ # Allows scoping of settings for certain environments. | |
+ def configure(*envs) | |
+ yield self if envs.empty? || envs.include?(environment.to_sym) | |
+ end | |
+ | |
+ # Use the specified Rack middleware | |
+ def use(middleware, *args, &block) | |
+ @prototype = nil | |
+ @middleware << [middleware, args, block] | |
+ end | |
+ | |
+ # Stop the self-hosted server if running. | |
+ def quit! | |
+ return unless running? | |
+ # Use Thin's hard #stop! if available, otherwise just #stop. | |
+ running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop | |
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i | |
+ set :running_server, nil | |
+ set :handler_name, nil | |
+ end | |
+ | |
+ alias_method :stop!, :quit! | |
+ | |
+ # Run the Sinatra app as a self-hosted server using | |
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call | |
+ # with the constructed handler once we have taken the stage. | |
+ def run!(options = {}, &block) | |
+ return if running? | |
+ set options | |
+ handler = detect_rack_handler | |
+ handler_name = handler.name.gsub(/.*::/, '') | |
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} | |
+ server_settings.merge!(:Port => port, :Host => bind) | |
+ | |
+ begin | |
+ start_server(handler, server_settings, handler_name, &block) | |
+ rescue Errno::EADDRINUSE | |
+ $stderr.puts "== Someone is already performing on port #{port}!" | |
+ raise | |
+ ensure | |
+ quit! | |
+ end | |
+ end | |
+ | |
+ alias_method :start!, :run! | |
+ | |
+ # Check whether the self-hosted server is running or not. | |
+ def running? | |
+ running_server? | |
+ end | |
+ | |
+ # The prototype instance used to process requests. | |
+ def prototype | |
+ @prototype ||= new | |
+ end | |
+ | |
+ # Create a new instance without middleware in front of it. | |
+ alias new! new unless method_defined? :new! | |
+ | |
+ # Create a new instance of the class fronted by its middleware | |
+ # pipeline. The object is guaranteed to respond to #call but may not be | |
+ # an instance of the class new was called on. | |
+ def new(*args, &bk) | |
+ instance = new!(*args, &bk) | |
+ Wrapper.new(build(instance).to_app, instance) | |
+ end | |
+ | |
+ # Creates a Rack::Builder instance with all the middleware set up and | |
+ # the given +app+ as end point. | |
+ def build(app) | |
+ builder = Rack::Builder.new | |
+ setup_default_middleware builder | |
+ setup_middleware builder | |
+ builder.run app | |
+ builder | |
+ end | |
+ | |
+ def call(env) | |
+ synchronize { prototype.call(env) } | |
+ end | |
+ | |
+ # Like Kernel#caller but excluding certain magic entries and without | |
+ # line / method information; the resulting array contains filenames only. | |
+ def caller_files | |
+ cleaned_caller(1).flatten | |
+ end | |
+ | |
+ # Like caller_files, but containing Arrays rather than strings with the | |
+ # first element being the file, and the second being the line. | |
+ def caller_locations | |
+ cleaned_caller 2 | |
+ end | |
+ | |
+ private | |
+ | |
+ # Starts the server by running the Rack Handler. | |
+ def start_server(handler, server_settings, handler_name) | |
+ handler.run(self, server_settings) do |server| | |
+ unless handler_name =~ /cgi/i | |
+ $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" | |
+ end | |
+ | |
+ setup_traps | |
+ set :running_server, server | |
+ set :handler_name, handler_name | |
+ server.threaded = settings.threaded if server.respond_to? :threaded= | |
+ | |
+ yield server if block_given? | |
+ end | |
+ end | |
+ | |
+ def setup_traps | |
+ if traps? | |
+ at_exit { quit! } | |
+ | |
+ [:INT, :TERM].each do |signal| | |
+ old_handler = trap(signal) do | |
+ quit! | |
+ old_handler.call if old_handler.respond_to?(:call) | |
+ end | |
+ end | |
+ | |
+ set :traps, false | |
+ end | |
+ end | |
+ | |
# Dynamically defines a method on settings. | |
def define_singleton(name, content = Proc.new) | |
# replace with call to singleton_class once we're 1.9 only | |
@@ -1232,8 +1560,11 @@ module Sinatra | |
types.map! { |t| mime_types(t) } | |
types.flatten! | |
condition do | |
- if type = request.preferred_type(types) | |
- content_type(type) | |
+ if type = response['Content-Type'] | |
+ types.include? type or types.include? type[/^[^;]+/] | |
+ elsif type = request.preferred_type(types) | |
+ params = (type.respond_to?(:params) ? type.params : {}) | |
+ content_type(type, params) | |
true | |
else | |
false | |
@@ -1241,26 +1572,7 @@ module Sinatra | |
end | |
end | |
- public | |
- # Defining a `GET` handler also automatically defines | |
- # a `HEAD` handler. | |
- def get(path, opts={}, &block) | |
- conditions = @conditions.dup | |
- route('GET', path, opts, &block) | |
- | |
- @conditions = conditions | |
- route('HEAD', path, opts, &block) | |
- end | |
- | |
- def put(path, opts={}, &bk) route 'PUT', path, opts, &bk end | |
- def post(path, opts={}, &bk) route 'POST', path, opts, &bk end | |
- def delete(path, opts={}, &bk) route 'DELETE', path, opts, &bk end | |
- def head(path, opts={}, &bk) route 'HEAD', path, opts, &bk end | |
- def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end | |
- def patch(path, opts={}, &bk) route 'PATCH', path, opts, &bk end | |
- | |
- private | |
- def route(verb, path, options={}, &block) | |
+ def route(verb, path, options = {}, &block) | |
# Because of self.options.host | |
host_name(options.delete(:host)) if options.key?(:host) | |
enable :empty_path_info if path == "" and empty_path_info.nil? | |
@@ -1275,6 +1587,7 @@ module Sinatra | |
end | |
def generate_method(method_name, &block) | |
+ method_name = method_name.to_sym | |
define_method(method_name, &block) | |
method = instance_method method_name | |
remove_method method_name | |
@@ -1288,136 +1601,103 @@ module Sinatra | |
pattern, keys = compile path | |
conditions, @conditions = @conditions, [] | |
- [ pattern, keys, conditions, block.arity != 0 ? | |
- proc { |a,p| unbound_method.bind(a).call(*p) } : | |
- proc { |a,p| unbound_method.bind(a).call } ] | |
+ wrapper = block.arity != 0 ? | |
+ proc { |a,p| unbound_method.bind(a).call(*p) } : | |
+ proc { |a,p| unbound_method.bind(a).call } | |
+ wrapper.instance_variable_set(:@route_name, method_name) | |
+ | |
+ [ pattern, keys, conditions, wrapper ] | |
end | |
def compile(path) | |
- keys = [] | |
if path.respond_to? :to_str | |
- pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) } | |
- pattern.gsub!(/((:\w+)|\*)/) do |match| | |
- if match == "*" | |
- keys << 'splat' | |
- "(.*?)" | |
- else | |
- keys << $2[1..-1] | |
- "([^/?#]+)" | |
+ keys = [] | |
+ | |
+ # Split the path into pieces in between forward slashes. | |
+ # A negative number is given as the second argument of path.split | |
+ # because with this number, the method does not ignore / at the end | |
+ # and appends an empty string at the end of the return value. | |
+ # | |
+ segments = path.split('/', -1).map! do |segment| | |
+ ignore = [] | |
+ | |
+ # Special character handling. | |
+ # | |
+ pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c| | |
+ ignore << escaped(c).join if c.match(/[\.@]/) | |
+ patt = encoded(c) | |
+ patt.gsub(/%[\da-fA-F]{2}/) do |match| | |
+ match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join | |
+ end | |
+ end | |
+ | |
+ ignore = ignore.uniq.join | |
+ | |
+ # Key handling. | |
+ # | |
+ pattern.gsub(/((:\w+)|\*)/) do |match| | |
+ if match == "*" | |
+ keys << 'splat' | |
+ "(.*?)" | |
+ else | |
+ keys << $2[1..-1] | |
+ ignore_pattern = safe_ignore(ignore) | |
+ | |
+ ignore_pattern | |
+ end | |
end | |
end | |
- [/^#{pattern}$/, keys] | |
+ | |
+ # Special case handling. | |
+ # | |
+ if last_segment = segments[-1] and last_segment.match(/\[\^\\\./) | |
+ parts = last_segment.rpartition(/\[\^\\\./) | |
+ parts[1] = '[^' | |
+ segments[-1] = parts.join | |
+ end | |
+ [/\A#{segments.join('/')}\z/, keys] | |
elsif path.respond_to?(:keys) && path.respond_to?(:match) | |
[path, path.keys] | |
elsif path.respond_to?(:names) && path.respond_to?(:match) | |
[path, path.names] | |
elsif path.respond_to? :match | |
- [path, keys] | |
+ [path, []] | |
else | |
raise TypeError, path | |
end | |
end | |
- URI = ::URI.const_defined?(:Parser) ? ::URI::Parser.new : ::URI | |
- | |
def encoded(char) | |
- enc = URI.escape(char) | |
- enc = "(?:#{Regexp.escape enc}|#{URI.escape char, /./})" if enc == char | |
+ enc = URI_INSTANCE.escape(char) | |
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char | |
enc = "(?:#{enc}|#{encoded('+')})" if char == " " | |
enc | |
end | |
- public | |
- # Makes the methods defined in the block and in the Modules given | |
- # in `extensions` available to the handlers and templates | |
- def helpers(*extensions, &block) | |
- class_eval(&block) if block_given? | |
- include(*extensions) if extensions.any? | |
+ def escaped(char, enc = URI_INSTANCE.escape(char)) | |
+ [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)] | |
end | |
- # Register an extension. Alternatively take a block from which an | |
- # extension will be created and registered on the fly. | |
- def register(*extensions, &block) | |
- extensions << Module.new(&block) if block_given? | |
- @extensions += extensions | |
- extensions.each do |extension| | |
- extend extension | |
- extension.registered(self) if extension.respond_to?(:registered) | |
+ def safe_ignore(ignore) | |
+ unsafe_ignore = [] | |
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex| | |
+ unsafe_ignore << hex[1..2] | |
+ '' | |
end | |
- end | |
- | |
- def development?; environment == :development end | |
- def production?; environment == :production end | |
- def test?; environment == :test end | |
- | |
- # Set configuration options for Sinatra and/or the app. | |
- # Allows scoping of settings for certain environments. | |
- def configure(*envs, &block) | |
- yield self if envs.empty? || envs.include?(environment.to_sym) | |
- end | |
- | |
- # Use the specified Rack middleware | |
- def use(middleware, *args, &block) | |
- @prototype = nil | |
- @middleware << [middleware, args, block] | |
- end | |
- | |
- def quit!(server, handler_name) | |
- # Use Thin's hard #stop! if available, otherwise just #stop. | |
- server.respond_to?(:stop!) ? server.stop! : server.stop | |
- $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i | |
- end | |
- | |
- # Run the Sinatra app as a self-hosted server using | |
- # Thin, Mongrel or WEBrick (in that order). If given a block, will call | |
- # with the constructed handler once we have taken the stage. | |
- def run!(options={}) | |
- set options | |
- handler = detect_rack_handler | |
- handler_name = handler.name.gsub(/.*::/, '') | |
- handler.run self, :Host => bind, :Port => port do |server| | |
- unless handler_name =~ /cgi/i | |
- $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " + | |
- "on #{port} for #{environment} with backup from #{handler_name}" | |
+ unsafe_patterns = unsafe_ignore.map! do |unsafe| | |
+ chars = unsafe.split(//).map! do |char| | |
+ char == char.downcase ? char : char + char.downcase | |
end | |
- [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } } | |
- server.threaded = settings.threaded if server.respond_to? :threaded= | |
- set :running, true | |
- yield server if block_given? | |
- end | |
- rescue Errno::EADDRINUSE | |
- $stderr.puts "== Someone is already performing on port #{port}!" | |
- end | |
- | |
- # The prototype instance used to process requests. | |
- def prototype | |
- @prototype ||= new | |
- end | |
- # Create a new instance without middleware in front of it. | |
- alias new! new unless method_defined? :new! | |
- | |
- # Create a new instance of the class fronted by its middleware | |
- # pipeline. The object is guaranteed to respond to #call but may not be | |
- # an instance of the class new was called on. | |
- def new(*args, &bk) | |
- build(Rack::Builder.new, *args, &bk).to_app | |
- end | |
- | |
- # Creates a Rack::Builder instance with all the middleware set up and | |
- # an instance of this class as end point. | |
- def build(builder, *args, &bk) | |
- setup_default_middleware builder | |
- setup_middleware builder | |
- builder.run new!(*args, &bk) | |
- builder | |
- end | |
- | |
- def call(env) | |
- synchronize { prototype.call(env) } | |
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])" | |
+ end | |
+ if unsafe_patterns.length > 0 | |
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)" | |
+ else | |
+ "([^#{ignore}/?#]+)" | |
+ end | |
end | |
- private | |
def setup_default_middleware(builder) | |
builder.use ExtendedRack | |
builder.use ShowExceptions if show_exceptions? | |
@@ -1460,8 +1740,9 @@ module Sinatra | |
def setup_protection(builder) | |
return unless protection? | |
options = Hash === protection ? protection.dup : {} | |
+ protect_session = options.fetch(:session) { sessions? } | |
options[:except] = Array options[:except] | |
- options[:except] += [:session_hijacking, :remote_token] unless sessions? | |
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session | |
options[:reaction] ||= :drop_session | |
builder.use Rack::Protection, options | |
end | |
@@ -1500,37 +1781,6 @@ module Sinatra | |
end | |
end | |
- public | |
- CALLERS_TO_IGNORE = [ # :nodoc: | |
- /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code | |
- /lib\/tilt.*\.rb$/, # all tilt code | |
- /^\(.*\)$/, # generated code | |
- /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks | |
- /active_support/, # active_support require hacks | |
- /bundler(\/runtime)?\.rb/, # bundler require hacks | |
- /<internal:/, # internal in ruby >= 1.9.2 | |
- /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files | |
- ] | |
- | |
- # contrary to what the comment said previously, rubinius never supported this | |
- if defined?(RUBY_IGNORE_CALLERS) | |
- warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" | |
- CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) | |
- end | |
- | |
- # Like Kernel#caller but excluding certain magic entries and without | |
- # line / method information; the resulting array contains filenames only. | |
- def caller_files | |
- cleaned_caller(1).flatten | |
- end | |
- | |
- # Like caller_files, but containing Arrays rather than strings with the | |
- # first element being the file, and the second being the line. | |
- def caller_locations | |
- cleaned_caller 2 | |
- end | |
- | |
- private | |
# used for deprecation warnings | |
def warn(message) | |
super message + "\n\tfrom #{cleaned_caller.first.join(':')}" | |
@@ -1539,7 +1789,7 @@ module Sinatra | |
# Like Kernel#caller but excluding certain magic entries | |
def cleaned_caller(keep = 3) | |
caller(1). | |
- map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. | |
+ map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. | |
reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } | |
end | |
end | |
@@ -1577,8 +1827,10 @@ module Sinatra | |
set :logging, false | |
set :protection, true | |
set :method_override, false | |
+ set :use_code, false | |
set :default_encoding, "utf-8" | |
- set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" } | |
+ set :x_cascade, true | |
+ set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } | |
settings.add_charset << /^text\// | |
# explicitly generating a session secret eagerly to play nice with preforking | |
@@ -1596,10 +1848,25 @@ module Sinatra | |
end | |
set :run, false # start server via at-exit hook? | |
- set :running, false # is the built-in server running now? | |
- set :server, %w[thin mongrel webrick] | |
- set :bind, '0.0.0.0' | |
- set :port, 4567 | |
+ set :running_server, nil | |
+ set :handler_name, nil | |
+ set :traps, true | |
+ set :server, %w[HTTP webrick] | |
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } | |
+ set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) | |
+ | |
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE | |
+ | |
+ if ruby_engine == 'macruby' | |
+ server.unshift 'control_tower' | |
+ else | |
+ server.unshift 'reel' | |
+ server.unshift 'mongrel' if ruby_engine.nil? | |
+ server.unshift 'puma' if ruby_engine != 'rbx' | |
+ server.unshift 'thin' if ruby_engine != 'jruby' | |
+ server.unshift 'puma' if ruby_engine == 'rbx' | |
+ server.unshift 'trinidad' if ruby_engine == 'jruby' | |
+ end | |
set :absolute_redirects, true | |
set :prefixed_redirects, false | |
@@ -1624,7 +1891,7 @@ module Sinatra | |
configure :development do | |
get '/__sinatra__/:image.png' do | |
- filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png" | |
+ filename = File.dirname(__FILE__) + "/images/#{params[:image].to_i}.png" | |
content_type :png | |
send_file filename | |
end | |
@@ -1632,25 +1899,44 @@ module Sinatra | |
error NotFound do | |
content_type 'text/html' | |
- (<<-HTML).gsub(/^ {8}/, '') | |
- <!DOCTYPE html> | |
- <html> | |
- <head> | |
- <style type="text/css"> | |
- body { text-align:center;font-family:helvetica,arial;font-size:22px; | |
- color:#888;margin:20px} | |
- #c {margin:0 auto;width:500px;text-align:left} | |
- </style> | |
- </head> | |
- <body> | |
- <h2>Sinatra doesn’t know this ditty.</h2> | |
- <img src='#{uri "/__sinatra__/404.png"}'> | |
- <div id="c"> | |
- Try this: | |
- <pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre> | |
- </div> | |
- </body> | |
- </html> | |
+ if self.class == Sinatra::Application | |
+ code = <<-RUBY.gsub(/^ {12}/, '') | |
+ #{request.request_method.downcase} '#{request.path_info}' do | |
+ "Hello World" | |
+ end | |
+ RUBY | |
+ else | |
+ code = <<-RUBY.gsub(/^ {12}/, '') | |
+ class #{self.class} | |
+ #{request.request_method.downcase} '#{request.path_info}' do | |
+ "Hello World" | |
+ end | |
+ end | |
+ RUBY | |
+ | |
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') | |
+ code = "# in #{file}\n#{code}" unless file.empty? | |
+ end | |
+ | |
+ (<<-HTML).gsub(/^ {10}/, '') | |
+ <!DOCTYPE html> | |
+ <html> | |
+ <head> | |
+ <style type="text/css"> | |
+ body { text-align:center;font-family:helvetica,arial;font-size:22px; | |
+ color:#888;margin:20px} | |
+ #c {margin:0 auto;width:500px;text-align:left} | |
+ </style> | |
+ </head> | |
+ <body> | |
+ <h2>Sinatra doesn’t know this ditty.</h2> | |
+ <img src='#{uri "/__sinatra__/404.png"}'> | |
+ <div id="c"> | |
+ Try this: | |
+ <pre>#{Rack::Utils.escape_html(code)}</pre> | |
+ </div> | |
+ </body> | |
+ </html> | |
HTML | |
end | |
end | |
@@ -1691,10 +1977,10 @@ module Sinatra | |
end | |
end | |
- delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout, | |
- :before, :after, :error, :not_found, :configure, :set, :mime_type, | |
- :enable, :disable, :use, :development?, :test?, :production?, | |
- :helpers, :settings | |
+ delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, | |
+ :template, :layout, :before, :after, :error, :not_found, :configure, | |
+ :set, :mime_type, :enable, :disable, :use, :development?, :test?, | |
+ :production?, :helpers, :settings, :register | |
class << self | |
attr_accessor :target | |
@@ -1703,9 +1989,30 @@ module Sinatra | |
self.target = Application | |
end | |
- # Create a new Sinatra application. The block is evaluated in the new app's | |
- # class scope. | |
- def self.new(base=Base, options={}, &block) | |
+ class Wrapper | |
+ def initialize(stack, instance) | |
+ @stack, @instance = stack, instance | |
+ end | |
+ | |
+ def settings | |
+ @instance.settings | |
+ end | |
+ | |
+ def helpers | |
+ @instance | |
+ end | |
+ | |
+ def call(env) | |
+ @stack.call(env) | |
+ end | |
+ | |
+ def inspect | |
+ "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" | |
+ end | |
+ end | |
+ | |
+ # Create a new Sinatra application; the block is evaluated in the class scope. | |
+ def self.new(base = Base, &block) | |
base = Class.new(base) | |
base.class_eval(&block) if block_given? | |
base | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment