rubysl-webrick 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rubysl/webrick.rb +2 -0
- data/lib/rubysl/webrick/version.rb +5 -0
- data/lib/rubysl/webrick/webrick.rb +29 -0
- data/lib/webrick.rb +1 -0
- data/lib/webrick/accesslog.rb +67 -0
- data/lib/webrick/cgi.rb +257 -0
- data/lib/webrick/compat.rb +15 -0
- data/lib/webrick/config.rb +97 -0
- data/lib/webrick/cookie.rb +110 -0
- data/lib/webrick/htmlutils.rb +25 -0
- data/lib/webrick/httpauth.rb +45 -0
- data/lib/webrick/httpauth/authenticator.rb +79 -0
- data/lib/webrick/httpauth/basicauth.rb +65 -0
- data/lib/webrick/httpauth/digestauth.rb +343 -0
- data/lib/webrick/httpauth/htdigest.rb +91 -0
- data/lib/webrick/httpauth/htgroup.rb +61 -0
- data/lib/webrick/httpauth/htpasswd.rb +83 -0
- data/lib/webrick/httpauth/userdb.rb +29 -0
- data/lib/webrick/httpproxy.rb +254 -0
- data/lib/webrick/httprequest.rb +365 -0
- data/lib/webrick/httpresponse.rb +327 -0
- data/lib/webrick/https.rb +63 -0
- data/lib/webrick/httpserver.rb +210 -0
- data/lib/webrick/httpservlet.rb +22 -0
- data/lib/webrick/httpservlet/abstract.rb +71 -0
- data/lib/webrick/httpservlet/cgi_runner.rb +45 -0
- data/lib/webrick/httpservlet/cgihandler.rb +104 -0
- data/lib/webrick/httpservlet/erbhandler.rb +54 -0
- data/lib/webrick/httpservlet/filehandler.rb +398 -0
- data/lib/webrick/httpservlet/prochandler.rb +33 -0
- data/lib/webrick/httpstatus.rb +126 -0
- data/lib/webrick/httputils.rb +391 -0
- data/lib/webrick/httpversion.rb +49 -0
- data/lib/webrick/log.rb +88 -0
- data/lib/webrick/server.rb +200 -0
- data/lib/webrick/ssl.rb +126 -0
- data/lib/webrick/utils.rb +100 -0
- data/lib/webrick/version.rb +13 -0
- data/rubysl-webrick.gemspec +23 -0
- metadata +145 -0
@@ -0,0 +1,365 @@
|
|
1
|
+
#
|
2
|
+
# httprequest.rb -- HTTPRequest 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: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'timeout'
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
require 'webrick/httpversion'
|
15
|
+
require 'webrick/httpstatus'
|
16
|
+
require 'webrick/httputils'
|
17
|
+
require 'webrick/cookie'
|
18
|
+
|
19
|
+
module WEBrick
|
20
|
+
|
21
|
+
class HTTPRequest
|
22
|
+
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
|
23
|
+
BUFSIZE = 1024*4
|
24
|
+
|
25
|
+
# Request line
|
26
|
+
attr_reader :request_line
|
27
|
+
attr_reader :request_method, :unparsed_uri, :http_version
|
28
|
+
|
29
|
+
# Request-URI
|
30
|
+
attr_reader :request_uri, :host, :port, :path
|
31
|
+
attr_accessor :script_name, :path_info, :query_string
|
32
|
+
|
33
|
+
# Header and entity body
|
34
|
+
attr_reader :raw_header, :header, :cookies
|
35
|
+
attr_reader :accept, :accept_charset
|
36
|
+
attr_reader :accept_encoding, :accept_language
|
37
|
+
|
38
|
+
# Misc
|
39
|
+
attr_accessor :user
|
40
|
+
attr_reader :addr, :peeraddr
|
41
|
+
attr_reader :attributes
|
42
|
+
attr_reader :keep_alive
|
43
|
+
attr_reader :request_time
|
44
|
+
|
45
|
+
def initialize(config)
|
46
|
+
@config = config
|
47
|
+
@logger = config[:Logger]
|
48
|
+
|
49
|
+
@request_line = @request_method =
|
50
|
+
@unparsed_uri = @http_version = nil
|
51
|
+
|
52
|
+
@request_uri = @host = @port = @path = nil
|
53
|
+
@script_name = @path_info = nil
|
54
|
+
@query_string = nil
|
55
|
+
@query = nil
|
56
|
+
@form_data = nil
|
57
|
+
|
58
|
+
@raw_header = Array.new
|
59
|
+
@header = nil
|
60
|
+
@cookies = []
|
61
|
+
@accept = []
|
62
|
+
@accept_charset = []
|
63
|
+
@accept_encoding = []
|
64
|
+
@accept_language = []
|
65
|
+
@body = ""
|
66
|
+
|
67
|
+
@addr = @peeraddr = nil
|
68
|
+
@attributes = {}
|
69
|
+
@user = nil
|
70
|
+
@keep_alive = false
|
71
|
+
@request_time = nil
|
72
|
+
|
73
|
+
@remaining_size = nil
|
74
|
+
@socket = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse(socket=nil)
|
78
|
+
@socket = socket
|
79
|
+
begin
|
80
|
+
@peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
|
81
|
+
@addr = socket.respond_to?(:addr) ? socket.addr : []
|
82
|
+
rescue Errno::ENOTCONN
|
83
|
+
raise HTTPStatus::EOFError
|
84
|
+
end
|
85
|
+
|
86
|
+
read_request_line(socket)
|
87
|
+
if @http_version.major > 0
|
88
|
+
read_header(socket)
|
89
|
+
@header['cookie'].each{|cookie|
|
90
|
+
@cookies += Cookie::parse(cookie)
|
91
|
+
}
|
92
|
+
@accept = HTTPUtils.parse_qvalues(self['accept'])
|
93
|
+
@accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
|
94
|
+
@accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
|
95
|
+
@accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
|
96
|
+
end
|
97
|
+
return if @request_method == "CONNECT"
|
98
|
+
return if @unparsed_uri == "*"
|
99
|
+
|
100
|
+
begin
|
101
|
+
@request_uri = parse_uri(@unparsed_uri)
|
102
|
+
@path = HTTPUtils::unescape(@request_uri.path)
|
103
|
+
@path = HTTPUtils::normalize_path(@path)
|
104
|
+
@host = @request_uri.host
|
105
|
+
@port = @request_uri.port
|
106
|
+
@query_string = @request_uri.query
|
107
|
+
@script_name = ""
|
108
|
+
@path_info = @path.dup
|
109
|
+
rescue
|
110
|
+
raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
|
111
|
+
end
|
112
|
+
|
113
|
+
if /close/io =~ self["connection"]
|
114
|
+
@keep_alive = false
|
115
|
+
elsif /keep-alive/io =~ self["connection"]
|
116
|
+
@keep_alive = true
|
117
|
+
elsif @http_version < "1.1"
|
118
|
+
@keep_alive = false
|
119
|
+
else
|
120
|
+
@keep_alive = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def body(&block)
|
125
|
+
block ||= Proc.new{|chunk| @body << chunk }
|
126
|
+
read_body(@socket, block)
|
127
|
+
@body.empty? ? nil : @body
|
128
|
+
end
|
129
|
+
|
130
|
+
def query
|
131
|
+
unless @query
|
132
|
+
parse_query()
|
133
|
+
end
|
134
|
+
@query
|
135
|
+
end
|
136
|
+
|
137
|
+
def content_length
|
138
|
+
return Integer(self['content-length'])
|
139
|
+
end
|
140
|
+
|
141
|
+
def content_type
|
142
|
+
return self['content-type']
|
143
|
+
end
|
144
|
+
|
145
|
+
def [](header_name)
|
146
|
+
if @header
|
147
|
+
value = @header[header_name.downcase]
|
148
|
+
value.empty? ? nil : value.join(", ")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def each
|
153
|
+
@header.each{|k, v|
|
154
|
+
value = @header[k]
|
155
|
+
yield(k, value.empty? ? nil : value.join(", "))
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def keep_alive?
|
160
|
+
@keep_alive
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
ret = @request_line.dup
|
165
|
+
@raw_header.each{|line| ret << line }
|
166
|
+
ret << CRLF
|
167
|
+
ret << body if body
|
168
|
+
ret
|
169
|
+
end
|
170
|
+
|
171
|
+
def fixup()
|
172
|
+
begin
|
173
|
+
body{|chunk| } # read remaining body
|
174
|
+
rescue HTTPStatus::Error => ex
|
175
|
+
@logger.error("HTTPRequest#fixup: #{ex.class} occured.")
|
176
|
+
@keep_alive = false
|
177
|
+
rescue => ex
|
178
|
+
@logger.error(ex)
|
179
|
+
@keep_alive = false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def meta_vars
|
184
|
+
# This method provides the metavariables defined by the revision 3
|
185
|
+
# of ``The WWW Common Gateway Interface Version 1.1''.
|
186
|
+
# (http://Web.Golux.Com/coar/cgi/)
|
187
|
+
|
188
|
+
meta = Hash.new
|
189
|
+
|
190
|
+
cl = self["Content-Length"]
|
191
|
+
ct = self["Content-Type"]
|
192
|
+
meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
|
193
|
+
meta["CONTENT_TYPE"] = ct.dup if ct
|
194
|
+
meta["GATEWAY_INTERFACE"] = "CGI/1.1"
|
195
|
+
meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
|
196
|
+
#meta["PATH_TRANSLATED"] = nil # no plan to be provided
|
197
|
+
meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
|
198
|
+
meta["REMOTE_ADDR"] = @peeraddr[3]
|
199
|
+
meta["REMOTE_HOST"] = @peeraddr[2]
|
200
|
+
#meta["REMOTE_IDENT"] = nil # no plan to be provided
|
201
|
+
meta["REMOTE_USER"] = @user
|
202
|
+
meta["REQUEST_METHOD"] = @request_method.dup
|
203
|
+
meta["REQUEST_URI"] = @request_uri.to_s
|
204
|
+
meta["SCRIPT_NAME"] = @script_name.dup
|
205
|
+
meta["SERVER_NAME"] = @host
|
206
|
+
meta["SERVER_PORT"] = @port.to_s
|
207
|
+
meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
|
208
|
+
meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
|
209
|
+
|
210
|
+
self.each{|key, val|
|
211
|
+
next if /^content-type$/i =~ key
|
212
|
+
next if /^content-length$/i =~ key
|
213
|
+
name = "HTTP_" + key
|
214
|
+
name.gsub!(/-/o, "_")
|
215
|
+
name.upcase!
|
216
|
+
meta[name] = val
|
217
|
+
}
|
218
|
+
|
219
|
+
meta
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def read_request_line(socket)
|
225
|
+
@request_line = read_line(socket) if socket
|
226
|
+
@request_time = Time.now
|
227
|
+
raise HTTPStatus::EOFError unless @request_line
|
228
|
+
if /^(\S+)\s+(\S+?)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
|
229
|
+
@request_method = $1
|
230
|
+
@unparsed_uri = $2
|
231
|
+
@http_version = HTTPVersion.new($3 ? $3 : "0.9")
|
232
|
+
else
|
233
|
+
rl = @request_line.sub(/\x0d?\x0a\z/o, '')
|
234
|
+
raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def read_header(socket)
|
239
|
+
if socket
|
240
|
+
while line = read_line(socket)
|
241
|
+
break if /\A(#{CRLF}|#{LF})\z/om =~ line
|
242
|
+
@raw_header << line
|
243
|
+
end
|
244
|
+
end
|
245
|
+
begin
|
246
|
+
@header = HTTPUtils::parse_header(@raw_header)
|
247
|
+
rescue => ex
|
248
|
+
raise HTTPStatus::BadRequest, ex.message
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def parse_uri(str, scheme="http")
|
253
|
+
if @config[:Escape8bitURI]
|
254
|
+
str = HTTPUtils::escape8bit(str)
|
255
|
+
end
|
256
|
+
uri = URI::parse(str)
|
257
|
+
return uri if uri.absolute?
|
258
|
+
if self["host"]
|
259
|
+
pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
|
260
|
+
host, port = *self['host'].scan(pattern)[0]
|
261
|
+
elsif @addr.size > 0
|
262
|
+
host, port = @addr[2], @addr[1]
|
263
|
+
else
|
264
|
+
host, port = @config[:ServerName], @config[:Port]
|
265
|
+
end
|
266
|
+
uri.scheme = scheme
|
267
|
+
uri.host = host
|
268
|
+
uri.port = port ? port.to_i : nil
|
269
|
+
return URI::parse(uri.to_s)
|
270
|
+
end
|
271
|
+
|
272
|
+
def read_body(socket, block)
|
273
|
+
return unless socket
|
274
|
+
if tc = self['transfer-encoding']
|
275
|
+
case tc
|
276
|
+
when /chunked/io then read_chunked(socket, block)
|
277
|
+
else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
|
278
|
+
end
|
279
|
+
elsif self['content-length'] || @remaining_size
|
280
|
+
@remaining_size ||= self['content-length'].to_i
|
281
|
+
while @remaining_size > 0
|
282
|
+
sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
|
283
|
+
break unless buf = read_data(socket, sz)
|
284
|
+
@remaining_size -= buf.size
|
285
|
+
block.call(buf)
|
286
|
+
end
|
287
|
+
if @remaining_size > 0 && @socket.eof?
|
288
|
+
raise HTTPStatus::BadRequest, "invalid body size."
|
289
|
+
end
|
290
|
+
elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
|
291
|
+
raise HTTPStatus::LengthRequired
|
292
|
+
end
|
293
|
+
return @body
|
294
|
+
end
|
295
|
+
|
296
|
+
def read_chunk_size(socket)
|
297
|
+
line = read_line(socket)
|
298
|
+
if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
|
299
|
+
chunk_size = $1.hex
|
300
|
+
chunk_ext = $2
|
301
|
+
[ chunk_size, chunk_ext ]
|
302
|
+
else
|
303
|
+
raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def read_chunked(socket, block)
|
308
|
+
chunk_size, = read_chunk_size(socket)
|
309
|
+
while chunk_size > 0
|
310
|
+
data = ""
|
311
|
+
while data.size < chunk_size
|
312
|
+
tmp = read_data(socket, chunk_size-data.size) # read chunk-data
|
313
|
+
break unless tmp
|
314
|
+
data << tmp
|
315
|
+
end
|
316
|
+
if data.nil? || data.size != chunk_size
|
317
|
+
raise BadRequest, "bad chunk data size."
|
318
|
+
end
|
319
|
+
read_line(socket) # skip CRLF
|
320
|
+
block.call(data)
|
321
|
+
chunk_size, = read_chunk_size(socket)
|
322
|
+
end
|
323
|
+
read_header(socket) # trailer + CRLF
|
324
|
+
@header.delete("transfer-encoding")
|
325
|
+
@remaining_size = 0
|
326
|
+
end
|
327
|
+
|
328
|
+
def _read_data(io, method, arg)
|
329
|
+
begin
|
330
|
+
timeout(@config[:RequestTimeout]){
|
331
|
+
return io.__send__(method, arg)
|
332
|
+
}
|
333
|
+
rescue Errno::ECONNRESET
|
334
|
+
return nil
|
335
|
+
rescue TimeoutError
|
336
|
+
raise HTTPStatus::RequestTimeout
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def read_line(io)
|
341
|
+
_read_data(io, :gets, LF)
|
342
|
+
end
|
343
|
+
|
344
|
+
def read_data(io, size)
|
345
|
+
_read_data(io, :read, size)
|
346
|
+
end
|
347
|
+
|
348
|
+
def parse_query()
|
349
|
+
begin
|
350
|
+
if @request_method == "GET" || @request_method == "HEAD"
|
351
|
+
@query = HTTPUtils::parse_query(@query_string)
|
352
|
+
elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
|
353
|
+
@query = HTTPUtils::parse_query(body)
|
354
|
+
elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
|
355
|
+
boundary = HTTPUtils::dequote($1)
|
356
|
+
@query = HTTPUtils::parse_form_data(body, boundary)
|
357
|
+
else
|
358
|
+
@query = Hash.new
|
359
|
+
end
|
360
|
+
rescue => ex
|
361
|
+
raise HTTPStatus::BadRequest, ex.message
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,327 @@
|
|
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
|
+
class HTTPResponse
|
19
|
+
BUFSIZE = 1024*4
|
20
|
+
|
21
|
+
attr_reader :http_version, :status, :header
|
22
|
+
attr_reader :cookies
|
23
|
+
attr_accessor :reason_phrase
|
24
|
+
attr_accessor :body
|
25
|
+
|
26
|
+
attr_accessor :request_method, :request_uri, :request_http_version
|
27
|
+
attr_accessor :filename
|
28
|
+
attr_accessor :keep_alive
|
29
|
+
attr_reader :config, :sent_size
|
30
|
+
|
31
|
+
def initialize(config)
|
32
|
+
@config = config
|
33
|
+
@logger = config[:Logger]
|
34
|
+
@header = Hash.new
|
35
|
+
@status = HTTPStatus::RC_OK
|
36
|
+
@reason_phrase = nil
|
37
|
+
@http_version = HTTPVersion::convert(@config[:HTTPVersion])
|
38
|
+
@body = ''
|
39
|
+
@keep_alive = true
|
40
|
+
@cookies = []
|
41
|
+
@request_method = nil
|
42
|
+
@request_uri = nil
|
43
|
+
@request_http_version = @http_version # temporary
|
44
|
+
@chunked = false
|
45
|
+
@filename = nil
|
46
|
+
@sent_size = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def status_line
|
50
|
+
"HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def status=(status)
|
54
|
+
@status = status
|
55
|
+
@reason_phrase = HTTPStatus::reason_phrase(status)
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](field)
|
59
|
+
@header[field.downcase]
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(field, value)
|
63
|
+
@header[field.downcase] = value.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def content_length
|
67
|
+
if len = self['content-length']
|
68
|
+
return Integer(len)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def content_length=(len)
|
73
|
+
self['content-length'] = len.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def content_type
|
77
|
+
self['content-type']
|
78
|
+
end
|
79
|
+
|
80
|
+
def content_type=(type)
|
81
|
+
self['content-type'] = type
|
82
|
+
end
|
83
|
+
|
84
|
+
def each
|
85
|
+
@header.each{|k, v| yield(k, v) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def chunked?
|
89
|
+
@chunked
|
90
|
+
end
|
91
|
+
|
92
|
+
def chunked=(val)
|
93
|
+
@chunked = val ? true : false
|
94
|
+
end
|
95
|
+
|
96
|
+
def keep_alive?
|
97
|
+
@keep_alive
|
98
|
+
end
|
99
|
+
|
100
|
+
def send_response(socket)
|
101
|
+
begin
|
102
|
+
setup_header()
|
103
|
+
send_header(socket)
|
104
|
+
send_body(socket)
|
105
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
|
106
|
+
@logger.debug(ex)
|
107
|
+
@keep_alive = false
|
108
|
+
rescue Exception => ex
|
109
|
+
@logger.error(ex)
|
110
|
+
@keep_alive = false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def setup_header()
|
115
|
+
@reason_phrase ||= HTTPStatus::reason_phrase(@status)
|
116
|
+
@header['server'] ||= @config[:ServerSoftware]
|
117
|
+
@header['date'] ||= Time.now.httpdate
|
118
|
+
|
119
|
+
# HTTP/0.9 features
|
120
|
+
if @request_http_version < "1.0"
|
121
|
+
@http_version = HTTPVersion.new("0.9")
|
122
|
+
@keep_alive = false
|
123
|
+
end
|
124
|
+
|
125
|
+
# HTTP/1.0 features
|
126
|
+
if @request_http_version < "1.1"
|
127
|
+
if chunked?
|
128
|
+
@chunked = false
|
129
|
+
ver = @request_http_version.to_s
|
130
|
+
msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
|
131
|
+
@logger.warn(msg)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Determine the message length (RFC2616 -- 4.4 Message Length)
|
136
|
+
if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
|
137
|
+
@header.delete('content-length')
|
138
|
+
@body = ""
|
139
|
+
elsif chunked?
|
140
|
+
@header["transfer-encoding"] = "chunked"
|
141
|
+
@header.delete('content-length')
|
142
|
+
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
143
|
+
@header.delete('content-length')
|
144
|
+
elsif @header['content-length'].nil?
|
145
|
+
unless @body.is_a?(IO)
|
146
|
+
@header['content-length'] = @body ? @body.size : 0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Keep-Alive connection.
|
151
|
+
if @header['connection'] == "close"
|
152
|
+
@keep_alive = false
|
153
|
+
elsif keep_alive?
|
154
|
+
if chunked? || @header['content-length']
|
155
|
+
@header['connection'] = "Keep-Alive"
|
156
|
+
end
|
157
|
+
else
|
158
|
+
@header['connection'] = "close"
|
159
|
+
end
|
160
|
+
|
161
|
+
# Location is a single absoluteURI.
|
162
|
+
if location = @header['location']
|
163
|
+
if @request_uri
|
164
|
+
@header['location'] = @request_uri.merge(location)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def send_header(socket)
|
170
|
+
if @http_version.major > 0
|
171
|
+
data = status_line()
|
172
|
+
@header.each{|key, value|
|
173
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
|
174
|
+
data << "#{tmp}: #{value}" << CRLF
|
175
|
+
}
|
176
|
+
@cookies.each{|cookie|
|
177
|
+
data << "Set-Cookie: " << cookie.to_s << CRLF
|
178
|
+
}
|
179
|
+
data << CRLF
|
180
|
+
_write_data(socket, data)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def send_body(socket)
|
185
|
+
case @body
|
186
|
+
when IO then send_body_io(socket)
|
187
|
+
else send_body_string(socket)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_s
|
192
|
+
ret = ""
|
193
|
+
send_response(ret)
|
194
|
+
ret
|
195
|
+
end
|
196
|
+
|
197
|
+
def set_redirect(status, url)
|
198
|
+
@body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
|
199
|
+
@header['location'] = url.to_s
|
200
|
+
raise status
|
201
|
+
end
|
202
|
+
|
203
|
+
def set_error(ex, backtrace=false)
|
204
|
+
case ex
|
205
|
+
when HTTPStatus::Status
|
206
|
+
@keep_alive = false if HTTPStatus::error?(ex.code)
|
207
|
+
self.status = ex.code
|
208
|
+
else
|
209
|
+
@keep_alive = false
|
210
|
+
self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
|
211
|
+
end
|
212
|
+
@header['content-type'] = "text/html; charset=ISO-8859-1"
|
213
|
+
|
214
|
+
if respond_to?(:create_error_page)
|
215
|
+
create_error_page()
|
216
|
+
return
|
217
|
+
end
|
218
|
+
|
219
|
+
if @request_uri
|
220
|
+
host, port = @request_uri.host, @request_uri.port
|
221
|
+
else
|
222
|
+
host, port = @config[:ServerName], @config[:Port]
|
223
|
+
end
|
224
|
+
|
225
|
+
@body = ''
|
226
|
+
@body << <<-_end_of_html_
|
227
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
228
|
+
<HTML>
|
229
|
+
<HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
|
230
|
+
<BODY>
|
231
|
+
<H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
|
232
|
+
#{HTMLUtils::escape(ex.message)}
|
233
|
+
<HR>
|
234
|
+
_end_of_html_
|
235
|
+
|
236
|
+
if backtrace && $DEBUG
|
237
|
+
@body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
|
238
|
+
@body << "#{HTMLUtils::escape(ex.message)}"
|
239
|
+
@body << "<PRE>"
|
240
|
+
ex.backtrace.each{|line| @body << "\t#{line}\n"}
|
241
|
+
@body << "</PRE><HR>"
|
242
|
+
end
|
243
|
+
|
244
|
+
@body << <<-_end_of_html_
|
245
|
+
<ADDRESS>
|
246
|
+
#{HTMLUtils::escape(@config[:ServerSoftware])} at
|
247
|
+
#{host}:#{port}
|
248
|
+
</ADDRESS>
|
249
|
+
</BODY>
|
250
|
+
</HTML>
|
251
|
+
_end_of_html_
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def send_body_io(socket)
|
257
|
+
begin
|
258
|
+
if @request_method == "HEAD"
|
259
|
+
# do nothing
|
260
|
+
elsif chunked?
|
261
|
+
while buf = @body.read(BUFSIZE)
|
262
|
+
next if buf.empty?
|
263
|
+
data = ""
|
264
|
+
data << format("%x", buf.size) << CRLF
|
265
|
+
data << buf << CRLF
|
266
|
+
_write_data(socket, data)
|
267
|
+
@sent_size += buf.size
|
268
|
+
end
|
269
|
+
_write_data(socket, "0#{CRLF}#{CRLF}")
|
270
|
+
else
|
271
|
+
size = @header['content-length'].to_i
|
272
|
+
_send_file(socket, @body, 0, size)
|
273
|
+
@sent_size = size
|
274
|
+
end
|
275
|
+
ensure
|
276
|
+
@body.close
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def send_body_string(socket)
|
281
|
+
if @request_method == "HEAD"
|
282
|
+
# do nothing
|
283
|
+
elsif chunked?
|
284
|
+
remain = body ? @body.size : 0
|
285
|
+
while buf = @body[@sent_size, BUFSIZE]
|
286
|
+
break if buf.empty?
|
287
|
+
data = ""
|
288
|
+
data << format("%x", buf.size) << CRLF
|
289
|
+
data << buf << CRLF
|
290
|
+
_write_data(socket, data)
|
291
|
+
@sent_size += buf.size
|
292
|
+
end
|
293
|
+
_write_data(socket, "0#{CRLF}#{CRLF}")
|
294
|
+
else
|
295
|
+
if @body && @body.size > 0
|
296
|
+
_write_data(socket, @body)
|
297
|
+
@sent_size = @body.size
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def _send_file(output, input, offset, size)
|
303
|
+
while offset > 0
|
304
|
+
sz = BUFSIZE < offset ? BUFSIZE : offset
|
305
|
+
buf = input.read(sz)
|
306
|
+
offset -= buf.size
|
307
|
+
end
|
308
|
+
|
309
|
+
if size == 0
|
310
|
+
while buf = input.read(BUFSIZE)
|
311
|
+
_write_data(output, buf)
|
312
|
+
end
|
313
|
+
else
|
314
|
+
while size > 0
|
315
|
+
sz = BUFSIZE < size ? BUFSIZE : size
|
316
|
+
buf = input.read(sz)
|
317
|
+
_write_data(output, buf)
|
318
|
+
size -= buf.size
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def _write_data(socket, data)
|
324
|
+
socket << data
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|