eac-rack 1.1.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.
Files changed (111) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +399 -0
  4. data/bin/rackup +4 -0
  5. data/contrib/rack_logo.svg +111 -0
  6. data/example/lobster.ru +4 -0
  7. data/example/protectedlobster.rb +14 -0
  8. data/example/protectedlobster.ru +8 -0
  9. data/lib/rack.rb +92 -0
  10. data/lib/rack/adapter/camping.rb +22 -0
  11. data/lib/rack/auth/abstract/handler.rb +37 -0
  12. data/lib/rack/auth/abstract/request.rb +37 -0
  13. data/lib/rack/auth/basic.rb +58 -0
  14. data/lib/rack/auth/digest/md5.rb +124 -0
  15. data/lib/rack/auth/digest/nonce.rb +51 -0
  16. data/lib/rack/auth/digest/params.rb +55 -0
  17. data/lib/rack/auth/digest/request.rb +40 -0
  18. data/lib/rack/builder.rb +80 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +49 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/config.rb +15 -0
  24. data/lib/rack/content_length.rb +29 -0
  25. data/lib/rack/content_type.rb +23 -0
  26. data/lib/rack/deflater.rb +96 -0
  27. data/lib/rack/directory.rb +157 -0
  28. data/lib/rack/etag.rb +23 -0
  29. data/lib/rack/file.rb +90 -0
  30. data/lib/rack/handler.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +89 -0
  34. data/lib/rack/handler/lsws.rb +63 -0
  35. data/lib/rack/handler/mongrel.rb +90 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +69 -0
  40. data/lib/rack/head.rb +19 -0
  41. data/lib/rack/lint.rb +575 -0
  42. data/lib/rack/lobster.rb +65 -0
  43. data/lib/rack/lock.rb +16 -0
  44. data/lib/rack/logger.rb +20 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +206 -0
  47. data/lib/rack/mock.rb +189 -0
  48. data/lib/rack/nulllogger.rb +18 -0
  49. data/lib/rack/recursive.rb +57 -0
  50. data/lib/rack/reloader.rb +109 -0
  51. data/lib/rack/request.rb +271 -0
  52. data/lib/rack/response.rb +149 -0
  53. data/lib/rack/rewindable_input.rb +100 -0
  54. data/lib/rack/runtime.rb +27 -0
  55. data/lib/rack/sendfile.rb +142 -0
  56. data/lib/rack/server.rb +212 -0
  57. data/lib/rack/session/abstract/id.rb +140 -0
  58. data/lib/rack/session/cookie.rb +90 -0
  59. data/lib/rack/session/memcache.rb +119 -0
  60. data/lib/rack/session/pool.rb +100 -0
  61. data/lib/rack/showexceptions.rb +349 -0
  62. data/lib/rack/showstatus.rb +106 -0
  63. data/lib/rack/static.rb +38 -0
  64. data/lib/rack/urlmap.rb +56 -0
  65. data/lib/rack/utils.rb +614 -0
  66. data/rack.gemspec +38 -0
  67. data/test/spec_rack_auth_basic.rb +73 -0
  68. data/test/spec_rack_auth_digest.rb +226 -0
  69. data/test/spec_rack_builder.rb +84 -0
  70. data/test/spec_rack_camping.rb +51 -0
  71. data/test/spec_rack_cascade.rb +48 -0
  72. data/test/spec_rack_cgi.rb +89 -0
  73. data/test/spec_rack_chunked.rb +62 -0
  74. data/test/spec_rack_commonlogger.rb +61 -0
  75. data/test/spec_rack_conditionalget.rb +41 -0
  76. data/test/spec_rack_config.rb +24 -0
  77. data/test/spec_rack_content_length.rb +43 -0
  78. data/test/spec_rack_content_type.rb +30 -0
  79. data/test/spec_rack_deflater.rb +127 -0
  80. data/test/spec_rack_directory.rb +61 -0
  81. data/test/spec_rack_etag.rb +17 -0
  82. data/test/spec_rack_fastcgi.rb +89 -0
  83. data/test/spec_rack_file.rb +75 -0
  84. data/test/spec_rack_handler.rb +43 -0
  85. data/test/spec_rack_head.rb +30 -0
  86. data/test/spec_rack_lint.rb +528 -0
  87. data/test/spec_rack_lobster.rb +45 -0
  88. data/test/spec_rack_lock.rb +38 -0
  89. data/test/spec_rack_logger.rb +21 -0
  90. data/test/spec_rack_methodoverride.rb +60 -0
  91. data/test/spec_rack_mock.rb +243 -0
  92. data/test/spec_rack_mongrel.rb +189 -0
  93. data/test/spec_rack_nulllogger.rb +13 -0
  94. data/test/spec_rack_recursive.rb +77 -0
  95. data/test/spec_rack_request.rb +545 -0
  96. data/test/spec_rack_response.rb +221 -0
  97. data/test/spec_rack_rewindable_input.rb +118 -0
  98. data/test/spec_rack_runtime.rb +35 -0
  99. data/test/spec_rack_sendfile.rb +86 -0
  100. data/test/spec_rack_session_cookie.rb +73 -0
  101. data/test/spec_rack_session_memcache.rb +273 -0
  102. data/test/spec_rack_session_pool.rb +172 -0
  103. data/test/spec_rack_showexceptions.rb +21 -0
  104. data/test/spec_rack_showstatus.rb +72 -0
  105. data/test/spec_rack_static.rb +37 -0
  106. data/test/spec_rack_thin.rb +91 -0
  107. data/test/spec_rack_urlmap.rb +215 -0
  108. data/test/spec_rack_utils.rb +554 -0
  109. data/test/spec_rack_webrick.rb +130 -0
  110. data/test/spec_rackup.rb +154 -0
  111. metadata +311 -0
@@ -0,0 +1,157 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ module Rack
6
+ # Rack::Directory serves entries below the +root+ given, according to the
7
+ # path info of the Rack request. If a directory is found, the file's contents
8
+ # will be presented in an html based index. If a file is found, the env will
9
+ # be passed to the specified +app+.
10
+ #
11
+ # If +app+ is not specified, a Rack::File of the same +root+ will be used.
12
+
13
+ class Directory
14
+ 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>"
15
+ DIR_PAGE = <<-PAGE
16
+ <html><head>
17
+ <title>%s</title>
18
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
19
+ <style type='text/css'>
20
+ table { width:100%%; }
21
+ .name { text-align:left; }
22
+ .size, .mtime { text-align:right; }
23
+ .type { width:11em; }
24
+ .mtime { width:15em; }
25
+ </style>
26
+ </head><body>
27
+ <h1>%s</h1>
28
+ <hr />
29
+ <table>
30
+ <tr>
31
+ <th class='name'>Name</th>
32
+ <th class='size'>Size</th>
33
+ <th class='type'>Type</th>
34
+ <th class='mtime'>Last Modified</th>
35
+ </tr>
36
+ %s
37
+ </table>
38
+ <hr />
39
+ </body></html>
40
+ PAGE
41
+
42
+ attr_reader :files
43
+ attr_accessor :root, :path
44
+
45
+ def initialize(root, app=nil)
46
+ @root = F.expand_path(root)
47
+ @app = app || Rack::File.new(@root)
48
+ end
49
+
50
+ def call(env)
51
+ dup._call(env)
52
+ end
53
+
54
+ F = ::File
55
+
56
+ def _call(env)
57
+ @env = env
58
+ @script_name = env['SCRIPT_NAME']
59
+ @path_info = Utils.unescape(env['PATH_INFO'])
60
+
61
+ if forbidden = check_forbidden
62
+ forbidden
63
+ else
64
+ @path = F.join(@root, @path_info)
65
+ list_path
66
+ end
67
+ end
68
+
69
+ def check_forbidden
70
+ return unless @path_info.include? ".."
71
+
72
+ body = "Forbidden\n"
73
+ size = Rack::Utils.bytesize(body)
74
+ return [403, {"Content-Type" => "text/plain",
75
+ "Content-Length" => size.to_s,
76
+ "X-Cascade" => "pass"}, [body]]
77
+ end
78
+
79
+ def list_directory
80
+ @files = [['../','Parent Directory','','','']]
81
+ glob = F.join(@path, '*')
82
+
83
+ Dir[glob].sort.each do |node|
84
+ stat = stat(node)
85
+ next unless stat
86
+ basename = F.basename(node)
87
+ ext = F.extname(node)
88
+
89
+ url = F.join(@script_name, @path_info, basename)
90
+ size = stat.size
91
+ type = stat.directory? ? 'directory' : Mime.mime_type(ext)
92
+ size = stat.directory? ? '-' : filesize_format(size)
93
+ mtime = stat.mtime.httpdate
94
+ url << '/' if stat.directory?
95
+ basename << '/' if stat.directory?
96
+
97
+ @files << [ url, basename, size, type, mtime ]
98
+ end
99
+
100
+ return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
101
+ end
102
+
103
+ def stat(node, max = 10)
104
+ F.stat(node)
105
+ rescue Errno::ENOENT, Errno::ELOOP
106
+ return nil
107
+ end
108
+
109
+ # TODO: add correct response if not readable, not sure if 404 is the best
110
+ # option
111
+ def list_path
112
+ @stat = F.stat(@path)
113
+
114
+ if @stat.readable?
115
+ return @app.call(@env) if @stat.file?
116
+ return list_directory if @stat.directory?
117
+ else
118
+ raise Errno::ENOENT, 'No such file or directory'
119
+ end
120
+
121
+ rescue Errno::ENOENT, Errno::ELOOP
122
+ return entity_not_found
123
+ end
124
+
125
+ def entity_not_found
126
+ body = "Entity not found: #{@path_info}\n"
127
+ size = Rack::Utils.bytesize(body)
128
+ return [404, {"Content-Type" => "text/plain",
129
+ "Content-Length" => size.to_s,
130
+ "X-Cascade" => "pass"}, [body]]
131
+ end
132
+
133
+ def each
134
+ show_path = @path.sub(/^#{@root}/,'')
135
+ files = @files.map{|f| DIR_FILE % f }*"\n"
136
+ page = DIR_PAGE % [ show_path, show_path , files ]
137
+ page.each_line{|l| yield l }
138
+ end
139
+
140
+ # Stolen from Ramaze
141
+
142
+ FILESIZE_FORMAT = [
143
+ ['%.1fT', 1 << 40],
144
+ ['%.1fG', 1 << 30],
145
+ ['%.1fM', 1 << 20],
146
+ ['%.1fK', 1 << 10],
147
+ ]
148
+
149
+ def filesize_format(int)
150
+ FILESIZE_FORMAT.each do |format, size|
151
+ return format % (int.to_f / size) if int >= size
152
+ end
153
+
154
+ int.to_s + 'B'
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,23 @@
1
+ require 'digest/md5'
2
+
3
+ module Rack
4
+ # Automatically sets the ETag header on all String bodies
5
+ class ETag
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+
13
+ if !headers.has_key?('ETag')
14
+ parts = []
15
+ body.each { |part| parts << part.to_s }
16
+ headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
17
+ [status, headers, parts]
18
+ else
19
+ [status, headers, body]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,90 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ module Rack
6
+ # Rack::File serves files below the +root+ given, according to the
7
+ # path info of the Rack request.
8
+ #
9
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
10
+ # like sendfile on the +path+.
11
+
12
+ class File
13
+ attr_accessor :root
14
+ attr_accessor :path
15
+
16
+ alias :to_path :path
17
+
18
+ def initialize(root)
19
+ @root = root
20
+ end
21
+
22
+ def call(env)
23
+ dup._call(env)
24
+ end
25
+
26
+ F = ::File
27
+
28
+ def _call(env)
29
+ @path_info = Utils.unescape(env["PATH_INFO"])
30
+ return forbidden if @path_info.include? ".."
31
+
32
+ @path = F.join(@root, @path_info)
33
+
34
+ begin
35
+ if F.file?(@path) && F.readable?(@path)
36
+ serving
37
+ else
38
+ raise Errno::EPERM
39
+ end
40
+ rescue SystemCallError
41
+ not_found
42
+ end
43
+ end
44
+
45
+ def forbidden
46
+ body = "Forbidden\n"
47
+ [403, {"Content-Type" => "text/plain",
48
+ "Content-Length" => body.size.to_s,
49
+ "X-Cascade" => "pass"},
50
+ [body]]
51
+ end
52
+
53
+ # NOTE:
54
+ # We check via File::size? whether this file provides size info
55
+ # via stat (e.g. /proc files often don't), otherwise we have to
56
+ # figure it out by reading the whole file into memory. And while
57
+ # we're at it we also use this as body then.
58
+
59
+ def serving
60
+ if size = F.size?(@path)
61
+ body = self
62
+ else
63
+ body = [F.read(@path)]
64
+ size = Utils.bytesize(body.first)
65
+ end
66
+
67
+ [200, {
68
+ "Last-Modified" => F.mtime(@path).httpdate,
69
+ "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
70
+ "Content-Length" => size.to_s
71
+ }, body]
72
+ end
73
+
74
+ def not_found
75
+ body = "File not found: #{@path_info}\n"
76
+ [404, {"Content-Type" => "text/plain",
77
+ "Content-Length" => body.size.to_s,
78
+ "X-Cascade" => "pass"},
79
+ [body]]
80
+ end
81
+
82
+ def each
83
+ F.open(@path, "rb") { |file|
84
+ while part = file.read(8192)
85
+ yield part
86
+ end
87
+ }
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,88 @@
1
+ module Rack
2
+ # *Handlers* connect web servers with Rack.
3
+ #
4
+ # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
5
+ # and LiteSpeed.
6
+ #
7
+ # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
8
+ # A second optional hash can be passed to include server-specific
9
+ # configuration.
10
+ module Handler
11
+ def self.get(server)
12
+ return unless server
13
+ server = server.to_s
14
+
15
+ if klass = @handlers[server]
16
+ obj = Object
17
+ klass.split("::").each { |x| obj = obj.const_get(x) }
18
+ obj
19
+ else
20
+ try_require('rack/handler', server)
21
+ const_get(server)
22
+ end
23
+ end
24
+
25
+ def self.default(options = {})
26
+ # Guess.
27
+ if ENV.include?("PHP_FCGI_CHILDREN")
28
+ # We already speak FastCGI
29
+ options.delete :File
30
+ options.delete :Port
31
+
32
+ Rack::Handler::FastCGI
33
+ elsif ENV.include?("REQUEST_METHOD")
34
+ Rack::Handler::CGI
35
+ else
36
+ begin
37
+ Rack::Handler::Mongrel
38
+ rescue LoadError => e
39
+ Rack::Handler::WEBrick
40
+ end
41
+ end
42
+ end
43
+
44
+ # Transforms server-name constants to their canonical form as filenames,
45
+ # then tries to require them but silences the LoadError if not found
46
+ #
47
+ # Naming convention:
48
+ #
49
+ # Foo # => 'foo'
50
+ # FooBar # => 'foo_bar.rb'
51
+ # FooBAR # => 'foobar.rb'
52
+ # FOObar # => 'foobar.rb'
53
+ # FOOBAR # => 'foobar.rb'
54
+ # FooBarBaz # => 'foo_bar_baz.rb'
55
+ def self.try_require(prefix, const_name)
56
+ file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
57
+ gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
58
+
59
+ require(::File.join(prefix, file))
60
+ rescue LoadError
61
+ end
62
+
63
+ def self.register(server, klass)
64
+ @handlers ||= {}
65
+ @handlers[server] = klass
66
+ end
67
+
68
+ autoload :CGI, "rack/handler/cgi"
69
+ autoload :FastCGI, "rack/handler/fastcgi"
70
+ autoload :Mongrel, "rack/handler/mongrel"
71
+ autoload :EventedMongrel, "rack/handler/evented_mongrel"
72
+ autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel"
73
+ autoload :WEBrick, "rack/handler/webrick"
74
+ autoload :LSWS, "rack/handler/lsws"
75
+ autoload :SCGI, "rack/handler/scgi"
76
+ autoload :Thin, "rack/handler/thin"
77
+
78
+ register 'cgi', 'Rack::Handler::CGI'
79
+ register 'fastcgi', 'Rack::Handler::FastCGI'
80
+ register 'mongrel', 'Rack::Handler::Mongrel'
81
+ register 'emongrel', 'Rack::Handler::EventedMongrel'
82
+ register 'smongrel', 'Rack::Handler::SwiftipliedMongrel'
83
+ register 'webrick', 'Rack::Handler::WEBrick'
84
+ register 'lsws', 'Rack::Handler::LSWS'
85
+ register 'scgi', 'Rack::Handler::SCGI'
86
+ register 'thin', 'Rack::Handler::Thin'
87
+ end
88
+ end
@@ -0,0 +1,61 @@
1
+ require 'rack/content_length'
2
+
3
+ module Rack
4
+ module Handler
5
+ class CGI
6
+ def self.run(app, options=nil)
7
+ serve app
8
+ end
9
+
10
+ def self.serve(app)
11
+ app = ContentLength.new(app)
12
+
13
+ env = ENV.to_hash
14
+ env.delete "HTTP_CONTENT_LENGTH"
15
+
16
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
17
+
18
+ env.update({"rack.version" => [1,1],
19
+ "rack.input" => $stdin,
20
+ "rack.errors" => $stderr,
21
+
22
+ "rack.multithread" => false,
23
+ "rack.multiprocess" => true,
24
+ "rack.run_once" => true,
25
+
26
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
27
+ })
28
+
29
+ env["QUERY_STRING"] ||= ""
30
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
31
+ env["REQUEST_PATH"] ||= "/"
32
+
33
+ status, headers, body = app.call(env)
34
+ begin
35
+ send_headers status, headers
36
+ send_body body
37
+ ensure
38
+ body.close if body.respond_to? :close
39
+ end
40
+ end
41
+
42
+ def self.send_headers(status, headers)
43
+ STDOUT.print "Status: #{status}\r\n"
44
+ headers.each { |k, vs|
45
+ vs.split("\n").each { |v|
46
+ STDOUT.print "#{k}: #{v}\r\n"
47
+ }
48
+ }
49
+ STDOUT.print "\r\n"
50
+ STDOUT.flush
51
+ end
52
+
53
+ def self.send_body(body)
54
+ body.each { |part|
55
+ STDOUT.print part
56
+ STDOUT.flush
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ require 'swiftcore/evented_mongrel'
2
+
3
+ module Rack
4
+ module Handler
5
+ class EventedMongrel < Handler::Mongrel
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,89 @@
1
+ require 'fcgi'
2
+ require 'socket'
3
+ require 'rack/content_length'
4
+ require 'rack/rewindable_input'
5
+
6
+ if defined? FCGI::Stream
7
+ class FCGI::Stream
8
+ alias _rack_read_without_buffer read
9
+
10
+ def read(n, buffer=nil)
11
+ buf = _rack_read_without_buffer n
12
+ buffer.replace(buf.to_s) if buffer
13
+ buf
14
+ end
15
+ end
16
+ end
17
+
18
+ module Rack
19
+ module Handler
20
+ class FastCGI
21
+ def self.run(app, options={})
22
+ file = options[:File] and STDIN.reopen(UNIXServer.new(file))
23
+ port = options[:Port] and STDIN.reopen(TCPServer.new(port))
24
+ FCGI.each { |request|
25
+ serve request, app
26
+ }
27
+ end
28
+
29
+ def self.serve(request, app)
30
+ app = Rack::ContentLength.new(app)
31
+
32
+ env = request.env
33
+ env.delete "HTTP_CONTENT_LENGTH"
34
+
35
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
36
+
37
+ rack_input = RewindableInput.new(request.in)
38
+
39
+ env.update({"rack.version" => [1,1],
40
+ "rack.input" => rack_input,
41
+ "rack.errors" => request.err,
42
+
43
+ "rack.multithread" => false,
44
+ "rack.multiprocess" => true,
45
+ "rack.run_once" => false,
46
+
47
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
48
+ })
49
+
50
+ env["QUERY_STRING"] ||= ""
51
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
52
+ env["REQUEST_PATH"] ||= "/"
53
+ env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
54
+ env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
55
+
56
+ begin
57
+ status, headers, body = app.call(env)
58
+ begin
59
+ send_headers request.out, status, headers
60
+ send_body request.out, body
61
+ ensure
62
+ body.close if body.respond_to? :close
63
+ end
64
+ ensure
65
+ rack_input.close
66
+ request.finish
67
+ end
68
+ end
69
+
70
+ def self.send_headers(out, status, headers)
71
+ out.print "Status: #{status}\r\n"
72
+ headers.each { |k, vs|
73
+ vs.split("\n").each { |v|
74
+ out.print "#{k}: #{v}\r\n"
75
+ }
76
+ }
77
+ out.print "\r\n"
78
+ out.flush
79
+ end
80
+
81
+ def self.send_body(out, body)
82
+ body.each { |part|
83
+ out.print part
84
+ out.flush
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end