ruby-web 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. data/ChangeLog +474 -0
  2. data/INSTALL.txt +9 -0
  3. data/InstalledFiles +180 -0
  4. data/LICENSE.txt +74 -0
  5. data/Rakefile +529 -0
  6. data/TODO +65 -0
  7. data/doc/additional.xml +149 -0
  8. data/doc/core.xml +652 -0
  9. data/doc/credits/index.xml +52 -0
  10. data/doc/credits/php.contributors.xml +118 -0
  11. data/doc/credits/php.language-snippets.ent +622 -0
  12. data/doc/install/index.xml +136 -0
  13. data/doc/install/mac/index.xml +21 -0
  14. data/doc/install/ruby-web.install.rb.instructions.xml +7 -0
  15. data/doc/install/unix/index.xml +46 -0
  16. data/doc/install/win/apache1.xml +166 -0
  17. data/doc/install/win/apache2.xml +141 -0
  18. data/doc/install/win/iis.xml +162 -0
  19. data/doc/install/win/index.xml +24 -0
  20. data/doc/install/win/installer.xml +31 -0
  21. data/doc/install/win/manual.xml +43 -0
  22. data/doc/manual.xml +69 -0
  23. data/doc/old/apache_cgi.txt +23 -0
  24. data/doc/old/fastcgi.txt +23 -0
  25. data/doc/old/mod_ruby.txt +21 -0
  26. data/doc/old/snippets.rdoc +183 -0
  27. data/doc/old/webrick.txt +23 -0
  28. data/doc/old/windows_cgi.txt +9 -0
  29. data/doc/tutorial.xml +14 -0
  30. data/doc/xsl/manual-multi.xsl +10 -0
  31. data/doc/xsl/manual-pdf.xsl +6 -0
  32. data/doc/xsl/manual-single.xsl +6 -0
  33. data/doc/xsl/manual.css +22 -0
  34. data/install.rb +1022 -0
  35. data/lib/formatter.rb +314 -0
  36. data/lib/html-parser.rb +429 -0
  37. data/lib/htmlrepair.rb +113 -0
  38. data/lib/htmlsplit.rb +842 -0
  39. data/lib/sgml-parser.rb +332 -0
  40. data/lib/web.rb +68 -0
  41. data/lib/web/assertinclude.rb +129 -0
  42. data/lib/web/config.rb +50 -0
  43. data/lib/web/connection.rb +1070 -0
  44. data/lib/web/convenience.rb +154 -0
  45. data/lib/web/formreader.rb +318 -0
  46. data/lib/web/htmlparser/html-parser.rb +429 -0
  47. data/lib/web/htmlparser/sgml-parser.rb +332 -0
  48. data/lib/web/htmltools/element.rb +296 -0
  49. data/lib/web/htmltools/stparser.rb +276 -0
  50. data/lib/web/htmltools/tags.rb +286 -0
  51. data/lib/web/htmltools/tree.rb +139 -0
  52. data/lib/web/htmltools/xmltree.rb +160 -0
  53. data/lib/web/htmltools/xpath.rb +71 -0
  54. data/lib/web/info.rb +63 -0
  55. data/lib/web/load.rb +210 -0
  56. data/lib/web/mime.rb +87 -0
  57. data/lib/web/phprb.rb +340 -0
  58. data/lib/web/resources/test/cookie.rb +33 -0
  59. data/lib/web/resources/test/counter.rb +20 -0
  60. data/lib/web/resources/test/multipart.rb +14 -0
  61. data/lib/web/resources/test/redirect.rb +8 -0
  62. data/lib/web/resources/test/stock.rb +33 -0
  63. data/lib/web/sapi/apache.rb +129 -0
  64. data/lib/web/sapi/fastcgi.rb +22 -0
  65. data/lib/web/sapi/install/apache.rb +180 -0
  66. data/lib/web/sapi/install/iis.rb +93 -0
  67. data/lib/web/sapi/install/macosx.rb +90 -0
  68. data/lib/web/sapi/webrick.rb +86 -0
  69. data/lib/web/session.rb +83 -0
  70. data/lib/web/shim/cgi.rb +129 -0
  71. data/lib/web/shim/rails.rb +175 -0
  72. data/lib/web/stringio.rb +78 -0
  73. data/lib/web/strscanparser.rb +24 -0
  74. data/lib/web/tagparser.rb +96 -0
  75. data/lib/web/testing.rb +666 -0
  76. data/lib/web/traceoutput.rb +75 -0
  77. data/lib/web/unit.rb +56 -0
  78. data/lib/web/upload.rb +59 -0
  79. data/lib/web/validate.rb +52 -0
  80. data/lib/web/wiki.rb +557 -0
  81. data/lib/web/wiki/linker.rb +72 -0
  82. data/lib/web/wiki/page.rb +201 -0
  83. data/lib/webunit.rb +27 -0
  84. data/lib/webunit/assert.rb +152 -0
  85. data/lib/webunit/converter.rb +154 -0
  86. data/lib/webunit/cookie.rb +118 -0
  87. data/lib/webunit/domwalker.rb +185 -0
  88. data/lib/webunit/exception.rb +14 -0
  89. data/lib/webunit/form.rb +116 -0
  90. data/lib/webunit/frame.rb +37 -0
  91. data/lib/webunit/htmlelem.rb +122 -0
  92. data/lib/webunit/image.rb +26 -0
  93. data/lib/webunit/jscript.rb +31 -0
  94. data/lib/webunit/link.rb +33 -0
  95. data/lib/webunit/params.rb +321 -0
  96. data/lib/webunit/parser.rb +229 -0
  97. data/lib/webunit/response.rb +464 -0
  98. data/lib/webunit/runtest.rb +41 -0
  99. data/lib/webunit/table.rb +148 -0
  100. data/lib/webunit/testcase.rb +45 -0
  101. data/lib/webunit/ui/cui/testrunner.rb +50 -0
  102. data/lib/webunit/utils.rb +68 -0
  103. data/lib/webunit/webunit.rb +28 -0
  104. data/test/dev/action.rb +83 -0
  105. data/test/dev/forms.rb +104 -0
  106. data/test/dev/forms2.rb +104 -0
  107. data/test/dev/parser.rb +17 -0
  108. data/test/dev/scripts/dump.rb +24 -0
  109. data/test/dev/scripts/makedist.rb +62 -0
  110. data/test/dev/scripts/uri.rb +41 -0
  111. data/test/dev/scripts/uri/common.rb +432 -0
  112. data/test/dev/scripts/uri/ftp.rb +149 -0
  113. data/test/dev/scripts/uri/generic.rb +1106 -0
  114. data/test/dev/scripts/uri/http.rb +76 -0
  115. data/test/dev/scripts/uri/https.rb +26 -0
  116. data/test/dev/scripts/uri/ldap.rb +238 -0
  117. data/test/dev/scripts/uri/mailto.rb +260 -0
  118. data/test/dev/scripts/urireg.rb +174 -0
  119. data/test/dev/simpledispatcher.rb +156 -0
  120. data/test/dev/test.action.rb +146 -0
  121. data/test/dev/test.formreader.rb +463 -0
  122. data/test/dev/test.simpledispatcher.rb +186 -0
  123. data/test/dev/webunit/conv/digit-0.rb +21 -0
  124. data/test/dev/webunit/conv/digit-1.rb +17 -0
  125. data/test/dev/webunit/conv/digit.rb +23 -0
  126. data/test/dev/webunit/conv/test_digit-0.rb +16 -0
  127. data/test/dev/webunit/conv/test_digit-1.rb +19 -0
  128. data/test/dev/webunit/conv/test_digit.rb +26 -0
  129. data/test/dev/webunit/conv/test_digit_view-0.rb +76 -0
  130. data/test/dev/webunit/conv/test_digit_view-1.rb +102 -0
  131. data/test/dev/webunit/conv/test_digit_view.rb +134 -0
  132. data/test/installation/htdocs/cgi_test.rb +296 -0
  133. data/test/installation/htdocs/test_install.rb +4 -0
  134. data/test/installation/runwebtest.rb +5 -0
  135. data/test/installation/test_cookie.rb +128 -0
  136. data/test/installation/test_form.rb +47 -0
  137. data/test/installation/test_multipart.rb +51 -0
  138. data/test/installation/test_request.rb +24 -0
  139. data/test/installation/test_response.rb +35 -0
  140. data/test/unit/htdocs/cookie.rb +32 -0
  141. data/test/unit/htdocs/multipart.rb +28 -0
  142. data/test/unit/htdocs/redirect.rb +12 -0
  143. data/test/unit/htdocs/simple.rb +13 -0
  144. data/test/unit/htdocs/stock.rb +33 -0
  145. data/test/unit/test_assert.rb +162 -0
  146. data/test/unit/test_cookie.rb +114 -0
  147. data/test/unit/test_domwalker.rb +77 -0
  148. data/test/unit/test_form.rb +42 -0
  149. data/test/unit/test_frame.rb +40 -0
  150. data/test/unit/test_htmlelem.rb +74 -0
  151. data/test/unit/test_image.rb +45 -0
  152. data/test/unit/test_jscript.rb +57 -0
  153. data/test/unit/test_link.rb +85 -0
  154. data/test/unit/test_multipart.rb +51 -0
  155. data/test/unit/test_params.rb +210 -0
  156. data/test/unit/test_parser.rb +53 -0
  157. data/test/unit/test_response.rb +150 -0
  158. data/test/unit/test_table.rb +70 -0
  159. data/test/unit/test_utils.rb +106 -0
  160. data/test/unit/test_webunit.rb +28 -0
  161. data/test/web/mod_ruby_stub.rb +39 -0
  162. data/test/web/test.assertinclude.rb +109 -0
  163. data/test/web/test.buffer.rb +182 -0
  164. data/test/web/test.code.loader.rb +78 -0
  165. data/test/web/test.config.rb +31 -0
  166. data/test/web/test.error.handling.rb +91 -0
  167. data/test/web/test.formreader-2.0.rb +352 -0
  168. data/test/web/test.load.rb +125 -0
  169. data/test/web/test.mime-type.rb +23 -0
  170. data/test/web/test.narf.cgi.rb +106 -0
  171. data/test/web/test.phprb.rb +239 -0
  172. data/test/web/test.request.rb +368 -0
  173. data/test/web/test.response.rb +637 -0
  174. data/test/web/test.ruby-web.rb +10 -0
  175. data/test/web/test.session.rb +50 -0
  176. data/test/web/test.shim.cgi.rb +96 -0
  177. data/test/web/test.tagparser.rb +65 -0
  178. data/test/web/test.template2.rb +297 -0
  179. data/test/web/test.testing2.rb +318 -0
  180. data/test/web/test.upload.rb +45 -0
  181. data/test/web/test.validate.rb +46 -0
  182. data/test/web/test.web.test.rb +495 -0
  183. data/test/wiki/test.history.rb +297 -0
  184. data/test/wiki/test.illustration_page.rb +287 -0
  185. data/test/wiki/test.linker.rb +197 -0
  186. data/test/wiki/test.tarpit.rb +56 -0
  187. data/test/wiki/test.wiki.rb +300 -0
  188. data/test/wikitestroot/admin.rb +7 -0
  189. data/test/wikitestroot/wiki.rb +6 -0
  190. metadata +234 -0
@@ -0,0 +1,50 @@
1
+ module Web
2
+ VERSION = "1.0.0"
3
+
4
+ def Web::docroot= docroot
5
+ Web::config[:docroot] = docroot
6
+ end
7
+
8
+ def Web::docroot
9
+ Web::config[:docroot]
10
+ end
11
+
12
+ class Config
13
+ def initialize
14
+ @config = {}
15
+ @docs = {}
16
+ end
17
+
18
+ attr_reader :docs
19
+
20
+ def [] (key)
21
+ @config[key.to_s]
22
+ end
23
+
24
+ def []= (key, value)
25
+ @config[key.to_s] = value
26
+ end
27
+
28
+ def describe( info={} )
29
+ @docs.merge! info
30
+ end
31
+
32
+ def set( values={} )
33
+ @config.merge! values
34
+ end
35
+
36
+ def method_missing( symbol, *args )
37
+ if args.empty?
38
+ self[symbol]
39
+ else
40
+ super( symbol, *args )
41
+ end
42
+ end
43
+ end
44
+
45
+ @@config = Config.new
46
+ def Web.config
47
+ @@config
48
+ end
49
+
50
+ end
@@ -0,0 +1,1070 @@
1
+ require 'web/stringio'
2
+ require 'web/session'
3
+ require 'web/upload'
4
+ require 'web/mime'
5
+
6
+ class Exception
7
+
8
+ def rbw_html
9
+ msg = "<b style='font-size:20px'>" + Web::html_encode(self.to_s).gsub(/\n/,"<br>") + "</b><br>"
10
+ msg += Time::now().to_s
11
+ msg
12
+ end
13
+
14
+ def rbw_backtrace
15
+ #application_trace_length = (Web::run_caller || []).length
16
+ #self.backtrace[0..(0-application_trace_length)]
17
+ self.backtrace
18
+ end
19
+
20
+ def rbw_backtrace_html
21
+ trace = self.rbw_backtrace
22
+ trace.unshift(self.to_s)
23
+
24
+ msg = <<-STYLE
25
+ <style type="text/css">
26
+ .columnHead {
27
+ background-color:CCCDDD;
28
+ text-align:center;
29
+ font-weight:bold;
30
+ }
31
+ .info {
32
+ text-align:center;
33
+ }
34
+ .info_row:hover{
35
+ background-color:F8FF80;
36
+ }
37
+ </style>
38
+ STYLE
39
+ msg += "<p><table onMouseover=\"changeto(event, '#F8FF80')\" onMouseout=\"changeback(event, '#eeeeff')\">\n"
40
+ msg += "<tr><td class='columnHead'>File</td><td class='columnHead'>&nbsp;Line&nbsp;</td><td class='columnHead'>Method</td></tr>\n"
41
+
42
+ trace.each do |level|
43
+ level = Web::parse_trace(level)
44
+ msg += "<tr class='info_row'>\n"
45
+ level.each{ |column| msg += "<td class='info'>" + (column || '') + "</td>\n" }
46
+ msg += "</tr>\n\n"
47
+ end
48
+
49
+ msg += "</table>"
50
+
51
+ msg
52
+ end
53
+
54
+ def rbw_to_s
55
+ str = self.class.to_s + ": " + self.to_s + "\n";
56
+ str += self.rbw_backtrace.collect do |line|
57
+ " " + line.chomp
58
+ end.join("\n")
59
+ str
60
+ end
61
+
62
+
63
+ end
64
+
65
+ module Web
66
+ class << self
67
+ def method_missing(method, *args, &block)
68
+ connection.send(method,*args, &block)
69
+ end
70
+
71
+ def connection= connection
72
+ Thread.current[:web_connection] = connection
73
+ end
74
+
75
+ def connection( options={} )
76
+ unless Thread.current[:web_connection]
77
+ self.connection = Web::Connection::create(options)
78
+ end
79
+
80
+ Thread.current[:web_connection]
81
+ end
82
+ end
83
+
84
+
85
+
86
+ class Connection
87
+ def initialize( options={} )
88
+ apply_options( options )
89
+ parse_request
90
+ setup_variables
91
+ end
92
+
93
+ def apply_options( options = {} )
94
+ @closed = false
95
+ @header_sent = false
96
+ @templates = [ ]
97
+
98
+ @options = options
99
+ options[:buffered] = true unless options.has_key? :buffered
100
+
101
+ @raw_post_data = options[:raw_post_data] || $stdin
102
+ @output = options[:out] || $stdout
103
+ @cookie = options[:cookie]
104
+ @request = options[:request]
105
+
106
+
107
+
108
+ # start error handler
109
+ if (options.has_key? :error_handler)
110
+ if ( options[:error_handler] )
111
+ @error_handler = options[:error_handler]
112
+ end
113
+ end
114
+ # end error handler
115
+
116
+ # start env
117
+ @env ||= ENV
118
+ @env = Connection::downcase_env(@env)
119
+ if options[:env] && options[:env].kind_of?(Hash) && !options[:env].keys.empty?
120
+ @env = Connection::downcase_env( @env.merge( options[:env] ) )
121
+ end
122
+ Web::Connection::ENV_KEYS.each { |symbol|
123
+ env[symbol.to_s.downcase] = options[symbol] if options[symbol]
124
+ }
125
+ # end env
126
+ end
127
+
128
+ def setup_variables
129
+ @local = {}
130
+ @application_loaded = false
131
+
132
+ @cookie = Connection::normalize(@cookie)
133
+ @post = Connection::normalize(@post)
134
+ @get = Connection::normalize(@get)
135
+ @request = Connection::normalize(@request || merge_get_and_post )
136
+
137
+ raw_post_data.rewind
138
+
139
+ # start output buffer
140
+ @content = StringIO.new
141
+ if (unbuffered?)
142
+ @buffer = @output
143
+ else
144
+ @buffer = BufferSet.new
145
+ end
146
+ @buffer.binmode if @buffer.respond_to? :binmode
147
+ # end output buffer
148
+
149
+
150
+ end
151
+
152
+ def merge_get_and_post
153
+ var_order = [ :post, :get, :cookie ]
154
+
155
+ if env["request_method"] == "POST"
156
+ var_order = [ :get, :post, :cookie ]
157
+ end
158
+
159
+ @request = Hash.new( [ ] )
160
+ var_order.each do |namespace|
161
+ variables = self.send( namespace )
162
+ if variables
163
+ variables.each do |k,v|
164
+ @request[k] = [ ]
165
+ v.each do |e|
166
+ @request[k].push(e.dup) if e
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ @request
173
+ end
174
+
175
+ attr_reader :options, :local, :request, :cgd, :env, :raw_post_data, :output, :get, :post, :content, :cookie
176
+ attr_accessor :application_loaded, :templates, :run_caller
177
+
178
+ def session
179
+ # start session
180
+ @session ||= if (options.has_key? :session)
181
+ options[:session]
182
+ else
183
+ Session.new( self, options )
184
+ end
185
+ # end session
186
+ end
187
+
188
+ def trace_output
189
+ templater = Narflates.new(CGI.trace_output_template,{})
190
+ templater.parse(self,{ "parameters" =>
191
+ request.collect { |key,value| { "key" => key, "value" => value } },
192
+ "cookie" =>
193
+ cookie.collect { |key,value| { "key" => key, "value" => value } },
194
+ "session" =>
195
+ session.collect { |key,value| { "key" => key, "value" => value } } })
196
+ flush
197
+ end
198
+
199
+ MULTIPLE_KEY = /\[\]\z/
200
+
201
+ # access parameters. If the key is array-style,
202
+ # aka param[], return the array. Otherwise,
203
+ # return the joined string.
204
+ def [] (key)
205
+ if (MULTIPLE_KEY =~ key)
206
+ self.request[key]
207
+ else
208
+ single_param(key)
209
+ end
210
+ end
211
+
212
+ # set param at the given key. This is useful to change state
213
+ # in your app without asking the browser to redirect
214
+ def []= (key, value)
215
+ unless value.kind_of? Array
216
+ value = [value]
217
+ end
218
+ request[key] = value
219
+ end
220
+
221
+ # If request[key][0] is a Web::Upload, returns that value.
222
+ # Otherwise it returns request[key].join( "," )
223
+ def single_param(key)
224
+ if (request[key].first.kind_of? Web::Upload)
225
+ request[key].first
226
+ else
227
+ request[key].join( "," )
228
+ end
229
+ end
230
+
231
+ def split_params #:nodoc:
232
+ Web::Testing::MultiHashTree.new(request).fields
233
+ end
234
+
235
+ # list the submitted params
236
+ def keys
237
+ request.keys
238
+ end
239
+
240
+ # test whether a param was submitted
241
+ def key? (key)
242
+ request.has_key? key
243
+ end
244
+
245
+ #----------------------------------
246
+ # Response methods
247
+ #----------------------------------
248
+
249
+ # send header to the client, and flush any buffered output
250
+ def flush
251
+ unless unbuffered?
252
+ begin
253
+ content = ob_flush
254
+ send_header
255
+ @output << content
256
+ rescue Exception => e
257
+ # protect against errors in output buffers
258
+ @buffer = BufferSet.new
259
+ Web::report_error(e)
260
+ self.flush
261
+ end
262
+ end
263
+ end
264
+
265
+ # TODO: at_close handlers
266
+ # flushes the output and, if applicable, saves the session
267
+ def close
268
+ flush
269
+ @session ||= nil
270
+ @session.save if (@session.respond_to? :save)
271
+ @closed = true
272
+ end
273
+
274
+ def closed?
275
+ @closed
276
+ end
277
+
278
+ # ----------------------------------
279
+ # Output buffers
280
+
281
+ # Append output to client
282
+ [ :<<, :puts, :write, :print ].each{ |symbol|
283
+ define_method(symbol) { |*args|
284
+ send_header if (unbuffered?)
285
+ unless closed?
286
+ @content.send( symbol, args ) if unbuffered?
287
+ @buffer.send( symbol, args )
288
+ end
289
+ }
290
+ }
291
+
292
+ # Reset output buffer. Fails if headers have been sent.
293
+ def clear()
294
+ if header_sent?
295
+ raise( Exception.new( "Can't call Web::clear()" ) )
296
+ else
297
+ unless unbuffered?
298
+ @buffer = BufferSet.new
299
+ @content = StringIO.new
300
+ end
301
+ end
302
+ end
303
+
304
+ # This method will replace the contents
305
+ # of the output buffer by reading from
306
+ # the given filename. If the content-type
307
+ # has not already been upset, it will try
308
+ # and guess an appropriate mime-type
309
+ # from the extension of the file.
310
+ def send_file( filename )
311
+ clear()
312
+ write( File.open( filename, 'rb' ) { |f| f.read } )
313
+ if self.content_type == "text/html"
314
+ self.content_type = Web::Mime::get_mimetype(filename)
315
+ end
316
+ Web::close()
317
+ end
318
+
319
+ def unbuffered?
320
+ ! @options[:buffered]
321
+ end
322
+
323
+ # returns the body content of the response
324
+ # (sans headers).
325
+ def get_content
326
+ #flush_buffers # (do I need to flush the buffers?)
327
+ @content.string
328
+ end
329
+
330
+ def ob_start( &callback )
331
+ @buffer.ob_start(&callback)
332
+ end
333
+ alias :filter :ob_start
334
+
335
+ def ob_get_flush
336
+ result = @buffer.last.get_contents
337
+ ob_flush
338
+ result
339
+ end
340
+
341
+ [ :ob_flush, :ob_end_flush ].each do |symbol|
342
+ define_method(symbol) do |*args|
343
+ result = @buffer.send( symbol )
344
+ @content << result
345
+ result
346
+ end
347
+ end
348
+
349
+ [:ob_clean, :ob_end_clean, :ob_get_clean, :ob_get_contents,
350
+ :ob_get_length, :ob_get_level, :ob_list_handlers ].each{ |symbol|
351
+ define_method(symbol) { |*args|
352
+ if (args.empty?)
353
+ @buffer.send( symbol )
354
+ else
355
+ @buffer.send( symbol, args )
356
+ end
357
+ }
358
+ }
359
+
360
+ class BufferSet
361
+ def initialize
362
+ @buffer_stack = [ ]
363
+ @buffer_stack.push CallbackBuffer.new
364
+ end
365
+
366
+ def ob_list_handlers
367
+ @buffer_stack
368
+ end
369
+
370
+ def ob_get_level
371
+ @buffer_stack.length
372
+ end
373
+
374
+ def last
375
+ @buffer_stack.last
376
+ end
377
+
378
+ def ob_clean
379
+ last.clean
380
+ end
381
+
382
+ def ob_end_clean
383
+ unless @buffer_stack.empty?
384
+ @buffer_stack.pop
385
+ true
386
+ end
387
+ end
388
+
389
+ def ob_get_clean
390
+ unless @buffer_stack.empty?
391
+ @buffer_stack.pop.get_contents
392
+ end
393
+ end
394
+
395
+ def ob_get_contents
396
+ @buffer_stack.last.get_contents
397
+ end
398
+
399
+ def ob_flush
400
+ result = nil
401
+ @buffer_stack.reverse.each do |b|
402
+ b << result if result
403
+ result = b.flush
404
+ end
405
+ #@buffer_stack = [ CallbackBuffer.new ]
406
+ result
407
+ end
408
+
409
+ def ob_end_flush
410
+ result = ob_flush
411
+ ob_end_clean
412
+ result
413
+ end
414
+
415
+ def ob_get_length
416
+ ob_get_contents.length
417
+ end
418
+
419
+ def ob_start( &callback )
420
+ @buffer_stack.push( CallbackBuffer.new(&callback) )
421
+ end
422
+
423
+ [ :<<, :puts, :write, :print ].each{ |symbol|
424
+ define_method(symbol) { |*args|
425
+ @buffer_stack.last.send( symbol, args )
426
+ }
427
+ }
428
+
429
+ end
430
+
431
+ class CallbackBuffer
432
+ attr_accessor :callback
433
+ def initialize( &callback )
434
+ @callback = callback
435
+ @buffer = StringIO.new
436
+ end
437
+
438
+ def flush
439
+ result = if @callback
440
+ @callback.call(@buffer.string)
441
+ else
442
+ @buffer.string
443
+ end
444
+ self.clean
445
+ result
446
+ end
447
+
448
+ def get_contents
449
+ @buffer.string
450
+ end
451
+
452
+ def clean
453
+ @buffer = StringIO.new
454
+ end
455
+
456
+ [ :<<, :puts, :write, :print ].each{ |symbol|
457
+ define_method(symbol) { |*args|
458
+ @buffer.send( symbol, args )
459
+ }
460
+ }
461
+
462
+ end
463
+
464
+ # -----------------------------------
465
+ # header methods
466
+
467
+ # There's a bit of special casing in here, for the follow reasons:
468
+ # * some headers should only be sent once. If you add
469
+ # Content-Encoding, Location, or Status, it will overwrite the old headers
470
+ # instead of adding a second header
471
+ # * content-type is a strange header. It is a combination of the content-type
472
+ # and charset attributes. setting content-type will cause the content-type
473
+ # attribute to be set; setting charset will cause the charset attribute to
474
+ # be set.
475
+ #
476
+ # I don't know if this is the correct behavior. Should this method assume that
477
+ # the content-type set here is the full content type, and try to split the
478
+ # header into it's two parts?
479
+ #
480
+ # * If the headers have been sent, this will throw a Web::Error
481
+ def add_header(name, value )
482
+ unless header_sent?
483
+ if (/content-encoding/i =~ name )
484
+ header['Content-Encoding'] = [value]
485
+ elsif( /location/i =~ name )
486
+ header['Location'] = [value]
487
+ elsif( /status/i =~ name )
488
+ if /^\d*$/ =~ value.to_s
489
+ self.status = value
490
+ else
491
+ header['Status'] = [value]
492
+ end
493
+ elsif(/content-type/i =~ name)
494
+ header['Content-Type'] = [value]
495
+ elsif(/charset/i =~ name)
496
+ self.charset = value
497
+ else
498
+ header[name] ||= []
499
+ header[name].push value
500
+ end
501
+ else
502
+ raise Web::Error.new( "Can't add_header after header have been sent to client" )
503
+ end
504
+ end
505
+
506
+ # returns an array of header values set with the given name.
507
+ def get_header name
508
+ header.keys.find_all { |key|
509
+ key.downcase == name.downcase
510
+ }.collect{ |key|
511
+ header[key].dup
512
+ }.flatten
513
+ end
514
+
515
+
516
+ # Send header to the client. No more header values can be sent after this method is called!
517
+ def send_header
518
+ unless header_sent?
519
+ unless @options[:noheader]
520
+ send_header_implementation
521
+ end
522
+ @header_sent = true
523
+ end
524
+ end
525
+
526
+ def send_header_implementation
527
+ if @options[:nph] || /IIS/.match(env["server_software"])
528
+ output << "#{ env['server_protocol'] || 'HTTP/1.0' }: "
529
+ output << "#{ header['Status'] }\r\n"
530
+ output << "Date: #{ Web::rfc1123_date(Time.now) }\r\n"
531
+
532
+ header.delete("Status")
533
+
534
+ unless header.has_key? "Server"
535
+ header["Server"] = [ env["server_software"] || "" ]
536
+ end
537
+ end
538
+
539
+ header.sort.each { |name, value|
540
+ value.each { |v|
541
+ output << "#{ name }: #{ v }\r\n"
542
+ }
543
+ }
544
+
545
+ output << "\r\n"
546
+ end
547
+
548
+ def header_sent?
549
+ @header_sent
550
+ end
551
+
552
+ def header
553
+ @header ||= {"Content-Type" => ["text/html"],
554
+ "Status" => ["200 OK"] }
555
+ @header
556
+ end
557
+
558
+ # the default status is 200
559
+ def status
560
+ get_header( "Status" ).first =~ /^(\d+)( .*)?$/
561
+ $1
562
+ end
563
+
564
+ HTTP_STATUS = {"200" => "OK",
565
+ "206" => "Partial Content",
566
+ "300" => "Multiple Choices",
567
+ "301" => "Moved Permanently",
568
+ "302" => "Found",
569
+ "304" => "Not Modified",
570
+ "400" => "Bad Request",
571
+ "401" => "Authorization Required",
572
+ "403" => "Forbidden",
573
+ "404" => "Not Found",
574
+ "405" => "Method Not Allowed",
575
+ "406" => "Not Acceptable",
576
+ "411" => "Length Required",
577
+ "412" => "Precondition Failed",
578
+ "500" => "Internal Server Error",
579
+ "501" => "Method Not Implemented",
580
+ "502" => "Bad Gateway",
581
+ "506" => "Variant Also Negotiates"
582
+ }
583
+ def status= new_status
584
+ add_header( "Status", "#{ new_status } #{ HTTP_STATUS[new_status.to_s] }" )
585
+ end
586
+
587
+ def location
588
+ get_header( "Location" ).first
589
+ end
590
+
591
+ # see set redirect
592
+ def location= location
593
+ add_header( "Location", location )
594
+ end
595
+
596
+ # Sets the status and the location appropriately.
597
+ def set_redirect( new_location )
598
+ self.status = "302"
599
+ self.location = new_location
600
+ end
601
+
602
+ def encoding
603
+ get_header( "Content-Encoding" ).first
604
+ end
605
+
606
+ def encoding=( new_encoding )
607
+ add_header( "Content-Encoding", new_encoding )
608
+ end
609
+
610
+ def split_content_type( target = full_content_type )
611
+ target.split( Regexp.new('; charset=') )
612
+ end
613
+
614
+ # The content type header is a combination of the content_type and the charset.
615
+ # This method returns that combination.
616
+ def full_content_type
617
+ get_header( "Content-Type" ).first
618
+ end
619
+
620
+ def set_full_content_type( new_content, new_charset )
621
+ add_header( "Content-type", [ new_content, new_charset ].compact.join('; charset=') )
622
+ end
623
+
624
+ # the default content-type is "text/html"
625
+ def content_type
626
+ split_content_type[0]
627
+ end
628
+
629
+ def content_type= new_content_type
630
+ content1, charset1 = split_content_type
631
+ content2, charset2 = split_content_type( new_content_type )
632
+
633
+ set_full_content_type( content2 || content1,
634
+ charset2 || charset1 )
635
+ end
636
+
637
+ def charset
638
+ split_content_type[1]
639
+ end
640
+
641
+ def charset= new_charset
642
+ set_full_content_type( content_type, new_charset )
643
+ end
644
+
645
+ # Cookies require a name and a value. You can also use these
646
+ # optional keyword arguments:
647
+ # <b>:path</b> => <i>string<i>:: path (need better description)
648
+ # <b>:domain</b> => <i>string</i>:: domain (need better description)
649
+ # <b>:expires</b> => <i>date</i>:: date this cookie should expire
650
+ # <b>:secure</b> => <i>true || false </i>:: whether this cookie should be
651
+ # tagged as secure.
652
+ def set_cookie( name, value, options={} )
653
+ #self.cookie[name] = [ ] unless self.cookie.has_key? name
654
+ #self.cookie[name].push value
655
+
656
+ value = Array(value).collect{ |field|
657
+ Web::escape(field)
658
+ }.join("&")
659
+
660
+ cookie_text = "#{ name }=#{ value }"
661
+
662
+
663
+ path = if (options[:path])
664
+ options[:path]
665
+ else
666
+ %r|^(.*/)|.match(env["script_name"])
667
+ ($1 or "")
668
+ end
669
+
670
+ cookie_text += "; path=#{ path }"
671
+
672
+ if (options[:domain])
673
+ cookie_text += "; domain=#{ options[:domain] }"
674
+ end
675
+
676
+ if (options[:expires])
677
+ cookie_text += "; expires=#{ Web::rfc1123_date( options[:expires] ) }"
678
+ end
679
+
680
+ if (options[:secure])
681
+ cookie_text += "; secure"
682
+ end
683
+
684
+ add_header( "Set-Cookie", cookie_text )
685
+ end
686
+
687
+ # returns an array of cookie values that have been set.
688
+ # path / expires / etc. info is currently not returned, should
689
+ # be added in?
690
+ def get_cookie key
691
+ #get_header("Set-Cookie").collect{ |cookie|
692
+ # /\A(#{ key })=([^;]*)/ =~ cookie
693
+ # $2
694
+ #}.compact
695
+ self.cookie[key]
696
+ end
697
+
698
+ # returns a hash of all the cookie n/v pairs that have
699
+ # been set on the cgi
700
+ def cookies_sent
701
+ cookies = {}
702
+ get_header("Set-Cookie").each do |cookie|
703
+ /\A(.*?)=([^;]*)/ =~ cookie
704
+ cookies[$1] ||= [ ]
705
+ cookies[$1].push $2
706
+ end
707
+ cookies
708
+ end
709
+
710
+ # Aside from when :nph is set in the options, scripts running in IIS always
711
+ # use nph mode. This code will probably be affected as cgi is re-organized
712
+ # to support multiple backends.
713
+ def nph?
714
+ @cgd.nph?
715
+ end
716
+
717
+ ENV_KEYS = [ :path_info, :document_root, :script_name ]
718
+
719
+ ENV_KEYS.each{ |symbol|
720
+ define_method( symbol ) {
721
+ env[symbol.to_s]
722
+ }
723
+ }
724
+
725
+ #------------------------------
726
+ # implementation details, aka
727
+ # refactoring targets below
728
+ #------------------------------
729
+ def report_error( error )
730
+ self.status = 500 unless self.header_sent?
731
+ self.content_type = "text/html"
732
+
733
+ Web::config::describe( :error_style => "Controls error reporting. One of three values: :development, :production, or :custom" )
734
+ Web::config[:error_style] ||= :development
735
+
736
+ case Web::config::error_style
737
+
738
+ when :development
739
+
740
+ Web::print_message( error.class.to_s,
741
+ error.rbw_html + error.rbw_backtrace_html )
742
+ self.report_error2stderr( error )
743
+
744
+ when :production
745
+
746
+ Web::print_message( error.class.to_s,
747
+ error.rbw_html )
748
+ self.report_error2stderr( error )
749
+
750
+ when :custom
751
+
752
+ Web::config::error_handler.call( error )
753
+
754
+ else
755
+
756
+ raise error
757
+
758
+ end
759
+ end
760
+
761
+ def report_error2stderr( err )
762
+ unless Web::env['server_software'] =~ /Microsoft-IIS/
763
+ $stderr.binmode
764
+ $stderr.puts err.rbw_to_s
765
+ end
766
+ end
767
+
768
+
769
+
770
+ def Connection::create(options={})
771
+ if (options[:connection])
772
+ options[:connection]
773
+ else
774
+ case Connection::server_sniff
775
+ when :fcgi
776
+ require 'web/sapi/fastcgi'
777
+ Web::FastCGIConnection.new( options )
778
+ when :mod_ruby
779
+ Web::ModRubyConnection.new( options )
780
+ else
781
+ Connection.new(options)
782
+ end
783
+ end
784
+ end
785
+
786
+ # try to include fastcgi
787
+ begin
788
+ require 'fcgi'
789
+ rescue LoadError
790
+
791
+ end
792
+
793
+ # returns one of these values:
794
+ # :cgi, :fastcgi, :mod_ruby, :webrick
795
+ def Connection::server_sniff
796
+ if( Object.const_defined?( "Apache" ) \
797
+ && Apache.const_defined?( "Request" ) \
798
+ && $stdin.kind_of?( Apache::Request ) )
799
+ :mod_ruby
800
+ elsif ( Object.const_defined?( "FCGI" ) \
801
+ && ! FCGI.is_cgi? )
802
+ :fcgi
803
+ else
804
+ :cgi
805
+ end
806
+ end
807
+
808
+ # This method counts on the attributes of a cgd: options, raw_post_data, env
809
+ # It will set @cookie, @get, @post, and @request
810
+ def parse_request
811
+ @cookie ||= CookieHash.new( env['http_cookie'] || env['cookie'] )
812
+ @get ||= Connection::parse_query_string(env["query_string"])
813
+ @post ||= if (multipart?)
814
+ parse_multipart
815
+ else
816
+ raw_post_data.binmode
817
+ query_string = raw_post_data.read(Integer(env['content_length'])) || ''
818
+ @raw_post_data = StringIO.new(query_string)
819
+ Connection::parse_query_string( query_string )
820
+ end
821
+ end
822
+
823
+ class CookieHash < Hash
824
+ def initialize( values )
825
+ super([])
826
+ update(values)
827
+ end
828
+
829
+ def update(values)
830
+ if values.kind_of? Hash
831
+ super(values)
832
+ elsif values
833
+ raw_cookie = values.to_s
834
+ raw_cookie.split(/; /).each do |pairs|
835
+ name, values = pairs.split('=',2)
836
+ name = Web::unescape(name)
837
+ values ||= ""
838
+ values = values.split('&').collect{|v| Web::unescape(v) }
839
+ if self.has_key?(name)
840
+ self[name].push(*values)
841
+ else
842
+ self[name] = values
843
+ end
844
+ end
845
+ end
846
+ end
847
+
848
+ def update_by_addcookie(line)
849
+ line.gsub!( /;.*$/, '' )
850
+ self.update(line)
851
+ end
852
+ end
853
+
854
+ def cookie=(other)
855
+ raise ArgumentError.new unless other
856
+ @cookie = CookieHash.new(other)
857
+ end
858
+
859
+ # returns a hash with downcased keys of the env_in variable
860
+ def Connection::downcase_env( env_in )
861
+ env = CaseInsensitiveHash.new
862
+ env_in.each{ |k, v|
863
+ env[k.downcase] = v
864
+ }
865
+ env
866
+ end
867
+
868
+ # normalizes a params hash
869
+ def Connection::normalize( params )
870
+ params.each { |key, value|
871
+ unless value.kind_of? Array
872
+ params[key] = [value]
873
+ end
874
+ }
875
+ params.default = []
876
+ params
877
+ end
878
+
879
+
880
+ # Parse a query_string into parameters
881
+ def Connection::parse_query_string(query)
882
+ query ||= ""
883
+ params = Hash.new([])
884
+
885
+ query.split(/[&;]/n).each do |pairs|
886
+ key, value = pairs.split('=',2).collect{|v| Web::unescape(v) }
887
+ if params.has_key?(key)
888
+ params[key].push(value)
889
+ else
890
+ params[key] = [value]
891
+ end
892
+ end
893
+
894
+ params
895
+ end
896
+
897
+ # this method solely exists b/c of difficulties setting MOD_RUBY dynamically.
898
+ # so this method exists so I can test the effect separately
899
+ # from the (currently untestable) cause
900
+ def mod_ruby_query_string #:nodoc:
901
+ Apache::request.args
902
+ end
903
+
904
+ def multipart?
905
+ ("POST" == env['request_method']) &&
906
+ (/multipart\/form-data/.match(env['content_type']))
907
+ end
908
+
909
+ # parse multipart/form-data
910
+ def parse_multipart
911
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(env['content_type'])
912
+ boundary = $1.dup
913
+ read_multipart(boundary, Integer(env['content_length']))
914
+ end
915
+
916
+ EOL="\r\n"
917
+
918
+ Web::config::describe( :multipart_progress_hook => "Lambda to enable progress reports for uploads. Added for rails" )
919
+
920
+ def progress_hook
921
+ Web::config::multipart_progress_hook
922
+ end
923
+ def call_progress_hook(full, read)
924
+ if (progress_hook)
925
+ progress_hook.call( full,
926
+ read )
927
+ end
928
+ end
929
+
930
+ def read_multipart(boundary, content_length)
931
+ require 'tempfile'
932
+
933
+ params = Hash.new([])
934
+ boundary = "--" + boundary
935
+ buf = ""
936
+ cached_raw_post_data = Tempfile.new("Web")
937
+ bufsize = 10 * 1024
938
+
939
+ # start multipart/form-data
940
+ raw_post_data.binmode
941
+ boundary_size = boundary.size + EOL.size
942
+ content_length -= boundary_size
943
+ status = raw_post_data.read(boundary_size)
944
+ if nil == status
945
+ raise EOFError, "no content body"
946
+ end
947
+ cached_raw_post_data << status
948
+
949
+ # ok... so what the hell does this do?
950
+ # I promise never to denigrate the accomplishments
951
+ # of my predecessors again :-)
952
+ # ~ pat
953
+ full_content_length = content_length
954
+ until -1 == content_length
955
+ call_progress_hook( full_content_length,
956
+ full_content_length - content_length )
957
+ head = nil
958
+ body = Tempfile.new("Web")
959
+ body.binmode
960
+
961
+ # until we have:
962
+ # * a header
963
+ # * and a buffer that has a boundary
964
+ # so far, make sense to me.
965
+ until head and /#{boundary}(?:#{EOL}|--)/n.match(buf)
966
+
967
+ # if we have a header....
968
+ if head
969
+ # !??!??!?!?!
970
+ trim_size = (EOL + boundary + EOL).size
971
+ if trim_size < buf.size
972
+ body.write buf[0 ... (buf.size - trim_size)]
973
+ buf[0 ... (buf.size - trim_size)] = ""
974
+ end
975
+
976
+ # If we have a double space (the smell of a header...)
977
+ elsif /#{EOL}#{EOL}/n.match(buf)
978
+ # extract the header, and erase it from the buffer
979
+ buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
980
+ head = $1.dup
981
+ ""
982
+ end
983
+ next
984
+ end
985
+
986
+ # read a chunk from the raw_post_data
987
+ c = if bufsize < content_length
988
+ raw_post_data.read(bufsize) or ''
989
+ else
990
+ raw_post_data.read(content_length) or ''
991
+ end
992
+ # add it to the raw_post_data, reduce our countdown
993
+ cached_raw_post_data << c
994
+ buf.concat c
995
+ ### debugging for making multipart_asserts work ~ pat
996
+ #$defout.puts [bufsize, content_length, c.size].inspect, raw_post_data.read(bufsize).inspect
997
+ content_length -= c.size
998
+ end
999
+
1000
+ /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
1001
+ filename = ($1 or "").dup
1002
+ if /Mac/ni.match(env['http_user_agent']) and
1003
+ /Mozilla/ni.match(env['http_user_agent']) and
1004
+ (not /MSIE/ni.match(env['http_user_agent']))
1005
+ filename = Web::unescape(filename)
1006
+ end
1007
+
1008
+ /Content-Type: (.*)/ni.match(head)
1009
+ content_type = ($1 or "").strip
1010
+
1011
+ # is this the part that is eating too much?
1012
+ #buf = buf.sub(/\A(.*?)(?:#{EOL})?#{boundary}(#{EOL}|--)/mn) do
1013
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{boundary}([\r\n]{1,2}|--)/n) do
1014
+ body.print $1
1015
+ if "--" == $2
1016
+ content_length = -1
1017
+ end
1018
+ ""
1019
+ end
1020
+
1021
+ body.rewind
1022
+
1023
+
1024
+ if (content_type.empty?)
1025
+ upload = body.read
1026
+ else
1027
+ upload = Web::Upload.new( body, content_type, filename )
1028
+ end
1029
+
1030
+ /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
1031
+ name = $1.dup
1032
+
1033
+
1034
+ body.rewind
1035
+
1036
+ if params.has_key?(name)
1037
+ params[name].push(upload)
1038
+ else
1039
+ params[name] = [upload]
1040
+ end
1041
+
1042
+ end
1043
+
1044
+ call_progress_hook( full_content_length,
1045
+ full_content_length - content_length - 1)
1046
+
1047
+ @raw_post_data = cached_raw_post_data
1048
+ params
1049
+ end # read_multipart
1050
+
1051
+ # == Purpose
1052
+ #
1053
+ # this hash is has case insensitive keys. Might be somewhat
1054
+ # incomplete
1055
+ class CaseInsensitiveHash < Hash #:nodoc:
1056
+ def [](key)
1057
+ super( key.to_s.downcase )
1058
+ end
1059
+
1060
+ def []= (key, value)
1061
+ super( key.to_s.downcase, value )
1062
+ end
1063
+
1064
+ def has_key? (key)
1065
+ super( key.to_s.downcase )
1066
+ end
1067
+ end
1068
+
1069
+ end
1070
+ end