Re-enable spec/library for full CI runs.
[rbx.git] / lib / webrick / httprequest.rb
blob1d32293a27433c5a90268957d0f23044e8001836
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
7 # reserved.
9 # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
11 require 'timeout'
12 require 'uri'
14 require 'webrick/httpversion'
15 require 'webrick/httpstatus'
16 require 'webrick/httputils'
17 require 'webrick/cookie'
19 module WEBrick
21   class HTTPRequest
22     BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
23     BUFSIZE = 1024*4
25     # Request line
26     attr_reader :request_line
27     attr_reader :request_method, :unparsed_uri, :http_version
29     # Request-URI
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
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
45     def initialize(config)
46       @config = 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
54       @query_string = nil
55       @query = nil
56       @form_data = nil
58       @raw_header = Array.new
59       @header = nil
60       @cookies = []
61       @accept = []
62       @accept_charset = []
63       @accept_encoding = []
64       @accept_language = []
65       @body = ""
67       @addr = @peeraddr = nil
68       @attributes = {}
69       @user = nil
70       @keep_alive = false
71       @request_time = nil
73       @remaining_size = nil
74       @socket = nil
75     end
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
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 == "*"
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
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
124     def body(&block)
125       block ||= Proc.new{|chunk| @body << chunk }
126       read_body(@socket, block)
127       @body.empty? ? nil : @body
128     end
130     def query
131       unless @query
132         parse_query()
133       end
134       @query
135     end
137     def content_length
138       return Integer(self['content-length'])
139     end
141     def content_type
142       return self['content-type']
143     end
145     def [](header_name)
146       if @header
147         value = @header[header_name.downcase]
148         value.empty? ? nil : value.join(", ")
149       end
150     end
152     def each
153       @header.each{|k, v|
154         value = @header[k]
155         yield(k, value.empty? ? nil : value.join(", "))
156       }
157     end
159     def keep_alive?
160       @keep_alive
161     end
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
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
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/)
188       meta = Hash.new
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
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       }
219       meta
220     end
222     private
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
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
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
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
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
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
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
340     def read_line(io)
341       _read_data(io, :gets, LF)
342     end
344     def read_data(io, size)
345       _read_data(io, :read, size)
346     end
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