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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
|
|
5
|
+
# Middleware that enables conditional GET using If-None-Match and
|
|
6
|
+
# If-Modified-Since. The application should set either or both of the
|
|
7
|
+
# Last-Modified or Etag response headers according to RFC 2616. When
|
|
8
|
+
# either of the conditions is met, the response body is set to be zero
|
|
9
|
+
# length and the response status is set to 304 Not Modified.
|
|
10
|
+
#
|
|
11
|
+
# Applications that defer response body generation until the body's each
|
|
12
|
+
# message is received will avoid response body generation completely when
|
|
13
|
+
# a conditional GET matches.
|
|
14
|
+
#
|
|
15
|
+
# Adapted from Michael Klishin's Merb implementation:
|
|
16
|
+
# http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
|
|
17
|
+
class ConditionalGet
|
|
18
|
+
def initialize(app)
|
|
19
|
+
@app = app
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(env)
|
|
23
|
+
return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])
|
|
24
|
+
|
|
25
|
+
status, headers, body = @app.call(env)
|
|
26
|
+
headers = Utils::HeaderHash.new(headers)
|
|
27
|
+
if etag_matches?(env, headers) || modified_since?(env, headers)
|
|
28
|
+
status = 304
|
|
29
|
+
headers.delete('Content-Type')
|
|
30
|
+
headers.delete('Content-Length')
|
|
31
|
+
body = []
|
|
32
|
+
end
|
|
33
|
+
[status, headers, body]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
def etag_matches?(env, headers)
|
|
38
|
+
etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def modified_since?(env, headers)
|
|
42
|
+
last_modified = headers['Last-Modified'] and
|
|
43
|
+
last_modified == env['HTTP_IF_MODIFIED_SINCE']
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Sets the Content-Length header on responses with fixed-length bodies.
|
|
5
|
+
class ContentLength
|
|
6
|
+
include Rack::Utils
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
status, headers, body = @app.call(env)
|
|
14
|
+
headers = HeaderHash.new(headers)
|
|
15
|
+
|
|
16
|
+
if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
|
|
17
|
+
!headers['Content-Length'] &&
|
|
18
|
+
!headers['Transfer-Encoding'] &&
|
|
19
|
+
(body.respond_to?(:to_ary) || body.respond_to?(:to_str))
|
|
20
|
+
|
|
21
|
+
body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
|
|
22
|
+
length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
|
|
23
|
+
headers['Content-Length'] = length.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
[status, headers, body]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
|
|
5
|
+
# Sets the Content-Type header on responses which don't have one.
|
|
6
|
+
#
|
|
7
|
+
# Builder Usage:
|
|
8
|
+
# use Rack::ContentType, "text/plain"
|
|
9
|
+
#
|
|
10
|
+
# When no content type argument is provided, "text/html" is assumed.
|
|
11
|
+
class ContentType
|
|
12
|
+
def initialize(app, content_type = "text/html")
|
|
13
|
+
@app, @content_type = app, content_type
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(env)
|
|
17
|
+
status, headers, body = @app.call(env)
|
|
18
|
+
headers = Utils::HeaderHash.new(headers)
|
|
19
|
+
headers['Content-Type'] ||= @content_type
|
|
20
|
+
[status, headers.to_hash, body]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require "zlib"
|
|
2
|
+
require "stringio"
|
|
3
|
+
require "time" # for Time.httpdate
|
|
4
|
+
require 'rack/utils'
|
|
5
|
+
|
|
6
|
+
module Rack
|
|
7
|
+
class Deflater
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
status, headers, body = @app.call(env)
|
|
14
|
+
headers = Utils::HeaderHash.new(headers)
|
|
15
|
+
|
|
16
|
+
# Skip compressing empty entity body responses and responses with
|
|
17
|
+
# no-transform set.
|
|
18
|
+
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
|
19
|
+
headers['Cache-Control'].to_s =~ /\bno-transform\b/
|
|
20
|
+
return [status, headers, body]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
request = Request.new(env)
|
|
24
|
+
|
|
25
|
+
encoding = Utils.select_best_encoding(%w(gzip deflate identity),
|
|
26
|
+
request.accept_encoding)
|
|
27
|
+
|
|
28
|
+
# Set the Vary HTTP header.
|
|
29
|
+
vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
|
|
30
|
+
unless vary.include?("*") || vary.include?("Accept-Encoding")
|
|
31
|
+
headers["Vary"] = vary.push("Accept-Encoding").join(",")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
case encoding
|
|
35
|
+
when "gzip"
|
|
36
|
+
headers['Content-Encoding'] = "gzip"
|
|
37
|
+
headers.delete('Content-Length')
|
|
38
|
+
mtime = headers.key?("Last-Modified") ?
|
|
39
|
+
Time.httpdate(headers["Last-Modified"]) : Time.now
|
|
40
|
+
[status, headers, GzipStream.new(body, mtime)]
|
|
41
|
+
when "deflate"
|
|
42
|
+
headers['Content-Encoding'] = "deflate"
|
|
43
|
+
headers.delete('Content-Length')
|
|
44
|
+
[status, headers, DeflateStream.new(body)]
|
|
45
|
+
when "identity"
|
|
46
|
+
[status, headers, body]
|
|
47
|
+
when nil
|
|
48
|
+
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
|
|
49
|
+
[406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class GzipStream
|
|
54
|
+
def initialize(body, mtime)
|
|
55
|
+
@body = body
|
|
56
|
+
@mtime = mtime
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def each(&block)
|
|
60
|
+
@writer = block
|
|
61
|
+
gzip =::Zlib::GzipWriter.new(self)
|
|
62
|
+
gzip.mtime = @mtime
|
|
63
|
+
@body.each { |part| gzip << part }
|
|
64
|
+
@body.close if @body.respond_to?(:close)
|
|
65
|
+
gzip.close
|
|
66
|
+
@writer = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def write(data)
|
|
70
|
+
@writer.call(data)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class DeflateStream
|
|
75
|
+
DEFLATE_ARGS = [
|
|
76
|
+
Zlib::DEFAULT_COMPRESSION,
|
|
77
|
+
# drop the zlib header which causes both Safari and IE to choke
|
|
78
|
+
-Zlib::MAX_WBITS,
|
|
79
|
+
Zlib::DEF_MEM_LEVEL,
|
|
80
|
+
Zlib::DEFAULT_STRATEGY
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
def initialize(body)
|
|
84
|
+
@body = body
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def each
|
|
88
|
+
deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
|
|
89
|
+
@body.each { |part| yield deflater.deflate(part) }
|
|
90
|
+
@body.close if @body.respond_to?(:close)
|
|
91
|
+
yield deflater.finish
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
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","Content-Length" => size.to_s}, [body]]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def list_directory
|
|
78
|
+
@files = [['../','Parent Directory','','','']]
|
|
79
|
+
glob = F.join(@path, '*')
|
|
80
|
+
|
|
81
|
+
Dir[glob].sort.each do |node|
|
|
82
|
+
stat = stat(node)
|
|
83
|
+
next unless stat
|
|
84
|
+
basename = F.basename(node)
|
|
85
|
+
ext = F.extname(node)
|
|
86
|
+
|
|
87
|
+
url = F.join(@script_name, @path_info, basename)
|
|
88
|
+
size = stat.size
|
|
89
|
+
type = stat.directory? ? 'directory' : Mime.mime_type(ext)
|
|
90
|
+
size = stat.directory? ? '-' : filesize_format(size)
|
|
91
|
+
mtime = stat.mtime.httpdate
|
|
92
|
+
url << '/' if stat.directory?
|
|
93
|
+
basename << '/' if stat.directory?
|
|
94
|
+
|
|
95
|
+
@files << [ url, basename, size, type, mtime ]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def stat(node, max = 10)
|
|
102
|
+
F.stat(node)
|
|
103
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
|
104
|
+
return nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# TODO: add correct response if not readable, not sure if 404 is the best
|
|
108
|
+
# option
|
|
109
|
+
def list_path
|
|
110
|
+
@stat = F.stat(@path)
|
|
111
|
+
|
|
112
|
+
if @stat.readable?
|
|
113
|
+
return @app.call(@env) if @stat.file?
|
|
114
|
+
return list_directory if @stat.directory?
|
|
115
|
+
else
|
|
116
|
+
raise Errno::ENOENT, 'No such file or directory'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
|
120
|
+
return entity_not_found
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def entity_not_found
|
|
124
|
+
body = "Entity not found: #{@path_info}\n"
|
|
125
|
+
size = Rack::Utils.bytesize(body)
|
|
126
|
+
return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def each
|
|
130
|
+
show_path = @path.sub(/^#{@root}/,'')
|
|
131
|
+
files = @files.map{|f| DIR_FILE % f }*"\n"
|
|
132
|
+
page = DIR_PAGE % [ show_path, show_path , files ]
|
|
133
|
+
page.each_line{|l| yield l }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Stolen from Ramaze
|
|
137
|
+
|
|
138
|
+
FILESIZE_FORMAT = [
|
|
139
|
+
['%.1fT', 1 << 40],
|
|
140
|
+
['%.1fG', 1 << 30],
|
|
141
|
+
['%.1fM', 1 << 20],
|
|
142
|
+
['%.1fK', 1 << 10],
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
def filesize_format(int)
|
|
146
|
+
FILESIZE_FORMAT.each do |format, size|
|
|
147
|
+
return format % (int.to_f / size) if int >= size
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
int.to_s + 'B'
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/rack/file.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
[body]]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# NOTE:
|
|
53
|
+
# We check via File::size? whether this file provides size info
|
|
54
|
+
# via stat (e.g. /proc files often don't), otherwise we have to
|
|
55
|
+
# figure it out by reading the whole file into memory. And while
|
|
56
|
+
# we're at it we also use this as body then.
|
|
57
|
+
|
|
58
|
+
def serving
|
|
59
|
+
if size = F.size?(@path)
|
|
60
|
+
body = self
|
|
61
|
+
else
|
|
62
|
+
body = [F.read(@path)]
|
|
63
|
+
size = Utils.bytesize(body.first)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
[200, {
|
|
67
|
+
"Last-Modified" => F.mtime(@path).httpdate,
|
|
68
|
+
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
|
|
69
|
+
"Content-Length" => size.to_s
|
|
70
|
+
}, body]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def not_found
|
|
74
|
+
body = "File not found: #{@path_info}\n"
|
|
75
|
+
[404, {"Content-Type" => "text/plain",
|
|
76
|
+
"Content-Length" => body.size.to_s},
|
|
77
|
+
[body]]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def each
|
|
81
|
+
F.open(@path, "rb") { |file|
|
|
82
|
+
while part = file.read(8192)
|
|
83
|
+
yield part
|
|
84
|
+
end
|
|
85
|
+
}
|
|
86
|
+
end
|
|
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,0],
|
|
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,88 @@
|
|
|
1
|
+
require 'fcgi'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'rack/content_length'
|
|
4
|
+
require 'rack/rewindable_input'
|
|
5
|
+
|
|
6
|
+
class FCGI::Stream
|
|
7
|
+
alias _rack_read_without_buffer read
|
|
8
|
+
|
|
9
|
+
def read(n, buffer=nil)
|
|
10
|
+
buf = _rack_read_without_buffer n
|
|
11
|
+
buffer.replace(buf.to_s) if buffer
|
|
12
|
+
buf
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Rack
|
|
17
|
+
module Handler
|
|
18
|
+
class FastCGI
|
|
19
|
+
def self.run(app, options={})
|
|
20
|
+
file = options[:File] and STDIN.reopen(UNIXServer.new(file))
|
|
21
|
+
port = options[:Port] and STDIN.reopen(TCPServer.new(port))
|
|
22
|
+
FCGI.each { |request|
|
|
23
|
+
serve request, app
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.serve(request, app)
|
|
28
|
+
app = Rack::ContentLength.new(app)
|
|
29
|
+
|
|
30
|
+
env = request.env
|
|
31
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
|
32
|
+
|
|
33
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
|
34
|
+
|
|
35
|
+
rack_input = RewindableInput.new(request.in)
|
|
36
|
+
|
|
37
|
+
env.update({"rack.version" => [1,0],
|
|
38
|
+
"rack.input" => rack_input,
|
|
39
|
+
"rack.errors" => request.err,
|
|
40
|
+
|
|
41
|
+
"rack.multithread" => false,
|
|
42
|
+
"rack.multiprocess" => true,
|
|
43
|
+
"rack.run_once" => false,
|
|
44
|
+
|
|
45
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
env["QUERY_STRING"] ||= ""
|
|
49
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
|
50
|
+
env["REQUEST_PATH"] ||= "/"
|
|
51
|
+
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
|
52
|
+
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
|
|
53
|
+
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
status, headers, body = app.call(env)
|
|
57
|
+
begin
|
|
58
|
+
send_headers request.out, status, headers
|
|
59
|
+
send_body request.out, body
|
|
60
|
+
ensure
|
|
61
|
+
body.close if body.respond_to? :close
|
|
62
|
+
end
|
|
63
|
+
ensure
|
|
64
|
+
rack_input.close
|
|
65
|
+
request.finish
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.send_headers(out, status, headers)
|
|
70
|
+
out.print "Status: #{status}\r\n"
|
|
71
|
+
headers.each { |k, vs|
|
|
72
|
+
vs.split("\n").each { |v|
|
|
73
|
+
out.print "#{k}: #{v}\r\n"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
out.print "\r\n"
|
|
77
|
+
out.flush
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.send_body(out, body)
|
|
81
|
+
body.each { |part|
|
|
82
|
+
out.print part
|
|
83
|
+
out.flush
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'lsapi'
|
|
2
|
+
require 'rack/content_length'
|
|
3
|
+
|
|
4
|
+
module Rack
|
|
5
|
+
module Handler
|
|
6
|
+
class LSWS
|
|
7
|
+
def self.run(app, options=nil)
|
|
8
|
+
while LSAPI.accept != nil
|
|
9
|
+
serve app
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
def self.serve(app)
|
|
13
|
+
app = Rack::ContentLength.new(app)
|
|
14
|
+
|
|
15
|
+
env = ENV.to_hash
|
|
16
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
|
17
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
|
18
|
+
|
|
19
|
+
rack_input = RewindableInput.new($stdin.read.to_s)
|
|
20
|
+
|
|
21
|
+
env.update(
|
|
22
|
+
"rack.version" => [1,0],
|
|
23
|
+
"rack.input" => rack_input,
|
|
24
|
+
"rack.errors" => $stderr,
|
|
25
|
+
"rack.multithread" => false,
|
|
26
|
+
"rack.multiprocess" => true,
|
|
27
|
+
"rack.run_once" => false,
|
|
28
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
env["QUERY_STRING"] ||= ""
|
|
32
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
|
33
|
+
env["REQUEST_PATH"] ||= "/"
|
|
34
|
+
status, headers, body = app.call(env)
|
|
35
|
+
begin
|
|
36
|
+
send_headers status, headers
|
|
37
|
+
send_body body
|
|
38
|
+
ensure
|
|
39
|
+
body.close if body.respond_to? :close
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
def self.send_headers(status, headers)
|
|
43
|
+
print "Status: #{status}\r\n"
|
|
44
|
+
headers.each { |k, vs|
|
|
45
|
+
vs.split("\n").each { |v|
|
|
46
|
+
print "#{k}: #{v}\r\n"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
print "\r\n"
|
|
50
|
+
STDOUT.flush
|
|
51
|
+
end
|
|
52
|
+
def self.send_body(body)
|
|
53
|
+
body.each { |part|
|
|
54
|
+
print part
|
|
55
|
+
STDOUT.flush
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|