2 # httprequest.rb -- HTTPRequest Class
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
9 # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
14 require 'webrick/httpversion'
15 require 'webrick/httpstatus'
16 require 'webrick/httputils'
17 require 'webrick/cookie'
22 BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
26 attr_reader :request_line
27 attr_reader :request_method, :unparsed_uri, :http_version
30 attr_reader :request_uri, :host, :port, :path
31 attr_accessor :script_name, :path_info, :query_string
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
40 attr_reader :addr, :peeraddr
41 attr_reader :attributes
42 attr_reader :keep_alive
43 attr_reader :request_time
45 def initialize(config)
47 @logger = config[:Logger]
49 @request_line = @request_method =
50 @unparsed_uri = @http_version = nil
52 @request_uri = @host = @port = @path = nil
53 @script_name = @path_info = nil
58 @raw_header = Array.new
67 @addr = @peeraddr = nil
80 @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
81 @addr = socket.respond_to?(:addr) ? socket.addr : []
82 rescue Errno::ENOTCONN
83 raise HTTPStatus::EOFError
86 read_request_line(socket)
87 if @http_version.major > 0
89 @header['cookie'].each{|cookie|
90 @cookies += Cookie::parse(cookie)
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'])
97 return if @request_method == "CONNECT"
98 return if @unparsed_uri == "*"
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
108 @path_info = @path.dup
110 raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
113 if /close/io =~ self["connection"]
115 elsif /keep-alive/io =~ self["connection"]
117 elsif @http_version < "1.1"
125 block ||= Proc.new{|chunk| @body << chunk }
126 read_body(@socket, block)
127 @body.empty? ? nil : @body
138 return Integer(self['content-length'])
142 return self['content-type']
147 value = @header[header_name.downcase]
148 value.empty? ? nil : value.join(", ")
155 yield(k, value.empty? ? nil : value.join(", "))
164 ret = @request_line.dup
165 @raw_header.each{|line| ret << line }
173 body{|chunk| } # read remaining body
174 rescue HTTPStatus::Error => ex
175 @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
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/)
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
211 next if /^content-type$/i =~ key
212 next if /^content-length$/i =~ key
214 name.gsub!(/-/o, "_")
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
231 @http_version = HTTPVersion.new($3 ? $3 : "0.9")
233 rl = @request_line.sub(/\x0d?\x0a\z/o, '')
234 raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
238 def read_header(socket)
240 while line = read_line(socket)
241 break if /\A(#{CRLF}|#{LF})\z/om =~ line
246 @header = HTTPUtils::parse_header(@raw_header)
248 raise HTTPStatus::BadRequest, ex.message
252 def parse_uri(str, scheme="http")
253 if @config[:Escape8bitURI]
254 str = HTTPUtils::escape8bit(str)
256 uri = URI::parse(str)
257 return uri if uri.absolute?
259 pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
260 host, port = *self['host'].scan(pattern)[0]
262 host, port = @addr[2], @addr[1]
264 host, port = @config[:ServerName], @config[:Port]
268 uri.port = port ? port.to_i : nil
269 return URI::parse(uri.to_s)
272 def read_body(socket, block)
274 if tc = self['transfer-encoding']
276 when /chunked/io then read_chunked(socket, block)
277 else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
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
287 if @remaining_size > 0 && @socket.eof?
288 raise HTTPStatus::BadRequest, "invalid body size."
290 elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
291 raise HTTPStatus::LengthRequired
296 def read_chunk_size(socket)
297 line = read_line(socket)
298 if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
301 [ chunk_size, chunk_ext ]
303 raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
307 def read_chunked(socket, block)
308 chunk_size, = read_chunk_size(socket)
311 while data.size < chunk_size
312 tmp = read_data(socket, chunk_size-data.size) # read chunk-data
316 if data.nil? || data.size != chunk_size
317 raise BadRequest, "bad chunk data size."
319 read_line(socket) # skip CRLF
321 chunk_size, = read_chunk_size(socket)
323 read_header(socket) # trailer + CRLF
324 @header.delete("transfer-encoding")
328 def _read_data(io, method, arg)
330 timeout(@config[:RequestTimeout]){
331 return io.__send__(method, arg)
333 rescue Errno::ECONNRESET
336 raise HTTPStatus::RequestTimeout
341 _read_data(io, :gets, LF)
344 def read_data(io, size)
345 _read_data(io, :read, size)
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)
361 raise HTTPStatus::BadRequest, ex.message