# external dependencies
require 'rack'
require 'tilt'
require 'rack/protection'
# stdlib dependencies
require 'thread'
require 'time'
require 'uri'
# other files we need
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
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
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).include?(type)
end
def preferred_type(*types)
accepts = accept # just evaluate once
return accepts.first if types.empty?
types.flatten!
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 secure? ssl?
def forwarded?
@env.include? "HTTP_X_FORWARDED_HOST"
end
def safe?
get? or head? or options? or trace?
end
def idempotent?
safe? or put? or delete? or link? or unlink?
end
def link?
request_method == "LINK"
end
def unlink?
request_method == "UNLINK"
end
private
class AcceptEntry
attr_accessor :params
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::Response::Helpers for
# more info:
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
class Response < Rack::Response
DROP_BODY_RESPONSES = [204, 205, 304]
def initialize(*)
super
headers['Content-Type'] ||= 'text/html'
end
def body=(value)
value = value.body while Rack::Response === value
@body = String === value ? [value.to_str] : value
end
def each
block_given? ? super : enum_for(:each)
end
def finish
result = body
if drop_content_info?
headers.delete "Content-Length"
headers.delete "Content-Type"
end
if drop_body?
close
result = []
end
if calculate_content_length?
# if some other code has already set Content-Length, don't muck with it
# currently, this would be the static file-handler
headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
end
[status.to_i, headers, result]
end
private
def calculate_content_length?
headers["Content-Type"] and not headers["Content-Length"] and Array === body
end
def drop_content_info?
status.to_i / 100 == 1 or drop_body?
end
def drop_body?
DROP_BODY_RESPONSES.include?(status.to_i)
end
end
# Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however,
# some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
# This middleware will detect an extended body object and will make sure it reaches the
# handler directly. We do this here, so our middleware and middleware set up by the app will
# still be able to run.
class ExtendedRack < Struct.new(:app)
def call(env)
result, callback = app.call(env), env['async.callback']
return result unless callback and async?(*result)
after_response { callback.call result }
setup_close(env, *result)
throw :async
end
private
def setup_close(env, status, headers, body)
return unless body.respond_to? :close and env.include? 'async.close'
env['async.close'].callback { body.close }
env['async.close'].errback { body.close }
end
def after_response(&block)
raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine
EventMachine.next_tick(&block)
end
def async?(status, headers, body)
return true if status == -1
body.respond_to? :callback and body.respond_to? :errback
end
end
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? @app.call(env) : super
end
superclass.class_eval do
alias call_without_check call unless method_defined? :call_without_check
def call(env)
env['sinatra.commonlogger'] = true
call_without_check(env)
end
end
end
class NotFound < NameError #:nodoc:
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)
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)
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
end
end
# Halt processing and redirect to the URI provided.
def redirect(uri, *args)
if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
status 303
else
status 302
end
# According to RFC 2616 section 14.30, "the field value consists of a
# single absolute URI"
response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
halt(*args)
end
# Generates the absolute URI for a given path in the app.
# Takes Rack routers and reverse proxies into account.
def uri(addr = nil, absolute = true, add_script_name = true)
return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
uri = [host = ""]
if absolute
host << "http#{'s' if request.secure?}://"
if request.forwarded? or request.port != (request.secure? ? 443 : 80)
host << request.host_with_port
else
host << request.host
end
end
uri << request.script_name.to_s if add_script_name
uri << (addr ? addr : request.path_info).to_s
File.join uri
end
alias url uri
alias to uri
# Halt processing and return the error status provided.
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)
error 404, body
end
# Set multiple response headers with Hash.
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
# Look up a media type by file extension in Rack's mime registry.
def mime_type(type)
Base.mime_type(type)
end
# Set the Content-Type of the response body given a media type or file
# extension.
def content_type(type = nil, params = {})
return response['Content-Type'] unless type
default = params.delete :default
mime_type = mime_type(type) || default
fail "Unknown media type: %p" % type if mime_type.nil?
mime_type = mime_type.dup
unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
params[:charset] = params.delete('charset') || settings.default_encoding
end
params.delete :charset if mime_type.include? 'charset'
unless params.empty?
mime_type << (mime_type.include?(';') ? ', ' : ';')
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, disposition = 'attachment')
response['Content-Disposition'] = disposition.to_s
if filename
params = '; filename="%s"' % File.basename(filename)
response['Content-Disposition'] << params
ext = File.extname(filename)
Loading ...