kastner-rack 0.3.171

Sign up to get free protection for your applications and to get access to all the features.
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