rack 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- data/AUTHORS +2 -0
- data/RDOX +46 -1
- data/README +19 -4
- data/Rakefile +9 -8
- data/bin/rackup +2 -0
- data/example/protectedlobster.rb +14 -0
- data/lib/rack.rb +19 -0
- data/lib/rack/adapter/camping.rb +6 -0
- data/lib/rack/auth/abstract/handler.rb +28 -0
- data/lib/rack/auth/abstract/request.rb +39 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +52 -0
- data/lib/rack/auth/digest/params.rb +55 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/commonlogger.rb +1 -1
- data/lib/rack/file.rb +1 -1
- data/lib/rack/handler/cgi.rb +7 -7
- data/lib/rack/handler/fastcgi.rb +1 -1
- data/lib/rack/handler/mongrel.rb +8 -7
- data/lib/rack/handler/webrick.rb +7 -6
- data/lib/rack/lint.rb +3 -3
- data/lib/rack/lobster.rb +4 -4
- data/lib/rack/mock.rb +9 -33
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -1
- data/lib/rack/request.rb +32 -9
- data/lib/rack/response.rb +54 -8
- data/lib/rack/session/cookie.rb +73 -0
- data/lib/rack/showexceptions.rb +2 -2
- data/lib/rack/showstatus.rb +103 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +1 -1
- data/lib/rack/utils.rb +45 -3
- data/test/spec_rack_auth_basic.rb +68 -0
- data/test/spec_rack_auth_digest.rb +167 -0
- data/test/spec_rack_camping.rb +3 -0
- data/test/spec_rack_mock.rb +2 -0
- data/test/spec_rack_mongrel.rb +12 -0
- data/test/spec_rack_request.rb +60 -0
- data/test/spec_rack_response.rb +50 -1
- data/test/spec_rack_session_cookie.rb +49 -0
- data/test/spec_rack_showstatus.rb +71 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_urlmap.rb +1 -7
- data/test/spec_rack_webrick.rb +17 -0
- metadata +23 -3
- data/lib/rack/adapter/rails.rb +0 -65
data/lib/rack/recursive.rb
CHANGED
data/lib/rack/reloader.rb
CHANGED
data/lib/rack/request.rb
CHANGED
@@ -8,11 +8,11 @@ module Rack
|
|
8
8
|
# req = Rack::Request.new(env)
|
9
9
|
# req.post?
|
10
10
|
# req.params["data"]
|
11
|
-
|
11
|
+
|
12
12
|
class Request
|
13
13
|
# The environment of the request.
|
14
14
|
attr_reader :env
|
15
|
-
|
15
|
+
|
16
16
|
def initialize(env)
|
17
17
|
@env = env
|
18
18
|
end
|
@@ -59,7 +59,7 @@ module Rack
|
|
59
59
|
else
|
60
60
|
@env["rack.request.form_input"] = @env["rack.input"]
|
61
61
|
unless @env["rack.request.form_hash"] =
|
62
|
-
Utils::Multipart.parse_multipart(env)
|
62
|
+
Utils::Multipart.parse_multipart(env)
|
63
63
|
@env["rack.request.form_vars"] = @env["rack.input"].read
|
64
64
|
@env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"])
|
65
65
|
end
|
@@ -72,6 +72,28 @@ module Rack
|
|
72
72
|
self.GET.update(self.POST)
|
73
73
|
end
|
74
74
|
|
75
|
+
# shortcut for request.params[key]
|
76
|
+
def [](key)
|
77
|
+
params[key.to_s]
|
78
|
+
end
|
79
|
+
|
80
|
+
# shortcut for request.params[key] = value
|
81
|
+
def []=(key, value)
|
82
|
+
params[key.to_s] = value
|
83
|
+
end
|
84
|
+
|
85
|
+
# like Hash#values_at
|
86
|
+
def values_at(*keys)
|
87
|
+
keys.map{|key| params[key] }
|
88
|
+
end
|
89
|
+
|
90
|
+
# the referer of the client or '/'
|
91
|
+
def referer
|
92
|
+
@env['HTTP_REFERER'] || '/'
|
93
|
+
end
|
94
|
+
alias referrer referer
|
95
|
+
|
96
|
+
|
75
97
|
def cookies
|
76
98
|
return {} unless @env["HTTP_COOKIE"]
|
77
99
|
|
@@ -99,14 +121,15 @@ module Rack
|
|
99
121
|
url << ":#{port}"
|
100
122
|
end
|
101
123
|
|
102
|
-
url <<
|
103
|
-
url << path_info
|
104
|
-
|
105
|
-
unless query_string.empty?
|
106
|
-
url << "?" << query_string
|
107
|
-
end
|
124
|
+
url << fullpath
|
108
125
|
|
109
126
|
url
|
110
127
|
end
|
128
|
+
|
129
|
+
def fullpath
|
130
|
+
path = script_name + path_info
|
131
|
+
path << "?" << query_string unless query_string.empty?
|
132
|
+
path
|
133
|
+
end
|
111
134
|
end
|
112
135
|
end
|
data/lib/rack/response.rb
CHANGED
@@ -2,18 +2,18 @@ require 'rack/request'
|
|
2
2
|
require 'rack/utils'
|
3
3
|
|
4
4
|
module Rack
|
5
|
-
# Rack::
|
5
|
+
# Rack::Response provides a convenient interface to create a Rack
|
6
6
|
# response.
|
7
7
|
#
|
8
8
|
# It allows setting of headers and cookies, and provides useful
|
9
9
|
# defaults (a OK response containing HTML).
|
10
10
|
#
|
11
|
-
# You can use
|
12
|
-
# but note that this is buffered by Rack::
|
11
|
+
# You can use Response#write to iteratively generate your response,
|
12
|
+
# but note that this is buffered by Rack::Response until you call
|
13
13
|
# +finish+. +finish+ however can take a block inside which calls to
|
14
14
|
# +write+ are syncronous with the Rack response.
|
15
15
|
#
|
16
|
-
# Your application's +call+ should end returning
|
16
|
+
# Your application's +call+ should end returning Response#finish.
|
17
17
|
|
18
18
|
class Response
|
19
19
|
def initialize(body=[], status=200, header={}, &block)
|
@@ -22,17 +22,18 @@ module Rack
|
|
22
22
|
merge(header))
|
23
23
|
|
24
24
|
@writer = lambda { |x| @body << x }
|
25
|
+
@block = nil
|
25
26
|
|
26
27
|
@body = []
|
27
28
|
|
28
|
-
if body.
|
29
|
-
write body
|
29
|
+
if body.respond_to? :to_str
|
30
|
+
write body.to_str
|
30
31
|
elsif body.respond_to?(:each)
|
31
32
|
body.each { |part|
|
32
33
|
write part.to_s
|
33
34
|
}
|
34
35
|
else
|
35
|
-
raise TypeError, "
|
36
|
+
raise TypeError, "stringable or iterable required"
|
36
37
|
end
|
37
38
|
|
38
39
|
yield self if block_given?
|
@@ -107,8 +108,53 @@ module Rack
|
|
107
108
|
end
|
108
109
|
|
109
110
|
def write(str)
|
110
|
-
@writer.call str
|
111
|
+
@writer.call str.to_s
|
111
112
|
str
|
112
113
|
end
|
114
|
+
|
115
|
+
def empty?
|
116
|
+
@block == nil && @body.empty?
|
117
|
+
end
|
118
|
+
|
119
|
+
alias headers header
|
120
|
+
|
121
|
+
module Helpers
|
122
|
+
def invalid?; @status < 100 || @status >= 600; end
|
123
|
+
|
124
|
+
def informational?; @status >= 100 && @status < 200; end
|
125
|
+
def successful?; @status >= 200 && @status < 300; end
|
126
|
+
def redirection?; @status >= 300 && @status < 400; end
|
127
|
+
def client_error?; @status >= 400 && @status < 500; end
|
128
|
+
def server_error?; @status >= 500 && @status < 600; end
|
129
|
+
|
130
|
+
def ok?; @status == 200; end
|
131
|
+
def forbidden?; @status == 403; end
|
132
|
+
def not_found?; @status == 404; end
|
133
|
+
|
134
|
+
def redirect?; [301, 302, 303, 307].include? @status; end
|
135
|
+
def empty?; [201, 204, 304].include? @status; end
|
136
|
+
|
137
|
+
# Headers
|
138
|
+
attr_reader :headers, :original_headers
|
139
|
+
|
140
|
+
def include?(header)
|
141
|
+
!!headers[header]
|
142
|
+
end
|
143
|
+
|
144
|
+
def content_type
|
145
|
+
headers["Content-Type"]
|
146
|
+
end
|
147
|
+
|
148
|
+
def content_length
|
149
|
+
cl = headers["Content-Length"]
|
150
|
+
cl ? cl.to_i : cl
|
151
|
+
end
|
152
|
+
|
153
|
+
def location
|
154
|
+
headers["Location"]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
include Helpers
|
113
159
|
end
|
114
160
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
module Session
|
6
|
+
|
7
|
+
# Rack::Session::Cookie provides simple cookie based session management.
|
8
|
+
# The session is a Ruby Hash stored as base64 encoded marshalled data
|
9
|
+
# set to :key (default: rack.session).
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# use Rack::Session::Cookie, :key => 'rack.session',
|
14
|
+
# :domain => 'foo.com',
|
15
|
+
# :path => '/',
|
16
|
+
# :expire_after => 2592000
|
17
|
+
#
|
18
|
+
# All parameters are optional.
|
19
|
+
|
20
|
+
class Cookie
|
21
|
+
|
22
|
+
def initialize(app, options={})
|
23
|
+
@app = app
|
24
|
+
@key = options[:key] || "rack.session"
|
25
|
+
@default_options = {:domain => nil,
|
26
|
+
:path => "/",
|
27
|
+
:expire_after => nil}.merge(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
load_session(env)
|
32
|
+
status, headers, body = @app.call(env)
|
33
|
+
commit_session(env, status, headers, body)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def load_session(env)
|
39
|
+
request = Rack::Request.new(env)
|
40
|
+
session_data = request.cookies[@key]
|
41
|
+
|
42
|
+
begin
|
43
|
+
session_data = Base64.decode64(session_data)
|
44
|
+
session_data = Marshal.load(session_data)
|
45
|
+
env["rack.session"] = session_data
|
46
|
+
rescue
|
47
|
+
env["rack.session"] = Hash.new
|
48
|
+
end
|
49
|
+
|
50
|
+
env["rack.session.options"] = @default_options.dup
|
51
|
+
end
|
52
|
+
|
53
|
+
def commit_session(env, status, headers, body)
|
54
|
+
session_data = Marshal.dump(env["rack.session"])
|
55
|
+
session_data = Base64.encode64(session_data)
|
56
|
+
|
57
|
+
if session_data.size > (4096 - @key.size)
|
58
|
+
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
|
59
|
+
[status, headers, body]
|
60
|
+
else
|
61
|
+
options = env["rack.session.options"]
|
62
|
+
cookie = Hash.new
|
63
|
+
cookie[:value] = session_data
|
64
|
+
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
|
65
|
+
response = Rack::Response.new(body, status, headers)
|
66
|
+
response.set_cookie(@key, cookie.merge(options))
|
67
|
+
response.to_a
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/rack/showexceptions.rb
CHANGED
@@ -10,7 +10,7 @@ module Rack
|
|
10
10
|
#
|
11
11
|
# Be careful when you use this on public-facing sites as it could
|
12
12
|
# reveal information helpful to attackers.
|
13
|
-
|
13
|
+
|
14
14
|
class ShowExceptions
|
15
15
|
CONTEXT = 7
|
16
16
|
|
@@ -60,7 +60,7 @@ module Rack
|
|
60
60
|
[@template.result(binding)]
|
61
61
|
end
|
62
62
|
|
63
|
-
def h(obj)
|
63
|
+
def h(obj) # :nodoc:
|
64
64
|
case obj
|
65
65
|
when String
|
66
66
|
Utils.escape_html(obj)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Rack::ShowStatus catches all empty responses the app it wraps and
|
7
|
+
# replaces them with a site explaining the error.
|
8
|
+
#
|
9
|
+
# Additional details can be put into <tt>rack.showstatus.detail</tt>
|
10
|
+
# and will be shown as HTML. If such details exist, the error page
|
11
|
+
# is always rendered, even if the reply was not empty.
|
12
|
+
|
13
|
+
class ShowStatus
|
14
|
+
def initialize(app)
|
15
|
+
@app = app
|
16
|
+
@template = ERB.new(TEMPLATE)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
status, headers, body = @app.call(env)
|
21
|
+
|
22
|
+
# client or server error, or explicit message
|
23
|
+
if status.to_i >= 400 &&
|
24
|
+
(body.empty? rescue false) || env["rack.showstatus.detail"]
|
25
|
+
req = Rack::Request.new(env)
|
26
|
+
message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
|
27
|
+
detail = env["rack.showstatus.detail"] || message
|
28
|
+
[status, headers.merge("Content-Type" => "text/html"), [@template.result(binding)]]
|
29
|
+
else
|
30
|
+
[status, headers, body]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def h(obj) # :nodoc:
|
35
|
+
case obj
|
36
|
+
when String
|
37
|
+
Utils.escape_html(obj)
|
38
|
+
else
|
39
|
+
Utils.escape_html(obj.inspect)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# :stopdoc:
|
44
|
+
|
45
|
+
# adapted from Django <djangoproject.com>
|
46
|
+
# Copyright (c) 2005, the Lawrence Journal-World
|
47
|
+
# Used under the modified BSD license:
|
48
|
+
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
49
|
+
TEMPLATE = <<'HTML'
|
50
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
51
|
+
<html lang="en">
|
52
|
+
<head>
|
53
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
54
|
+
<title><%=h message %> at <%=h req.script_name + req.path_info %></title>
|
55
|
+
<meta name="robots" content="NONE,NOARCHIVE" />
|
56
|
+
<style type="text/css">
|
57
|
+
html * { padding:0; margin:0; }
|
58
|
+
body * { padding:10px 20px; }
|
59
|
+
body * * { padding:0; }
|
60
|
+
body { font:small sans-serif; background:#eee; }
|
61
|
+
body>div { border-bottom:1px solid #ddd; }
|
62
|
+
h1 { font-weight:normal; margin-bottom:.4em; }
|
63
|
+
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
64
|
+
table { border:none; border-collapse: collapse; width:100%; }
|
65
|
+
td, th { vertical-align:top; padding:2px 3px; }
|
66
|
+
th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
67
|
+
#info { background:#f6f6f6; }
|
68
|
+
#info ol { margin: 0.5em 4em; }
|
69
|
+
#info ol li { font-family: monospace; }
|
70
|
+
#summary { background: #ffc; }
|
71
|
+
#explanation { background:#eee; border-bottom: 0px none; }
|
72
|
+
</style>
|
73
|
+
</head>
|
74
|
+
<body>
|
75
|
+
<div id="summary">
|
76
|
+
<h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
|
77
|
+
<table class="meta">
|
78
|
+
<tr>
|
79
|
+
<th>Request Method:</th>
|
80
|
+
<td><%=h req.request_method %></td>
|
81
|
+
</tr>
|
82
|
+
<tr>
|
83
|
+
<th>Request URL:</th>
|
84
|
+
<td><%=h req.url %></td>
|
85
|
+
</tr>
|
86
|
+
</table>
|
87
|
+
</div>
|
88
|
+
<div id="info">
|
89
|
+
<p><%= detail %></p>
|
90
|
+
</div>
|
91
|
+
|
92
|
+
<div id="explanation">
|
93
|
+
<p>
|
94
|
+
You're seeing this error because you use <code>Rack::ShowStatus</code>.
|
95
|
+
</p>
|
96
|
+
</div>
|
97
|
+
</body>
|
98
|
+
</html>
|
99
|
+
HTML
|
100
|
+
|
101
|
+
# :startdoc:
|
102
|
+
end
|
103
|
+
end
|
data/lib/rack/static.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rack
|
2
|
+
|
3
|
+
# The Rack::Static middleware intercepts requests for static files
|
4
|
+
# (javascript files, images, stylesheets, etc) based on the url prefixes
|
5
|
+
# passed in the options, and serves them using a Rack::File object. This
|
6
|
+
# allows a Rack stack to serve both static and dynamic content.
|
7
|
+
#
|
8
|
+
# Examples:
|
9
|
+
# use Rack::Static, :urls => ["/media"]
|
10
|
+
# will serve all requests beginning with /media from the "media" folder
|
11
|
+
# located in the current directory (ie media/*).
|
12
|
+
#
|
13
|
+
# use Rack::Static, :urls => ["/css", "/images"], :root => "public"
|
14
|
+
# will serve all requests beginning with /css or /images from the folder
|
15
|
+
# "public" in the current directory (ie public/css/* and public/images/*)
|
16
|
+
|
17
|
+
class Static
|
18
|
+
|
19
|
+
def initialize(app, options={})
|
20
|
+
@app = app
|
21
|
+
@urls = options[:urls] || ["/favicon.ico"]
|
22
|
+
root = options[:root] || Dir.pwd
|
23
|
+
@file_server = Rack::File.new(root)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
path = env["PATH_INFO"]
|
28
|
+
can_serve = @urls.any? { |url| path.index(url) == 0 }
|
29
|
+
|
30
|
+
if can_serve
|
31
|
+
@file_server.call(env)
|
32
|
+
else
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/rack/urlmap.rb
CHANGED
data/lib/rack/utils.rb
CHANGED
@@ -3,7 +3,7 @@ require 'tempfile'
|
|
3
3
|
module Rack
|
4
4
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
5
5
|
# applications adopted from all kinds of Ruby libraries.
|
6
|
-
|
6
|
+
|
7
7
|
module Utils
|
8
8
|
# Performs URI escaping so that you can construct proper
|
9
9
|
# query strings faster. Use this rather than the cgi.rb
|
@@ -27,7 +27,7 @@ module Rack
|
|
27
27
|
# Parses a query string by breaking it up at the '&'
|
28
28
|
# and ';' characters. You can also use this to parse
|
29
29
|
# cookies by changing the characters used in the second
|
30
|
-
# parameter (which defaults to '&;'.
|
30
|
+
# parameter (which defaults to '&;').
|
31
31
|
|
32
32
|
def parse_query(qs, d = '&;')
|
33
33
|
params = {}
|
@@ -81,10 +81,52 @@ module Rack
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
# Every standard HTTP code mapped to the appropriate message.
|
85
|
+
# Stolen from Mongrel.
|
86
|
+
HTTP_STATUS_CODES = {
|
87
|
+
100 => 'Continue',
|
88
|
+
101 => 'Switching Protocols',
|
89
|
+
200 => 'OK',
|
90
|
+
201 => 'Created',
|
91
|
+
202 => 'Accepted',
|
92
|
+
203 => 'Non-Authoritative Information',
|
93
|
+
204 => 'No Content',
|
94
|
+
205 => 'Reset Content',
|
95
|
+
206 => 'Partial Content',
|
96
|
+
300 => 'Multiple Choices',
|
97
|
+
301 => 'Moved Permanently',
|
98
|
+
302 => 'Moved Temporarily',
|
99
|
+
303 => 'See Other',
|
100
|
+
304 => 'Not Modified',
|
101
|
+
305 => 'Use Proxy',
|
102
|
+
400 => 'Bad Request',
|
103
|
+
401 => 'Unauthorized',
|
104
|
+
402 => 'Payment Required',
|
105
|
+
403 => 'Forbidden',
|
106
|
+
404 => 'Not Found',
|
107
|
+
405 => 'Method Not Allowed',
|
108
|
+
406 => 'Not Acceptable',
|
109
|
+
407 => 'Proxy Authentication Required',
|
110
|
+
408 => 'Request Time-out',
|
111
|
+
409 => 'Conflict',
|
112
|
+
410 => 'Gone',
|
113
|
+
411 => 'Length Required',
|
114
|
+
412 => 'Precondition Failed',
|
115
|
+
413 => 'Request Entity Too Large',
|
116
|
+
414 => 'Request-URI Too Large',
|
117
|
+
415 => 'Unsupported Media Type',
|
118
|
+
500 => 'Internal Server Error',
|
119
|
+
501 => 'Not Implemented',
|
120
|
+
502 => 'Bad Gateway',
|
121
|
+
503 => 'Service Unavailable',
|
122
|
+
504 => 'Gateway Time-out',
|
123
|
+
505 => 'HTTP Version not supported'
|
124
|
+
}
|
125
|
+
|
84
126
|
# A multipart form data parser, adapted from IOWA.
|
85
127
|
#
|
86
128
|
# Usually, Rack::Request#POST takes care of calling this.
|
87
|
-
|
129
|
+
|
88
130
|
module Multipart
|
89
131
|
EOL = "\r\n"
|
90
132
|
|