rack-contrib 0.9.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,10 +23,26 @@ interface:
23
23
  * Rack::Profiler - Uses ruby-prof to measure request time.
24
24
  * Rack::Sendfile - Enables X-Sendfile support for bodies that can be served
25
25
  from file.
26
+ * Rack::Signals - Installs signal handlers that are safely processed after
27
+ a request
26
28
  * Rack::TimeZone - Detects the clients timezone using JavaScript and sets
27
29
  a variable in Rack's environment with the offset from UTC.
28
30
  * Rack::Evil - Lets the rack application return a response to the client from any place.
29
31
  * Rack::Callbacks - Implements DLS for pure before/after filter like Middlewares.
32
+ * Rack::Config - Shared configuration for cooperative middleware.
33
+ * Rack::NotFound - A default 404 application.
34
+ * Rack::CSSHTTPRequest - Adds CSSHTTPRequest support by encoding responses as
35
+ CSS for cross-site AJAX-style data loading
36
+ * Rack::Deflect - Helps protect against DoS attacks.
37
+ * Rack::ResponseCache - Caches responses to requests without query strings
38
+ to Disk or a user provider Ruby object. Similar to Rails' page caching.
39
+ * Rack::RelativeRedirect - Transforms relative paths in redirects to
40
+ absolute URLs.
41
+ * Rack::Backstage - Returns content of specified file if it exists, which makes
42
+ it convenient for putting up maintenance pages.
43
+ * Rack::AcceptFormat - Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.
44
+ * Rack::HostMeta - Configures /host-meta using a block
45
+ * Rack::Cookies - Adds simple cookie jar hash to env
30
46
 
31
47
  === Use
32
48
 
data/Rakefile CHANGED
@@ -10,11 +10,16 @@ task "RDOX" do
10
10
  sh "specrb -Ilib:test -a --rdox >RDOX"
11
11
  end
12
12
 
13
- desc "Run all the fast tests"
13
+ desc "Run specs with test/unit style output"
14
14
  task :test do
15
15
  sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
16
16
  end
17
17
 
18
+ desc "Run specs with specdoc style output"
19
+ task :spec do
20
+ sh "specrb -Ilib:test -s -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
21
+ end
22
+
18
23
  desc "Run all the tests"
19
24
  task :fulltest do
20
25
  sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
@@ -3,11 +3,15 @@ require 'rack'
3
3
  module Rack
4
4
  module Contrib
5
5
  def self.release
6
- "0.9"
6
+ "0.9.1"
7
7
  end
8
8
  end
9
9
 
10
+ autoload :AcceptFormat, "rack/contrib/accept_format"
10
11
  autoload :BounceFavicon, "rack/contrib/bounce_favicon"
12
+ autoload :Cookies, "rack/contrib/cookies"
13
+ autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
14
+ autoload :Deflect, "rack/contrib/deflect"
11
15
  autoload :ETag, "rack/contrib/etag"
12
16
  autoload :GarbageCollector, "rack/contrib/garbagecollector"
13
17
  autoload :JSONP, "rack/contrib/jsonp"
@@ -18,8 +22,13 @@ module Rack
18
22
  autoload :ProcTitle, "rack/contrib/proctitle"
19
23
  autoload :Profiler, "rack/contrib/profiler"
20
24
  autoload :Sendfile, "rack/contrib/sendfile"
25
+ autoload :Signals, "rack/contrib/signals"
21
26
  autoload :TimeZone, "rack/contrib/time_zone"
22
27
  autoload :Evil, "rack/contrib/evil"
23
28
  autoload :Callbacks, "rack/contrib/callbacks"
24
29
  autoload :NestedParams, "rack/contrib/nested_params"
30
+ autoload :Config, "rack/contrib/config"
31
+ autoload :NotFound, "rack/contrib/not_found"
32
+ autoload :ResponseCache, "rack/contrib/response_cache"
33
+ autoload :RelativeRedirect, "rack/contrib/relative_redirect"
25
34
  end
@@ -0,0 +1,46 @@
1
+ module Rack
2
+ #
3
+ # A Rack middleware for automatically adding a <tt>format</tt> token at the end of the request path
4
+ # when there is none. It can detect formats passed in the HTTP_ACCEPT header to populate this token.
5
+ #
6
+ # e.g.:
7
+ # GET /some/resource HTTP/1.1
8
+ # Accept: application/json
9
+ # ->
10
+ # GET /some/resource.json HTTP/1.1
11
+ # Accept: application/json
12
+ #
13
+ # You can add custom types with this kind of function (taken from sinatra):
14
+ # def mime(ext, type)
15
+ # ext = ".#{ext}" unless ext.to_s[0] == ?.
16
+ # Rack::Mime::MIME_TYPES[ext.to_s] = type
17
+ # end
18
+ # and then:
19
+ # mime :json, 'application/json'
20
+ #
21
+ # Note: it does not take into account multiple media types in the Accept header.
22
+ # The first media type takes precedence over all the others.
23
+ #
24
+ # MIT-License - Cyril Rohr
25
+ #
26
+ class AcceptFormat
27
+
28
+ def initialize(app, default_extention = '.html')
29
+ @ext = default_extention.to_s.strip
30
+ @ext = ".#{@ext}" unless @ext[0] == ?.
31
+ @app = app
32
+ end
33
+
34
+ def call(env)
35
+ req = Rack::Request.new(env)
36
+
37
+ if ::File.extname(req.path_info).empty?
38
+ accept = env['HTTP_ACCEPT'].to_s.scan(/[^;,\s]*\/[^;,\s]*/)[0].to_s
39
+ extension = Rack::Mime::MIME_TYPES.invert[accept] || @ext
40
+ req.path_info = req.path_info+"#{extension}"
41
+ end
42
+
43
+ @app.call(env)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ module Rack
2
+ class Backstage
3
+ File = ::File
4
+
5
+ def initialize(app, path)
6
+ @app = app
7
+ @file = File.expand_path(path)
8
+ end
9
+
10
+ def call(env)
11
+ if File.exists?(@file)
12
+ content = File.read(@file)
13
+ length = "".respond_to?(:bytesize) ? content.bytesize.to_s : content.size.to_s
14
+ [503, {'Content-Type' => 'text/html', 'Content-Length' => length}, [content]]
15
+ else
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Rack
2
+
3
+ # Rack::Config modifies the environment using the block given during
4
+ # initialization.
5
+ class Config
6
+ def initialize(app, &block)
7
+ @app = app
8
+ @block = block
9
+ end
10
+
11
+ def call(env)
12
+ @block.call(env)
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ require 'csshttprequest'
2
+
3
+ module Rack
4
+
5
+ # A Rack middleware for providing CSSHTTPRequest responses.
6
+ class CSSHTTPRequest
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ # Proxies the request to the application then encodes the response with
13
+ # the CSSHTTPRequest encoder
14
+ def call(env)
15
+ status, headers, response = @app.call(env)
16
+ if chr_request?(env)
17
+ response = encode(response)
18
+ modify_headers!(headers, response)
19
+ end
20
+ [status, headers, response]
21
+ end
22
+
23
+ def chr_request?(env)
24
+ env['csshttprequest.chr'] ||=
25
+ !(/\.chr$/.match(env['PATH_INFO'])).nil? || Rack::Request.new(env).params['_format'] == 'chr'
26
+ end
27
+
28
+ def encode(response, assembled_body="")
29
+ response.each { |s| assembled_body << s.to_s } # call down the stack
30
+ return ::CSSHTTPRequest.encode(assembled_body)
31
+ end
32
+
33
+ def modify_headers!(headers, encoded_response)
34
+ headers['Content-Length'] = encoded_response.length.to_s
35
+ headers['Content-Type'] = 'text/css'
36
+ nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,137 @@
1
+ require 'thread'
2
+
3
+ # TODO: optional stats
4
+ # TODO: performance
5
+ # TODO: clean up tests
6
+
7
+ module Rack
8
+
9
+ ##
10
+ # Rack middleware for protecting against Denial-of-service attacks
11
+ # http://en.wikipedia.org/wiki/Denial-of-service_attack.
12
+ #
13
+ # This middleware is designed for small deployments, which most likely
14
+ # are not utilizing load balancing from other software or hardware. Deflect
15
+ # current supports the following functionality:
16
+ #
17
+ # * Saturation prevention (small DoS attacks, or request abuse)
18
+ # * Blacklisting of remote addresses
19
+ # * Whitelisting of remote addresses
20
+ # * Logging
21
+ #
22
+ # === Options:
23
+ #
24
+ # :log When false logging will be bypassed, otherwise pass an object responding to #puts
25
+ # :log_format Alter the logging format
26
+ # :log_date_format Alter the logging date format
27
+ # :request_threshold Number of requests allowed within the set :interval. Defaults to 100
28
+ # :interval Duration in seconds until the request counter is reset. Defaults to 5
29
+ # :block_duration Duration in seconds that a remote address will be blocked. Defaults to 900 (15 minutes)
30
+ # :whitelist Array of remote addresses which bypass Deflect. NOTE: this does not block others
31
+ # :blacklist Array of remote addresses immediately considered malicious
32
+ #
33
+ # === Examples:
34
+ #
35
+ # use Rack::Deflect, :log => $stdout, :request_threshold => 20, :interval => 2, :block_duration => 60
36
+ #
37
+ # CREDIT: TJ Holowaychuk <tj@vision-media.ca>
38
+ #
39
+
40
+ class Deflect
41
+
42
+ attr_reader :options
43
+
44
+ def initialize app, options = {}
45
+ @mutex = Mutex.new
46
+ @remote_addr_map = {}
47
+ @app, @options = app, {
48
+ :log => false,
49
+ :log_format => 'deflect(%s): %s',
50
+ :log_date_format => '%m/%d/%Y',
51
+ :request_threshold => 100,
52
+ :interval => 5,
53
+ :block_duration => 900,
54
+ :whitelist => [],
55
+ :blacklist => []
56
+ }.merge(options)
57
+ end
58
+
59
+ def call env
60
+ return deflect! if deflect? env
61
+ status, headers, body = @app.call env
62
+ [status, headers, body]
63
+ end
64
+
65
+ def deflect!
66
+ [403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, '']
67
+ end
68
+
69
+ def deflect? env
70
+ @remote_addr = env['REMOTE_ADDR']
71
+ return false if options[:whitelist].include? @remote_addr
72
+ return true if options[:blacklist].include? @remote_addr
73
+ sync { watch }
74
+ end
75
+
76
+ def log message
77
+ return unless options[:log]
78
+ options[:log].puts(options[:log_format] % [Time.now.strftime(options[:log_date_format]), message])
79
+ end
80
+
81
+ def sync &block
82
+ @mutex.synchronize(&block)
83
+ end
84
+
85
+ def map
86
+ @remote_addr_map[@remote_addr] ||= {
87
+ :expires => Time.now + options[:interval],
88
+ :requests => 0
89
+ }
90
+ end
91
+
92
+ def watch
93
+ increment_requests
94
+ clear! if watch_expired? and not blocked?
95
+ clear! if blocked? and block_expired?
96
+ block! if watching? and exceeded_request_threshold?
97
+ blocked?
98
+ end
99
+
100
+ def block!
101
+ return if blocked?
102
+ log "blocked #{@remote_addr}"
103
+ map[:block_expires] = Time.now + options[:block_duration]
104
+ end
105
+
106
+ def blocked?
107
+ map.has_key? :block_expires
108
+ end
109
+
110
+ def block_expired?
111
+ map[:block_expires] < Time.now rescue false
112
+ end
113
+
114
+ def watching?
115
+ @remote_addr_map.has_key? @remote_addr
116
+ end
117
+
118
+ def clear!
119
+ return unless watching?
120
+ log "released #{@remote_addr}" if blocked?
121
+ @remote_addr_map.delete @remote_addr
122
+ end
123
+
124
+ def increment_requests
125
+ map[:requests] += 1
126
+ end
127
+
128
+ def exceeded_request_threshold?
129
+ map[:requests] > options[:request_threshold]
130
+ end
131
+
132
+ def watch_expired?
133
+ map[:expires] <= Time.now
134
+ end
135
+
136
+ end
137
+ end
@@ -1,38 +1,41 @@
1
1
  module Rack
2
-
2
+
3
3
  # A Rack middleware for providing JSON-P support.
4
- #
4
+ #
5
5
  # Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
6
- #
6
+ #
7
7
  class JSONP
8
-
8
+
9
9
  def initialize(app)
10
10
  @app = app
11
11
  end
12
-
12
+
13
13
  # Proxies the request to the application, stripping out the JSON-P callback
14
14
  # method and padding the response with the appropriate callback format.
15
- #
15
+ #
16
16
  # Changes nothing if no <tt>callback</tt> param is specified.
17
- #
17
+ #
18
18
  def call(env)
19
19
  status, headers, response = @app.call(env)
20
20
  request = Rack::Request.new(env)
21
- response = pad(request.params.delete('callback'), response) if request.params.include?('callback')
22
- [status, headers, response]
21
+ if request.params.include?('callback')
22
+ response = pad(request.params.delete('callback'), response)
23
+ headers['Content-Length'] = response.length.to_s
24
+ end
25
+ [status, headers, [response]]
23
26
  end
24
-
27
+
25
28
  # Pads the response with the appropriate callback format according to the
26
29
  # JSON-P spec/requirements.
27
- #
30
+ #
28
31
  # The Rack response spec indicates that it should be enumerable. The method
29
- # of combining all of the data into a sinle string makes sense since JSON
32
+ # of combining all of the data into a single string makes sense since JSON
30
33
  # is returned as a full string.
31
- #
34
+ #
32
35
  def pad(callback, response, body = "")
33
- response.each{ |s| body << s }
36
+ response.each{ |s| body << s.to_s }
34
37
  "#{callback}(#{body})"
35
38
  end
36
-
39
+
37
40
  end
38
41
  end
@@ -0,0 +1,18 @@
1
+ module Rack
2
+ # Rack::NotFound is a default endpoint. Initialize with the path to
3
+ # your 404 page.
4
+
5
+ class NotFound
6
+ F = ::File
7
+
8
+ def initialize(path)
9
+ file = F.expand_path(path)
10
+ @content = F.read(file)
11
+ @length = @content.size.to_s
12
+ end
13
+
14
+ def call(env)
15
+ [404, {'Content-Type' => 'text/html', 'Content-Length' => @length}, [@content]]
16
+ end
17
+ end
18
+ end
@@ -5,29 +5,29 @@ rescue LoadError => e
5
5
  end
6
6
 
7
7
  module Rack
8
-
8
+
9
9
  # A Rack middleware for parsing POST/PUT body data when Content-Type is
10
10
  # not one of the standard supported types, like <tt>application/json</tt>.
11
- #
11
+ #
12
12
  # TODO: Find a better name.
13
- #
13
+ #
14
14
  class PostBodyContentTypeParser
15
-
15
+
16
16
  # Constants
17
- #
17
+ #
18
18
  CONTENT_TYPE = 'CONTENT_TYPE'.freeze
19
19
  POST_BODY = 'rack.input'.freeze
20
20
  FORM_INPUT = 'rack.request.form_input'.freeze
21
21
  FORM_HASH = 'rack.request.form_hash'.freeze
22
-
22
+
23
23
  # Supported Content-Types
24
- #
24
+ #
25
25
  APPLICATION_JSON = 'application/json'.freeze
26
-
26
+
27
27
  def initialize(app)
28
28
  @app = app
29
29
  end
30
-
30
+
31
31
  def call(env)
32
32
  case env[CONTENT_TYPE]
33
33
  when APPLICATION_JSON
@@ -35,6 +35,6 @@ module Rack
35
35
  end
36
36
  @app.call(env)
37
37
  end
38
-
38
+
39
39
  end
40
40
  end