webrick 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of webrick might be problematic. Click here for more details.
- data/README.txt +21 -0
- data/lib/webrick.rb +227 -0
- data/lib/webrick/accesslog.rb +151 -0
- data/lib/webrick/cgi.rb +260 -0
- data/lib/webrick/compat.rb +35 -0
- data/lib/webrick/config.rb +121 -0
- data/lib/webrick/cookie.rb +110 -0
- data/lib/webrick/htmlutils.rb +28 -0
- data/lib/webrick/httpauth.rb +95 -0
- data/lib/webrick/httpauth/authenticator.rb +112 -0
- data/lib/webrick/httpauth/basicauth.rb +108 -0
- data/lib/webrick/httpauth/digestauth.rb +392 -0
- data/lib/webrick/httpauth/htdigest.rb +128 -0
- data/lib/webrick/httpauth/htgroup.rb +93 -0
- data/lib/webrick/httpauth/htpasswd.rb +121 -0
- data/lib/webrick/httpauth/userdb.rb +52 -0
- data/lib/webrick/httpproxy.rb +305 -0
- data/lib/webrick/httprequest.rb +461 -0
- data/lib/webrick/httpresponse.rb +399 -0
- data/lib/webrick/https.rb +64 -0
- data/lib/webrick/httpserver.rb +264 -0
- data/lib/webrick/httpservlet.rb +22 -0
- data/lib/webrick/httpservlet/abstract.rb +153 -0
- data/lib/webrick/httpservlet/cgi_runner.rb +46 -0
- data/lib/webrick/httpservlet/cgihandler.rb +108 -0
- data/lib/webrick/httpservlet/erbhandler.rb +87 -0
- data/lib/webrick/httpservlet/filehandler.rb +470 -0
- data/lib/webrick/httpservlet/prochandler.rb +33 -0
- data/lib/webrick/httpstatus.rb +184 -0
- data/lib/webrick/httputils.rb +394 -0
- data/lib/webrick/httpversion.rb +49 -0
- data/lib/webrick/log.rb +136 -0
- data/lib/webrick/server.rb +218 -0
- data/lib/webrick/ssl.rb +127 -0
- data/lib/webrick/utils.rb +241 -0
- data/lib/webrick/version.rb +13 -0
- data/sample/webrick/demo-app.rb +66 -0
- data/sample/webrick/demo-multipart.cgi +12 -0
- data/sample/webrick/demo-servlet.rb +6 -0
- data/sample/webrick/demo-urlencoded.cgi +12 -0
- data/sample/webrick/hello.cgi +11 -0
- data/sample/webrick/hello.rb +8 -0
- data/sample/webrick/httpd.rb +23 -0
- data/sample/webrick/httpproxy.rb +25 -0
- data/sample/webrick/httpsd.rb +33 -0
- data/test/openssl/utils.rb +313 -0
- data/test/ruby/envutil.rb +208 -0
- data/test/webrick/test_cgi.rb +134 -0
- data/test/webrick/test_cookie.rb +131 -0
- data/test/webrick/test_filehandler.rb +285 -0
- data/test/webrick/test_httpauth.rb +167 -0
- data/test/webrick/test_httpproxy.rb +282 -0
- data/test/webrick/test_httprequest.rb +411 -0
- data/test/webrick/test_httpresponse.rb +49 -0
- data/test/webrick/test_httpserver.rb +305 -0
- data/test/webrick/test_httputils.rb +96 -0
- data/test/webrick/test_httpversion.rb +40 -0
- data/test/webrick/test_server.rb +67 -0
- data/test/webrick/test_utils.rb +64 -0
- data/test/webrick/utils.rb +58 -0
- data/test/webrick/webrick.cgi +36 -0
- data/test/webrick/webrick_long_filename.cgi +36 -0
- metadata +106 -0
@@ -0,0 +1,399 @@
|
|
1
|
+
#
|
2
|
+
# httpresponse.rb -- HTTPResponse Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'time'
|
12
|
+
require 'webrick/httpversion'
|
13
|
+
require 'webrick/htmlutils'
|
14
|
+
require 'webrick/httputils'
|
15
|
+
require 'webrick/httpstatus'
|
16
|
+
|
17
|
+
module WEBrick
|
18
|
+
##
|
19
|
+
# An HTTP response.
|
20
|
+
|
21
|
+
class HTTPResponse
|
22
|
+
attr_reader :http_version, :status, :header
|
23
|
+
attr_reader :cookies
|
24
|
+
attr_accessor :reason_phrase
|
25
|
+
|
26
|
+
##
|
27
|
+
# Body may be a String or IO subclass.
|
28
|
+
|
29
|
+
attr_accessor :body
|
30
|
+
|
31
|
+
attr_accessor :request_method, :request_uri, :request_http_version
|
32
|
+
attr_accessor :filename
|
33
|
+
attr_accessor :keep_alive
|
34
|
+
attr_reader :config, :sent_size
|
35
|
+
|
36
|
+
##
|
37
|
+
# Creates a new HTTP response object
|
38
|
+
|
39
|
+
def initialize(config)
|
40
|
+
@config = config
|
41
|
+
@buffer_size = config[:OutputBufferSize]
|
42
|
+
@logger = config[:Logger]
|
43
|
+
@header = Hash.new
|
44
|
+
@status = HTTPStatus::RC_OK
|
45
|
+
@reason_phrase = nil
|
46
|
+
@http_version = HTTPVersion::convert(@config[:HTTPVersion])
|
47
|
+
@body = ''
|
48
|
+
@keep_alive = true
|
49
|
+
@cookies = []
|
50
|
+
@request_method = nil
|
51
|
+
@request_uri = nil
|
52
|
+
@request_http_version = @http_version # temporary
|
53
|
+
@chunked = false
|
54
|
+
@filename = nil
|
55
|
+
@sent_size = 0
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# The response's HTTP status line
|
60
|
+
|
61
|
+
def status_line
|
62
|
+
"HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Sets the response's status to the +status+ code
|
67
|
+
|
68
|
+
def status=(status)
|
69
|
+
@status = status
|
70
|
+
@reason_phrase = HTTPStatus::reason_phrase(status)
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Retrieves the response header +field+
|
75
|
+
|
76
|
+
def [](field)
|
77
|
+
@header[field.downcase]
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Sets the response header +field+ to +value+
|
82
|
+
|
83
|
+
def []=(field, value)
|
84
|
+
@header[field.downcase] = value.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# The content-length header
|
89
|
+
|
90
|
+
def content_length
|
91
|
+
if len = self['content-length']
|
92
|
+
return Integer(len)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Sets the content-length header to +len+
|
98
|
+
|
99
|
+
def content_length=(len)
|
100
|
+
self['content-length'] = len.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# The content-type header
|
105
|
+
|
106
|
+
def content_type
|
107
|
+
self['content-type']
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Sets the content-type header to +type+
|
112
|
+
|
113
|
+
def content_type=(type)
|
114
|
+
self['content-type'] = type
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Iterates over each header in the resopnse
|
119
|
+
|
120
|
+
def each
|
121
|
+
@header.each{|field, value| yield(field, value) }
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Will this response body be returned using chunked transfer-encoding?
|
126
|
+
|
127
|
+
def chunked?
|
128
|
+
@chunked
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Enables chunked transfer encoding.
|
133
|
+
|
134
|
+
def chunked=(val)
|
135
|
+
@chunked = val ? true : false
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Will this response's connection be kept alive?
|
140
|
+
|
141
|
+
def keep_alive?
|
142
|
+
@keep_alive
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Sends the response on +socket+
|
147
|
+
|
148
|
+
def send_response(socket)
|
149
|
+
begin
|
150
|
+
setup_header()
|
151
|
+
send_header(socket)
|
152
|
+
send_body(socket)
|
153
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
|
154
|
+
@logger.debug(ex)
|
155
|
+
@keep_alive = false
|
156
|
+
rescue Exception => ex
|
157
|
+
@logger.error(ex)
|
158
|
+
@keep_alive = false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Sets up the headers for sending
|
164
|
+
|
165
|
+
def setup_header()
|
166
|
+
@reason_phrase ||= HTTPStatus::reason_phrase(@status)
|
167
|
+
@header['server'] ||= @config[:ServerSoftware]
|
168
|
+
@header['date'] ||= Time.now.httpdate
|
169
|
+
|
170
|
+
# HTTP/0.9 features
|
171
|
+
if @request_http_version < "1.0"
|
172
|
+
@http_version = HTTPVersion.new("0.9")
|
173
|
+
@keep_alive = false
|
174
|
+
end
|
175
|
+
|
176
|
+
# HTTP/1.0 features
|
177
|
+
if @request_http_version < "1.1"
|
178
|
+
if chunked?
|
179
|
+
@chunked = false
|
180
|
+
ver = @request_http_version.to_s
|
181
|
+
msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
|
182
|
+
@logger.warn(msg)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Determine the message length (RFC2616 -- 4.4 Message Length)
|
187
|
+
if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
|
188
|
+
@header.delete('content-length')
|
189
|
+
@body = ""
|
190
|
+
elsif chunked?
|
191
|
+
@header["transfer-encoding"] = "chunked"
|
192
|
+
@header.delete('content-length')
|
193
|
+
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
194
|
+
@header.delete('content-length')
|
195
|
+
elsif @header['content-length'].nil?
|
196
|
+
unless @body.is_a?(IO)
|
197
|
+
@header['content-length'] = @body ? @body.bytesize : 0
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Keep-Alive connection.
|
202
|
+
if @header['connection'] == "close"
|
203
|
+
@keep_alive = false
|
204
|
+
elsif keep_alive?
|
205
|
+
if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
|
206
|
+
@header['connection'] = "Keep-Alive"
|
207
|
+
else
|
208
|
+
msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
|
209
|
+
@logger.warn(msg)
|
210
|
+
@header['connection'] = "close"
|
211
|
+
@keep_alive = false
|
212
|
+
end
|
213
|
+
else
|
214
|
+
@header['connection'] = "close"
|
215
|
+
end
|
216
|
+
|
217
|
+
# Location is a single absoluteURI.
|
218
|
+
if location = @header['location']
|
219
|
+
if @request_uri
|
220
|
+
@header['location'] = @request_uri.merge(location)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Sends the headers on +socket+
|
227
|
+
|
228
|
+
def send_header(socket)
|
229
|
+
if @http_version.major > 0
|
230
|
+
data = status_line()
|
231
|
+
@header.each{|key, value|
|
232
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
|
233
|
+
data << "#{tmp}: #{value}" << CRLF
|
234
|
+
}
|
235
|
+
@cookies.each{|cookie|
|
236
|
+
data << "Set-Cookie: " << cookie.to_s << CRLF
|
237
|
+
}
|
238
|
+
data << CRLF
|
239
|
+
_write_data(socket, data)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Sends the body on +socket+
|
245
|
+
|
246
|
+
def send_body(socket)
|
247
|
+
case @body
|
248
|
+
when IO then send_body_io(socket)
|
249
|
+
else send_body_string(socket)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_s # :nodoc:
|
254
|
+
ret = ""
|
255
|
+
send_response(ret)
|
256
|
+
ret
|
257
|
+
end
|
258
|
+
|
259
|
+
##
|
260
|
+
# Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
|
261
|
+
#
|
262
|
+
# Example:
|
263
|
+
#
|
264
|
+
# res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
|
265
|
+
|
266
|
+
def set_redirect(status, url)
|
267
|
+
@body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
|
268
|
+
@header['location'] = url.to_s
|
269
|
+
raise status
|
270
|
+
end
|
271
|
+
|
272
|
+
##
|
273
|
+
# Creates an error page for exception +ex+ with an optional +backtrace+
|
274
|
+
|
275
|
+
def set_error(ex, backtrace=false)
|
276
|
+
case ex
|
277
|
+
when HTTPStatus::Status
|
278
|
+
@keep_alive = false if HTTPStatus::error?(ex.code)
|
279
|
+
self.status = ex.code
|
280
|
+
else
|
281
|
+
@keep_alive = false
|
282
|
+
self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
|
283
|
+
end
|
284
|
+
@header['content-type'] = "text/html; charset=ISO-8859-1"
|
285
|
+
|
286
|
+
if respond_to?(:create_error_page)
|
287
|
+
create_error_page()
|
288
|
+
return
|
289
|
+
end
|
290
|
+
|
291
|
+
if @request_uri
|
292
|
+
host, port = @request_uri.host, @request_uri.port
|
293
|
+
else
|
294
|
+
host, port = @config[:ServerName], @config[:Port]
|
295
|
+
end
|
296
|
+
|
297
|
+
@body = ''
|
298
|
+
@body << <<-_end_of_html_
|
299
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
300
|
+
<HTML>
|
301
|
+
<HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
|
302
|
+
<BODY>
|
303
|
+
<H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
|
304
|
+
#{HTMLUtils::escape(ex.message)}
|
305
|
+
<HR>
|
306
|
+
_end_of_html_
|
307
|
+
|
308
|
+
if backtrace && $DEBUG
|
309
|
+
@body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
|
310
|
+
@body << "#{HTMLUtils::escape(ex.message)}"
|
311
|
+
@body << "<PRE>"
|
312
|
+
ex.backtrace.each{|line| @body << "\t#{line}\n"}
|
313
|
+
@body << "</PRE><HR>"
|
314
|
+
end
|
315
|
+
|
316
|
+
@body << <<-_end_of_html_
|
317
|
+
<ADDRESS>
|
318
|
+
#{HTMLUtils::escape(@config[:ServerSoftware])} at
|
319
|
+
#{host}:#{port}
|
320
|
+
</ADDRESS>
|
321
|
+
</BODY>
|
322
|
+
</HTML>
|
323
|
+
_end_of_html_
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
def send_body_io(socket)
|
329
|
+
begin
|
330
|
+
if @request_method == "HEAD"
|
331
|
+
# do nothing
|
332
|
+
elsif chunked?
|
333
|
+
while buf = @body.read(@buffer_size)
|
334
|
+
next if buf.empty?
|
335
|
+
data = ""
|
336
|
+
data << format("%x", buf.bytesize) << CRLF
|
337
|
+
data << buf << CRLF
|
338
|
+
_write_data(socket, data)
|
339
|
+
@sent_size += buf.bytesize
|
340
|
+
end
|
341
|
+
_write_data(socket, "0#{CRLF}#{CRLF}")
|
342
|
+
else
|
343
|
+
size = @header['content-length'].to_i
|
344
|
+
_send_file(socket, @body, 0, size)
|
345
|
+
@sent_size = size
|
346
|
+
end
|
347
|
+
ensure
|
348
|
+
@body.close
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def send_body_string(socket)
|
353
|
+
if @request_method == "HEAD"
|
354
|
+
# do nothing
|
355
|
+
elsif chunked?
|
356
|
+
body ? @body.bytesize : 0
|
357
|
+
while buf = @body[@sent_size, @buffer_size]
|
358
|
+
break if buf.empty?
|
359
|
+
data = ""
|
360
|
+
data << format("%x", buf.bytesize) << CRLF
|
361
|
+
data << buf << CRLF
|
362
|
+
_write_data(socket, data)
|
363
|
+
@sent_size += buf.bytesize
|
364
|
+
end
|
365
|
+
_write_data(socket, "0#{CRLF}#{CRLF}")
|
366
|
+
else
|
367
|
+
if @body && @body.bytesize > 0
|
368
|
+
_write_data(socket, @body)
|
369
|
+
@sent_size = @body.bytesize
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def _send_file(output, input, offset, size)
|
375
|
+
while offset > 0
|
376
|
+
sz = @buffer_size < size ? @buffer_size : size
|
377
|
+
buf = input.read(sz)
|
378
|
+
offset -= buf.bytesize
|
379
|
+
end
|
380
|
+
|
381
|
+
if size == 0
|
382
|
+
while buf = input.read(@buffer_size)
|
383
|
+
_write_data(output, buf)
|
384
|
+
end
|
385
|
+
else
|
386
|
+
while size > 0
|
387
|
+
sz = @buffer_size < size ? @buffer_size : size
|
388
|
+
buf = input.read(sz)
|
389
|
+
_write_data(output, buf)
|
390
|
+
size -= buf.bytesize
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def _write_data(socket, data)
|
396
|
+
socket << data
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# https.rb -- SSL/TLS enhancement for HTTPServer
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2001 GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'webrick/ssl'
|
12
|
+
|
13
|
+
module WEBrick
|
14
|
+
module Config
|
15
|
+
HTTP.update(SSL)
|
16
|
+
end
|
17
|
+
|
18
|
+
class HTTPRequest
|
19
|
+
attr_reader :cipher, :server_cert, :client_cert
|
20
|
+
|
21
|
+
alias orig_parse parse
|
22
|
+
|
23
|
+
def parse(socket=nil)
|
24
|
+
if socket.respond_to?(:cert)
|
25
|
+
@server_cert = socket.cert || @config[:SSLCertificate]
|
26
|
+
@client_cert = socket.peer_cert
|
27
|
+
@client_cert_chain = socket.peer_cert_chain
|
28
|
+
@cipher = socket.cipher
|
29
|
+
end
|
30
|
+
orig_parse(socket)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias orig_parse_uri parse_uri
|
34
|
+
|
35
|
+
def parse_uri(str, scheme="https")
|
36
|
+
if server_cert
|
37
|
+
return orig_parse_uri(str, scheme)
|
38
|
+
end
|
39
|
+
return orig_parse_uri(str)
|
40
|
+
end
|
41
|
+
private :parse_uri
|
42
|
+
|
43
|
+
alias orig_meta_vars meta_vars
|
44
|
+
|
45
|
+
def meta_vars
|
46
|
+
meta = orig_meta_vars
|
47
|
+
if server_cert
|
48
|
+
meta["HTTPS"] = "on"
|
49
|
+
meta["SSL_SERVER_CERT"] = @server_cert.to_pem
|
50
|
+
meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : ""
|
51
|
+
if @client_cert_chain
|
52
|
+
@client_cert_chain.each_with_index{|cert, i|
|
53
|
+
meta["SSL_CLIENT_CERT_CHAIN_#{i}"] = cert.to_pem
|
54
|
+
}
|
55
|
+
end
|
56
|
+
meta["SSL_CIPHER"] = @cipher[0]
|
57
|
+
meta["SSL_PROTOCOL"] = @cipher[1]
|
58
|
+
meta["SSL_CIPHER_USEKEYSIZE"] = @cipher[2].to_s
|
59
|
+
meta["SSL_CIPHER_ALGKEYSIZE"] = @cipher[3].to_s
|
60
|
+
end
|
61
|
+
meta
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|