delagoya (owner)

Fork Of

Revisions

gist: 33468 Download_button fork
public
Public Clone URL: git://gist.github.com/33468.git
Embed All Files: show embed
README #
1
A collection of Rack middleware, found all over the internet and part of Merb's core.
application.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
 
cache_headers.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 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
 
conditional_get.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
 
content_length.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
 
csrf.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
 
jsonp.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
 
limit_post_body_size.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
 
middleware.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
 
 
path_prefix.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
post_body_content_type_parsers.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
 
profiler.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
static.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
subdomain_to_path_info.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
 
tracer.rb #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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