ruby-web 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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