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
11require 'uri'
12require 'webrick/httpversion'
13require 'webrick/httpstatus'
14require 'webrick/httputils'
15require 'webrick/cookie'
16
17module WEBrick
18
19  ##
20  # An HTTP request.  This is consumed by service and do_* methods in
21  # WEBrick servlets
22
23  class HTTPRequest
24
25    BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] # :nodoc:
26
27    # :section: Request line
28
29    ##
30    # The complete request line such as:
31    #
32    #   GET / HTTP/1.1
33
34    attr_reader :request_line
35
36    ##
37    # The request method, GET, POST, PUT, etc.
38
39    attr_reader :request_method
40
41    ##
42    # The unparsed URI of the request
43
44    attr_reader :unparsed_uri
45
46    ##
47    # The HTTP version of the request
48
49    attr_reader :http_version
50
51    # :section: Request-URI
52
53    ##
54    # The parsed URI of the request
55
56    attr_reader :request_uri
57
58    ##
59    # The request path
60
61    attr_reader :path
62
63    ##
64    # The script name (CGI variable)
65
66    attr_accessor :script_name
67
68    ##
69    # The path info (CGI variable)
70
71    attr_accessor :path_info
72
73    ##
74    # The query from the URI of the request
75
76    attr_accessor :query_string
77
78    # :section: Header and entity body
79
80    ##
81    # The raw header of the request
82
83    attr_reader :raw_header
84
85    ##
86    # The parsed header of the request
87
88    attr_reader :header
89
90    ##
91    # The parsed request cookies
92
93    attr_reader :cookies
94
95    ##
96    # The Accept header value
97
98    attr_reader :accept
99
100    ##
101    # The Accept-Charset header value
102
103    attr_reader :accept_charset
104
105    ##
106    # The Accept-Encoding header value
107
108    attr_reader :accept_encoding
109
110    ##
111    # The Accept-Language header value
112
113    attr_reader :accept_language
114
115    # :section:
116
117    ##
118    # The remote user (CGI variable)
119
120    attr_accessor :user
121
122    ##
123    # The socket address of the server
124
125    attr_reader :addr
126
127    ##
128    # The socket address of the client
129
130    attr_reader :peeraddr
131
132    ##
133    # Hash of request attributes
134
135    attr_reader :attributes
136
137    ##
138    # Is this a keep-alive connection?
139
140    attr_reader :keep_alive
141
142    ##
143    # The local time this request was received
144
145    attr_reader :request_time
146
147    ##
148    # Creates a new HTTP request.  WEBrick::Config::HTTP is the default
149    # configuration.
150
151    def initialize(config)
152      @config = config
153      @buffer_size = @config[:InputBufferSize]
154      @logger = config[:Logger]
155
156      @request_line = @request_method =
157        @unparsed_uri = @http_version = nil
158
159      @request_uri = @host = @port = @path = nil
160      @script_name = @path_info = nil
161      @query_string = nil
162      @query = nil
163      @form_data = nil
164
165      @raw_header = Array.new
166      @header = nil
167      @cookies = []
168      @accept = []
169      @accept_charset = []
170      @accept_encoding = []
171      @accept_language = []
172      @body = ""
173
174      @addr = @peeraddr = nil
175      @attributes = {}
176      @user = nil
177      @keep_alive = false
178      @request_time = nil
179
180      @remaining_size = nil
181      @socket = nil
182
183      @forwarded_proto = @forwarded_host = @forwarded_port =
184        @forwarded_server = @forwarded_for = nil
185    end
186
187    ##
188    # Parses a request from +socket+.  This is called internally by
189    # WEBrick::HTTPServer.
190
191    def parse(socket=nil)
192      @socket = socket
193      begin
194        @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
195        @addr = socket.respond_to?(:addr) ? socket.addr : []
196      rescue Errno::ENOTCONN
197        raise HTTPStatus::EOFError
198      end
199
200      read_request_line(socket)
201      if @http_version.major > 0
202        read_header(socket)
203        @header['cookie'].each{|cookie|
204          @cookies += Cookie::parse(cookie)
205        }
206        @accept = HTTPUtils.parse_qvalues(self['accept'])
207        @accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
208        @accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
209        @accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
210      end
211      return if @request_method == "CONNECT"
212      return if @unparsed_uri == "*"
213
214      begin
215        setup_forwarded_info
216        @request_uri = parse_uri(@unparsed_uri)
217        @path = HTTPUtils::unescape(@request_uri.path)
218        @path = HTTPUtils::normalize_path(@path)
219        @host = @request_uri.host
220        @port = @request_uri.port
221        @query_string = @request_uri.query
222        @script_name = ""
223        @path_info = @path.dup
224      rescue
225        raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
226      end
227
228      if /close/io =~ self["connection"]
229        @keep_alive = false
230      elsif /keep-alive/io =~ self["connection"]
231        @keep_alive = true
232      elsif @http_version < "1.1"
233        @keep_alive = false
234      else
235        @keep_alive = true
236      end
237    end
238
239    ##
240    # Generate HTTP/1.1 100 continue response if the client expects it,
241    # otherwise does nothing.
242
243    def continue # :nodoc:
244      if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
245        @socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
246        @header.delete('expect')
247      end
248    end
249
250    ##
251    # Returns the request body.
252
253    def body(&block) # :yields: body_chunk
254      block ||= Proc.new{|chunk| @body << chunk }
255      read_body(@socket, block)
256      @body.empty? ? nil : @body
257    end
258
259    ##
260    # Request query as a Hash
261
262    def query
263      unless @query
264        parse_query()
265      end
266      @query
267    end
268
269    ##
270    # The content-length header
271
272    def content_length
273      return Integer(self['content-length'])
274    end
275
276    ##
277    # The content-type header
278
279    def content_type
280      return self['content-type']
281    end
282
283    ##
284    # Retrieves +header_name+
285
286    def [](header_name)
287      if @header
288        value = @header[header_name.downcase]
289        value.empty? ? nil : value.join(", ")
290      end
291    end
292
293    ##
294    # Iterates over the request headers
295
296    def each
297      if @header
298        @header.each{|k, v|
299          value = @header[k]
300          yield(k, value.empty? ? nil : value.join(", "))
301        }
302      end
303    end
304
305    ##
306    # The host this request is for
307
308    def host
309      return @forwarded_host || @host
310    end
311
312    ##
313    # The port this request is for
314
315    def port
316      return @forwarded_port || @port
317    end
318
319    ##
320    # The server name this request is for
321
322    def server_name
323      return @forwarded_server || @config[:ServerName]
324    end
325
326    ##
327    # The client's IP address
328
329    def remote_ip
330      return self["client-ip"] || @forwarded_for || @peeraddr[3]
331    end
332
333    ##
334    # Is this an SSL request?
335
336    def ssl?
337      return @request_uri.scheme == "https"
338    end
339
340    ##
341    # Should the connection this request was made on be kept alive?
342
343    def keep_alive?
344      @keep_alive
345    end
346
347    def to_s # :nodoc:
348      ret = @request_line.dup
349      @raw_header.each{|line| ret << line }
350      ret << CRLF
351      ret << body if body
352      ret
353    end
354
355    ##
356    # Consumes any remaining body and updates keep-alive status
357
358    def fixup() # :nodoc:
359      begin
360        body{|chunk| }   # read remaining body
361      rescue HTTPStatus::Error => ex
362        @logger.error("HTTPRequest#fixup: #{ex.class} occurred.")
363        @keep_alive = false
364      rescue => ex
365        @logger.error(ex)
366        @keep_alive = false
367      end
368    end
369
370    # This method provides the metavariables defined by the revision 3
371    # of "The WWW Common Gateway Interface Version 1.1"
372    # http://Web.Golux.Com/coar/cgi/
373
374    def meta_vars
375      meta = Hash.new
376
377      cl = self["Content-Length"]
378      ct = self["Content-Type"]
379      meta["CONTENT_LENGTH"]    = cl if cl.to_i > 0
380      meta["CONTENT_TYPE"]      = ct.dup if ct
381      meta["GATEWAY_INTERFACE"] = "CGI/1.1"
382      meta["PATH_INFO"]         = @path_info ? @path_info.dup : ""
383     #meta["PATH_TRANSLATED"]   = nil      # no plan to be provided
384      meta["QUERY_STRING"]      = @query_string ? @query_string.dup : ""
385      meta["REMOTE_ADDR"]       = @peeraddr[3]
386      meta["REMOTE_HOST"]       = @peeraddr[2]
387     #meta["REMOTE_IDENT"]      = nil      # no plan to be provided
388      meta["REMOTE_USER"]       = @user
389      meta["REQUEST_METHOD"]    = @request_method.dup
390      meta["REQUEST_URI"]       = @request_uri.to_s
391      meta["SCRIPT_NAME"]       = @script_name.dup
392      meta["SERVER_NAME"]       = @host
393      meta["SERVER_PORT"]       = @port.to_s
394      meta["SERVER_PROTOCOL"]   = "HTTP/" + @config[:HTTPVersion].to_s
395      meta["SERVER_SOFTWARE"]   = @config[:ServerSoftware].dup
396
397      self.each{|key, val|
398        next if /^content-type$/i =~ key
399        next if /^content-length$/i =~ key
400        name = "HTTP_" + key
401        name.gsub!(/-/o, "_")
402        name.upcase!
403        meta[name] = val
404      }
405
406      meta
407    end
408
409    private
410
411    # :stopdoc:
412
413    MAX_URI_LENGTH = 2083 # :nodoc:
414
415    def read_request_line(socket)
416      @request_line = read_line(socket, MAX_URI_LENGTH) if socket
417      if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
418        raise HTTPStatus::RequestURITooLarge
419      end
420      @request_time = Time.now
421      raise HTTPStatus::EOFError unless @request_line
422      if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
423        @request_method = $1
424        @unparsed_uri   = $2
425        @http_version   = HTTPVersion.new($3 ? $3 : "0.9")
426      else
427        rl = @request_line.sub(/\x0d?\x0a\z/o, '')
428        raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
429      end
430    end
431
432    def read_header(socket)
433      if socket
434        while line = read_line(socket)
435          break if /\A(#{CRLF}|#{LF})\z/om =~ line
436          @raw_header << line
437        end
438      end
439      @header = HTTPUtils::parse_header(@raw_header.join)
440    end
441
442    def parse_uri(str, scheme="http")
443      if @config[:Escape8bitURI]
444        str = HTTPUtils::escape8bit(str)
445      end
446      str.sub!(%r{\A/+}o, '/')
447      uri = URI::parse(str)
448      return uri if uri.absolute?
449      if @forwarded_host
450        host, port = @forwarded_host, @forwarded_port
451      elsif self["host"]
452        pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
453        host, port = *self['host'].scan(pattern)[0]
454      elsif @addr.size > 0
455        host, port = @addr[2], @addr[1]
456      else
457        host, port = @config[:ServerName], @config[:Port]
458      end
459      uri.scheme = @forwarded_proto || scheme
460      uri.host = host
461      uri.port = port ? port.to_i : nil
462      return URI::parse(uri.to_s)
463    end
464
465    def read_body(socket, block)
466      return unless socket
467      if tc = self['transfer-encoding']
468        case tc
469        when /chunked/io then read_chunked(socket, block)
470        else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
471        end
472      elsif self['content-length'] || @remaining_size
473        @remaining_size ||= self['content-length'].to_i
474        while @remaining_size > 0
475          sz = [@buffer_size, @remaining_size].min
476          break unless buf = read_data(socket, sz)
477          @remaining_size -= buf.bytesize
478          block.call(buf)
479        end
480        if @remaining_size > 0 && @socket.eof?
481          raise HTTPStatus::BadRequest, "invalid body size."
482        end
483      elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
484        raise HTTPStatus::LengthRequired
485      end
486      return @body
487    end
488
489    def read_chunk_size(socket)
490      line = read_line(socket)
491      if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
492        chunk_size = $1.hex
493        chunk_ext = $2
494        [ chunk_size, chunk_ext ]
495      else
496        raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
497      end
498    end
499
500    def read_chunked(socket, block)
501      chunk_size, = read_chunk_size(socket)
502      while chunk_size > 0
503        data = read_data(socket, chunk_size) # read chunk-data
504        if data.nil? || data.bytesize != chunk_size
505          raise BadRequest, "bad chunk data size."
506        end
507        read_line(socket)                    # skip CRLF
508        block.call(data)
509        chunk_size, = read_chunk_size(socket)
510      end
511      read_header(socket)                    # trailer + CRLF
512      @header.delete("transfer-encoding")
513      @remaining_size = 0
514    end
515
516    def _read_data(io, method, *arg)
517      begin
518        WEBrick::Utils.timeout(@config[:RequestTimeout]){
519          return io.__send__(method, *arg)
520        }
521      rescue Errno::ECONNRESET
522        return nil
523      rescue TimeoutError
524        raise HTTPStatus::RequestTimeout
525      end
526    end
527
528    def read_line(io, size=4096)
529      _read_data(io, :gets, LF, size)
530    end
531
532    def read_data(io, size)
533      _read_data(io, :read, size)
534    end
535
536    def parse_query()
537      begin
538        if @request_method == "GET" || @request_method == "HEAD"
539          @query = HTTPUtils::parse_query(@query_string)
540        elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
541          @query = HTTPUtils::parse_query(body)
542        elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
543          boundary = HTTPUtils::dequote($1)
544          @query = HTTPUtils::parse_form_data(body, boundary)
545        else
546          @query = Hash.new
547        end
548      rescue => ex
549        raise HTTPStatus::BadRequest, ex.message
550      end
551    end
552
553    PrivateNetworkRegexp = /
554      ^unknown$|
555      ^((::ffff:)?127.0.0.1|::1)$|
556      ^(::ffff:)?(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.
557    /ixo
558
559    # It's said that all X-Forwarded-* headers will contain more than one
560    # (comma-separated) value if the original request already contained one of
561    # these headers. Since we could use these values as Host header, we choose
562    # the initial(first) value. (apr_table_mergen() adds new value after the
563    # existing value with ", " prefix)
564    def setup_forwarded_info
565      if @forwarded_server = self["x-forwarded-server"]
566        @forwarded_server = @forwarded_server.split(",", 2).first
567      end
568      @forwarded_proto = self["x-forwarded-proto"]
569      if host_port = self["x-forwarded-host"]
570        host_port = host_port.split(",", 2).first
571        @forwarded_host, tmp = host_port.split(":", 2)
572        @forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
573      end
574      if addrs = self["x-forwarded-for"]
575        addrs = addrs.split(",").collect(&:strip)
576        addrs.reject!{|ip| PrivateNetworkRegexp =~ ip }
577        @forwarded_for = addrs.first
578      end
579    end
580
581    # :startdoc:
582  end
583end
584