require 'rubygems'
require 'metaid'
require 'uri'
require 'thread'
require 'time'
if ENV['SWIFT']
require 'swiftcore/swiftiplied_mongrel'
puts "Using Swiftiplied Mongrel"
elsif ENV['EVENT']
require 'swiftcore/evented_mongrel'
puts "Using Evented Mongrel"
end
require 'rack'
require 'ostruct'
class Class
def dslify_writter(*syms)
syms.each do |sym|
class_eval <<-end_eval
def #{sym}(v=nil)
self.send "#{sym}=", v if v
v
end
end_eval
end
end
end
module Rack #:nodoc:
class Request #:nodoc:
# Set of request method names allowed via the _method parameter hack. By default,
# all request methods defined in RFC2616 are included, with the exception of
# TRACE and CONNECT.
POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
# Return the HTTP request method with support for method tunneling using the POST
# _method parameter hack. If the real request method is POST and a _method param is
# given and the value is one defined in +POST_TUNNEL_METHODS_ALLOWED+, return the value
# of the _method param instead.
def request_method
if post_tunnel_method_hack?
params['_method'].upcase
else
@env['REQUEST_METHOD']
end
end
def user_agent
env['HTTP_USER_AGENT']
end
private
# Return truthfully if and only if the following conditions are met: 1.) the
# *actual* request method is POST, 2.) the request content-type is one of
# 'application/x-www-form-urlencoded' or 'multipart/form-data', 3.) there is a
# "_method" parameter in the POST body (not in the query string), and 4.) the
# method parameter is one of the verbs listed in the POST_TUNNEL_METHODS_ALLOWED
# list.
def post_tunnel_method_hack?
@env['REQUEST_METHOD'] == 'POST' &&
POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
end
end
module Utils
extend self
end
end
module Sinatra
extend self
module Version
MAJOR = '0'
MINOR = '2'
REVISION = '0'
def self.combined
[MAJOR, MINOR, REVISION].join('.')
end
end
class NotFound < RuntimeError; end
class ServerError < RuntimeError; end
Result = Struct.new(:block, :params, :status) unless defined?(Result)
def options
application.options
end
def application
unless @app
@app = Application.new
Sinatra::Environment.setup!
end
@app
end
def application=(app)
@app = app
end
def port
application.options.port
end
def env
application.options.env
end
def build_application
Rack::CommonLogger.new(application)
end
def run
begin
puts "== Sinatra has taken the stage on port #{port} for #{env}"
require 'pp'
Rack::Handler::Mongrel.run(build_application, :Port => port) do |server|
trap(:INT) do
server.stop
puts "\n== Sinatra has ended his set (crowd applauds)"
end
end
rescue Errno::EADDRINUSE => e
puts "== Someone is already performing on port #{port}!"
end
end
class Event
URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
SPLAT = /(.*?)/
attr_reader :path, :block, :param_keys, :pattern, :options
def initialize(path, options = {}, &b)
@path = path
@block = b
@param_keys = []
@options = options
regex = @path.to_s.gsub(PARAM) do
@param_keys << $1.intern
"(#{URI_CHAR}+)"
end
regex.gsub!('*', SPLAT.to_s)
@pattern = /^#{regex}$/
end
def invoke(request)
params = {}
if options[:agent]
return unless request.user_agent =~ options[:agent]
params[:agent] = $~[1..-1]
end
return unless pattern =~ request.path_info.squeeze('/')
params.merge!(param_keys.zip($~.captures.map(&:from_param)).to_hash)
Result.new(block, params, 200)
end
end
class Error
attr_reader :code, :block
def initialize(code, &b)
@code, @block = code, b
end
def invoke(request)
Result.new(block, {}, 404)
end
end
class Static
def invoke(request)
return unless File.file?(
Sinatra.application.options.public + request.path_info
)
Result.new(block, {}, 200)
end
def block
Proc.new do
send_file Sinatra.application.options.public + request.path_info,
:disposition => nil
end
end
end
# Adapted from actionpack
# Methods for sending files and streams to the browser instead of rendering.
module Streaming
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
:disposition => 'attachment'.freeze,
:stream => true,
:buffer_size => 4096
}.freeze
class FileStreamer
attr_reader :path, :options
def initialize(path, options)
@path, @options = path, options
end
def to_result(cx, *args)
self
end
def each
File.open(path, 'rb') do |file|
while buf = file.read(options[:buffer_size])
yield buf
end
end
end
end
protected
# Sends the file by streaming it 4096 bytes at a time. This way the
# whole file doesn't need to be read into memory at once. This makes
# it feasible to send even large files.
#
# Be careful to sanitize the path parameter if it coming from a web
# page. send_file(params[:path]) allows a malicious user to
# download any file on your server.
#
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to File.basename(path).
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default). When set to nil, the
# Content-Disposition and Content-Transfer-Encoding headers are omitted entirely.
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
# or to read the entire file before sending (false). Defaults to true.
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
# Defaults to 4096.
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
# indicating the last modified time of the file. If the request includes an
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
# is sent instead of the file. Defaults to the file's last modified
# time.
#
# The default Content-Type and Content-Disposition headers are
# set to download arbitrary binary files in as many browsers as
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
# a variety of quirks (especially when downloading over SSL).
#
# Simple download:
# send_file '/path/to.zip'
#
# Show a JPEG in the browser:
# send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
#
# Show a 404 page in the browser:
# send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
#
# Read about the other Content-* HTTP headers if you'd like to
# provide the user with more information (such as Content-Description).
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
#
# Also be aware that the document may be cached by proxies and browsers.
# The Pragma and Cache-Control headers declare how the file may be cached
# by intermediaries. They default to require clients to validate with
# the server before releasing cached responses. See
# http://www.mnot.net/cache_docs/ for an overview of web caching and
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
# for the Cache-Control header spec.
def send_file(path, options = {}) #:doc:
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
options[:length] ||= File.size(path)
options[:filename] ||= File.basename(path)
options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
options[:last_modified] ||= File.mtime(path).httpdate
send_file_headers! options
if options[:stream]
throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
else
File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, file.read] }
end
end
# Send binary data to the user as a file download. May set content type, apparent file name,
# and specify whether to show data inline or download as an attachment.
#
# Options:
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type.
# Defaults to 'application/octet-stream'.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
# indicating the last modified time of the response entity. If the request includes an
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
# is sent instead of the data.
#
# Generic data download:
# send_data buffer
#
# Download a dynamically-generated tarball:
# send_data generate_tgz('dir'), :filename => 'dir.tgz'
#
# Display an image Active Record in the browser:
# send_data image.data, :type => image.content_type, :disposition => 'inline'
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
send_file_headers! options.merge(:length => data.size)
throw :halt, [options[:status] || 200, data]
end
private
def send_file_headers!(options)
options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
[:length, :type, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
end
# Send a "304 Not Modified" if the last_modified option is provided and matches
# the If-Modified-Since request header value.
if last_modified = options[:last_modified]
header 'Last-Modified' => last_modified
throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
end
headers(
'Content-Length' => options[:length].to_s,
'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
)
# Omit Content-Disposition and Content-Transfer-Encoding headers if
# the :disposition option set to nil.
if !options[:disposition].nil?
disposition = options[:disposition].dup || 'attachment'
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
end
# Fix a problem with IE 6.0 on opening downloaded files:
# If Cache-Control: no-cache is set (which Rails does by default),
# IE removes the file it just downloaded from its cache immediately
# after it displays the "open/save" dialog, which means that if you
# hit "open" the file isn't there anymore when the application that
# is called for handling the download is run, so let's workaround that
header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
end
end
module ResponseHelpers
def redirect(path, *args)
status(302)
headers 'Location' => path
throw :halt, *args
end
def headers(header = nil)
@response.headers.merge!(header) if header
@response.headers
end
alias :header :headers
end
module RenderingHelpers
def render(renderer, template, options={})
m = method("render_#{renderer}")
result = m.call(resolve_template(renderer, template, options), options)
if layout = determine_layout(renderer, template, options)
result = m.call(resolve_template(renderer, layout, options), options) { result }
end
result
end
def determine_layout(renderer, template, options)
return if options[:layout] == false
layout_from_options = options[:layout] || :layout
resolve_template(renderer, layout_from_options, options, false)
end
private
def resolve_template(renderer, template, options, scream = true)
case template
when String
template
when Proc
template.call
when Symbol
if proc = templates[template]
resolve_template(renderer, proc, options, scream)
else
read_template_file(renderer, template, options, scream)
end
else
nil
end
end
def read_template_file(renderer, template, options, scream = true)
path = File.join(
options[:views_directory] || Sinatra.application.options.views,
"#{template}.#{renderer}"
)
unless File.exists?(path)
raise Errno::ENOENT.new(path) if scream
nil
else
File.read(path)
end
end
def templates
Sinatra.application.templates
end
end
module Erb
def erb(content, options={})
require 'erb'
render(:erb, content, options)
end
private
def render_erb(content, options = {})
::ERB.new(content).result(binding)
end
end
module Haml
def haml(content, options={})
require 'haml'
render(:haml, content, options)
end
private
def render_haml(content, options = {}, &b)