Skip to content

Instantly share code, notes, and snippets.

@delagoya
Forked from mtodd/README
Created December 8, 2008 14:27
Show Gist options
  • Save delagoya/33468 to your computer and use it in GitHub Desktop.
Save delagoya/33468 to your computer and use it in GitHub Desktop.
A collection of Rack middleware, found all over the internet and part of Merb's core.
module Merb
module Rack
class Application
def call(env)
begin
rack_response = ::Merb::Dispatcher.handle(Merb::Request.new(env))
rescue Object => e
return [500, {Merb::Const::CONTENT_TYPE => "text/html"}, e.message + "<br/>" + e.backtrace.join("<br/>")]
end
Merb.logger.info "\n\n"
Merb.logger.flush
# unless controller.headers[Merb::Const::DATE]
# require "time"
# controller.headers[Merb::Const::DATE] = Time.now.rfc2822.to_s
# end
rack_response
end
def deferred?(env)
path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp('/') : ""
if path =~ Merb.deferred_actions
Merb.logger.info! "Deferring Request: #{path}"
true
else
false
end
end # deferred?(env)
end # Application
end # Rack
end # Merb
# use with::
#
# use CacheSettings, {
# /\/static\// =>
# { :cache_control => "max-age=86400, public",
# :expires => 86400
# }
# }
# use Rack::Static, :urls => ["/static"] # for example
class CacheSettings
def initialize app, pat
@app = app
@pat = pat
end
def call env
res = @app.call(env)
path = env["REQUEST_PATH"]
@pat.each do |pattern,data|
if path =~ pattern
res[1]["Cache-Control"] = data[:cache_control] if data.has_key?(:cache_control)
res[1]["Expires"] = (Time.now + data[:expires]).utc.rfc2822 if data.has_key?(:expires)
return res
end
end
res
end
end
module Merb
module Rack
class ConditionalGet < Merb::Rack::Middleware
def call(env)
status, headers, body = @app.call(env)
if document_not_modified?(env, headers)
status = 304
body = ""
# set Date header using RFC1123 date format as specified by HTTP
# RFC2616 section 3.3.1.
end
[status, headers, body]
end
private
def document_not_modified?(env, headers)
if etag = headers[Merb::Const::ETAG]
etag == env[Merb::Const::HTTP_IF_NONE_MATCH]
elsif last_modified = headers[Merb::Const::LAST_MODIFIED]
last_modified == env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
end
end
end
end
end
module Merb
module Rack
class ContentLength < Merb::Rack::Middleware
def call(env)
status, headers, body = @app.call(env)
# to_s is because Rack spec expects header
# values to be iterable and yield strings
header = 'Content-Length'.freeze
headers[header] = body.size.to_s unless headers.has_key?(header)
[status, headers, body]
end
end
end
end
require 'digest/md5'
module Merb
module Rack
class Csrf < Merb::Rack::Middleware
HTML_TYPES = %w(text/html application/xhtml+xml)
POST_FORM_RE = Regexp.compile('(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', Regexp::IGNORECASE)
ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'.freeze
def call(env)
status, header, body = @app.call(env)
body = body.to_s
if env[Merb::Const::REQUEST_METHOD] == Merb::Const::GET
body = process_response(body) if valid_content_type?(header[Merb::Const::CONTENT_TYPE])
elsif env[Merb::Const::REQUEST_METHOD] == Merb::Const::POST
status, body = process_request(env, status, body)
end
[status, header, body]
end
private
def process_request(env, status, body)
session_id = Merb::Config[:session_id_key]
csrf_token = _make_token(session_id)
request_csrf_token = env['csrf_authentication_token']
unless csrf_token == request_csrf_token
exception = Merb::ControllerExceptions::Forbidden.new(ERROR_MSG)
status = exception.status
body = exception.message
return [status, body]
end
return [status, body]
end
def process_response(body)
session_id = Merb::Config[:session_id_key]
csrf_token = _make_token(session_id)
if csrf_token
modified_body = ''
body.scan(POST_FORM_RE) do |match|
modified_body << add_csrf_field($~, csrf_token)
end
body = modified_body
end
body
end
def add_csrf_field(match, csrf_token)
modified_body = match.pre_match
modified_body << match.to_s
modified_body << "<div style='display: none;'><input type='hidden' id='csrf_authentication_token' name='csrf_authentication_token' value='#{csrf_token}' /></div>"
modified_body << match.post_match
end
def valid_content_type?(content_type)
HTML_TYPES.include?(content_type.split(';').first)
end
def _make_token(session_id)
Digest::MD5.hexdigest(Merb::Config[:session_secret_key] + session_id)
end
end
end
end
module Rack
# A Rack middleware for providing JSON-P support.
#
# Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
#
class JSONP
def initialize(app)
@app = app
end
# Proxies the request to the application, stripping out the JSON-P callback
# method and padding the response with the appropriate callback format.
#
# Changes nothing if no <tt>callback</tt> param is specified.
#
def call(env)
status, headers, response = @app.call(env)
request = Rack::Request.new(env)
response = pad(request.params.delete('callback'), response) if request.params.include?('callback')
[status, headers, response]
end
# Pads the response with the appropriate callback format according to the
# JSON-P spec/requirements.
#
# The Rack response spec indicates that it should be enumerable. The method
# of combining all of the data into a sinle string makes sense since JSON
# is returned as a full string.
#
def pad(callback, response, body = "")
response.each{ |s| body << s }
"#{callback}(#{body})"
end
end
end
class LimitPostBodySize
TYPES = /(errors|reports|alerts)/
CLIENT_ID = /([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/
PLUGIN_ID = /(\d+)/
PATH = %r{/clients/#{CLIENT_ID}/plugins/#{PLUGIN_ID}/#{TYPES}\.scout}
VERSION = /version=(\d+\.\d+\.\d+)/
attr_accessor :app, :max_size
def initialize(app, limit, logger = nil)
@app, @limit, @logger = app, limit, logger
@logger = Logger.new(@logger) unless @logger.is_a?(Logger)
end
def call(env)
post_body_size = case env['rack.input']
when StringIO
env['rack.input'].size
when IO
env['rack.input'].stat.size
else
puts env['rack.input'].class.to_s
0
end
if post_body_size > @limit
puts env['PATH_INFO']
puts env['PATH_INFO'] =~ PATH
client_id, plugin_id, type = $1, $2, $3 if env['PATH_INFO'] =~ PATH
version = $1 if env['QUERY_STRING'] =~ VERSION
logger.info("[#{Time.now.strftime("%Y-%m-%d %H:%I:%S")}] #{type.upcase} ClientID:#{client_id} PluginID:#{plugin_id.to_s} PostBodySize:#{post_body_size.to_s} (Version:#{version})")
end
@app.call(env)
end
def logger
@logger ||= Logger.new(File.join(File.dirname(__FILE__), '..', 'log', 'excessive_post_body.log'))
end
end
module Merb
module Rack
class Middleware
def initialize(app)
@app = app
end
def deferred?(env)
@app.deferred?(env)
end
def call(env)
@app.call(env)
end
end
end
end
module Merb
module Rack
class PathPrefix < Merb::Rack::Middleware
def initialize(app, path_prefix = nil)
super(app)
@path_prefix = /^#{Regexp.escape(path_prefix)}/
end
def deferred?(env)
strip_path_prefix(env)
@app.deferred?(env)
end
def call(env)
strip_path_prefix(env)
@app.call(env)
end
def strip_path_prefix(env)
['PATH_INFO', 'REQUEST_URI'].each do |path_key|
if env[path_key] =~ @path_prefix
env[path_key].sub!(@path_prefix, '')
env[path_key] = '/' if env[path_key].empty?
end
end
end
end
end
end
begin
require 'json'
rescue LoadError => e
require 'json/pure'
end
module Rack
# A Rack middleware for parsing POST/PUT body data when Content-Type is
# not one of the standard supported types, like <tt>application/json</tt>.
#
class PostBodyContentTypeParsers
# Constants
#
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
POST_BODY = 'rack.input'.freeze
FORM_INPUT = 'rack.request.form_input'.freeze
FORM_HASH = 'rack.request.form_hash'.freeze
# Supported Content-Types
#
APPLICATION_JSON = 'application/json'.freeze
def initialize(app)
@app = app
end
def call(env)
case env[CONTENT_TYPE]
when APPLICATION_JSON
env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
end
@app.call(env)
end
end
end
module Merb
module Rack
class Profiler < Merb::Rack::Middleware
def initialize(app, min=1, iter=1)
super(app)
@min, @iter = min, iter
end
def call(env)
__profile__("profile_output", @min, @iter) do
@app.call(env)
end
end
end
end
end
module Merb
module Rack
class Static < Merb::Rack::Middleware
def initialize(app,directory)
super(app)
@static_server = ::Rack::File.new(directory)
end
def call(env)
path = env['PATH_INFO'] ? env['PATH_INFO'].chomp('/') : ""
cached_path = (path.empty? ? 'index' : path) + '.html'
if file_exist?(path) && env['REQUEST_METHOD'] =~ /GET|HEAD/ # Serve the file if it's there and the request method is GET or HEAD
serve_static(env)
elsif file_exist?(cached_path) && env['REQUEST_METHOD'] =~ /GET|HEAD/ # Serve the page cache if it's there and the request method is GET or HEAD
env['PATH_INFO'] = cached_path
serve_static(env)
elsif path =~ /favicon\.ico/
return [404, {"Content-Type"=>"text/html"}, "404 Not Found."]
else
@app.call(env)
end
end
# ==== Parameters
# path<String>:: The path to the file relative to the server root.
#
# ==== Returns
# Boolean:: True if file exists under the server root and is readable.
def file_exist?(path)
full_path = ::File.join(@static_server.root, ::Merb::Request.unescape(path))
::File.file?(full_path) && ::File.readable?(full_path)
end
# ==== Parameters
# env<Hash>:: Environment variables to pass on to the server.
def serve_static(env)
env["PATH_INFO"] = ::Merb::Request.unescape(env["PATH_INFO"])
@static_server.call(env)
end
end
end
end
class SubdomainToPathInfo # man I'm bad with names
def initialize(app)
@app = app
end
def call(env)
@app.call(filter(env))
end
def filter(env)
if env['SERVER_NAME'] =~ /(.*)\.domain\.com/
env['PATH_INFO'] = "#{$1}#{env['PATH_INFO']}"
end
env
end
end
module Merb
module Rack
class Tracer < Merb::Rack::Middleware
def call(env)
Merb.logger.debug!("Rack environment:\n" + env.inspect + "\n\n")
status, headers, body = @app.call(env)
Merb.logger.debug!("Status: #{status.inspect}")
Merb.logger.debug!("Headers: #{headers.inspect}")
Merb.logger.debug!("Body: #{body.inspect}")
[status, headers, body]
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment