qoobaa-rack 1.0.0.1
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/COPYING +18 -0
- data/KNOWN-ISSUES +18 -0
- data/RDOX +0 -0
- data/README +353 -0
- data/Rakefile +164 -0
- data/SPEC +164 -0
- data/bin/rackup +176 -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/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +37 -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 +487 -0
- data/lib/rack/builder.rb +63 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/commonlogger.rb +52 -0
- data/lib/rack/conditionalget.rb +47 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +153 -0
- data/lib/rack/file.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +88 -0
- data/lib/rack/handler/lsws.rb +60 -0
- data/lib/rack/handler/mongrel.rb +87 -0
- data/lib/rack/handler/scgi.rb +62 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +18 -0
- data/lib/rack/handler/webrick.rb +71 -0
- data/lib/rack/handler.rb +69 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +546 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +204 -0
- data/lib/rack/mock.rb +187 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +107 -0
- data/lib/rack/request.rb +248 -0
- data/lib/rack/response.rb +183 -0
- data/lib/rack/rewindable_input.rb +100 -0
- data/lib/rack/session/abstract/id.rb +142 -0
- data/lib/rack/session/cookie.rb +91 -0
- data/lib/rack/session/memcache.rb +109 -0
- data/lib/rack/session/pool.rb +100 -0
- data/lib/rack/showexceptions.rb +349 -0
- data/lib/rack/showstatus.rb +106 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +528 -0
- data/lib/rack.rb +90 -0
- data/rack.gemspec +60 -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/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +73 -0
- data/test/spec_rack_auth_digest.rb +226 -0
- data/test/spec_rack_auth_openid.rb +84 -0
- data/test/spec_rack_builder.rb +84 -0
- data/test/spec_rack_camping.rb +51 -0
- data/test/spec_rack_cascade.rb +48 -0
- data/test/spec_rack_cgi.rb +89 -0
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_commonlogger.rb +61 -0
- data/test/spec_rack_conditionalget.rb +41 -0
- data/test/spec_rack_content_length.rb +43 -0
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +127 -0
- data/test/spec_rack_directory.rb +61 -0
- data/test/spec_rack_fastcgi.rb +89 -0
- data/test/spec_rack_file.rb +75 -0
- data/test/spec_rack_handler.rb +43 -0
- data/test/spec_rack_head.rb +30 -0
- data/test/spec_rack_lint.rb +521 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_methodoverride.rb +60 -0
- data/test/spec_rack_mock.rb +243 -0
- data/test/spec_rack_mongrel.rb +189 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +504 -0
- data/test/spec_rack_response.rb +218 -0
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_cookie.rb +82 -0
- data/test/spec_rack_session_memcache.rb +250 -0
- data/test/spec_rack_session_pool.rb +172 -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_thin.rb +91 -0
- data/test/spec_rack_urlmap.rb +185 -0
- data/test/spec_rack_utils.rb +467 -0
- data/test/spec_rack_webrick.rb +130 -0
- data/test/testrequest.rb +57 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +276 -0
data/lib/rack/utils.rb
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module Rack
|
|
7
|
+
# Rack::Utils contains a grab-bag of useful methods for writing web
|
|
8
|
+
# applications adopted from all kinds of Ruby libraries.
|
|
9
|
+
|
|
10
|
+
module Utils
|
|
11
|
+
# Performs URI escaping so that you can construct proper
|
|
12
|
+
# query strings faster. Use this rather than the cgi.rb
|
|
13
|
+
# version since it's faster. (Stolen from Camping).
|
|
14
|
+
def escape(s)
|
|
15
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
|
16
|
+
'%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
|
|
17
|
+
}.tr(' ', '+')
|
|
18
|
+
end
|
|
19
|
+
module_function :escape
|
|
20
|
+
|
|
21
|
+
# Unescapes a URI escaped string. (Stolen from Camping).
|
|
22
|
+
def unescape(s)
|
|
23
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
|
24
|
+
[$1.delete('%')].pack('H*')
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
module_function :unescape
|
|
28
|
+
|
|
29
|
+
DEFAULT_SEP = /[&;] */n
|
|
30
|
+
|
|
31
|
+
# Stolen from Mongrel, with some small modifications:
|
|
32
|
+
# Parses a query string by breaking it up at the '&'
|
|
33
|
+
# and ';' characters. You can also use this to parse
|
|
34
|
+
# cookies by changing the characters used in the second
|
|
35
|
+
# parameter (which defaults to '&;').
|
|
36
|
+
def parse_query(qs, d = nil)
|
|
37
|
+
params = {}
|
|
38
|
+
|
|
39
|
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
|
40
|
+
k, v = p.split('=', 2).map { |x| unescape(x) }
|
|
41
|
+
|
|
42
|
+
if cur = params[k]
|
|
43
|
+
if cur.class == Array
|
|
44
|
+
params[k] << v
|
|
45
|
+
else
|
|
46
|
+
params[k] = [cur, v]
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
params[k] = v
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return params
|
|
54
|
+
end
|
|
55
|
+
module_function :parse_query
|
|
56
|
+
|
|
57
|
+
def parse_nested_query(qs, d = nil)
|
|
58
|
+
params = {}
|
|
59
|
+
|
|
60
|
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
|
61
|
+
k, v = unescape(p).split('=', 2)
|
|
62
|
+
normalize_params(params, k, v)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return params
|
|
66
|
+
end
|
|
67
|
+
module_function :parse_nested_query
|
|
68
|
+
|
|
69
|
+
def normalize_params(params, name, v = nil)
|
|
70
|
+
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
|
71
|
+
k = $1 || ''
|
|
72
|
+
after = $' || ''
|
|
73
|
+
|
|
74
|
+
return if k.empty?
|
|
75
|
+
|
|
76
|
+
if after == ""
|
|
77
|
+
params[k] = v
|
|
78
|
+
elsif after == "[]"
|
|
79
|
+
params[k] ||= []
|
|
80
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
81
|
+
params[k] << v
|
|
82
|
+
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
|
83
|
+
child_key = $1
|
|
84
|
+
params[k] ||= []
|
|
85
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
86
|
+
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
|
|
87
|
+
normalize_params(params[k].last, child_key, v)
|
|
88
|
+
else
|
|
89
|
+
params[k] << normalize_params({}, child_key, v)
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
params[k] ||= {}
|
|
93
|
+
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
|
|
94
|
+
params[k] = normalize_params(params[k], after, v)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return params
|
|
98
|
+
end
|
|
99
|
+
module_function :normalize_params
|
|
100
|
+
|
|
101
|
+
def build_query(params)
|
|
102
|
+
params.map { |k, v|
|
|
103
|
+
if v.class == Array
|
|
104
|
+
build_query(v.map { |x| [k, x] })
|
|
105
|
+
else
|
|
106
|
+
"#{escape(k)}=#{escape(v)}"
|
|
107
|
+
end
|
|
108
|
+
}.join("&")
|
|
109
|
+
end
|
|
110
|
+
module_function :build_query
|
|
111
|
+
|
|
112
|
+
def build_nested_query(value, prefix = nil)
|
|
113
|
+
case value
|
|
114
|
+
when Array
|
|
115
|
+
value.map { |v|
|
|
116
|
+
build_nested_query(v, "#{prefix}[]")
|
|
117
|
+
}.join("&")
|
|
118
|
+
when Hash
|
|
119
|
+
value.map { |k, v|
|
|
120
|
+
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
|
121
|
+
}.join("&")
|
|
122
|
+
when String
|
|
123
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
124
|
+
"#{prefix}=#{escape(value)}"
|
|
125
|
+
else
|
|
126
|
+
prefix
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
module_function :build_nested_query
|
|
130
|
+
|
|
131
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
|
132
|
+
def escape_html(string)
|
|
133
|
+
string.to_s.gsub("&", "&").
|
|
134
|
+
gsub("<", "<").
|
|
135
|
+
gsub(">", ">").
|
|
136
|
+
gsub("'", "'").
|
|
137
|
+
gsub('"', """)
|
|
138
|
+
end
|
|
139
|
+
module_function :escape_html
|
|
140
|
+
|
|
141
|
+
def select_best_encoding(available_encodings, accept_encoding)
|
|
142
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
143
|
+
|
|
144
|
+
expanded_accept_encoding =
|
|
145
|
+
accept_encoding.map { |m, q|
|
|
146
|
+
if m == "*"
|
|
147
|
+
(available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
|
|
148
|
+
else
|
|
149
|
+
[[m, q]]
|
|
150
|
+
end
|
|
151
|
+
}.inject([]) { |mem, list|
|
|
152
|
+
mem + list
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
|
|
156
|
+
|
|
157
|
+
unless encoding_candidates.include?("identity")
|
|
158
|
+
encoding_candidates.push("identity")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
expanded_accept_encoding.find_all { |m, q|
|
|
162
|
+
q == 0.0
|
|
163
|
+
}.each { |m, _|
|
|
164
|
+
encoding_candidates.delete(m)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (encoding_candidates & available_encodings)[0]
|
|
168
|
+
end
|
|
169
|
+
module_function :select_best_encoding
|
|
170
|
+
|
|
171
|
+
# Return the bytesize of String; uses String#length under Ruby 1.8 and
|
|
172
|
+
# String#bytesize under 1.9.
|
|
173
|
+
if ''.respond_to?(:bytesize)
|
|
174
|
+
def bytesize(string)
|
|
175
|
+
string.bytesize
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
def bytesize(string)
|
|
179
|
+
string.bytesize
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
module_function :bytesize
|
|
183
|
+
|
|
184
|
+
# Context allows the use of a compatible middleware at different points
|
|
185
|
+
# in a request handling stack. A compatible middleware must define
|
|
186
|
+
# #context which should take the arguments env and app. The first of which
|
|
187
|
+
# would be the request environment. The second of which would be the rack
|
|
188
|
+
# application that the request would be forwarded to.
|
|
189
|
+
class Context
|
|
190
|
+
attr_reader :for, :app
|
|
191
|
+
|
|
192
|
+
def initialize(app_f, app_r)
|
|
193
|
+
raise 'running context does not respond to #context' unless app_f.respond_to? :context
|
|
194
|
+
@for, @app = app_f, app_r
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def call(env)
|
|
198
|
+
@for.context(env, @app)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def recontext(app)
|
|
202
|
+
self.class.new(@for, app)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def context(env, app=@app)
|
|
206
|
+
recontext(app).call(env)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# A case-insensitive Hash that preserves the original case of a
|
|
211
|
+
# header when set.
|
|
212
|
+
class HeaderHash < Hash
|
|
213
|
+
def initialize(hash={})
|
|
214
|
+
super()
|
|
215
|
+
@names = {}
|
|
216
|
+
hash.each { |k, v| self[k] = v }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def to_hash
|
|
220
|
+
inject({}) do |hash, (k,v)|
|
|
221
|
+
if v.respond_to? :to_ary
|
|
222
|
+
hash[k] = v.to_ary.join("\n")
|
|
223
|
+
else
|
|
224
|
+
hash[k] = v
|
|
225
|
+
end
|
|
226
|
+
hash
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def [](k)
|
|
231
|
+
super(@names[k] ||= @names[k.downcase])
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def []=(k, v)
|
|
235
|
+
delete k
|
|
236
|
+
@names[k] = @names[k.downcase] = k
|
|
237
|
+
super k, v
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def delete(k)
|
|
241
|
+
canonical = k.downcase
|
|
242
|
+
result = super @names.delete(canonical)
|
|
243
|
+
@names.delete_if { |name,| name.downcase == canonical }
|
|
244
|
+
result
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def include?(k)
|
|
248
|
+
@names.include?(k) || @names.include?(k.downcase)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
alias_method :has_key?, :include?
|
|
252
|
+
alias_method :member?, :include?
|
|
253
|
+
alias_method :key?, :include?
|
|
254
|
+
|
|
255
|
+
def merge!(other)
|
|
256
|
+
other.each { |k, v| self[k] = v }
|
|
257
|
+
self
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def merge(other)
|
|
261
|
+
hash = dup
|
|
262
|
+
hash.merge! other
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def replace(other)
|
|
266
|
+
clear
|
|
267
|
+
other.each { |k, v| self[k] = v }
|
|
268
|
+
self
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Every standard HTTP code mapped to the appropriate message.
|
|
273
|
+
# Stolen from Mongrel.
|
|
274
|
+
HTTP_STATUS_CODES = {
|
|
275
|
+
100 => 'Continue',
|
|
276
|
+
101 => 'Switching Protocols',
|
|
277
|
+
200 => 'OK',
|
|
278
|
+
201 => 'Created',
|
|
279
|
+
202 => 'Accepted',
|
|
280
|
+
203 => 'Non-Authoritative Information',
|
|
281
|
+
204 => 'No Content',
|
|
282
|
+
205 => 'Reset Content',
|
|
283
|
+
206 => 'Partial Content',
|
|
284
|
+
300 => 'Multiple Choices',
|
|
285
|
+
301 => 'Moved Permanently',
|
|
286
|
+
302 => 'Found',
|
|
287
|
+
303 => 'See Other',
|
|
288
|
+
304 => 'Not Modified',
|
|
289
|
+
305 => 'Use Proxy',
|
|
290
|
+
307 => 'Temporary Redirect',
|
|
291
|
+
400 => 'Bad Request',
|
|
292
|
+
401 => 'Unauthorized',
|
|
293
|
+
402 => 'Payment Required',
|
|
294
|
+
403 => 'Forbidden',
|
|
295
|
+
404 => 'Not Found',
|
|
296
|
+
405 => 'Method Not Allowed',
|
|
297
|
+
406 => 'Not Acceptable',
|
|
298
|
+
407 => 'Proxy Authentication Required',
|
|
299
|
+
408 => 'Request Timeout',
|
|
300
|
+
409 => 'Conflict',
|
|
301
|
+
410 => 'Gone',
|
|
302
|
+
411 => 'Length Required',
|
|
303
|
+
412 => 'Precondition Failed',
|
|
304
|
+
413 => 'Request Entity Too Large',
|
|
305
|
+
414 => 'Request-URI Too Large',
|
|
306
|
+
415 => 'Unsupported Media Type',
|
|
307
|
+
416 => 'Requested Range Not Satisfiable',
|
|
308
|
+
417 => 'Expectation Failed',
|
|
309
|
+
500 => 'Internal Server Error',
|
|
310
|
+
501 => 'Not Implemented',
|
|
311
|
+
502 => 'Bad Gateway',
|
|
312
|
+
503 => 'Service Unavailable',
|
|
313
|
+
504 => 'Gateway Timeout',
|
|
314
|
+
505 => 'HTTP Version Not Supported'
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# Responses with HTTP status codes that should not have an entity body
|
|
318
|
+
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
|
|
319
|
+
|
|
320
|
+
# A multipart form data parser, adapted from IOWA.
|
|
321
|
+
#
|
|
322
|
+
# Usually, Rack::Request#POST takes care of calling this.
|
|
323
|
+
|
|
324
|
+
module Multipart
|
|
325
|
+
class UploadedFile
|
|
326
|
+
# The filename, *not* including the path, of the "uploaded" file
|
|
327
|
+
attr_reader :original_filename
|
|
328
|
+
|
|
329
|
+
# The content type of the "uploaded" file
|
|
330
|
+
attr_accessor :content_type
|
|
331
|
+
|
|
332
|
+
def initialize(path, content_type = "text/plain", binary = false)
|
|
333
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
|
334
|
+
@content_type = content_type
|
|
335
|
+
@original_filename = ::File.basename(path)
|
|
336
|
+
@tempfile = Tempfile.new(@original_filename)
|
|
337
|
+
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
|
338
|
+
@tempfile.binmode if binary
|
|
339
|
+
FileUtils.copy_file(path, @tempfile.path)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def path
|
|
343
|
+
@tempfile.path
|
|
344
|
+
end
|
|
345
|
+
alias_method :local_path, :path
|
|
346
|
+
|
|
347
|
+
def method_missing(method_name, *args, &block) #:nodoc:
|
|
348
|
+
@tempfile.__send__(method_name, *args, &block)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
EOL = "\r\n"
|
|
353
|
+
MULTIPART_BOUNDARY = "AaB03x"
|
|
354
|
+
|
|
355
|
+
def self.parse_multipart(env)
|
|
356
|
+
unless env['CONTENT_TYPE'] =~
|
|
357
|
+
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
|
358
|
+
nil
|
|
359
|
+
else
|
|
360
|
+
boundary = "--#{$1}"
|
|
361
|
+
|
|
362
|
+
params = {}
|
|
363
|
+
buf = ""
|
|
364
|
+
content_length = env['CONTENT_LENGTH'].to_i
|
|
365
|
+
input = env['rack.input']
|
|
366
|
+
input.rewind
|
|
367
|
+
|
|
368
|
+
boundary_size = Utils.bytesize(boundary) + EOL.bytesize
|
|
369
|
+
bufsize = 16384
|
|
370
|
+
|
|
371
|
+
content_length -= boundary_size
|
|
372
|
+
|
|
373
|
+
read_buffer = ''
|
|
374
|
+
|
|
375
|
+
status = input.read(boundary_size, read_buffer)
|
|
376
|
+
raise EOFError, "bad content body" unless status == boundary + EOL
|
|
377
|
+
|
|
378
|
+
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
|
|
379
|
+
|
|
380
|
+
loop {
|
|
381
|
+
head = nil
|
|
382
|
+
body = ''
|
|
383
|
+
filename = content_type = name = nil
|
|
384
|
+
|
|
385
|
+
until head && buf =~ rx
|
|
386
|
+
if !head && i = buf.index(EOL+EOL)
|
|
387
|
+
head = buf.slice!(0, i+2) # First \r\n
|
|
388
|
+
buf.slice!(0, 2) # Second \r\n
|
|
389
|
+
|
|
390
|
+
filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
|
|
391
|
+
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
|
|
392
|
+
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
|
|
393
|
+
|
|
394
|
+
if content_type || filename
|
|
395
|
+
body = Tempfile.new("RackMultipart")
|
|
396
|
+
body.binmode if body.respond_to?(:binmode)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
next
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Save the read body part.
|
|
403
|
+
if head && (boundary_size+4 < buf.bytesize)
|
|
404
|
+
body << buf.slice!(0, buf.bytesize - (boundary_size+4))
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
|
|
408
|
+
raise EOFError, "bad content body" if c.nil? || c.empty?
|
|
409
|
+
buf << c
|
|
410
|
+
content_length -= c.bytesize
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Save the rest.
|
|
414
|
+
if i = buf.index(rx)
|
|
415
|
+
body << buf.slice!(0, i)
|
|
416
|
+
buf.slice!(0, boundary_size+2)
|
|
417
|
+
|
|
418
|
+
content_length = -1 if $1 == "--"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
if filename == ""
|
|
422
|
+
# filename is blank which means no file has been selected
|
|
423
|
+
data = nil
|
|
424
|
+
elsif filename
|
|
425
|
+
body.rewind
|
|
426
|
+
|
|
427
|
+
# Take the basename of the upload's original filename.
|
|
428
|
+
# This handles the full Windows paths given by Internet Explorer
|
|
429
|
+
# (and perhaps other broken user agents) without affecting
|
|
430
|
+
# those which give the lone filename.
|
|
431
|
+
filename =~ /^(?:.*[:\\\/])?(.*)/m
|
|
432
|
+
filename = $1
|
|
433
|
+
|
|
434
|
+
data = {:filename => filename, :type => content_type,
|
|
435
|
+
:name => name, :tempfile => body, :head => head}
|
|
436
|
+
elsif !filename && content_type
|
|
437
|
+
body.rewind
|
|
438
|
+
|
|
439
|
+
# Generic multipart cases, not coming from a form
|
|
440
|
+
data = {:type => content_type,
|
|
441
|
+
:name => name, :tempfile => body, :head => head}
|
|
442
|
+
else
|
|
443
|
+
data = body
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
Utils.normalize_params(params, name, data) unless data.nil?
|
|
447
|
+
|
|
448
|
+
break if buf.empty? || content_length == -1
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
input.rewind
|
|
452
|
+
|
|
453
|
+
params
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def self.build_multipart(params, first = true)
|
|
458
|
+
if first
|
|
459
|
+
unless params.is_a?(Hash)
|
|
460
|
+
raise ArgumentError, "value must be a Hash"
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
multipart = false
|
|
464
|
+
query = lambda { |value|
|
|
465
|
+
case value
|
|
466
|
+
when Array
|
|
467
|
+
value.each(&query)
|
|
468
|
+
when Hash
|
|
469
|
+
value.values.each(&query)
|
|
470
|
+
when UploadedFile
|
|
471
|
+
multipart = true
|
|
472
|
+
end
|
|
473
|
+
}
|
|
474
|
+
params.values.each(&query)
|
|
475
|
+
return nil unless multipart
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
flattened_params = Hash.new
|
|
479
|
+
|
|
480
|
+
params.each do |key, value|
|
|
481
|
+
k = first ? key.to_s : "[#{key}]"
|
|
482
|
+
|
|
483
|
+
case value
|
|
484
|
+
when Array
|
|
485
|
+
value.map { |v|
|
|
486
|
+
build_multipart(v, false).each { |subkey, subvalue|
|
|
487
|
+
flattened_params["#{k}[]#{subkey}"] = subvalue
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
when Hash
|
|
491
|
+
build_multipart(value, false).each { |subkey, subvalue|
|
|
492
|
+
flattened_params[k + subkey] = subvalue
|
|
493
|
+
}
|
|
494
|
+
else
|
|
495
|
+
flattened_params[k] = value
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
if first
|
|
500
|
+
flattened_params.map { |name, file|
|
|
501
|
+
if file.respond_to?(:original_filename)
|
|
502
|
+
::File.open(file.path, "rb") do |f|
|
|
503
|
+
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
|
504
|
+
<<-EOF
|
|
505
|
+
--#{MULTIPART_BOUNDARY}\r
|
|
506
|
+
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
|
|
507
|
+
Content-Type: #{file.content_type}\r
|
|
508
|
+
Content-Length: #{::File.stat(file.path).size}\r
|
|
509
|
+
\r
|
|
510
|
+
#{f.read}\r
|
|
511
|
+
EOF
|
|
512
|
+
end
|
|
513
|
+
else
|
|
514
|
+
<<-EOF
|
|
515
|
+
--#{MULTIPART_BOUNDARY}\r
|
|
516
|
+
Content-Disposition: form-data; name="#{name}"\r
|
|
517
|
+
\r
|
|
518
|
+
#{file}\r
|
|
519
|
+
EOF
|
|
520
|
+
end
|
|
521
|
+
}.join + "--#{MULTIPART_BOUNDARY}--\r"
|
|
522
|
+
else
|
|
523
|
+
flattened_params
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
end
|
data/lib/rack.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
|
|
2
|
+
#
|
|
3
|
+
# Rack is freely distributable under the terms of an MIT-style license.
|
|
4
|
+
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
5
|
+
|
|
6
|
+
path = File.expand_path(File.dirname(__FILE__))
|
|
7
|
+
$:.unshift(path) unless $:.include?(path)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# The Rack main module, serving as a namespace for all core Rack
|
|
11
|
+
# modules and classes.
|
|
12
|
+
#
|
|
13
|
+
# All modules meant for use in your application are <tt>autoload</tt>ed here,
|
|
14
|
+
# so it should be enough just to <tt>require rack.rb</tt> in your code.
|
|
15
|
+
|
|
16
|
+
module Rack
|
|
17
|
+
# The Rack protocol version number implemented.
|
|
18
|
+
VERSION = [1,0]
|
|
19
|
+
|
|
20
|
+
# Return the Rack protocol version as a dotted string.
|
|
21
|
+
def self.version
|
|
22
|
+
VERSION.join(".")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Return the Rack release as a dotted string.
|
|
26
|
+
def self.release
|
|
27
|
+
"1.0"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
autoload :Builder, "rack/builder"
|
|
31
|
+
autoload :Cascade, "rack/cascade"
|
|
32
|
+
autoload :Chunked, "rack/chunked"
|
|
33
|
+
autoload :CommonLogger, "rack/commonlogger"
|
|
34
|
+
autoload :ConditionalGet, "rack/conditionalget"
|
|
35
|
+
autoload :ContentLength, "rack/content_length"
|
|
36
|
+
autoload :ContentType, "rack/content_type"
|
|
37
|
+
autoload :File, "rack/file"
|
|
38
|
+
autoload :Deflater, "rack/deflater"
|
|
39
|
+
autoload :Directory, "rack/directory"
|
|
40
|
+
autoload :ForwardRequest, "rack/recursive"
|
|
41
|
+
autoload :Handler, "rack/handler"
|
|
42
|
+
autoload :Head, "rack/head"
|
|
43
|
+
autoload :Lint, "rack/lint"
|
|
44
|
+
autoload :Lock, "rack/lock"
|
|
45
|
+
autoload :MethodOverride, "rack/methodoverride"
|
|
46
|
+
autoload :Mime, "rack/mime"
|
|
47
|
+
autoload :Recursive, "rack/recursive"
|
|
48
|
+
autoload :Reloader, "rack/reloader"
|
|
49
|
+
autoload :ShowExceptions, "rack/showexceptions"
|
|
50
|
+
autoload :ShowStatus, "rack/showstatus"
|
|
51
|
+
autoload :Static, "rack/static"
|
|
52
|
+
autoload :URLMap, "rack/urlmap"
|
|
53
|
+
autoload :Utils, "rack/utils"
|
|
54
|
+
|
|
55
|
+
autoload :MockRequest, "rack/mock"
|
|
56
|
+
autoload :MockResponse, "rack/mock"
|
|
57
|
+
|
|
58
|
+
autoload :Request, "rack/request"
|
|
59
|
+
autoload :Response, "rack/response"
|
|
60
|
+
|
|
61
|
+
module Auth
|
|
62
|
+
autoload :Basic, "rack/auth/basic"
|
|
63
|
+
autoload :AbstractRequest, "rack/auth/abstract/request"
|
|
64
|
+
autoload :AbstractHandler, "rack/auth/abstract/handler"
|
|
65
|
+
autoload :OpenID, "rack/auth/openid"
|
|
66
|
+
module Digest
|
|
67
|
+
autoload :MD5, "rack/auth/digest/md5"
|
|
68
|
+
autoload :Nonce, "rack/auth/digest/nonce"
|
|
69
|
+
autoload :Params, "rack/auth/digest/params"
|
|
70
|
+
autoload :Request, "rack/auth/digest/request"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module Session
|
|
75
|
+
autoload :Cookie, "rack/session/cookie"
|
|
76
|
+
autoload :Pool, "rack/session/pool"
|
|
77
|
+
autoload :Memcache, "rack/session/memcache"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# *Adapters* connect Rack with third party web frameworks.
|
|
81
|
+
#
|
|
82
|
+
# Rack includes an adapter for Camping, see README for other
|
|
83
|
+
# frameworks supporting Rack in their code bases.
|
|
84
|
+
#
|
|
85
|
+
# Refer to the submodules for framework-specific calling details.
|
|
86
|
+
|
|
87
|
+
module Adapter
|
|
88
|
+
autoload :Camping, "rack/adapter/camping"
|
|
89
|
+
end
|
|
90
|
+
end
|
data/rack.gemspec
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{rack}
|
|
5
|
+
s.version = "1.0.0.1"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["Christian Neukirchen"]
|
|
9
|
+
s.date = %q{2009-07-21}
|
|
10
|
+
s.default_executable = %q{rackup}
|
|
11
|
+
s.description = %q{Rack provides minimal, modular and adaptable interface for developing
|
|
12
|
+
web applications in Ruby. By wrapping HTTP requests and responses in
|
|
13
|
+
the simplest way possible, it unifies and distills the API for web
|
|
14
|
+
servers, web frameworks, and software in between (the so-called
|
|
15
|
+
middleware) into a single method call.
|
|
16
|
+
|
|
17
|
+
Also see http://rack.rubyforge.org.
|
|
18
|
+
}
|
|
19
|
+
s.email = %q{chneukirchen@gmail.com}
|
|
20
|
+
s.executables = ["rackup"]
|
|
21
|
+
s.extra_rdoc_files = ["README", "SPEC", "RDOX", "KNOWN-ISSUES"]
|
|
22
|
+
s.files = ["COPYING", "KNOWN-ISSUES", "README", "Rakefile", "bin/rackup", "contrib/rack_logo.svg", "example/lobster.ru", "example/protectedlobster.rb", "example/protectedlobster.ru", "lib/rack.rb", "lib/rack/adapter/camping.rb", "lib/rack/auth/abstract/handler.rb", "lib/rack/auth/abstract/request.rb", "lib/rack/auth/basic.rb", "lib/rack/auth/digest/md5.rb", "lib/rack/auth/digest/nonce.rb", "lib/rack/auth/digest/params.rb", "lib/rack/auth/digest/request.rb", "lib/rack/auth/openid.rb", "lib/rack/builder.rb", "lib/rack/cascade.rb", "lib/rack/chunked.rb", "lib/rack/commonlogger.rb", "lib/rack/conditionalget.rb", "lib/rack/content_length.rb", "lib/rack/content_type.rb", "lib/rack/deflater.rb", "lib/rack/directory.rb", "lib/rack/file.rb", "lib/rack/handler.rb", "lib/rack/handler/cgi.rb", "lib/rack/handler/evented_mongrel.rb", "lib/rack/handler/fastcgi.rb", "lib/rack/handler/lsws.rb", "lib/rack/handler/mongrel.rb", "lib/rack/handler/scgi.rb", "lib/rack/handler/swiftiplied_mongrel.rb", "lib/rack/handler/thin.rb", "lib/rack/handler/webrick.rb", "lib/rack/head.rb", "lib/rack/lint.rb", "lib/rack/lobster.rb", "lib/rack/lock.rb", "lib/rack/methodoverride.rb", "lib/rack/mime.rb", "lib/rack/mock.rb", "lib/rack/recursive.rb", "lib/rack/reloader.rb", "lib/rack/request.rb", "lib/rack/response.rb", "lib/rack/rewindable_input.rb", "lib/rack/session/abstract/id.rb", "lib/rack/session/cookie.rb", "lib/rack/session/memcache.rb", "lib/rack/session/pool.rb", "lib/rack/showexceptions.rb", "lib/rack/showstatus.rb", "lib/rack/static.rb", "lib/rack/urlmap.rb", "lib/rack/utils.rb", "test/cgi/lighttpd.conf", "test/cgi/test", "test/cgi/test.fcgi", "test/cgi/test.ru", "test/multipart/binary", "test/multipart/empty", "test/multipart/file1.txt", "test/multipart/ie", "test/multipart/nested", "test/multipart/none", "test/multipart/text", "test/spec_rack_auth_basic.rb", "test/spec_rack_auth_digest.rb", "test/spec_rack_auth_openid.rb", "test/spec_rack_builder.rb", "test/spec_rack_camping.rb", "test/spec_rack_cascade.rb", "test/spec_rack_cgi.rb", "test/spec_rack_chunked.rb", "test/spec_rack_commonlogger.rb", "test/spec_rack_conditionalget.rb", "test/spec_rack_content_length.rb", "test/spec_rack_content_type.rb", "test/spec_rack_deflater.rb", "test/spec_rack_directory.rb", "test/spec_rack_fastcgi.rb", "test/spec_rack_file.rb", "test/spec_rack_handler.rb", "test/spec_rack_head.rb", "test/spec_rack_lint.rb", "test/spec_rack_lobster.rb", "test/spec_rack_lock.rb", "test/spec_rack_methodoverride.rb", "test/spec_rack_mock.rb", "test/spec_rack_mongrel.rb", "test/spec_rack_recursive.rb", "test/spec_rack_request.rb", "test/spec_rack_response.rb", "test/spec_rack_rewindable_input.rb", "test/spec_rack_session_cookie.rb", "test/spec_rack_session_memcache.rb", "test/spec_rack_session_pool.rb", "test/spec_rack_showexceptions.rb", "test/spec_rack_showstatus.rb", "test/spec_rack_static.rb", "test/spec_rack_thin.rb", "test/spec_rack_urlmap.rb", "test/spec_rack_utils.rb", "test/spec_rack_webrick.rb", "test/testrequest.rb", "test/unregistered_handler/rack/handler/unregistered.rb", "test/unregistered_handler/rack/handler/unregistered_long_one.rb", "SPEC", "RDOX", "rack.gemspec"]
|
|
23
|
+
s.homepage = %q{http://rack.rubyforge.org}
|
|
24
|
+
s.require_paths = ["lib"]
|
|
25
|
+
s.rubyforge_project = %q{rack}
|
|
26
|
+
s.rubygems_version = %q{1.3.4}
|
|
27
|
+
s.summary = %q{a modular Ruby webserver interface}
|
|
28
|
+
s.test_files = ["test/spec_rack_auth_openid.rb", "test/spec_rack_session_cookie.rb", "test/spec_rack_commonlogger.rb", "test/spec_rack_session_memcache.rb", "test/spec_rack_mock.rb", "test/spec_rack_deflater.rb", "test/spec_rack_directory.rb", "test/spec_rack_conditionalget.rb", "test/spec_rack_cascade.rb", "test/spec_rack_urlmap.rb", "test/spec_rack_mongrel.rb", "test/spec_rack_head.rb", "test/spec_rack_static.rb", "test/spec_rack_lint.rb", "test/spec_rack_handler.rb", "test/spec_rack_auth_basic.rb", "test/spec_rack_rewindable_input.rb", "test/spec_rack_showexceptions.rb", "test/spec_rack_thin.rb", "test/spec_rack_lock.rb", "test/spec_rack_camping.rb", "test/spec_rack_file.rb", "test/spec_rack_utils.rb", "test/spec_rack_auth_digest.rb", "test/spec_rack_webrick.rb", "test/spec_rack_chunked.rb", "test/spec_rack_fastcgi.rb", "test/spec_rack_lobster.rb", "test/spec_rack_cgi.rb", "test/spec_rack_showstatus.rb", "test/spec_rack_response.rb", "test/spec_rack_methodoverride.rb", "test/spec_rack_content_length.rb", "test/spec_rack_recursive.rb", "test/spec_rack_request.rb", "test/spec_rack_session_pool.rb", "test/spec_rack_content_type.rb", "test/spec_rack_builder.rb"]
|
|
29
|
+
|
|
30
|
+
if s.respond_to? :specification_version then
|
|
31
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
32
|
+
s.specification_version = 3
|
|
33
|
+
|
|
34
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
35
|
+
s.add_development_dependency(%q<test-spec>, [">= 0"])
|
|
36
|
+
s.add_development_dependency(%q<camping>, [">= 0"])
|
|
37
|
+
s.add_development_dependency(%q<fcgi>, [">= 0"])
|
|
38
|
+
s.add_development_dependency(%q<memcache-client>, [">= 0"])
|
|
39
|
+
s.add_development_dependency(%q<mongrel>, [">= 0"])
|
|
40
|
+
s.add_development_dependency(%q<ruby-openid>, ["~> 2.0.0"])
|
|
41
|
+
s.add_development_dependency(%q<thin>, [">= 0"])
|
|
42
|
+
else
|
|
43
|
+
s.add_dependency(%q<test-spec>, [">= 0"])
|
|
44
|
+
s.add_dependency(%q<camping>, [">= 0"])
|
|
45
|
+
s.add_dependency(%q<fcgi>, [">= 0"])
|
|
46
|
+
s.add_dependency(%q<memcache-client>, [">= 0"])
|
|
47
|
+
s.add_dependency(%q<mongrel>, [">= 0"])
|
|
48
|
+
s.add_dependency(%q<ruby-openid>, ["~> 2.0.0"])
|
|
49
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
s.add_dependency(%q<test-spec>, [">= 0"])
|
|
53
|
+
s.add_dependency(%q<camping>, [">= 0"])
|
|
54
|
+
s.add_dependency(%q<fcgi>, [">= 0"])
|
|
55
|
+
s.add_dependency(%q<memcache-client>, [">= 0"])
|
|
56
|
+
s.add_dependency(%q<mongrel>, [">= 0"])
|
|
57
|
+
s.add_dependency(%q<ruby-openid>, ["~> 2.0.0"])
|
|
58
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
|
59
|
+
end
|
|
60
|
+
end
|