nitro 0.20.0 → 0.21.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.
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>