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.
Files changed (94) hide show
  1. data/AUTHORS +8 -0
  2. data/COPYING +18 -0
  3. data/KNOWN-ISSUES +18 -0
  4. data/README +273 -0
  5. data/Rakefile +185 -0
  6. data/bin/rackup +172 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +85 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +28 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +437 -0
  21. data/lib/rack/builder.rb +67 -0
  22. data/lib/rack/cascade.rb +36 -0
  23. data/lib/rack/commonlogger.rb +61 -0
  24. data/lib/rack/conditionalget.rb +42 -0
  25. data/lib/rack/deflater.rb +63 -0
  26. data/lib/rack/directory.rb +149 -0
  27. data/lib/rack/file.rb +84 -0
  28. data/lib/rack/handler.rb +46 -0
  29. data/lib/rack/handler/cgi.rb +57 -0
  30. data/lib/rack/handler/evented_mongrel.rb +8 -0
  31. data/lib/rack/handler/fastcgi.rb +86 -0
  32. data/lib/rack/handler/lsws.rb +52 -0
  33. data/lib/rack/handler/mongrel.rb +78 -0
  34. data/lib/rack/handler/scgi.rb +57 -0
  35. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  36. data/lib/rack/handler/webrick.rb +61 -0
  37. data/lib/rack/head.rb +19 -0
  38. data/lib/rack/lint.rb +463 -0
  39. data/lib/rack/lobster.rb +65 -0
  40. data/lib/rack/methodoverride.rb +21 -0
  41. data/lib/rack/mime.rb +204 -0
  42. data/lib/rack/mock.rb +160 -0
  43. data/lib/rack/recursive.rb +57 -0
  44. data/lib/rack/reloader.rb +64 -0
  45. data/lib/rack/request.rb +217 -0
  46. data/lib/rack/response.rb +171 -0
  47. data/lib/rack/session/abstract/id.rb +140 -0
  48. data/lib/rack/session/cookie.rb +89 -0
  49. data/lib/rack/session/memcache.rb +97 -0
  50. data/lib/rack/session/pool.rb +73 -0
  51. data/lib/rack/showexceptions.rb +348 -0
  52. data/lib/rack/showstatus.rb +105 -0
  53. data/lib/rack/static.rb +38 -0
  54. data/lib/rack/urlmap.rb +48 -0
  55. data/lib/rack/utils.rb +318 -0
  56. data/rack.gemspec +31 -0
  57. data/test/cgi/lighttpd.conf +20 -0
  58. data/test/cgi/test +9 -0
  59. data/test/cgi/test.fcgi +8 -0
  60. data/test/cgi/test.ru +7 -0
  61. data/test/spec_rack_auth_basic.rb +69 -0
  62. data/test/spec_rack_auth_digest.rb +169 -0
  63. data/test/spec_rack_auth_openid.rb +137 -0
  64. data/test/spec_rack_builder.rb +84 -0
  65. data/test/spec_rack_camping.rb +51 -0
  66. data/test/spec_rack_cascade.rb +50 -0
  67. data/test/spec_rack_cgi.rb +89 -0
  68. data/test/spec_rack_commonlogger.rb +32 -0
  69. data/test/spec_rack_conditionalget.rb +41 -0
  70. data/test/spec_rack_deflater.rb +70 -0
  71. data/test/spec_rack_directory.rb +56 -0
  72. data/test/spec_rack_fastcgi.rb +89 -0
  73. data/test/spec_rack_file.rb +57 -0
  74. data/test/spec_rack_handler.rb +24 -0
  75. data/test/spec_rack_head.rb +30 -0
  76. data/test/spec_rack_lint.rb +371 -0
  77. data/test/spec_rack_lobster.rb +45 -0
  78. data/test/spec_rack_methodoverride.rb +31 -0
  79. data/test/spec_rack_mock.rb +152 -0
  80. data/test/spec_rack_mongrel.rb +170 -0
  81. data/test/spec_rack_recursive.rb +77 -0
  82. data/test/spec_rack_request.rb +426 -0
  83. data/test/spec_rack_response.rb +173 -0
  84. data/test/spec_rack_session_cookie.rb +78 -0
  85. data/test/spec_rack_session_memcache.rb +132 -0
  86. data/test/spec_rack_session_pool.rb +84 -0
  87. data/test/spec_rack_showexceptions.rb +21 -0
  88. data/test/spec_rack_showstatus.rb +72 -0
  89. data/test/spec_rack_static.rb +37 -0
  90. data/test/spec_rack_urlmap.rb +175 -0
  91. data/test/spec_rack_utils.rb +174 -0
  92. data/test/spec_rack_webrick.rb +123 -0
  93. data/test/testrequest.rb +45 -0
  94. 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
@@ -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
@@ -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("&", "&amp;").
67
+ gsub("<", "&lt;").
68
+ gsub(">", "&gt;").
69
+ gsub("'", "&#39;").
70
+ gsub('"', "&quot;")
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