nitro 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/CHANGELOG +752 -543
  2. data/INSTALL +38 -38
  3. data/README +264 -225
  4. data/Rakefile +48 -49
  5. data/bin/nitro +3 -3
  6. data/bin/nitrogen +6 -6
  7. data/doc/AUTHORS +10 -10
  8. data/doc/CHANGELOG.1 +1939 -1939
  9. data/doc/CHANGELOG.2 +954 -954
  10. data/doc/LICENSE +3 -3
  11. data/doc/MIGRATION +28 -0
  12. data/doc/RELEASES +814 -643
  13. data/doc/config.txt +5 -5
  14. data/install.rb +7 -17
  15. data/lib/nitro.rb +38 -9
  16. data/lib/nitro/adapter/cgi.rb +311 -312
  17. data/lib/nitro/adapter/fastcgi.rb +18 -25
  18. data/lib/nitro/adapter/webrick.rb +128 -137
  19. data/lib/nitro/adapter/wee.rb +51 -0
  20. data/lib/nitro/caching.rb +20 -20
  21. data/lib/nitro/caching/actions.rb +43 -43
  22. data/lib/nitro/caching/fragments.rb +46 -46
  23. data/lib/nitro/caching/invalidation.rb +11 -11
  24. data/lib/nitro/caching/output.rb +65 -65
  25. data/lib/nitro/caching/stores.rb +67 -67
  26. data/lib/nitro/compiler.rb +262 -0
  27. data/lib/nitro/compiler/elements.rb +0 -0
  28. data/lib/nitro/compiler/errors.rb +65 -0
  29. data/lib/nitro/compiler/localization.rb +25 -0
  30. data/lib/nitro/compiler/markup.rb +19 -0
  31. data/lib/nitro/compiler/shaders.rb +206 -0
  32. data/lib/nitro/compiler/squeeze.rb +20 -0
  33. data/lib/nitro/compiler/xslt.rb +61 -0
  34. data/lib/nitro/context.rb +87 -88
  35. data/lib/nitro/controller.rb +151 -158
  36. data/lib/nitro/cookie.rb +34 -34
  37. data/lib/nitro/dispatcher.rb +195 -186
  38. data/lib/nitro/element.rb +132 -126
  39. data/lib/nitro/element/java_script.rb +6 -6
  40. data/lib/nitro/flash.rb +66 -66
  41. data/lib/nitro/mail.rb +192 -192
  42. data/lib/nitro/mixin/buffer.rb +66 -0
  43. data/lib/nitro/mixin/debug.rb +16 -16
  44. data/lib/nitro/mixin/form.rb +88 -0
  45. data/lib/nitro/mixin/helper.rb +2 -2
  46. data/lib/nitro/mixin/javascript.rb +108 -108
  47. data/lib/nitro/mixin/markup.rb +144 -0
  48. data/lib/nitro/mixin/pager.rb +202 -202
  49. data/lib/nitro/mixin/rss.rb +67 -0
  50. data/lib/nitro/mixin/table.rb +63 -0
  51. data/lib/nitro/mixin/xhtml.rb +75 -0
  52. data/lib/nitro/mixin/xml.rb +124 -0
  53. data/lib/nitro/render.rb +183 -359
  54. data/lib/nitro/request.rb +140 -140
  55. data/lib/nitro/response.rb +27 -27
  56. data/lib/nitro/routing.rb +21 -21
  57. data/lib/nitro/scaffold.rb +124 -118
  58. data/lib/nitro/server.rb +117 -80
  59. data/lib/nitro/server/runner.rb +341 -0
  60. data/lib/nitro/service.rb +12 -12
  61. data/lib/nitro/service/xmlrpc.rb +22 -22
  62. data/lib/nitro/session.rb +122 -120
  63. data/lib/nitro/session/drb.rb +9 -9
  64. data/lib/nitro/session/drbserver.rb +34 -34
  65. data/lib/nitro/template.rb +171 -155
  66. data/lib/nitro/testing/assertions.rb +90 -90
  67. data/lib/nitro/testing/context.rb +16 -16
  68. data/lib/nitro/testing/testcase.rb +34 -34
  69. data/proto/conf/lhttpd.conf +9 -9
  70. data/proto/public/error.xhtml +75 -75
  71. data/proto/public/index.xhtml +18 -18
  72. data/proto/public/js/behaviour.js +65 -65
  73. data/proto/public/js/controls.js +1 -1
  74. data/proto/public/js/prototype.js +3 -3
  75. data/proto/public/settings.xhtml +61 -61
  76. data/proto/run.rb +1 -5
  77. data/test/nitro/adapter/raw_post1.bin +0 -0
  78. data/test/nitro/adapter/tc_cgi.rb +57 -57
  79. data/test/nitro/adapter/tc_webrick.rb +4 -4
  80. data/test/nitro/mixin/tc_pager.rb +25 -25
  81. data/test/nitro/mixin/tc_rss.rb +24 -0
  82. data/test/nitro/mixin/tc_table.rb +31 -0
  83. data/test/nitro/mixin/tc_xhtml.rb +13 -0
  84. data/test/nitro/tc_caching.rb +10 -10
  85. data/test/nitro/tc_context.rb +8 -8
  86. data/test/nitro/tc_controller.rb +48 -48
  87. data/test/nitro/tc_cookie.rb +6 -6
  88. data/test/nitro/tc_dispatcher.rb +64 -64
  89. data/test/nitro/tc_element.rb +27 -27
  90. data/test/nitro/tc_flash.rb +31 -31
  91. data/test/nitro/tc_mail.rb +63 -63
  92. data/test/nitro/tc_server.rb +26 -26
  93. data/test/nitro/tc_session.rb +9 -9
  94. data/test/nitro/tc_template.rb +19 -19
  95. data/test/public/blog/list.xhtml +1 -1
  96. metadata +31 -37
  97. data/lib/nitro/buffering.rb +0 -45
  98. data/lib/nitro/builder/form.rb +0 -104
  99. data/lib/nitro/builder/rss.rb +0 -104
  100. data/lib/nitro/builder/table.rb +0 -80
  101. data/lib/nitro/builder/xhtml.rb +0 -132
  102. data/lib/nitro/builder/xml.rb +0 -131
  103. data/lib/nitro/conf.rb +0 -36
  104. data/lib/nitro/environment.rb +0 -21
  105. data/lib/nitro/errors.rb +0 -69
  106. data/lib/nitro/localization.rb +0 -153
  107. data/lib/nitro/markup.rb +0 -147
  108. data/lib/nitro/output.rb +0 -24
  109. data/lib/nitro/runner.rb +0 -348
  110. data/lib/nitro/shaders.rb +0 -206
  111. data/test/nitro/builder/tc_rss.rb +0 -23
  112. data/test/nitro/builder/tc_table.rb +0 -30
  113. data/test/nitro/builder/tc_xhtml.rb +0 -39
  114. data/test/nitro/builder/tc_xml.rb +0 -56
  115. data/test/nitro/tc_localization.rb +0 -49
data/doc/config.txt CHANGED
@@ -9,13 +9,13 @@ Selects the mechanism employed for storing the sessions.
9
9
  Available values are:
10
10
 
11
11
  [+:memory+]
12
- This is the default mechanism, sessions are stored
13
- in memory. Only useful in multithreaded environments
14
- like WEBrick.
12
+ This is the default mechanism, sessions are stored
13
+ in memory. Only useful in multithreaded environments
14
+ like WEBrick.
15
15
 
16
16
  [+:drb+]
17
- Distributed Sessions using DRb. An independed DRb
18
- server stores the sessions.
17
+ Distributed Sessions using DRb. An independed DRb
18
+ server stores the sessions.
19
19
 
20
20
  In the future there will be more options available (:memcache,
21
21
  :filesys, :db)
data/install.rb CHANGED
@@ -1,28 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # * George Moschovitis <gm@navel.gr>
4
- # (c) 2004-2005 Navel, all rights reserved.
5
- # $Id: install.rb 1 2005-04-11 11:04:30Z gmosx $
6
-
7
3
  require 'rbconfig'
8
4
  require 'ftools'
9
5
 
10
6
  dst_dir = Config::CONFIG['sitelibdir']
11
7
 
12
8
  Dir.chdir('lib') do
13
- Dir['**/*.rb'].each do |file|
14
- File.mkpath File.join(dst_dir, File.dirname(file)), true
15
- File.install file, File.join(dst_dir, file), 0644, true
16
- end
17
- end
18
-
19
- # gmosx: this is potentially dangerous, rethink.
20
-
21
- Dir.chdir('vendor') do
22
- Dir['**/*.rb'].each do |file|
23
- File.mkpath File.join(dst_dir, File.dirname(file)), true
24
- File.install file, File.join(dst_dir, file), 0644, true
25
- end
9
+ Dir['**/*.rb'].each do |file|
10
+ File.mkpath File.join(dst_dir, File.dirname(file)), true
11
+ File.install file, File.join(dst_dir, file), 0644, true
12
+ end
26
13
  end
27
14
 
28
15
  puts %{
@@ -45,3 +32,6 @@ at the command line.
45
32
 
46
33
  Enjoy the magic of Nitro!
47
34
  }
35
+
36
+ # * George Moschovitis <gm@navel.gr>
37
+
data/lib/nitro.rb CHANGED
@@ -19,16 +19,21 @@
19
19
 
20
20
  require 'glue'
21
21
  require 'glue/logger'
22
+ require 'glue/configuration'
22
23
 
23
24
  module Nitro
24
-
25
- # The version.
25
+
26
+ # The version.
26
27
 
27
- Version = '0.20.0'
28
+ Version = '0.21.0'
28
29
 
29
- # Library path.
30
+ # Library path.
30
31
 
31
- LibPath = File.dirname(__FILE__)
32
+ LibPath = File.dirname(__FILE__)
33
+
34
+ # The path to the prototype application.
35
+
36
+ setting :proto_path, :default => "#{LibPath}/../proto", :doc => 'The path to the prototype application'
32
37
 
33
38
  end
34
39
 
@@ -40,11 +45,35 @@ require 'nitro/context'
40
45
  require 'nitro/controller'
41
46
  require 'nitro/dispatcher'
42
47
  require 'nitro/render'
43
-
44
48
  require 'nitro/server'
45
- require 'nitro/conf'
46
49
 
47
- require 'nitro/runner'
48
- require 'nitro/environment' unless $NITRO_NO_ENVIRONMENT
50
+ unless $NITRO_NO_ENVIRONMENT
51
+ # Setup up the proposed environment. You are free
52
+ # to skip this if you dont like it. Just set
53
+ #
54
+ # $NITRO_NO_ENVIRONMENT = true
55
+ #
56
+ # before requiring nitro.
57
+
58
+ Dir.chdir(File.dirname($0))
59
+
60
+ # Application code come here.
61
+
62
+ $LOAD_PATH.unshift 'src'
63
+
64
+ # Library code come here.
65
+
66
+ $LOAD_PATH.unshift 'lib'
67
+ end
68
+
69
+ module Nitro
70
+
71
+ # A helper method to start a Nitro application.
72
+
73
+ def self.run(controller = nil)
74
+ Server.run(controller)
75
+ end
76
+
77
+ end
49
78
 
50
79
  # * George Moschovitis <gm@navel.gr>
@@ -1,6 +1,3 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # * James Britt <james_b@neurogami.com>
3
-
4
1
  require 'cgi'
5
2
  require 'stringio'
6
3
  require 'tempfile'
@@ -14,328 +11,330 @@ Socket.do_not_reverse_lookup = true
14
11
  module Nitro
15
12
 
16
13
  class Cgi
17
- # Maximum content length allowed in requests.
18
-
19
- cattr_accessor :max_content_length, (2 * 1024 * 1024)
14
+ # Maximum content length allowed in requests.
20
15
 
21
- # Multipart parsing buffer size.
16
+ cattr_accessor :max_content_length, (2 * 1024 * 1024)
22
17
 
23
- cattr_accessor :buffer_size, (10 * 1024)
18
+ # Multipart parsing buffer size.
24
19
 
25
- def self.start( conf )
26
- cgi = CGI.new
27
- conf = Flexob.new(conf) unless conf.is_a?(Flexob)
28
- context = Context.new(conf)
29
- context.headers = ENV
30
-
31
- CgiUtils.parse_params(context)
32
- CgiUtils.parse_cookies(context)
20
+ cattr_accessor :buffer_size, (10 * 1024)
33
21
 
34
- # gmosx, TODO: move this into a filter.
35
- # Og.db.get_connection if defined?(Og) and Og.db
22
+ #--
23
+ # No need for connection pooling, CGI uses process.
24
+ #++
25
+
26
+ def self.start(server)
27
+ cgi = CGI.new
28
+
29
+ context = Context.new(server)
30
+ context.headers = ENV
31
+
32
+ CgiUtils.parse_params(context)
33
+ CgiUtils.parse_cookies(context)
36
34
 
37
- context.render(context.path)
35
+ context.render(context.path)
38
36
 
39
- # Og.db.put_connection if defined?(Og) and Og.db
40
-
41
- cgi.print(CgiUtils.response_headers(context))
42
- cgi.print(context.out)
43
- end
37
+ cgi.print(CgiUtils.response_headers(context))
38
+ cgi.print(context.out)
39
+ end
44
40
  end
45
41
 
46
42
  # CGI utility methods.
47
43
 
48
44
  class CgiUtils
49
45
 
50
- # HTTP protocol EOL constants
51
-
52
- CR = "\x0d"
53
- LF = "\x0a"
54
- CRLF = "\x0d\x0a"
55
- EOL = CRLF
56
-
57
- # Constants for readable code
58
-
59
- STATUS_OK = 200
60
- STATUS_PARTIAL_CONTENT = 206
61
- STATUS_MOVED = 301
62
- STATUS_REDIRECT = 302
63
- STATUS_SEE_OTHER = 303 # gmosx: VERIFY THIS
64
- STATUS_SEE_OTHER_307 = 307 # gmosx: VERIFY THIS
65
- STATUS_NOT_MODIFIED = 304
66
- STATUS_BAD_REQUEST = 400
67
- STATUS_AUTH_REQUIRED = 401
68
- STATUS_FORBIDDEN = 403
69
- STATUS_NOT_FOUND = 404
70
- STATUS_METHOD_NOT_ALLOWED = 405
71
- STATUS_NOT_ACCEPTABLE = 406
72
- STATUS_LENGTH_REQUIRED = 411
73
- STATUS_PRECONDITION_FAILED = 412
74
- STATUS_SERVER_ERROR = 500
75
- STATUS_NOT_IMPLEMENTED = 501
76
- STATUS_BAD_GATEWAY = 502
77
- STATUS_VARIANT_ALSO_VARIES = 506
78
-
79
- # Hash to allow id to description maping.
80
-
81
- STATUS_STRINGS = {
82
- 200 => "OK",
83
- 206 => "Partial Content",
84
- 300 => "Multiple Choices",
85
- 301 => "Moved Permanently",
86
- 302 => "Found",
87
- 303 => "See other", # gmosx: VERIFY THIS
88
- 304 => "Not Modified",
89
- 307 => "See other 07", # gmosx: VERIFY THIS
90
- 400 => "Bad Request",
91
- 401 => "Authorization Required",
92
- 403 => "Forbidden",
93
- 404 => "Not Found",
94
- 405 => "Method Not Allowed",
95
- 406 => "Not Acceptable",
96
- 411 => "Length Required",
97
- 412 => "Precondition Failed",
98
- 500 => "Internal Server Error",
99
- 501 => "Method Not Implemented",
100
- 502 => "Bad Gateway",
101
- 506 => "Variant Also Negotiates"
102
- }
103
-
104
- # Returns a hash with the pairs from the query
105
- # string. The implicit hash construction that is done
106
- # in parse_request_params is not done here.
107
- #
108
- # Parameters in the form xxx[] are converted
109
- # to arrays.
110
-
111
- def self.parse_query_string(query_string)
112
- params = {}
113
-
114
- # gmosx, THINK: better return nil here?
115
- return params if (query_string.nil? or query_string.empty?)
116
-
117
- query_string.split(/[&;]/).each do |p|
118
- key, val = p.split('=')
119
-
120
- key = CGI.unescape(key) unless key.nil?
121
- val = CGI.unescape(val) unless val.nil?
122
-
123
- if key =~ /(.*)\[\]$/
124
- if params.has_key?($1)
125
- params[$1] << val
126
- else
127
- params[$1] = [val]
128
- end
129
- else
130
- params[key] = val.nil? ? nil : val
131
- end
132
- end
133
-
134
- return params
135
- end
136
-
137
- # Parse the HTTP_COOKIE header and returns the
138
- # cookies as a key->value hash. For efficiency no
139
- # cookie objects are created.
140
- #
141
- # [+context+]
142
- # The context
143
-
144
- def self.parse_cookies(context)
145
- env = context.env
146
-
147
- # FIXME: dont precreate?
148
- context.cookies = {}
149
-
150
- if env.has_key?('HTTP_COOKIE') or env.has_key?('COOKIE')
151
- (env['HTTP_COOKIE'] or env['COOKIE']).split(/; /).each do |c|
152
- key, val = c.split(/=/, 2)
153
-
154
- key = CGI.unescape(key)
155
- val = val.split(/&/).collect{|v| CGI::unescape(v)}.join("\0")
156
-
157
- if context.cookies.include?(key)
158
- context.cookies[key] += "\0" + val
159
- else
160
- context.cookies[key] = val
161
- end
162
- end
163
- end
164
- end
165
-
166
- # Build the response headers for the context.
167
- #
168
- # [+context+]
169
- # The context of the response.
170
- #
171
- # [+proto+]
172
- # If true emmit the protocol line. Useful for MOD_RUBY.
173
- #--
174
- # FIXME: return the correct protocol from env.
175
- # TODO: Perhaps I can optimize status calc.
176
- #++
177
-
178
- def self.response_headers(context, proto = false)
179
- reason = STATUS_STRINGS[context.status]
180
-
181
- if proto
182
- buf = "HTTP/1.1 #{context.status} #{reason}#{EOL}Date: #{CGI::rfc1123_date(Time.now)}#{EOL}"
183
- else
184
- buf = "Status: #{context.status} #{reason}#{EOL}"
185
- end
186
-
187
- context.response_headers.each do |key, value|
188
- tmp = key.gsub(/\bwww|^te$|\b\w/) { |s| s.upcase }
189
- buf << "#{tmp}: #{value}" << EOL
190
- end
191
-
192
- context.response_cookies.each do |cookie|
193
- buf << "Set-Cookie: " << cookie.to_s << EOL
194
- end if context.response_cookies
195
-
196
- buf << EOL
197
-
198
- return buf
199
- end
200
-
201
- # Initialize the request params.
202
- # Handles multipart forms (in particular, forms that involve
203
- # file uploads). Reads query parameters in the @params field,
204
- # and cookies into @cookies.
205
-
206
- def self.parse_params(context)
207
- method = context.method
208
- if (:post == method) and
209
- %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(context.headers['CONTENT_TYPE'])
210
- boundary = $1.dup
211
- # context.params = read_multipart(boundary, Integer(context.headers['CONTENT_LENGTH']), context.in, context.headers)
212
- context.params = parse_multipart(context, boundary)
213
- else
214
- case method
215
- when :get, :head
216
- context.params = CgiUtils.parse_query_string(context.query_string)
217
- when :post
218
- context.in.binmode # if defined?(context.in.binmode)
219
- context.params = CgiUtils.parse_query_string(context.in.read(context.content_length) || '')
220
- end
221
- end
222
- end
223
-
224
- # Parse a multipart request.
225
- # Adapted from Ruby's cgi.rb
226
- #--
227
- # TODO: optimize and rationalize this.
228
- #++
229
-
230
- def self.parse_multipart(context, boundary)
231
- input = context.in
232
- content_length = context.content_length
233
- env_table = context.env
234
-
235
- params = Hash.new([])
236
- boundary = "--" + boundary
237
- buf = ""
238
-
239
- input.binmode if defined? input.binmode
240
- boundary_size = boundary.size + EOL.size
241
- content_length -= boundary_size
242
- status = input.read(boundary_size)
243
-
244
- if nil == status
245
- raise EOFError, "no content body"
246
- elsif boundary + EOL != status
247
- raise EOFError, "bad content body"
248
- end
249
-
250
- loop do
251
- head = nil
252
-
253
- if 10240 < content_length
254
- body = Tempfile.new("CGI")
255
- else
256
- begin
257
- body = StringIO.new
258
- rescue LoadError
259
- body = Tempfile.new("CGI")
260
- end
261
- end
262
- body.binmode if defined? body.binmode
263
-
264
- until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
265
-
266
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
267
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
268
- head = $1.dup
269
- ""
270
- end
271
- next
272
- end
273
-
274
- if head and ( (EOL + boundary + EOL).size < buf.size )
275
- body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
276
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
277
- end
278
-
279
- c = if Cgi.buffer_size < content_length
280
- input.read(Cgi.buffer_size)
281
- else
282
- input.read(content_length)
283
- end
284
- if c.nil?
285
- raise EOFError, "bad content body"
286
- end
287
- buf.concat(c)
288
- content_length -= c.size
289
- end
290
-
291
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do
292
- body.print $1
293
- if "--" == $2
294
- content_length = -1
295
- end
296
- ""
297
- end
298
-
299
- body.rewind
300
-
301
- /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
302
-
303
- filename = ($1 or "")
304
-
305
- if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
306
- /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
307
- (not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
308
- filename = CGI::unescape(filename)
309
- end
310
-
311
- /Content-Type: (.*)/ni.match(head)
312
- content_type = ($1 or "")
313
-
314
- (class << body; self; end).class_eval do
315
- alias local_path path
316
- define_method(:original_filename) { filename.dup.taint }
317
- define_method(:content_type) { content_type.dup.taint }
318
-
319
- # gmosx: this hides the performance hit!!
320
- define_method(:to_s) { read }
321
- end
322
-
323
- /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
324
- name = $1.dup
325
-
326
- if params.has_key?(name)
327
- params[name] = [params[name]] << body
328
- else
329
- params[name] = body
330
- end
331
-
332
- break if buf.size == 0
333
- break if content_length === -1
334
- end
335
-
336
- return params
337
- end
46
+ # HTTP protocol EOL constants
47
+
48
+ CR = "\x0d"
49
+ LF = "\x0a"
50
+ CRLF = "\x0d\x0a"
51
+ EOL = CRLF
52
+
53
+ # Constants for readable code
54
+
55
+ STATUS_OK = 200
56
+ STATUS_PARTIAL_CONTENT = 206
57
+ STATUS_MOVED = 301
58
+ STATUS_REDIRECT = 302
59
+ STATUS_SEE_OTHER = 303 # gmosx: VERIFY THIS
60
+ STATUS_SEE_OTHER_307 = 307 # gmosx: VERIFY THIS
61
+ STATUS_NOT_MODIFIED = 304
62
+ STATUS_BAD_REQUEST = 400
63
+ STATUS_AUTH_REQUIRED = 401
64
+ STATUS_FORBIDDEN = 403
65
+ STATUS_NOT_FOUND = 404
66
+ STATUS_METHOD_NOT_ALLOWED = 405
67
+ STATUS_NOT_ACCEPTABLE = 406
68
+ STATUS_LENGTH_REQUIRED = 411
69
+ STATUS_PRECONDITION_FAILED = 412
70
+ STATUS_SERVER_ERROR = 500
71
+ STATUS_NOT_IMPLEMENTED = 501
72
+ STATUS_BAD_GATEWAY = 502
73
+ STATUS_VARIANT_ALSO_VARIES = 506
74
+
75
+ # Hash to allow id to description maping.
76
+
77
+ STATUS_STRINGS = {
78
+ 200 => "OK",
79
+ 206 => "Partial Content",
80
+ 300 => "Multiple Choices",
81
+ 301 => "Moved Permanently",
82
+ 302 => "Found",
83
+ 303 => "See other", # gmosx: VERIFY THIS
84
+ 304 => "Not Modified",
85
+ 307 => "See other 07", # gmosx: VERIFY THIS
86
+ 400 => "Bad Request",
87
+ 401 => "Authorization Required",
88
+ 403 => "Forbidden",
89
+ 404 => "Not Found",
90
+ 405 => "Method Not Allowed",
91
+ 406 => "Not Acceptable",
92
+ 411 => "Length Required",
93
+ 412 => "Precondition Failed",
94
+ 500 => "Internal Server Error",
95
+ 501 => "Method Not Implemented",
96
+ 502 => "Bad Gateway",
97
+ 506 => "Variant Also Negotiates"
98
+ }
99
+
100
+ # Returns a hash with the pairs from the query
101
+ # string. The implicit hash construction that is done
102
+ # in parse_request_params is not done here.
103
+ #
104
+ # Parameters in the form xxx[] are converted
105
+ # to arrays.
106
+
107
+ def self.parse_query_string(query_string)
108
+ params = {}
109
+
110
+ # gmosx, THINK: better return nil here?
111
+ return params if (query_string.nil? or query_string.empty?)
112
+
113
+ query_string.split(/[&;]/).each do |p|
114
+ key, val = p.split('=')
115
+
116
+ key = CGI.unescape(key) unless key.nil?
117
+ val = CGI.unescape(val) unless val.nil?
118
+
119
+ if key =~ /(.*)\[\]$/
120
+ if params.has_key?($1)
121
+ params[$1] << val
122
+ else
123
+ params[$1] = [val]
124
+ end
125
+ else
126
+ params[key] = val.nil? ? nil : val
127
+ end
128
+ end
129
+
130
+ return params
131
+ end
132
+
133
+ # Parse the HTTP_COOKIE header and returns the
134
+ # cookies as a key->value hash. For efficiency no
135
+ # cookie objects are created.
136
+ #
137
+ # [+context+]
138
+ # The context
139
+
140
+ def self.parse_cookies(context)
141
+ env = context.env
142
+
143
+ # FIXME: dont precreate?
144
+ context.cookies = {}
145
+
146
+ if env.has_key?('HTTP_COOKIE') or env.has_key?('COOKIE')
147
+ (env['HTTP_COOKIE'] or env['COOKIE']).split(/; /).each do |c|
148
+ key, val = c.split(/=/, 2)
149
+
150
+ key = CGI.unescape(key)
151
+ val = val.split(/&/).collect{|v| CGI::unescape(v)}.join("\0")
152
+
153
+ if context.cookies.include?(key)
154
+ context.cookies[key] += "\0" + val
155
+ else
156
+ context.cookies[key] = val
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ # Build the response headers for the context.
163
+ #
164
+ # [+context+]
165
+ # The context of the response.
166
+ #
167
+ # [+proto+]
168
+ # If true emmit the protocol line. Useful for MOD_RUBY.
169
+ #--
170
+ # FIXME: return the correct protocol from env.
171
+ # TODO: Perhaps I can optimize status calc.
172
+ #++
173
+
174
+ def self.response_headers(context, proto = false)
175
+ reason = STATUS_STRINGS[context.status]
176
+
177
+ if proto
178
+ buf = "HTTP/1.1 #{context.status} #{reason}#{EOL}Date: #{CGI::rfc1123_date(Time.now)}#{EOL}"
179
+ else
180
+ buf = "Status: #{context.status} #{reason}#{EOL}"
181
+ end
182
+
183
+ context.response_headers.each do |key, value|
184
+ tmp = key.gsub(/\bwww|^te$|\b\w/) { |s| s.upcase }
185
+ buf << "#{tmp}: #{value}" << EOL
186
+ end
187
+
188
+ context.response_cookies.each do |cookie|
189
+ buf << "Set-Cookie: " << cookie.to_s << EOL
190
+ end if context.response_cookies
191
+
192
+ buf << EOL
193
+
194
+ return buf
195
+ end
196
+
197
+ # Initialize the request params.
198
+ # Handles multipart forms (in particular, forms that involve
199
+ # file uploads). Reads query parameters in the @params field,
200
+ # and cookies into @cookies.
201
+
202
+ def self.parse_params(context)
203
+ method = context.method
204
+ if (:post == method) and
205
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(context.headers['CONTENT_TYPE'])
206
+ boundary = $1.dup
207
+ # context.params = read_multipart(boundary, Integer(context.headers['CONTENT_LENGTH']), context.in, context.headers)
208
+ context.params = parse_multipart(context, boundary)
209
+ else
210
+ case method
211
+ when :get, :head
212
+ context.params = CgiUtils.parse_query_string(context.query_string)
213
+ when :post
214
+ context.in.binmode # if defined?(context.in.binmode)
215
+ context.params = CgiUtils.parse_query_string(context.in.read(context.content_length) || '')
216
+ end
217
+ end
218
+ end
219
+
220
+ # Parse a multipart request.
221
+ # Adapted from Ruby's cgi.rb
222
+ #--
223
+ # TODO: optimize and rationalize this.
224
+ #++
225
+
226
+ def self.parse_multipart(context, boundary)
227
+ input = context.in
228
+ content_length = context.content_length
229
+ env_table = context.env
230
+
231
+ params = Hash.new([])
232
+ boundary = "--" + boundary
233
+ buf = ""
234
+
235
+ input.binmode if defined? input.binmode
236
+ boundary_size = boundary.size + EOL.size
237
+ content_length -= boundary_size
238
+ status = input.read(boundary_size)
239
+
240
+ if nil == status
241
+ raise EOFError, "no content body"
242
+ elsif boundary + EOL != status
243
+ raise EOFError, "bad content body"
244
+ end
245
+
246
+ loop do
247
+ head = nil
248
+
249
+ if 10240 < content_length
250
+ body = Tempfile.new("CGI")
251
+ else
252
+ begin
253
+ body = StringIO.new
254
+ rescue LoadError
255
+ body = Tempfile.new("CGI")
256
+ end
257
+ end
258
+ body.binmode if defined? body.binmode
259
+
260
+ until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
261
+
262
+ if (not head) and /#{EOL}#{EOL}/n.match(buf)
263
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
264
+ head = $1.dup
265
+ ""
266
+ end
267
+ next
268
+ end
269
+
270
+ if head and ( (EOL + boundary + EOL).size < buf.size )
271
+ body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
272
+ buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
273
+ end
274
+
275
+ c = if Cgi.buffer_size < content_length
276
+ input.read(Cgi.buffer_size)
277
+ else
278
+ input.read(content_length)
279
+ end
280
+ if c.nil?
281
+ raise EOFError, "bad content body"
282
+ end
283
+ buf.concat(c)
284
+ content_length -= c.size
285
+ end
286
+
287
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do
288
+ body.print $1
289
+ if "--" == $2
290
+ content_length = -1
291
+ end
292
+ ""
293
+ end
294
+
295
+ body.rewind
296
+
297
+ /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
298
+
299
+ filename = ($1 or "")
300
+
301
+ if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
302
+ /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
303
+ (not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
304
+ filename = CGI::unescape(filename)
305
+ end
306
+
307
+ /Content-Type: (.*)/ni.match(head)
308
+ content_type = ($1 or "")
309
+
310
+ (class << body; self; end).class_eval do
311
+ alias local_path path
312
+ define_method(:original_filename) { filename.dup.taint }
313
+ define_method(:content_type) { content_type.dup.taint }
314
+
315
+ # gmosx: this hides the performance hit!!
316
+ define_method(:to_s) { read }
317
+ end
318
+
319
+ /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
320
+ name = $1.dup
321
+
322
+ if params.has_key?(name)
323
+ params[name] = [params[name]] << body
324
+ else
325
+ params[name] = body
326
+ end
327
+
328
+ break if buf.size == 0
329
+ break if content_length === -1
330
+ end
331
+
332
+ return params
333
+ end
338
334
 
339
335
  end
340
336
 
341
337
  end
338
+
339
+ # * George Moschovitis <gm@navel.gr>
340
+ # * James Britt <james_b@neurogami.com>