nitro 0.23.0 → 0.24.0
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/CHANGELOG +350 -0
- data/INSTALL +2 -2
- data/ProjectInfo +61 -0
- data/README +5 -4
- data/Rakefile +5 -4
- data/bin/nitrogen +3 -1
- data/doc/AUTHORS +27 -3
- data/doc/RELEASES +193 -0
- data/doc/lhttpd.txt +4 -0
- data/lib/nitro.rb +1 -1
- data/lib/nitro/adapter/cgi.rb +6 -321
- data/lib/nitro/adapter/fastcgi.rb +2 -14
- data/lib/nitro/adapter/scgi.rb +237 -71
- data/lib/nitro/adapter/webrick.rb +25 -7
- data/lib/nitro/caching.rb +1 -0
- data/lib/nitro/cgi.rb +296 -0
- data/lib/nitro/{cookie.rb → cgi/cookie.rb} +0 -0
- data/lib/nitro/cgi/http.rb +62 -0
- data/lib/nitro/{request.rb → cgi/request.rb} +4 -1
- data/lib/nitro/{response.rb → cgi/response.rb} +0 -0
- data/lib/nitro/cgi/stream.rb +43 -0
- data/lib/nitro/cgi/utils.rb +38 -0
- data/lib/nitro/compiler.rb +23 -11
- data/lib/nitro/compiler/css.rb +8 -0
- data/lib/nitro/compiler/morphing.rb +66 -0
- data/lib/nitro/context.rb +21 -30
- data/lib/nitro/controller.rb +23 -100
- data/lib/nitro/dispatcher.rb +18 -8
- data/lib/nitro/element.rb +6 -2
- data/lib/nitro/flash.rb +2 -2
- data/lib/nitro/mixin/buffer.rb +2 -2
- data/lib/nitro/mixin/form.rb +204 -93
- data/lib/nitro/mixin/javascript.rb +170 -11
- data/lib/nitro/mixin/markup.rb +1 -0
- data/lib/nitro/mixin/pager.rb +7 -4
- data/lib/nitro/mixin/rss.rb +2 -0
- data/lib/nitro/mixin/table.rb +23 -6
- data/lib/nitro/mixin/xhtml.rb +2 -2
- data/lib/nitro/render.rb +19 -5
- data/lib/nitro/scaffold.rb +12 -6
- data/lib/nitro/server.rb +4 -6
- data/lib/nitro/server/runner.rb +2 -2
- data/lib/nitro/session.rb +8 -1
- data/lib/nitro/session/file.rb +40 -0
- data/lib/part/admin.rb +2 -0
- data/lib/part/admin/controller.rb +7 -3
- data/lib/part/admin/skin.rb +8 -1
- data/lib/part/admin/template/index.xhtml +39 -1
- data/proto/public/error.xhtml +5 -3
- data/proto/public/js/behaviour.js +254 -254
- data/proto/public/js/controls.js +427 -165
- data/proto/public/js/dragdrop.js +255 -276
- data/proto/public/js/effects.js +476 -277
- data/proto/public/js/prototype.js +561 -127
- data/proto/public/js/scaffold.js +74 -0
- data/proto/public/js/scriptaculous.js +44 -0
- data/proto/public/js/util.js +548 -0
- data/proto/public/scaffold/list.xhtml +4 -1
- data/proto/scgi.rb +333 -0
- data/script/scgi_ctl +221 -0
- data/script/scgi_service +120 -0
- data/test/nitro/adapter/raw_post1.bin +0 -0
- data/test/nitro/{tc_cookie.rb → cgi/tc_cookie.rb} +1 -1
- data/test/nitro/{tc_request.rb → cgi/tc_request.rb} +1 -1
- data/test/nitro/mixin/tc_xhtml.rb +1 -1
- data/test/nitro/{adapter/tc_cgi.rb → tc_cgi.rb} +12 -12
- data/test/nitro/tc_controller.rb +9 -5
- metadata +159 -169
- data/benchmark/bench.rb +0 -5
- data/benchmark/simple-webrick-n-200.txt +0 -44
- data/benchmark/static-webrick-n-200.txt +0 -43
- data/benchmark/tiny-lhttpd-n-200-c-5.txt +0 -43
- data/benchmark/tiny-webrick-n-200-c-5.txt +0 -44
- data/benchmark/tiny-webrick-n-200.txt +0 -44
- data/benchmark/tiny2-webrick-n-200.txt +0 -44
- data/examples/README +0 -7
@@ -3,7 +3,7 @@ require 'fcgi'
|
|
3
3
|
|
4
4
|
require 'nitro/context'
|
5
5
|
require 'nitro/dispatcher'
|
6
|
-
require 'nitro/
|
6
|
+
require 'nitro/cgi'
|
7
7
|
|
8
8
|
require 'glue/flexob'
|
9
9
|
|
@@ -25,19 +25,7 @@ class FastCGI
|
|
25
25
|
def self.start(server)
|
26
26
|
FCGI.each do |cgi|
|
27
27
|
begin
|
28
|
-
|
29
|
-
|
30
|
-
context.in = cgi.in
|
31
|
-
context.headers = cgi.env
|
32
|
-
|
33
|
-
CgiUtils.parse_params(context)
|
34
|
-
CgiUtils.parse_cookies(context)
|
35
|
-
|
36
|
-
context.render(context.path)
|
37
|
-
|
38
|
-
cgi.out.print(CgiUtils.response_headers(context))
|
39
|
-
cgi.out.print(context.out)
|
40
|
-
|
28
|
+
Cgi.process(server, cgi, cgi.in, cgi.out)
|
41
29
|
cgi.finish
|
42
30
|
end
|
43
31
|
end
|
data/lib/nitro/adapter/scgi.rb
CHANGED
@@ -1,80 +1,246 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'cgi'
|
4
|
-
require 'scgi'
|
5
3
|
require 'stringio'
|
6
|
-
|
7
|
-
require '
|
8
|
-
require '
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'socket'
|
7
|
+
require 'cgi'
|
8
|
+
require 'monitor'
|
9
|
+
require 'singleton'
|
10
|
+
|
11
|
+
module SCGI
|
12
|
+
|
13
|
+
class LogFactory < Monitor
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super()
|
18
|
+
@@logs = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(file)
|
22
|
+
result = nil
|
23
|
+
synchronize do
|
24
|
+
result = @@logs[file]
|
25
|
+
if not result
|
26
|
+
result = Log.new(file)
|
27
|
+
@@logs[file] = result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
return result
|
31
|
+
end
|
33
32
|
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
33
|
+
|
34
|
+
class Log < Monitor
|
35
|
+
def initialize(file)
|
36
|
+
super()
|
37
|
+
@out = open(file, "a+")
|
38
|
+
@out.sync = true
|
39
|
+
@pid = Process.pid
|
40
|
+
@info = "[INF][#{Process.pid}] "
|
41
|
+
@error = "[ERR][#{Process.pid}] "
|
42
|
+
end
|
43
|
+
|
44
|
+
def info(msg)
|
45
|
+
synchronize do
|
46
|
+
@out.print @info, msg,"\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def error(msg, exc=nil)
|
51
|
+
if exc
|
52
|
+
synchronize do
|
53
|
+
@out.print @error, "#{msg}: #{exc}\n"
|
54
|
+
@out.print @error, exc.backtrace.join("\n"), "\n"
|
55
|
+
end
|
56
|
+
else
|
57
|
+
synchronize do
|
58
|
+
@out.print @error, msg,"\n"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Modifies CGI so that we can use it.
|
65
|
+
class SCGIFixed < ::CGI
|
66
|
+
public :env_table
|
67
|
+
|
68
|
+
def initialize(params, data, out, *args)
|
69
|
+
@env_table = params
|
70
|
+
@args = *args
|
71
|
+
@input = StringIO.new(data)
|
72
|
+
@out = out
|
73
|
+
super(*args)
|
74
|
+
end
|
75
|
+
def args
|
76
|
+
@args
|
77
|
+
end
|
78
|
+
def env_table
|
79
|
+
@env_table
|
80
|
+
end
|
81
|
+
def stdinput
|
82
|
+
@input
|
83
|
+
end
|
84
|
+
def stdoutput
|
85
|
+
@out
|
86
|
+
end
|
55
87
|
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.start(server)
|
59
|
-
|
60
|
-
puts '-------------1'
|
61
|
-
children = "5"
|
62
|
-
host = "127.0.0.0"
|
63
|
-
port = "8888"
|
64
88
|
|
65
|
-
socket = TCPServer.new(host, port)
|
66
|
-
socket.fcntl(Fcntl::F_SETFL, socket.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
|
67
89
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
90
|
+
class SCGIProcessor < Monitor
|
91
|
+
attr_reader :settings
|
92
|
+
|
93
|
+
def initialize(settings = {})
|
94
|
+
@conns = 0
|
95
|
+
@shutdown = false
|
96
|
+
@dead = false
|
97
|
+
super()
|
98
|
+
|
99
|
+
configure(settings)
|
100
|
+
end
|
101
|
+
|
102
|
+
def configure(settings)
|
103
|
+
@settings = settings
|
104
|
+
@log = LogFactory.instance.create(settings[:logfile] || "log/scgi.log")
|
105
|
+
@maxconns = settings[:maxconns] || 2**30-1
|
106
|
+
@started = Time.now
|
107
|
+
|
108
|
+
if settings[:socket]
|
109
|
+
@socket = settings[:socket]
|
110
|
+
else
|
111
|
+
@host = settings[:host] || "127.0.0.1"
|
112
|
+
@port = settings[:port] || "9999"
|
113
|
+
end
|
114
|
+
|
115
|
+
@throttle_sleep = 1.0/settings[:conns_second] if settings[:conns_second]
|
116
|
+
end
|
117
|
+
|
118
|
+
def listen
|
119
|
+
@socket = TCPServer.new(@host, @port)
|
120
|
+
|
121
|
+
begin
|
122
|
+
while true
|
123
|
+
handle_client(@socket.accept)
|
124
|
+
sleep @throttle_sleep if @throttle_sleep
|
125
|
+
break if @shutdown and @conns <= 0
|
126
|
+
end
|
127
|
+
rescue Interrupt
|
128
|
+
@log.info("Shutting down from SIGINT.")
|
129
|
+
rescue Object
|
130
|
+
@log.error("while listening for connections on #@host:#@port -- #{$!.class}", $!)
|
131
|
+
end
|
132
|
+
|
133
|
+
@socket.close if not @socket.closed?
|
134
|
+
@dead = true
|
135
|
+
@log.info("Exited accept loop. Shutdown complete.")
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def handle_client(socket)
|
140
|
+
Thread.new do
|
141
|
+
begin
|
142
|
+
# remember if we were doing a shutdown so we can avoid increment later
|
143
|
+
in_shutdown = @shutdown
|
144
|
+
synchronize { @conns += 1 if not in_shutdown }
|
145
|
+
|
146
|
+
len = ""
|
147
|
+
# we only read 10 bytes of the length. any request longer than this is invalid
|
148
|
+
while len.length <= 10
|
149
|
+
c = socket.read(1)
|
150
|
+
if c == ':'
|
151
|
+
# found the terminal, len now has a length in it so read the payload
|
152
|
+
break
|
153
|
+
else
|
154
|
+
len << c
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# we should now either have a payload length to get
|
159
|
+
payload = socket.read(len.to_i)
|
160
|
+
if (c = socket.read(1)) != ','
|
161
|
+
@log.error("Malformed request, does not end with ','")
|
162
|
+
else
|
163
|
+
read_header(socket, payload, @conns)
|
164
|
+
end
|
165
|
+
rescue IOError
|
166
|
+
@log.error("received IOError #$! when handling client. Your web server doesn't like me.")
|
167
|
+
rescue Object
|
168
|
+
@log.error("after accepting client #@host:#@port -- #{$!.class}", $!)
|
169
|
+
ensure
|
170
|
+
synchronize { @conns -= 1 if not in_shutdown}
|
171
|
+
socket.close if not socket.closed?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def read_header(socket, payload, conns)
|
178
|
+
return if socket.closed?
|
179
|
+
request = split_body(payload)
|
180
|
+
if request["CONTENT_LENGTH"]
|
181
|
+
length = request["CONTENT_LENGTH"].to_i
|
182
|
+
if length > 0
|
183
|
+
body = socket.read(length)
|
184
|
+
else
|
185
|
+
body = ""
|
186
|
+
end
|
187
|
+
|
188
|
+
if @shutdown or @conns > @maxconns
|
189
|
+
socket.write("Location: /busy.html\r\n")
|
190
|
+
socket.write("Cache-control: no-cache, must-revalidate\r\n")
|
191
|
+
socket.write("Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n")
|
192
|
+
socket.write("Status: 307 Temporary Redirect\r\n\r\n")
|
193
|
+
else
|
194
|
+
process_request(request, body, socket)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def process_request(request, body, socket)
|
201
|
+
raise "You must implement process_request"
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def split_body(data)
|
206
|
+
result = {}
|
207
|
+
el = data.split("\0")
|
208
|
+
i = 0
|
209
|
+
len = el.length
|
210
|
+
while i < len
|
211
|
+
result[el[i]] = el[i+1]
|
212
|
+
i += 2
|
213
|
+
end
|
214
|
+
|
215
|
+
return result
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
def status
|
220
|
+
{
|
221
|
+
:time => Time.now, :pid => Process.pid, :settings => @settings,
|
222
|
+
:env => @settings[:env], :started => @started,
|
223
|
+
:max_conns => @maxconns, :conns => @conns, :systimes => Process.times,
|
224
|
+
:conns_second => @throttle, :shutdown => @shutdown, :dead => @dead
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
# Graceful shutdown is done by setting the maxconns to 0 so that all new requests
|
229
|
+
# get the 503 Service Unavailable status. The handler code checks if @shutdown is
|
230
|
+
# set and if so it will not increase the @conns count, but it will decrease.
|
231
|
+
# Once the @conns count is down to 0 it will exit the loop.
|
232
|
+
def shutdown(force = false)
|
233
|
+
@shutdown = true;
|
234
|
+
|
235
|
+
if force
|
236
|
+
@socket.close
|
237
|
+
@log.info("FORCED shutdown requested. Oh well.")
|
238
|
+
else
|
239
|
+
@log.info("Shutdown requested. Beginning graceful shutdown with #@conns connected.")
|
240
|
+
end
|
241
|
+
end
|
75
242
|
end
|
76
|
-
end
|
77
|
-
|
78
243
|
end
|
79
244
|
|
80
|
-
|
245
|
+
# * Zed A Shaw <zedshaw@zedshaw.com>
|
246
|
+
# * George Moschovitis <gm@navel.gr>
|
@@ -2,6 +2,7 @@ require 'webrick'
|
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
require 'glue/flexob'
|
5
|
+
require 'nitro/cgi'
|
5
6
|
require 'nitro/context'
|
6
7
|
require 'nitro/dispatcher'
|
7
8
|
|
@@ -18,6 +19,8 @@ class Webrick
|
|
18
19
|
class << self
|
19
20
|
attr_accessor :webrick
|
20
21
|
|
22
|
+
# Start the Webrick adapter.
|
23
|
+
|
21
24
|
def start(server)
|
22
25
|
if RUBY_PLATFORM !~ /mswin32/
|
23
26
|
wblog = WEBrick::BasicLog::new('/dev/null')
|
@@ -41,11 +44,27 @@ class Webrick
|
|
41
44
|
)
|
42
45
|
@webrick = WEBrick::HTTPServer.new(webrick_options)
|
43
46
|
|
44
|
-
trap('INT') {
|
47
|
+
trap('INT') { stop }
|
45
48
|
|
46
49
|
@webrick.mount('/', WebrickAdapter, server)
|
50
|
+
|
51
|
+
initialize_webrick(server)
|
52
|
+
|
47
53
|
@webrick.start
|
48
54
|
end
|
55
|
+
|
56
|
+
# Stop the Webrick adapter.
|
57
|
+
|
58
|
+
def stop
|
59
|
+
@webrick.shutdown
|
60
|
+
end
|
61
|
+
|
62
|
+
# Override this method to perform customized webrick
|
63
|
+
# initialization.
|
64
|
+
|
65
|
+
def initialize_webrick(server)
|
66
|
+
end
|
67
|
+
|
49
68
|
end
|
50
69
|
|
51
70
|
end
|
@@ -105,8 +124,6 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
105
124
|
begin
|
106
125
|
path = req.request_uri.path
|
107
126
|
|
108
|
-
# REQUEST_MUTEX.lock
|
109
|
-
|
110
127
|
context = Context.new(@server)
|
111
128
|
|
112
129
|
context.in = StringIO.new(req.body || "")
|
@@ -119,8 +136,8 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
119
136
|
context.headers['REQUEST_URI'].slice!(/http:\/\/(.*?)\//)
|
120
137
|
context.headers['REQUEST_URI'] << '/'
|
121
138
|
|
122
|
-
|
123
|
-
|
139
|
+
Cgi.parse_params(context)
|
140
|
+
Cgi.parse_cookies(context)
|
124
141
|
|
125
142
|
context.render(path)
|
126
143
|
|
@@ -128,10 +145,11 @@ class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet
|
|
128
145
|
res.instance_variable_set(:@header, context.response_headers || {})
|
129
146
|
res.instance_variable_set(:@cookies, context.response_cookies || {})
|
130
147
|
res.body = context.out
|
131
|
-
|
148
|
+
res.chunked = true if context.out.is_a?(IO)
|
149
|
+
|
132
150
|
context.close
|
133
151
|
ensure
|
134
|
-
|
152
|
+
Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager)
|
135
153
|
end
|
136
154
|
end
|
137
155
|
end
|
data/lib/nitro/caching.rb
CHANGED
data/lib/nitro/cgi.rb
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'glue/configuration'
|
2
|
+
require 'nitro/cgi/http'
|
3
|
+
|
4
|
+
module Nitro
|
5
|
+
|
6
|
+
class Cgi
|
7
|
+
include Http
|
8
|
+
|
9
|
+
# Maximum content length allowed in requests.
|
10
|
+
|
11
|
+
setting :max_content_length, :default => (2 * 1024 * 1024), :doc => 'Maximum content length allowed in requests'
|
12
|
+
|
13
|
+
# Multipart parsing buffer size.
|
14
|
+
|
15
|
+
setting :buffer_size, :default => (10 * 1024), :doc => 'Multipart parsing buffer size'
|
16
|
+
|
17
|
+
# Process a CGI request. This is a general method reused by
|
18
|
+
# many adapters.
|
19
|
+
|
20
|
+
def self.process(server, cgi, inp, out)
|
21
|
+
context = Context.new(server)
|
22
|
+
|
23
|
+
context.in = inp
|
24
|
+
context.headers = cgi.env
|
25
|
+
|
26
|
+
Cgi.parse_params(context)
|
27
|
+
Cgi.parse_cookies(context)
|
28
|
+
context.render(context.path)
|
29
|
+
|
30
|
+
out.print(Cgi.response_headers(context))
|
31
|
+
|
32
|
+
if context.out.is_a?(IO)
|
33
|
+
while buf = context.out.read(4096)
|
34
|
+
out.write(buf)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
out.print(context.out)
|
38
|
+
end
|
39
|
+
|
40
|
+
context.close
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a hash with the pairs from the query
|
44
|
+
# string. The implicit hash construction that is done
|
45
|
+
# in parse_request_params is not done here.
|
46
|
+
#
|
47
|
+
# Parameters in the form xxx[] are converted
|
48
|
+
# to arrays.
|
49
|
+
#
|
50
|
+
# Use the field.attr or field[attr] notation to pass
|
51
|
+
# compound objects.
|
52
|
+
|
53
|
+
def self.parse_query_string(query_string)
|
54
|
+
params = {}
|
55
|
+
|
56
|
+
# gmosx, THINK: better return nil here?
|
57
|
+
return params if (query_string.nil? or query_string.empty?)
|
58
|
+
|
59
|
+
query_string.split(/[&;]/).each do |p|
|
60
|
+
key, val = p.split('=')
|
61
|
+
|
62
|
+
key = CGI.unescape(key) unless key.nil?
|
63
|
+
val = CGI.unescape(val) unless val.nil?
|
64
|
+
|
65
|
+
if key =~ /(.*)\[\]$/
|
66
|
+
# Multiple values, for example a checkbox collection.
|
67
|
+
# Stored as an array.
|
68
|
+
if params.has_key?($1)
|
69
|
+
params[$1] << val
|
70
|
+
else
|
71
|
+
params[$1] = [val]
|
72
|
+
end
|
73
|
+
elsif key =~ /(.*)\[(.*)\]$/ or key =~ /(.*)\.(.*)$/
|
74
|
+
# A compound object with attributes.
|
75
|
+
# Stored as a Hash.
|
76
|
+
params[$1] ||= {}
|
77
|
+
params[$1][$2] = val
|
78
|
+
else
|
79
|
+
# Standard single valued parameter.
|
80
|
+
params[key] = val.nil? ? nil : val
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return params
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parse the HTTP_COOKIE header and returns the
|
88
|
+
# cookies as a key->value hash. For efficiency no
|
89
|
+
# cookie objects are created.
|
90
|
+
#
|
91
|
+
# [+context+]
|
92
|
+
# The context
|
93
|
+
|
94
|
+
def self.parse_cookies(context)
|
95
|
+
env = context.env
|
96
|
+
|
97
|
+
# FIXME: dont precreate?
|
98
|
+
context.cookies = {}
|
99
|
+
|
100
|
+
if env.has_key?('HTTP_COOKIE') or env.has_key?('COOKIE')
|
101
|
+
(env['HTTP_COOKIE'] or env['COOKIE']).split(/; /).each do |c|
|
102
|
+
key, val = c.split(/=/, 2)
|
103
|
+
|
104
|
+
key = CGI.unescape(key)
|
105
|
+
val = val.split(/&/).collect{|v| CGI::unescape(v)}.join("\0")
|
106
|
+
|
107
|
+
if context.cookies.include?(key)
|
108
|
+
context.cookies[key] += "\0" + val
|
109
|
+
else
|
110
|
+
context.cookies[key] = val
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Build the response headers for the context.
|
117
|
+
#
|
118
|
+
# [+context+]
|
119
|
+
# The context of the response.
|
120
|
+
#
|
121
|
+
# [+proto+]
|
122
|
+
# If true emmit the protocol line. Useful for MOD_RUBY.
|
123
|
+
#--
|
124
|
+
# FIXME: return the correct protocol from env.
|
125
|
+
# TODO: Perhaps I can optimize status calc.
|
126
|
+
#++
|
127
|
+
|
128
|
+
def self.response_headers(context, proto = false)
|
129
|
+
reason = STATUS_STRINGS[context.status]
|
130
|
+
|
131
|
+
if proto
|
132
|
+
buf = "HTTP/1.1 #{context.status} #{reason}#{EOL}Date: #{CGI::rfc1123_date(Time.now)}#{EOL}"
|
133
|
+
else
|
134
|
+
buf = "Status: #{context.status} #{reason}#{EOL}"
|
135
|
+
end
|
136
|
+
|
137
|
+
context.response_headers.each do |key, value|
|
138
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/) { |s| s.upcase }
|
139
|
+
buf << "#{tmp}: #{value}" << EOL
|
140
|
+
end
|
141
|
+
|
142
|
+
context.response_cookies.each do |cookie|
|
143
|
+
buf << "Set-Cookie: " << cookie.to_s << EOL
|
144
|
+
end if context.response_cookies
|
145
|
+
|
146
|
+
buf << EOL
|
147
|
+
|
148
|
+
return buf
|
149
|
+
end
|
150
|
+
|
151
|
+
# Initialize the request params.
|
152
|
+
# Handles multipart forms (in particular, forms that involve
|
153
|
+
# file uploads). Reads query parameters in the @params field,
|
154
|
+
# and cookies into @cookies.
|
155
|
+
|
156
|
+
def self.parse_params(context)
|
157
|
+
method = context.method
|
158
|
+
if (:post == method) and
|
159
|
+
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(context.headers['CONTENT_TYPE'])
|
160
|
+
boundary = $1.dup
|
161
|
+
context.params = parse_multipart(context, boundary)
|
162
|
+
|
163
|
+
# Also include the URL parameters.
|
164
|
+
context.params.update(Cgi.parse_query_string(context.query_string))
|
165
|
+
else
|
166
|
+
case method
|
167
|
+
when :get, :head
|
168
|
+
context.params = Cgi.parse_query_string(context.query_string)
|
169
|
+
when :post
|
170
|
+
context.in.binmode # if defined?(context.in.binmode)
|
171
|
+
context.params = Cgi.parse_query_string(context.in.read(context.content_length) || '')
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Parse a multipart request.
|
177
|
+
# Adapted from Ruby's cgi.rb
|
178
|
+
#--
|
179
|
+
# TODO: optimize and rationalize this.
|
180
|
+
#++
|
181
|
+
|
182
|
+
def self.parse_multipart(context, boundary)
|
183
|
+
input = context.in
|
184
|
+
content_length = context.content_length
|
185
|
+
env_table = context.env
|
186
|
+
|
187
|
+
params = Hash.new([])
|
188
|
+
boundary = "--" + boundary
|
189
|
+
buf = ""
|
190
|
+
|
191
|
+
input.binmode if defined? input.binmode
|
192
|
+
boundary_size = boundary.size + EOL.size
|
193
|
+
content_length -= boundary_size
|
194
|
+
status = input.read(boundary_size)
|
195
|
+
|
196
|
+
if nil == status
|
197
|
+
raise EOFError, "no content body"
|
198
|
+
elsif boundary + EOL != status
|
199
|
+
raise EOFError, "bad content body"
|
200
|
+
end
|
201
|
+
|
202
|
+
loop do
|
203
|
+
head = nil
|
204
|
+
|
205
|
+
if 10240 < content_length
|
206
|
+
body = Tempfile.new("CGI")
|
207
|
+
else
|
208
|
+
begin
|
209
|
+
body = StringIO.new
|
210
|
+
rescue LoadError
|
211
|
+
body = Tempfile.new("CGI")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
body.binmode if defined? body.binmode
|
215
|
+
|
216
|
+
until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
|
217
|
+
|
218
|
+
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
219
|
+
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
220
|
+
head = $1.dup
|
221
|
+
""
|
222
|
+
end
|
223
|
+
next
|
224
|
+
end
|
225
|
+
|
226
|
+
if head and ( (EOL + boundary + EOL).size < buf.size )
|
227
|
+
body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
228
|
+
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
229
|
+
end
|
230
|
+
|
231
|
+
c = if Cgi.buffer_size < content_length
|
232
|
+
input.read(Cgi.buffer_size)
|
233
|
+
else
|
234
|
+
input.read(content_length)
|
235
|
+
end
|
236
|
+
if c.nil?
|
237
|
+
raise EOFError, "bad content body"
|
238
|
+
end
|
239
|
+
buf.concat(c)
|
240
|
+
content_length -= c.size
|
241
|
+
end
|
242
|
+
|
243
|
+
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do
|
244
|
+
body.print $1
|
245
|
+
if "--" == $2
|
246
|
+
content_length = -1
|
247
|
+
end
|
248
|
+
""
|
249
|
+
end
|
250
|
+
|
251
|
+
body.rewind
|
252
|
+
|
253
|
+
/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
|
254
|
+
|
255
|
+
filename = ($1 or "")
|
256
|
+
|
257
|
+
if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
|
258
|
+
/Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
|
259
|
+
(not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
|
260
|
+
filename = CGI::unescape(filename)
|
261
|
+
end
|
262
|
+
|
263
|
+
/Content-Type: (.*)/ni.match(head)
|
264
|
+
content_type = ($1 or "")
|
265
|
+
|
266
|
+
(class << body; self; end).class_eval do
|
267
|
+
alias local_path path
|
268
|
+
define_method(:original_filename) { filename.dup.taint }
|
269
|
+
define_method(:content_type) { content_type.dup.taint }
|
270
|
+
|
271
|
+
# gmosx: this hides the performance hit!!
|
272
|
+
define_method(:to_s) { read }
|
273
|
+
end
|
274
|
+
|
275
|
+
/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
|
276
|
+
name = $1.dup
|
277
|
+
|
278
|
+
if params.has_key?(name)
|
279
|
+
params[name] = [params[name]] << body
|
280
|
+
else
|
281
|
+
params[name] = body
|
282
|
+
end
|
283
|
+
|
284
|
+
break if buf.size == 0
|
285
|
+
break if content_length === -1
|
286
|
+
end
|
287
|
+
|
288
|
+
return params
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
# * George Moschovitis <gm@navel.gr>
|
296
|
+
# * Guillaume Pierronnet <guillaume.pierronnet@gmail.com>
|