kastner-rack 0.3.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +8 -0
- data/COPYING +18 -0
- data/KNOWN-ISSUES +18 -0
- data/README +273 -0
- data/Rakefile +185 -0
- data/bin/rackup +172 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +85 -0
- data/lib/rack/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +28 -0
- data/lib/rack/auth/abstract/request.rb +37 -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 +51 -0
- data/lib/rack/auth/digest/params.rb +55 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/auth/openid.rb +437 -0
- data/lib/rack/builder.rb +67 -0
- data/lib/rack/cascade.rb +36 -0
- data/lib/rack/commonlogger.rb +61 -0
- data/lib/rack/conditionalget.rb +42 -0
- data/lib/rack/deflater.rb +63 -0
- data/lib/rack/directory.rb +149 -0
- data/lib/rack/file.rb +84 -0
- data/lib/rack/handler.rb +46 -0
- data/lib/rack/handler/cgi.rb +57 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +86 -0
- data/lib/rack/handler/lsws.rb +52 -0
- data/lib/rack/handler/mongrel.rb +78 -0
- data/lib/rack/handler/scgi.rb +57 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/webrick.rb +61 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +463 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/methodoverride.rb +21 -0
- data/lib/rack/mime.rb +204 -0
- data/lib/rack/mock.rb +160 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +64 -0
- data/lib/rack/request.rb +217 -0
- data/lib/rack/response.rb +171 -0
- data/lib/rack/session/abstract/id.rb +140 -0
- data/lib/rack/session/cookie.rb +89 -0
- data/lib/rack/session/memcache.rb +97 -0
- data/lib/rack/session/pool.rb +73 -0
- data/lib/rack/showexceptions.rb +348 -0
- data/lib/rack/showstatus.rb +105 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +48 -0
- data/lib/rack/utils.rb +318 -0
- data/rack.gemspec +31 -0
- data/test/cgi/lighttpd.conf +20 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +7 -0
- data/test/spec_rack_auth_basic.rb +69 -0
- data/test/spec_rack_auth_digest.rb +169 -0
- data/test/spec_rack_auth_openid.rb +137 -0
- data/test/spec_rack_builder.rb +84 -0
- data/test/spec_rack_camping.rb +51 -0
- data/test/spec_rack_cascade.rb +50 -0
- data/test/spec_rack_cgi.rb +89 -0
- data/test/spec_rack_commonlogger.rb +32 -0
- data/test/spec_rack_conditionalget.rb +41 -0
- data/test/spec_rack_deflater.rb +70 -0
- data/test/spec_rack_directory.rb +56 -0
- data/test/spec_rack_fastcgi.rb +89 -0
- data/test/spec_rack_file.rb +57 -0
- data/test/spec_rack_handler.rb +24 -0
- data/test/spec_rack_head.rb +30 -0
- data/test/spec_rack_lint.rb +371 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_methodoverride.rb +31 -0
- data/test/spec_rack_mock.rb +152 -0
- data/test/spec_rack_mongrel.rb +170 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +426 -0
- data/test/spec_rack_response.rb +173 -0
- data/test/spec_rack_session_cookie.rb +78 -0
- data/test/spec_rack_session_memcache.rb +132 -0
- data/test/spec_rack_session_pool.rb +84 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_showstatus.rb +72 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_urlmap.rb +175 -0
- data/test/spec_rack_utils.rb +174 -0
- data/test/spec_rack_webrick.rb +123 -0
- data/test/testrequest.rb +45 -0
- metadata +177 -0
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
empty = headers['Content-Length'].to_i <= 0
|
|
22
|
+
|
|
23
|
+
# client or server error, or explicit message
|
|
24
|
+
if (status.to_i >= 400 && empty) || 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
|
+
body = @template.result(binding)
|
|
29
|
+
size = body.respond_to?(:bytesize) ? body.bytesize : body.size
|
|
30
|
+
[status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
|
|
31
|
+
else
|
|
32
|
+
[status, headers, body]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def h(obj) # :nodoc:
|
|
37
|
+
case obj
|
|
38
|
+
when String
|
|
39
|
+
Utils.escape_html(obj)
|
|
40
|
+
else
|
|
41
|
+
Utils.escape_html(obj.inspect)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# :stopdoc:
|
|
46
|
+
|
|
47
|
+
# adapted from Django <djangoproject.com>
|
|
48
|
+
# Copyright (c) 2005, the Lawrence Journal-World
|
|
49
|
+
# Used under the modified BSD license:
|
|
50
|
+
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
|
51
|
+
TEMPLATE = <<'HTML'
|
|
52
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
53
|
+
<html lang="en">
|
|
54
|
+
<head>
|
|
55
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
56
|
+
<title><%=h message %> at <%=h req.script_name + req.path_info %></title>
|
|
57
|
+
<meta name="robots" content="NONE,NOARCHIVE" />
|
|
58
|
+
<style type="text/css">
|
|
59
|
+
html * { padding:0; margin:0; }
|
|
60
|
+
body * { padding:10px 20px; }
|
|
61
|
+
body * * { padding:0; }
|
|
62
|
+
body { font:small sans-serif; background:#eee; }
|
|
63
|
+
body>div { border-bottom:1px solid #ddd; }
|
|
64
|
+
h1 { font-weight:normal; margin-bottom:.4em; }
|
|
65
|
+
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
|
66
|
+
table { border:none; border-collapse: collapse; width:100%; }
|
|
67
|
+
td, th { vertical-align:top; padding:2px 3px; }
|
|
68
|
+
th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
|
69
|
+
#info { background:#f6f6f6; }
|
|
70
|
+
#info ol { margin: 0.5em 4em; }
|
|
71
|
+
#info ol li { font-family: monospace; }
|
|
72
|
+
#summary { background: #ffc; }
|
|
73
|
+
#explanation { background:#eee; border-bottom: 0px none; }
|
|
74
|
+
</style>
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
<div id="summary">
|
|
78
|
+
<h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
|
|
79
|
+
<table class="meta">
|
|
80
|
+
<tr>
|
|
81
|
+
<th>Request Method:</th>
|
|
82
|
+
<td><%=h req.request_method %></td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<th>Request URL:</th>
|
|
86
|
+
<td><%=h req.url %></td>
|
|
87
|
+
</tr>
|
|
88
|
+
</table>
|
|
89
|
+
</div>
|
|
90
|
+
<div id="info">
|
|
91
|
+
<p><%= detail %></p>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div id="explanation">
|
|
95
|
+
<p>
|
|
96
|
+
You're seeing this error because you use <code>Rack::ShowStatus</code>.
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
HTML
|
|
102
|
+
|
|
103
|
+
# :startdoc:
|
|
104
|
+
end
|
|
105
|
+
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
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
|
3
|
+
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
|
4
|
+
# the URLs start with <tt>http://</tt> or <tt>https://</tt>.
|
|
5
|
+
#
|
|
6
|
+
# URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
|
|
7
|
+
# relevant for dispatch is in the SCRIPT_NAME, and the rest in the
|
|
8
|
+
# PATH_INFO. This should be taken care of when you need to
|
|
9
|
+
# reconstruct the URL in order to create links.
|
|
10
|
+
#
|
|
11
|
+
# URLMap dispatches in such a way that the longest paths are tried
|
|
12
|
+
# first, since they are most specific.
|
|
13
|
+
|
|
14
|
+
class URLMap
|
|
15
|
+
def initialize(map)
|
|
16
|
+
@mapping = map.map { |location, app|
|
|
17
|
+
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
|
18
|
+
host, location = $1, $2
|
|
19
|
+
else
|
|
20
|
+
host = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
unless location[0] == ?/
|
|
24
|
+
raise ArgumentError, "paths need to start with /"
|
|
25
|
+
end
|
|
26
|
+
location = location.chomp('/')
|
|
27
|
+
|
|
28
|
+
[host, location, app]
|
|
29
|
+
}.sort_by { |(h, l, a)| -l.size } # Longest path first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call(env)
|
|
33
|
+
path = env["PATH_INFO"].to_s.squeeze("/")
|
|
34
|
+
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
|
|
35
|
+
@mapping.each { |host, location, app|
|
|
36
|
+
next unless (hHost == host || sName == host \
|
|
37
|
+
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
|
|
38
|
+
next unless location == path[0, location.size]
|
|
39
|
+
next unless path[location.size] == nil || path[location.size] == ?/
|
|
40
|
+
env["SCRIPT_NAME"] += location
|
|
41
|
+
env["PATH_INFO"] = path[location.size..-1]
|
|
42
|
+
return app.call(env)
|
|
43
|
+
}
|
|
44
|
+
[404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
data/lib/rack/utils.rb
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
require 'tempfile'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Rack::Utils contains a grab-bag of useful methods for writing web
|
|
5
|
+
# applications adopted from all kinds of Ruby libraries.
|
|
6
|
+
|
|
7
|
+
module Utils
|
|
8
|
+
# Performs URI escaping so that you can construct proper
|
|
9
|
+
# query strings faster. Use this rather than the cgi.rb
|
|
10
|
+
# version since it's faster. (Stolen from Camping).
|
|
11
|
+
def escape(s)
|
|
12
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
|
13
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
|
14
|
+
}.tr(' ', '+')
|
|
15
|
+
end
|
|
16
|
+
module_function :escape
|
|
17
|
+
|
|
18
|
+
# Unescapes a URI escaped string. (Stolen from Camping).
|
|
19
|
+
def unescape(s)
|
|
20
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
|
21
|
+
[$1.delete('%')].pack('H*')
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
module_function :unescape
|
|
25
|
+
|
|
26
|
+
# Stolen from Mongrel, with some small modifications:
|
|
27
|
+
# Parses a query string by breaking it up at the '&'
|
|
28
|
+
# and ';' characters. You can also use this to parse
|
|
29
|
+
# cookies by changing the characters used in the second
|
|
30
|
+
# parameter (which defaults to '&;').
|
|
31
|
+
|
|
32
|
+
def parse_query(qs, d = '&;')
|
|
33
|
+
params = {}
|
|
34
|
+
|
|
35
|
+
(qs || '').split(/[#{d}] */n).each do |p|
|
|
36
|
+
k, v = unescape(p).split('=', 2)
|
|
37
|
+
|
|
38
|
+
if cur = params[k]
|
|
39
|
+
if cur.class == Array
|
|
40
|
+
params[k] << v
|
|
41
|
+
else
|
|
42
|
+
params[k] = [cur, v]
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
params[k] = v
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return params
|
|
50
|
+
end
|
|
51
|
+
module_function :parse_query
|
|
52
|
+
|
|
53
|
+
def build_query(params)
|
|
54
|
+
params.map { |k, v|
|
|
55
|
+
if v.class == Array
|
|
56
|
+
build_query(v.map { |x| [k, x] })
|
|
57
|
+
else
|
|
58
|
+
escape(k) + "=" + escape(v)
|
|
59
|
+
end
|
|
60
|
+
}.join("&")
|
|
61
|
+
end
|
|
62
|
+
module_function :build_query
|
|
63
|
+
|
|
64
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
|
65
|
+
def escape_html(string)
|
|
66
|
+
string.to_s.gsub("&", "&").
|
|
67
|
+
gsub("<", "<").
|
|
68
|
+
gsub(">", ">").
|
|
69
|
+
gsub("'", "'").
|
|
70
|
+
gsub('"', """)
|
|
71
|
+
end
|
|
72
|
+
module_function :escape_html
|
|
73
|
+
|
|
74
|
+
def select_best_encoding(available_encodings, accept_encoding)
|
|
75
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
76
|
+
|
|
77
|
+
expanded_accept_encoding =
|
|
78
|
+
accept_encoding.map { |m, q|
|
|
79
|
+
if m == "*"
|
|
80
|
+
(available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
|
|
81
|
+
else
|
|
82
|
+
[[m, q]]
|
|
83
|
+
end
|
|
84
|
+
}.inject([]) { |mem, list|
|
|
85
|
+
mem + list
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
|
|
89
|
+
|
|
90
|
+
unless encoding_candidates.include?("identity")
|
|
91
|
+
encoding_candidates.push("identity")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
expanded_accept_encoding.find_all { |m, q|
|
|
95
|
+
q == 0.0
|
|
96
|
+
}.each { |m, _|
|
|
97
|
+
encoding_candidates.delete(m)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return (encoding_candidates & available_encodings)[0]
|
|
101
|
+
end
|
|
102
|
+
module_function :select_best_encoding
|
|
103
|
+
|
|
104
|
+
# The recommended manner in which to implement a contexting application
|
|
105
|
+
# is to define a method #context in which a new Context is instantiated.
|
|
106
|
+
#
|
|
107
|
+
# As a Context is a glorified block, it is highly recommended that you
|
|
108
|
+
# define the contextual block within the application's operational scope.
|
|
109
|
+
# This would typically the application as you're place into Rack's stack.
|
|
110
|
+
#
|
|
111
|
+
# class MyObject
|
|
112
|
+
# ...
|
|
113
|
+
# def context app
|
|
114
|
+
# Rack::Utils::Context.new app do |env|
|
|
115
|
+
# do_stuff
|
|
116
|
+
# response = app.call(env)
|
|
117
|
+
# do_more_stuff
|
|
118
|
+
# end
|
|
119
|
+
# end
|
|
120
|
+
# ...
|
|
121
|
+
# end
|
|
122
|
+
#
|
|
123
|
+
# mobj = MyObject.new
|
|
124
|
+
# app = mobj.context other_app
|
|
125
|
+
# Rack::Handler::Mongrel.new app
|
|
126
|
+
class Context < Proc
|
|
127
|
+
alias_method :old_inspect, :inspect
|
|
128
|
+
attr_reader :for, :app
|
|
129
|
+
def initialize app_f, app_r
|
|
130
|
+
raise 'running context not provided' unless app_f
|
|
131
|
+
raise 'running context does not respond to #context' unless app_f.respond_to? :context
|
|
132
|
+
raise 'application context not provided' unless app_r
|
|
133
|
+
raise 'application context does not respond to #call' unless app_r.respond_to? :call
|
|
134
|
+
@for = app_f
|
|
135
|
+
@app = app_r
|
|
136
|
+
end
|
|
137
|
+
def inspect
|
|
138
|
+
"#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}"
|
|
139
|
+
end
|
|
140
|
+
def context app_r
|
|
141
|
+
raise 'new application context not provided' unless app_r
|
|
142
|
+
raise 'new application context does not respond to #call' unless app_r.respond_to? :call
|
|
143
|
+
@for.context app_r
|
|
144
|
+
end
|
|
145
|
+
def pretty_print pp
|
|
146
|
+
pp.text old_inspect
|
|
147
|
+
pp.nest 1 do
|
|
148
|
+
pp.breakable
|
|
149
|
+
pp.text '=for> '
|
|
150
|
+
pp.pp @for
|
|
151
|
+
pp.breakable
|
|
152
|
+
pp.text '=app> '
|
|
153
|
+
pp.pp @app
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A case-normalizing Hash, adjusting on [] and []=.
|
|
159
|
+
class HeaderHash < Hash
|
|
160
|
+
def initialize(hash={})
|
|
161
|
+
hash.each { |k, v| self[k] = v }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def to_hash
|
|
165
|
+
{}.replace(self)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def [](k)
|
|
169
|
+
super capitalize(k)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def []=(k, v)
|
|
173
|
+
super capitalize(k), v
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def capitalize(k)
|
|
177
|
+
k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Every standard HTTP code mapped to the appropriate message.
|
|
182
|
+
# Stolen from Mongrel.
|
|
183
|
+
HTTP_STATUS_CODES = {
|
|
184
|
+
100 => 'Continue',
|
|
185
|
+
101 => 'Switching Protocols',
|
|
186
|
+
200 => 'OK',
|
|
187
|
+
201 => 'Created',
|
|
188
|
+
202 => 'Accepted',
|
|
189
|
+
203 => 'Non-Authoritative Information',
|
|
190
|
+
204 => 'No Content',
|
|
191
|
+
205 => 'Reset Content',
|
|
192
|
+
206 => 'Partial Content',
|
|
193
|
+
300 => 'Multiple Choices',
|
|
194
|
+
301 => 'Moved Permanently',
|
|
195
|
+
302 => 'Moved Temporarily',
|
|
196
|
+
303 => 'See Other',
|
|
197
|
+
304 => 'Not Modified',
|
|
198
|
+
305 => 'Use Proxy',
|
|
199
|
+
400 => 'Bad Request',
|
|
200
|
+
401 => 'Unauthorized',
|
|
201
|
+
402 => 'Payment Required',
|
|
202
|
+
403 => 'Forbidden',
|
|
203
|
+
404 => 'Not Found',
|
|
204
|
+
405 => 'Method Not Allowed',
|
|
205
|
+
406 => 'Not Acceptable',
|
|
206
|
+
407 => 'Proxy Authentication Required',
|
|
207
|
+
408 => 'Request Time-out',
|
|
208
|
+
409 => 'Conflict',
|
|
209
|
+
410 => 'Gone',
|
|
210
|
+
411 => 'Length Required',
|
|
211
|
+
412 => 'Precondition Failed',
|
|
212
|
+
413 => 'Request Entity Too Large',
|
|
213
|
+
414 => 'Request-URI Too Large',
|
|
214
|
+
415 => 'Unsupported Media Type',
|
|
215
|
+
500 => 'Internal Server Error',
|
|
216
|
+
501 => 'Not Implemented',
|
|
217
|
+
502 => 'Bad Gateway',
|
|
218
|
+
503 => 'Service Unavailable',
|
|
219
|
+
504 => 'Gateway Time-out',
|
|
220
|
+
505 => 'HTTP Version not supported'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# A multipart form data parser, adapted from IOWA.
|
|
224
|
+
#
|
|
225
|
+
# Usually, Rack::Request#POST takes care of calling this.
|
|
226
|
+
|
|
227
|
+
module Multipart
|
|
228
|
+
EOL = "\r\n"
|
|
229
|
+
|
|
230
|
+
def self.parse_multipart(env)
|
|
231
|
+
unless env['CONTENT_TYPE'] =~
|
|
232
|
+
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
|
233
|
+
nil
|
|
234
|
+
else
|
|
235
|
+
boundary = "--#{$1}"
|
|
236
|
+
|
|
237
|
+
params = {}
|
|
238
|
+
buf = ""
|
|
239
|
+
content_length = env['CONTENT_LENGTH'].to_i
|
|
240
|
+
input = env['rack.input']
|
|
241
|
+
|
|
242
|
+
boundary_size = boundary.size + EOL.size
|
|
243
|
+
bufsize = 16384
|
|
244
|
+
|
|
245
|
+
content_length -= boundary_size
|
|
246
|
+
|
|
247
|
+
status = input.read(boundary_size)
|
|
248
|
+
raise EOFError, "bad content body" unless status == boundary + EOL
|
|
249
|
+
|
|
250
|
+
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/
|
|
251
|
+
|
|
252
|
+
loop {
|
|
253
|
+
head = nil
|
|
254
|
+
body = ''
|
|
255
|
+
filename = content_type = name = nil
|
|
256
|
+
|
|
257
|
+
until head && buf =~ rx
|
|
258
|
+
if !head && i = buf.index("\r\n\r\n")
|
|
259
|
+
head = buf.slice!(0, i+2) # First \r\n
|
|
260
|
+
buf.slice!(0, 2) # Second \r\n
|
|
261
|
+
|
|
262
|
+
filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
|
|
263
|
+
content_type = head[/Content-Type: (.*)\r\n/ni, 1]
|
|
264
|
+
name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
|
|
265
|
+
|
|
266
|
+
if filename
|
|
267
|
+
body = Tempfile.new("RackMultipart")
|
|
268
|
+
body.binmode if body.respond_to?(:binmode)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
next
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Save the read body part.
|
|
275
|
+
if head && (boundary_size+4 < buf.size)
|
|
276
|
+
body << buf.slice!(0, buf.size - (boundary_size+4))
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
c = input.read(bufsize < content_length ? bufsize : content_length)
|
|
280
|
+
raise EOFError, "bad content body" if c.nil? || c.empty?
|
|
281
|
+
buf << c
|
|
282
|
+
content_length -= c.size
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Save the rest.
|
|
286
|
+
if i = buf.index(rx)
|
|
287
|
+
body << buf.slice!(0, i)
|
|
288
|
+
buf.slice!(0, boundary_size+2)
|
|
289
|
+
|
|
290
|
+
content_length = -1 if $1 == "--"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
if filename
|
|
294
|
+
body.rewind
|
|
295
|
+
data = {:filename => filename, :type => content_type,
|
|
296
|
+
:name => name, :tempfile => body, :head => head}
|
|
297
|
+
else
|
|
298
|
+
data = body
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
if name
|
|
302
|
+
if name =~ /\[\]\z/
|
|
303
|
+
params[name] ||= []
|
|
304
|
+
params[name] << data
|
|
305
|
+
else
|
|
306
|
+
params[name] = data
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
break if buf.empty? || content_length == -1
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
params
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|