webrick 1.3.1 → 1.6.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.
Potentially problematic release.
This version of webrick might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/webrick.rb +7 -7
- data/lib/webrick/accesslog.rb +12 -6
- data/lib/webrick/cgi.rb +58 -5
- data/lib/webrick/compat.rb +2 -1
- data/lib/webrick/config.rb +47 -10
- data/lib/webrick/cookie.rb +69 -7
- data/lib/webrick/htmlutils.rb +4 -2
- data/lib/webrick/httpauth.rb +6 -5
- data/lib/webrick/httpauth/authenticator.rb +13 -8
- data/lib/webrick/httpauth/basicauth.rb +16 -8
- data/lib/webrick/httpauth/digestauth.rb +35 -32
- data/lib/webrick/httpauth/htdigest.rb +12 -8
- data/lib/webrick/httpauth/htgroup.rb +10 -6
- data/lib/webrick/httpauth/htpasswd.rb +46 -9
- data/lib/webrick/httpauth/userdb.rb +1 -0
- data/lib/webrick/httpproxy.rb +93 -48
- data/lib/webrick/httprequest.rb +201 -31
- data/lib/webrick/httpresponse.rb +235 -70
- data/lib/webrick/https.rb +90 -2
- data/lib/webrick/httpserver.rb +45 -15
- data/lib/webrick/httpservlet.rb +6 -5
- data/lib/webrick/httpservlet/abstract.rb +5 -6
- data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
- data/lib/webrick/httpservlet/cgihandler.rb +29 -11
- data/lib/webrick/httpservlet/erbhandler.rb +4 -3
- data/lib/webrick/httpservlet/filehandler.rb +136 -65
- data/lib/webrick/httpservlet/prochandler.rb +15 -1
- data/lib/webrick/httpstatus.rb +24 -14
- data/lib/webrick/httputils.rb +134 -17
- data/lib/webrick/httpversion.rb +28 -1
- data/lib/webrick/log.rb +25 -5
- data/lib/webrick/server.rb +234 -74
- data/lib/webrick/ssl.rb +100 -12
- data/lib/webrick/utils.rb +98 -69
- data/lib/webrick/version.rb +6 -1
- data/webrick.gemspec +76 -0
- metadata +73 -72
- data/README.txt +0 -21
- data/sample/webrick/demo-app.rb +0 -66
- data/sample/webrick/demo-multipart.cgi +0 -12
- data/sample/webrick/demo-servlet.rb +0 -6
- data/sample/webrick/demo-urlencoded.cgi +0 -12
- data/sample/webrick/hello.cgi +0 -11
- data/sample/webrick/hello.rb +0 -8
- data/sample/webrick/httpd.rb +0 -23
- data/sample/webrick/httpproxy.rb +0 -25
- data/sample/webrick/httpsd.rb +0 -33
- data/test/openssl/utils.rb +0 -313
- data/test/ruby/envutil.rb +0 -208
- data/test/webrick/test_cgi.rb +0 -134
- data/test/webrick/test_cookie.rb +0 -131
- data/test/webrick/test_filehandler.rb +0 -285
- data/test/webrick/test_httpauth.rb +0 -167
- data/test/webrick/test_httpproxy.rb +0 -282
- data/test/webrick/test_httprequest.rb +0 -411
- data/test/webrick/test_httpresponse.rb +0 -49
- data/test/webrick/test_httpserver.rb +0 -305
- data/test/webrick/test_httputils.rb +0 -96
- data/test/webrick/test_httpversion.rb +0 -40
- data/test/webrick/test_server.rb +0 -67
- data/test/webrick/test_utils.rb +0 -64
- data/test/webrick/utils.rb +0 -58
- data/test/webrick/webrick.cgi +0 -36
- data/test/webrick/webrick_long_filename.cgi +0 -36
data/lib/webrick/httpresponse.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpresponse.rb -- HTTPResponse Class
|
3
4
|
#
|
@@ -9,32 +10,104 @@
|
|
9
10
|
# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
|
10
11
|
|
11
12
|
require 'time'
|
12
|
-
require '
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
require 'uri'
|
14
|
+
require_relative 'httpversion'
|
15
|
+
require_relative 'htmlutils'
|
16
|
+
require_relative 'httputils'
|
17
|
+
require_relative 'httpstatus'
|
16
18
|
|
17
19
|
module WEBrick
|
18
20
|
##
|
19
|
-
# An HTTP response.
|
21
|
+
# An HTTP response. This is filled in by the service or do_* methods of a
|
22
|
+
# WEBrick HTTP Servlet.
|
20
23
|
|
21
24
|
class HTTPResponse
|
22
|
-
|
25
|
+
class InvalidHeader < StandardError
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# HTTP Response version
|
30
|
+
|
31
|
+
attr_reader :http_version
|
32
|
+
|
33
|
+
##
|
34
|
+
# Response status code (200)
|
35
|
+
|
36
|
+
attr_reader :status
|
37
|
+
|
38
|
+
##
|
39
|
+
# Response header
|
40
|
+
|
41
|
+
attr_reader :header
|
42
|
+
|
43
|
+
##
|
44
|
+
# Response cookies
|
45
|
+
|
23
46
|
attr_reader :cookies
|
47
|
+
|
48
|
+
##
|
49
|
+
# Response reason phrase ("OK")
|
50
|
+
|
24
51
|
attr_accessor :reason_phrase
|
25
52
|
|
26
53
|
##
|
27
|
-
# Body may be
|
54
|
+
# Body may be:
|
55
|
+
# * a String;
|
56
|
+
# * an IO-like object that responds to +#read+ and +#readpartial+;
|
57
|
+
# * a Proc-like object that responds to +#call+.
|
58
|
+
#
|
59
|
+
# In the latter case, either #chunked= should be set to +true+,
|
60
|
+
# or <code>header['content-length']</code> explicitly provided.
|
61
|
+
# Example:
|
62
|
+
#
|
63
|
+
# server.mount_proc '/' do |req, res|
|
64
|
+
# res.chunked = true
|
65
|
+
# # or
|
66
|
+
# # res.header['content-length'] = 10
|
67
|
+
# res.body = proc { |out| out.write(Time.now.to_s) }
|
68
|
+
# end
|
28
69
|
|
29
70
|
attr_accessor :body
|
30
71
|
|
31
|
-
|
72
|
+
##
|
73
|
+
# Request method for this response
|
74
|
+
|
75
|
+
attr_accessor :request_method
|
76
|
+
|
77
|
+
##
|
78
|
+
# Request URI for this response
|
79
|
+
|
80
|
+
attr_accessor :request_uri
|
81
|
+
|
82
|
+
##
|
83
|
+
# Request HTTP version for this response
|
84
|
+
|
85
|
+
attr_accessor :request_http_version
|
86
|
+
|
87
|
+
##
|
88
|
+
# Filename of the static file in this response. Only used by the
|
89
|
+
# FileHandler servlet.
|
90
|
+
|
32
91
|
attr_accessor :filename
|
92
|
+
|
93
|
+
##
|
94
|
+
# Is this a keep-alive response?
|
95
|
+
|
33
96
|
attr_accessor :keep_alive
|
34
|
-
attr_reader :config, :sent_size
|
35
97
|
|
36
98
|
##
|
37
|
-
#
|
99
|
+
# Configuration for this response
|
100
|
+
|
101
|
+
attr_reader :config
|
102
|
+
|
103
|
+
##
|
104
|
+
# Bytes sent in this response
|
105
|
+
|
106
|
+
attr_reader :sent_size
|
107
|
+
|
108
|
+
##
|
109
|
+
# Creates a new HTTP response object. WEBrick::Config::HTTP is the
|
110
|
+
# default configuration.
|
38
111
|
|
39
112
|
def initialize(config)
|
40
113
|
@config = config
|
@@ -53,13 +126,14 @@ module WEBrick
|
|
53
126
|
@chunked = false
|
54
127
|
@filename = nil
|
55
128
|
@sent_size = 0
|
129
|
+
@bodytempfile = nil
|
56
130
|
end
|
57
131
|
|
58
132
|
##
|
59
133
|
# The response's HTTP status line
|
60
134
|
|
61
135
|
def status_line
|
62
|
-
"HTTP/#@http_version #@status #@reason_phrase
|
136
|
+
"HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
|
63
137
|
end
|
64
138
|
|
65
139
|
##
|
@@ -81,6 +155,7 @@ module WEBrick
|
|
81
155
|
# Sets the response header +field+ to +value+
|
82
156
|
|
83
157
|
def []=(field, value)
|
158
|
+
@chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
|
84
159
|
@header[field.downcase] = value.to_s
|
85
160
|
end
|
86
161
|
|
@@ -115,7 +190,7 @@ module WEBrick
|
|
115
190
|
end
|
116
191
|
|
117
192
|
##
|
118
|
-
# Iterates over each header in the
|
193
|
+
# Iterates over each header in the response
|
119
194
|
|
120
195
|
def each
|
121
196
|
@header.each{|field, value| yield(field, value) }
|
@@ -145,7 +220,7 @@ module WEBrick
|
|
145
220
|
##
|
146
221
|
# Sends the response on +socket+
|
147
222
|
|
148
|
-
def send_response(socket)
|
223
|
+
def send_response(socket) # :nodoc:
|
149
224
|
begin
|
150
225
|
setup_header()
|
151
226
|
send_header(socket)
|
@@ -162,7 +237,7 @@ module WEBrick
|
|
162
237
|
##
|
163
238
|
# Sets up the headers for sending
|
164
239
|
|
165
|
-
def setup_header()
|
240
|
+
def setup_header() # :nodoc:
|
166
241
|
@reason_phrase ||= HTTPStatus::reason_phrase(@status)
|
167
242
|
@header['server'] ||= @config[:ServerSoftware]
|
168
243
|
@header['date'] ||= Time.now.httpdate
|
@@ -193,8 +268,11 @@ module WEBrick
|
|
193
268
|
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
194
269
|
@header.delete('content-length')
|
195
270
|
elsif @header['content-length'].nil?
|
196
|
-
|
197
|
-
|
271
|
+
if @body.respond_to? :readpartial
|
272
|
+
elsif @body.respond_to? :call
|
273
|
+
make_body_tempfile
|
274
|
+
else
|
275
|
+
@header['content-length'] = (@body ? @body.bytesize : 0).to_s
|
198
276
|
end
|
199
277
|
end
|
200
278
|
|
@@ -217,45 +295,74 @@ module WEBrick
|
|
217
295
|
# Location is a single absoluteURI.
|
218
296
|
if location = @header['location']
|
219
297
|
if @request_uri
|
220
|
-
@header['location'] = @request_uri.merge(location)
|
298
|
+
@header['location'] = @request_uri.merge(location).to_s
|
221
299
|
end
|
222
300
|
end
|
223
301
|
end
|
224
302
|
|
303
|
+
def make_body_tempfile # :nodoc:
|
304
|
+
return if @bodytempfile
|
305
|
+
bodytempfile = Tempfile.create("webrick")
|
306
|
+
if @body.nil?
|
307
|
+
# nothing
|
308
|
+
elsif @body.respond_to? :readpartial
|
309
|
+
IO.copy_stream(@body, bodytempfile)
|
310
|
+
@body.close
|
311
|
+
elsif @body.respond_to? :call
|
312
|
+
@body.call(bodytempfile)
|
313
|
+
else
|
314
|
+
bodytempfile.write @body
|
315
|
+
end
|
316
|
+
bodytempfile.rewind
|
317
|
+
@body = @bodytempfile = bodytempfile
|
318
|
+
@header['content-length'] = bodytempfile.stat.size.to_s
|
319
|
+
end
|
320
|
+
|
321
|
+
def remove_body_tempfile # :nodoc:
|
322
|
+
if @bodytempfile
|
323
|
+
@bodytempfile.close
|
324
|
+
File.unlink @bodytempfile.path
|
325
|
+
@bodytempfile = nil
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
|
225
330
|
##
|
226
331
|
# Sends the headers on +socket+
|
227
332
|
|
228
|
-
def send_header(socket)
|
333
|
+
def send_header(socket) # :nodoc:
|
229
334
|
if @http_version.major > 0
|
230
335
|
data = status_line()
|
231
336
|
@header.each{|key, value|
|
232
337
|
tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
|
233
|
-
data << "#{tmp}: #{value}" << CRLF
|
338
|
+
data << "#{tmp}: #{check_header(value)}" << CRLF
|
234
339
|
}
|
235
340
|
@cookies.each{|cookie|
|
236
|
-
data << "Set-Cookie: " << cookie.to_s << CRLF
|
341
|
+
data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
|
237
342
|
}
|
238
343
|
data << CRLF
|
239
|
-
|
344
|
+
socket.write(data)
|
240
345
|
end
|
346
|
+
rescue InvalidHeader => e
|
347
|
+
@header.clear
|
348
|
+
@cookies.clear
|
349
|
+
set_error e
|
350
|
+
retry
|
241
351
|
end
|
242
352
|
|
243
353
|
##
|
244
354
|
# Sends the body on +socket+
|
245
355
|
|
246
|
-
def send_body(socket)
|
247
|
-
|
248
|
-
|
249
|
-
|
356
|
+
def send_body(socket) # :nodoc:
|
357
|
+
if @body.respond_to? :readpartial then
|
358
|
+
send_body_io(socket)
|
359
|
+
elsif @body.respond_to?(:call) then
|
360
|
+
send_body_proc(socket)
|
361
|
+
else
|
362
|
+
send_body_string(socket)
|
250
363
|
end
|
251
364
|
end
|
252
365
|
|
253
|
-
def to_s # :nodoc:
|
254
|
-
ret = ""
|
255
|
-
send_response(ret)
|
256
|
-
ret
|
257
|
-
end
|
258
|
-
|
259
366
|
##
|
260
367
|
# Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
|
261
368
|
#
|
@@ -264,8 +371,9 @@ module WEBrick
|
|
264
371
|
# res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
|
265
372
|
|
266
373
|
def set_redirect(status, url)
|
267
|
-
|
268
|
-
@
|
374
|
+
url = URI(url).to_s
|
375
|
+
@body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
|
376
|
+
@header['location'] = url
|
269
377
|
raise status
|
270
378
|
end
|
271
379
|
|
@@ -294,6 +402,23 @@ module WEBrick
|
|
294
402
|
host, port = @config[:ServerName], @config[:Port]
|
295
403
|
end
|
296
404
|
|
405
|
+
error_body(backtrace, ex, host, port)
|
406
|
+
end
|
407
|
+
|
408
|
+
private
|
409
|
+
|
410
|
+
def check_header(header_value)
|
411
|
+
header_value = header_value.to_s
|
412
|
+
if /[\r\n]/ =~ header_value
|
413
|
+
raise InvalidHeader
|
414
|
+
else
|
415
|
+
header_value
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# :stopdoc:
|
420
|
+
|
421
|
+
def error_body(backtrace, ex, host, port)
|
297
422
|
@body = ''
|
298
423
|
@body << <<-_end_of_html_
|
299
424
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
@@ -323,30 +448,44 @@ module WEBrick
|
|
323
448
|
_end_of_html_
|
324
449
|
end
|
325
450
|
|
326
|
-
private
|
327
|
-
|
328
451
|
def send_body_io(socket)
|
329
452
|
begin
|
330
453
|
if @request_method == "HEAD"
|
331
454
|
# do nothing
|
332
455
|
elsif chunked?
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
data
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
456
|
+
buf = ''
|
457
|
+
begin
|
458
|
+
@body.readpartial(@buffer_size, buf)
|
459
|
+
size = buf.bytesize
|
460
|
+
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
461
|
+
socket.write(data)
|
462
|
+
data.clear
|
463
|
+
@sent_size += size
|
464
|
+
rescue EOFError
|
465
|
+
break
|
466
|
+
end while true
|
467
|
+
buf.clear
|
468
|
+
socket.write("0#{CRLF}#{CRLF}")
|
342
469
|
else
|
343
|
-
|
344
|
-
|
345
|
-
|
470
|
+
if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
|
471
|
+
offset = $1.to_i
|
472
|
+
size = $2.to_i - offset + 1
|
473
|
+
else
|
474
|
+
offset = nil
|
475
|
+
size = @header['content-length']
|
476
|
+
size = size.to_i if size
|
477
|
+
end
|
478
|
+
begin
|
479
|
+
@sent_size = IO.copy_stream(@body, socket, size, offset)
|
480
|
+
rescue NotImplementedError
|
481
|
+
@body.seek(offset, IO::SEEK_SET)
|
482
|
+
@sent_size = IO.copy_stream(@body, socket, size)
|
483
|
+
end
|
346
484
|
end
|
347
485
|
ensure
|
348
486
|
@body.close
|
349
487
|
end
|
488
|
+
remove_body_tempfile
|
350
489
|
end
|
351
490
|
|
352
491
|
def send_body_string(socket)
|
@@ -356,44 +495,70 @@ module WEBrick
|
|
356
495
|
body ? @body.bytesize : 0
|
357
496
|
while buf = @body[@sent_size, @buffer_size]
|
358
497
|
break if buf.empty?
|
359
|
-
|
360
|
-
data
|
361
|
-
|
362
|
-
|
363
|
-
@sent_size +=
|
498
|
+
size = buf.bytesize
|
499
|
+
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
500
|
+
buf.clear
|
501
|
+
socket.write(data)
|
502
|
+
@sent_size += size
|
364
503
|
end
|
365
|
-
|
504
|
+
socket.write("0#{CRLF}#{CRLF}")
|
366
505
|
else
|
367
506
|
if @body && @body.bytesize > 0
|
368
|
-
|
507
|
+
socket.write(@body)
|
369
508
|
@sent_size = @body.bytesize
|
370
509
|
end
|
371
510
|
end
|
372
511
|
end
|
373
512
|
|
374
|
-
def
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
if size == 0
|
382
|
-
while buf = input.read(@buffer_size)
|
383
|
-
_write_data(output, buf)
|
384
|
-
end
|
513
|
+
def send_body_proc(socket)
|
514
|
+
if @request_method == "HEAD"
|
515
|
+
# do nothing
|
516
|
+
elsif chunked?
|
517
|
+
@body.call(ChunkedWrapper.new(socket, self))
|
518
|
+
socket.write("0#{CRLF}#{CRLF}")
|
385
519
|
else
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
520
|
+
size = @header['content-length'].to_i
|
521
|
+
if @bodytempfile
|
522
|
+
@bodytempfile.rewind
|
523
|
+
IO.copy_stream(@bodytempfile, socket)
|
524
|
+
else
|
525
|
+
@body.call(socket)
|
391
526
|
end
|
527
|
+
@sent_size = size
|
392
528
|
end
|
393
529
|
end
|
394
530
|
|
531
|
+
class ChunkedWrapper
|
532
|
+
def initialize(socket, resp)
|
533
|
+
@socket = socket
|
534
|
+
@resp = resp
|
535
|
+
end
|
536
|
+
|
537
|
+
def write(buf)
|
538
|
+
return 0 if buf.empty?
|
539
|
+
socket = @socket
|
540
|
+
@resp.instance_eval {
|
541
|
+
size = buf.bytesize
|
542
|
+
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
543
|
+
socket.write(data)
|
544
|
+
data.clear
|
545
|
+
@sent_size += size
|
546
|
+
size
|
547
|
+
}
|
548
|
+
end
|
549
|
+
|
550
|
+
def <<(*buf)
|
551
|
+
write(buf)
|
552
|
+
self
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
# preserved for compatibility with some 3rd-party handlers
|
395
557
|
def _write_data(socket, data)
|
396
558
|
socket << data
|
397
559
|
end
|
560
|
+
|
561
|
+
# :startdoc:
|
398
562
|
end
|
563
|
+
|
399
564
|
end
|