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.
- data/ChangeLog +474 -0
- data/INSTALL.txt +9 -0
- data/InstalledFiles +180 -0
- data/LICENSE.txt +74 -0
- data/Rakefile +529 -0
- data/TODO +65 -0
- data/doc/additional.xml +149 -0
- data/doc/core.xml +652 -0
- data/doc/credits/index.xml +52 -0
- data/doc/credits/php.contributors.xml +118 -0
- data/doc/credits/php.language-snippets.ent +622 -0
- data/doc/install/index.xml +136 -0
- data/doc/install/mac/index.xml +21 -0
- data/doc/install/ruby-web.install.rb.instructions.xml +7 -0
- data/doc/install/unix/index.xml +46 -0
- data/doc/install/win/apache1.xml +166 -0
- data/doc/install/win/apache2.xml +141 -0
- data/doc/install/win/iis.xml +162 -0
- data/doc/install/win/index.xml +24 -0
- data/doc/install/win/installer.xml +31 -0
- data/doc/install/win/manual.xml +43 -0
- data/doc/manual.xml +69 -0
- data/doc/old/apache_cgi.txt +23 -0
- data/doc/old/fastcgi.txt +23 -0
- data/doc/old/mod_ruby.txt +21 -0
- data/doc/old/snippets.rdoc +183 -0
- data/doc/old/webrick.txt +23 -0
- data/doc/old/windows_cgi.txt +9 -0
- data/doc/tutorial.xml +14 -0
- data/doc/xsl/manual-multi.xsl +10 -0
- data/doc/xsl/manual-pdf.xsl +6 -0
- data/doc/xsl/manual-single.xsl +6 -0
- data/doc/xsl/manual.css +22 -0
- data/install.rb +1022 -0
- data/lib/formatter.rb +314 -0
- data/lib/html-parser.rb +429 -0
- data/lib/htmlrepair.rb +113 -0
- data/lib/htmlsplit.rb +842 -0
- data/lib/sgml-parser.rb +332 -0
- data/lib/web.rb +68 -0
- data/lib/web/assertinclude.rb +129 -0
- data/lib/web/config.rb +50 -0
- data/lib/web/connection.rb +1070 -0
- data/lib/web/convenience.rb +154 -0
- data/lib/web/formreader.rb +318 -0
- data/lib/web/htmlparser/html-parser.rb +429 -0
- data/lib/web/htmlparser/sgml-parser.rb +332 -0
- data/lib/web/htmltools/element.rb +296 -0
- data/lib/web/htmltools/stparser.rb +276 -0
- data/lib/web/htmltools/tags.rb +286 -0
- data/lib/web/htmltools/tree.rb +139 -0
- data/lib/web/htmltools/xmltree.rb +160 -0
- data/lib/web/htmltools/xpath.rb +71 -0
- data/lib/web/info.rb +63 -0
- data/lib/web/load.rb +210 -0
- data/lib/web/mime.rb +87 -0
- data/lib/web/phprb.rb +340 -0
- data/lib/web/resources/test/cookie.rb +33 -0
- data/lib/web/resources/test/counter.rb +20 -0
- data/lib/web/resources/test/multipart.rb +14 -0
- data/lib/web/resources/test/redirect.rb +8 -0
- data/lib/web/resources/test/stock.rb +33 -0
- data/lib/web/sapi/apache.rb +129 -0
- data/lib/web/sapi/fastcgi.rb +22 -0
- data/lib/web/sapi/install/apache.rb +180 -0
- data/lib/web/sapi/install/iis.rb +93 -0
- data/lib/web/sapi/install/macosx.rb +90 -0
- data/lib/web/sapi/webrick.rb +86 -0
- data/lib/web/session.rb +83 -0
- data/lib/web/shim/cgi.rb +129 -0
- data/lib/web/shim/rails.rb +175 -0
- data/lib/web/stringio.rb +78 -0
- data/lib/web/strscanparser.rb +24 -0
- data/lib/web/tagparser.rb +96 -0
- data/lib/web/testing.rb +666 -0
- data/lib/web/traceoutput.rb +75 -0
- data/lib/web/unit.rb +56 -0
- data/lib/web/upload.rb +59 -0
- data/lib/web/validate.rb +52 -0
- data/lib/web/wiki.rb +557 -0
- data/lib/web/wiki/linker.rb +72 -0
- data/lib/web/wiki/page.rb +201 -0
- data/lib/webunit.rb +27 -0
- data/lib/webunit/assert.rb +152 -0
- data/lib/webunit/converter.rb +154 -0
- data/lib/webunit/cookie.rb +118 -0
- data/lib/webunit/domwalker.rb +185 -0
- data/lib/webunit/exception.rb +14 -0
- data/lib/webunit/form.rb +116 -0
- data/lib/webunit/frame.rb +37 -0
- data/lib/webunit/htmlelem.rb +122 -0
- data/lib/webunit/image.rb +26 -0
- data/lib/webunit/jscript.rb +31 -0
- data/lib/webunit/link.rb +33 -0
- data/lib/webunit/params.rb +321 -0
- data/lib/webunit/parser.rb +229 -0
- data/lib/webunit/response.rb +464 -0
- data/lib/webunit/runtest.rb +41 -0
- data/lib/webunit/table.rb +148 -0
- data/lib/webunit/testcase.rb +45 -0
- data/lib/webunit/ui/cui/testrunner.rb +50 -0
- data/lib/webunit/utils.rb +68 -0
- data/lib/webunit/webunit.rb +28 -0
- data/test/dev/action.rb +83 -0
- data/test/dev/forms.rb +104 -0
- data/test/dev/forms2.rb +104 -0
- data/test/dev/parser.rb +17 -0
- data/test/dev/scripts/dump.rb +24 -0
- data/test/dev/scripts/makedist.rb +62 -0
- data/test/dev/scripts/uri.rb +41 -0
- data/test/dev/scripts/uri/common.rb +432 -0
- data/test/dev/scripts/uri/ftp.rb +149 -0
- data/test/dev/scripts/uri/generic.rb +1106 -0
- data/test/dev/scripts/uri/http.rb +76 -0
- data/test/dev/scripts/uri/https.rb +26 -0
- data/test/dev/scripts/uri/ldap.rb +238 -0
- data/test/dev/scripts/uri/mailto.rb +260 -0
- data/test/dev/scripts/urireg.rb +174 -0
- data/test/dev/simpledispatcher.rb +156 -0
- data/test/dev/test.action.rb +146 -0
- data/test/dev/test.formreader.rb +463 -0
- data/test/dev/test.simpledispatcher.rb +186 -0
- data/test/dev/webunit/conv/digit-0.rb +21 -0
- data/test/dev/webunit/conv/digit-1.rb +17 -0
- data/test/dev/webunit/conv/digit.rb +23 -0
- data/test/dev/webunit/conv/test_digit-0.rb +16 -0
- data/test/dev/webunit/conv/test_digit-1.rb +19 -0
- data/test/dev/webunit/conv/test_digit.rb +26 -0
- data/test/dev/webunit/conv/test_digit_view-0.rb +76 -0
- data/test/dev/webunit/conv/test_digit_view-1.rb +102 -0
- data/test/dev/webunit/conv/test_digit_view.rb +134 -0
- data/test/installation/htdocs/cgi_test.rb +296 -0
- data/test/installation/htdocs/test_install.rb +4 -0
- data/test/installation/runwebtest.rb +5 -0
- data/test/installation/test_cookie.rb +128 -0
- data/test/installation/test_form.rb +47 -0
- data/test/installation/test_multipart.rb +51 -0
- data/test/installation/test_request.rb +24 -0
- data/test/installation/test_response.rb +35 -0
- data/test/unit/htdocs/cookie.rb +32 -0
- data/test/unit/htdocs/multipart.rb +28 -0
- data/test/unit/htdocs/redirect.rb +12 -0
- data/test/unit/htdocs/simple.rb +13 -0
- data/test/unit/htdocs/stock.rb +33 -0
- data/test/unit/test_assert.rb +162 -0
- data/test/unit/test_cookie.rb +114 -0
- data/test/unit/test_domwalker.rb +77 -0
- data/test/unit/test_form.rb +42 -0
- data/test/unit/test_frame.rb +40 -0
- data/test/unit/test_htmlelem.rb +74 -0
- data/test/unit/test_image.rb +45 -0
- data/test/unit/test_jscript.rb +57 -0
- data/test/unit/test_link.rb +85 -0
- data/test/unit/test_multipart.rb +51 -0
- data/test/unit/test_params.rb +210 -0
- data/test/unit/test_parser.rb +53 -0
- data/test/unit/test_response.rb +150 -0
- data/test/unit/test_table.rb +70 -0
- data/test/unit/test_utils.rb +106 -0
- data/test/unit/test_webunit.rb +28 -0
- data/test/web/mod_ruby_stub.rb +39 -0
- data/test/web/test.assertinclude.rb +109 -0
- data/test/web/test.buffer.rb +182 -0
- data/test/web/test.code.loader.rb +78 -0
- data/test/web/test.config.rb +31 -0
- data/test/web/test.error.handling.rb +91 -0
- data/test/web/test.formreader-2.0.rb +352 -0
- data/test/web/test.load.rb +125 -0
- data/test/web/test.mime-type.rb +23 -0
- data/test/web/test.narf.cgi.rb +106 -0
- data/test/web/test.phprb.rb +239 -0
- data/test/web/test.request.rb +368 -0
- data/test/web/test.response.rb +637 -0
- data/test/web/test.ruby-web.rb +10 -0
- data/test/web/test.session.rb +50 -0
- data/test/web/test.shim.cgi.rb +96 -0
- data/test/web/test.tagparser.rb +65 -0
- data/test/web/test.template2.rb +297 -0
- data/test/web/test.testing2.rb +318 -0
- data/test/web/test.upload.rb +45 -0
- data/test/web/test.validate.rb +46 -0
- data/test/web/test.web.test.rb +495 -0
- data/test/wiki/test.history.rb +297 -0
- data/test/wiki/test.illustration_page.rb +287 -0
- data/test/wiki/test.linker.rb +197 -0
- data/test/wiki/test.tarpit.rb +56 -0
- data/test/wiki/test.wiki.rb +300 -0
- data/test/wikitestroot/admin.rb +7 -0
- data/test/wikitestroot/wiki.rb +6 -0
- metadata +234 -0
data/lib/web/config.rb
ADDED
|
@@ -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'> Line </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
|