webrick 1.3.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|