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,67 @@
1
+ module Rack
2
+ # Rack::Builder implements a small DSL to iteratively construct Rack
3
+ # applications.
4
+ #
5
+ # Example:
6
+ #
7
+ # app = Rack::Builder.new {
8
+ # use Rack::CommonLogger
9
+ # use Rack::ShowExceptions
10
+ # map "/lobster" do
11
+ # use Rack::Lint
12
+ # run Rack::Lobster.new
13
+ # end
14
+ # }
15
+ #
16
+ # Or
17
+ #
18
+ # app = Rack::Builder.app do
19
+ # use Rack::CommonLogger
20
+ # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
21
+ # end
22
+ #
23
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
24
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
25
+
26
+ class Builder
27
+ def initialize(&block)
28
+ @ins = []
29
+ instance_eval(&block) if block_given?
30
+ end
31
+
32
+ def self.app(&block)
33
+ self.new(&block).to_app
34
+ end
35
+
36
+ def use(middleware, *args, &block)
37
+ @ins << if block_given?
38
+ lambda { |app| middleware.new(app, *args, &block) }
39
+ else
40
+ lambda { |app| middleware.new(app, *args) }
41
+ end
42
+ end
43
+
44
+ def run(app)
45
+ @ins << app #lambda { |nothing| app }
46
+ end
47
+
48
+ def map(path, &block)
49
+ if @ins.last.kind_of? Hash
50
+ @ins.last[path] = Rack::Builder.new(&block).to_app
51
+ else
52
+ @ins << {}
53
+ map(path, &block)
54
+ end
55
+ end
56
+
57
+ def to_app
58
+ @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
59
+ inner_app = @ins.last
60
+ @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
61
+ end
62
+
63
+ def call(env)
64
+ to_app.call(env)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,36 @@
1
+ module Rack
2
+ # Rack::Cascade tries an request on several apps, and returns the
3
+ # first response that is not 404 (or in a list of configurable
4
+ # status codes).
5
+
6
+ class Cascade
7
+ attr_reader :apps
8
+
9
+ def initialize(apps, catch=404)
10
+ @apps = apps
11
+ @catch = [*catch]
12
+ end
13
+
14
+ def call(env)
15
+ status = headers = body = nil
16
+ raise ArgumentError, "empty cascade" if @apps.empty?
17
+ @apps.each { |app|
18
+ begin
19
+ status, headers, body = app.call(env)
20
+ break unless @catch.include?(status.to_i)
21
+ end
22
+ }
23
+ [status, headers, body]
24
+ end
25
+
26
+ def add app
27
+ @apps << app
28
+ end
29
+
30
+ def include? app
31
+ @apps.include? app
32
+ end
33
+
34
+ alias_method :<<, :add
35
+ end
36
+ end
@@ -0,0 +1,61 @@
1
+ module Rack
2
+ # Rack::CommonLogger forwards every request to an +app+ given, and
3
+ # logs a line in the Apache common log format to the +logger+, or
4
+ # rack.errors by default.
5
+
6
+ class CommonLogger
7
+ def initialize(app, logger=nil)
8
+ @app = app
9
+ @logger = logger
10
+ end
11
+
12
+ def call(env)
13
+ dup._call(env)
14
+ end
15
+
16
+ def _call(env)
17
+ @env = env
18
+ @logger ||= self
19
+ @time = Time.now
20
+ @status, @header, @body = @app.call(env)
21
+ [@status, @header, self]
22
+ end
23
+
24
+ def close
25
+ @body.close if @body.respond_to? :close
26
+ end
27
+
28
+ # By default, log to rack.errors.
29
+ def <<(str)
30
+ @env["rack.errors"].write(str)
31
+ @env["rack.errors"].flush
32
+ end
33
+
34
+ def each
35
+ length = 0
36
+ @body.each { |part|
37
+ length += part.size
38
+ yield part
39
+ }
40
+
41
+ @now = Time.now
42
+
43
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
44
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
45
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
46
+ @logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
47
+ [
48
+ @env['HTTP_X_FORWARDED_FOR'] || @env["REMOTE_ADDR"] || "-",
49
+ @env["REMOTE_USER"] || "-",
50
+ @now.strftime("%d/%b/%Y %H:%M:%S"),
51
+ @env["REQUEST_METHOD"],
52
+ @env["PATH_INFO"],
53
+ @env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"],
54
+ @env["HTTP_VERSION"],
55
+ @status.to_s[0..3],
56
+ (length.zero? ? "-" : length.to_s),
57
+ @now - @time
58
+ ]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,42 @@
1
+ module Rack
2
+
3
+ # Middleware that enables conditional GET using If-None-Match and
4
+ # If-Modified-Since. The application should set either or both of the
5
+ # Last-Modified or Etag response headers according to RFC 2616. When
6
+ # either of the conditions is met, the response body is set to be zero
7
+ # length and the response status is set to 304 Not Modified.
8
+ #
9
+ # Applications that defer response body generation until the body's each
10
+ # message is received will avoid response body generation completely when
11
+ # a conditional GET matches.
12
+ #
13
+ # Adapted from Michael Klishin's Merb implementation:
14
+ # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
15
+ class ConditionalGet
16
+ def initialize(app)
17
+ @app = app
18
+ end
19
+
20
+ def call(env)
21
+ return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])
22
+
23
+ status, headers, body = @app.call(env)
24
+ if etag_matches?(env, headers) || modified_since?(env, headers)
25
+ status = 304
26
+ body = []
27
+ end
28
+ [status, headers, body]
29
+ end
30
+
31
+ private
32
+ def etag_matches?(env, headers)
33
+ etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
34
+ end
35
+
36
+ def modified_since?(env, headers)
37
+ last_modified = headers['Last-Modified'] and
38
+ last_modified == env['HTTP_IF_MODIFIED_SINCE']
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,63 @@
1
+ require "zlib"
2
+ require "stringio"
3
+
4
+ module Rack
5
+
6
+ class Deflater
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, body = @app.call(env)
13
+
14
+ request = Request.new(env)
15
+
16
+ encoding = Utils.select_best_encoding(%w(gzip deflate identity), request.accept_encoding)
17
+
18
+ case encoding
19
+ when "gzip"
20
+ mtime = headers["Last-Modified"] || Time.now
21
+ [status, headers.merge("Content-Encoding" => "gzip"), self.class.gzip(body, mtime)]
22
+ when "deflate"
23
+ [status, headers.merge("Content-Encoding" => "deflate"), self.class.deflate(body)]
24
+ when "identity"
25
+ [status, headers, body]
26
+ when nil
27
+ message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
28
+ [406, {"Content-Type" => "text/plain"}, message]
29
+ end
30
+ end
31
+
32
+ def self.gzip(body, mtime)
33
+ io = StringIO.new
34
+ gzip = Zlib::GzipWriter.new(io)
35
+ gzip.mtime = mtime
36
+
37
+ # TODO: Add streaming
38
+ body.each { |part| gzip << part }
39
+
40
+ gzip.close
41
+ return io.string
42
+ end
43
+
44
+ DEFLATE_ARGS = [
45
+ Zlib::DEFAULT_COMPRESSION,
46
+ # drop the zlib header which causes both Safari and IE to choke
47
+ -Zlib::MAX_WBITS,
48
+ Zlib::DEF_MEM_LEVEL,
49
+ Zlib::DEFAULT_STRATEGY
50
+ ]
51
+
52
+ # Loosely based on Mongrel's Deflate handler
53
+ def self.deflate(body)
54
+ deflater = Zlib::Deflate.new(*DEFLATE_ARGS)
55
+
56
+ # TODO: Add streaming
57
+ body.each { |part| deflater << part }
58
+
59
+ return deflater.finish
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,149 @@
1
+ require 'time'
2
+ require 'rack/mime'
3
+
4
+ module Rack
5
+ # Rack::Directory serves entries below the +root+ given, according to the
6
+ # path info of the Rack request. If a directory is found, the file's contents
7
+ # will be presented in an html based index. If a file is found, the env will
8
+ # be passed to the specified +app+.
9
+ #
10
+ # If +app+ is not specified, a Rack::File of the same +root+ will be used.
11
+
12
+ class Directory
13
+ DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
14
+ DIR_PAGE = <<-PAGE
15
+ <html><head>
16
+ <title>%s</title>
17
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
18
+ <style type='text/css'>
19
+ table { width:100%%; }
20
+ .name { text-align:left; }
21
+ .size, .mtime { text-align:right; }
22
+ .type { width:11em; }
23
+ .mtime { width:15em; }
24
+ </style>
25
+ </head><body>
26
+ <h1>%s</h1>
27
+ <hr />
28
+ <table>
29
+ <tr>
30
+ <th class='name'>Name</th>
31
+ <th class='size'>Size</th>
32
+ <th class='type'>Type</th>
33
+ <th class='mtime'>Last Modified</th>
34
+ </tr>
35
+ %s
36
+ </table>
37
+ <hr />
38
+ </body></html>
39
+ PAGE
40
+
41
+ attr_reader :files
42
+ attr_accessor :root, :path
43
+
44
+ def initialize(root, app=nil)
45
+ @root = F.expand_path(root)
46
+ @app = app || Rack::File.new(@root)
47
+ end
48
+
49
+ def call(env)
50
+ dup._call(env)
51
+ end
52
+
53
+ F = ::File
54
+
55
+ def _call(env)
56
+ @env = env
57
+ @path_info, @script_name = env.values_at('PATH_INFO', 'SCRIPT_NAME')
58
+
59
+ if forbidden = check_forbidden
60
+ forbidden
61
+ else
62
+ @path = F.join(@root, Utils.unescape(@path_info))
63
+ list_path
64
+ end
65
+ end
66
+
67
+ def check_forbidden
68
+ return unless @path_info.include? ".."
69
+
70
+ body = "Forbidden\n"
71
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.size
72
+ return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
73
+ end
74
+
75
+ def list_directory
76
+ @files = [['../','Parent Directory','','','']]
77
+ glob = F.join(@path, '*')
78
+
79
+ Dir[glob].sort.each do |node|
80
+ stat = stat(node)
81
+ next unless stat
82
+ basename = F.basename(node)
83
+ ext = F.extname(node)
84
+
85
+ url = F.join(@script_name, @path_info, basename)
86
+ size = stat.size
87
+ type = stat.directory? ? 'directory' : Mime.mime_type(ext)
88
+ size = stat.directory? ? '-' : filesize_format(size)
89
+ mtime = stat.mtime.httpdate
90
+
91
+ @files << [ url, basename, size, type, mtime ]
92
+ end
93
+
94
+ return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
95
+ end
96
+
97
+ def stat(node, max = 10)
98
+ F.stat(node)
99
+ rescue Errno::ENOENT, Errno::ELOOP
100
+ return nil
101
+ end
102
+
103
+ # TODO: add correct response if not readable, not sure if 404 is the best
104
+ # option
105
+ def list_path
106
+ @stat = F.stat(@path)
107
+
108
+ if @stat.readable?
109
+ return @app.call(@env) if @stat.file?
110
+ return list_directory if @stat.directory?
111
+ else
112
+ raise Errno::ENOENT, 'No such file or directory'
113
+ end
114
+
115
+ rescue Errno::ENOENT, Errno::ELOOP
116
+ return entity_not_found
117
+ end
118
+
119
+ def entity_not_found
120
+ body = "Entity not found: #{@path_info}\n"
121
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.size
122
+ return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
123
+ end
124
+
125
+ def each
126
+ show_path = @path.sub(/^#{@root}/,'')
127
+ files = @files.map{|f| DIR_FILE % f }*"\n"
128
+ page = DIR_PAGE % [ show_path, show_path , files ]
129
+ page.each_line{|l| yield l }
130
+ end
131
+
132
+ # Stolen from Ramaze
133
+
134
+ FILESIZE_FORMAT = [
135
+ ['%.1fT', 1 << 40],
136
+ ['%.1fG', 1 << 30],
137
+ ['%.1fM', 1 << 20],
138
+ ['%.1fK', 1 << 10],
139
+ ]
140
+
141
+ def filesize_format(int)
142
+ FILESIZE_FORMAT.each do |format, size|
143
+ return format % (int.to_f / size) if int >= size
144
+ end
145
+
146
+ int.to_s + 'B'
147
+ end
148
+ end
149
+ end
data/lib/rack/file.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'time'
2
+ require 'rack/mime'
3
+
4
+ module Rack
5
+ # Rack::File serves files below the +root+ given, according to the
6
+ # path info of the Rack request.
7
+ #
8
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
9
+ # like sendfile on the +path+.
10
+
11
+ class File
12
+ attr_accessor :root
13
+ attr_accessor :path
14
+
15
+ def initialize(root)
16
+ @root = root
17
+ end
18
+
19
+ def call(env)
20
+ dup._call(env)
21
+ end
22
+
23
+ F = ::File
24
+
25
+ def _call(env)
26
+ return forbidden if env["PATH_INFO"].include? ".."
27
+
28
+ @path = F.join(@root, Utils.unescape(env["PATH_INFO"]))
29
+
30
+ begin
31
+ if F.file?(@path) && F.readable?(@path)
32
+ serving
33
+ else
34
+ raise Errno::EPERM
35
+ end
36
+ rescue SystemCallError
37
+ not_found
38
+ end
39
+ end
40
+
41
+ def forbidden
42
+ body = "Forbidden\n"
43
+ [403, {"Content-Type" => "text/plain",
44
+ "Content-Length" => body.size.to_s},
45
+ [body]]
46
+ end
47
+
48
+ # NOTE:
49
+ # We check via File::size? whether this file provides size info
50
+ # via stat (e.g. /proc files often don't), otherwise we have to
51
+ # figure it out by reading the whole file into memory. And while
52
+ # we're at it we also use this as body then.
53
+
54
+ def serving
55
+ if size = F.size?(@path)
56
+ body = self
57
+ else
58
+ body = [F.read(@path)]
59
+ size = body.first.size
60
+ end
61
+
62
+ [200, {
63
+ "Last-Modified" => F.mtime(@path).httpdate,
64
+ "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
65
+ "Content-Length" => size.to_s
66
+ }, body]
67
+ end
68
+
69
+ def not_found
70
+ body = "File not found: #{@path}\n"
71
+ [404, {"Content-Type" => "text/plain",
72
+ "Content-Length" => body.size.to_s},
73
+ [body]]
74
+ end
75
+
76
+ def each
77
+ F.open(@path, "rb") { |file|
78
+ while part = file.read(8192)
79
+ yield part
80
+ end
81
+ }
82
+ end
83
+ end
84
+ end