PageRenderTime 54ms CodeModel.GetById 10ms app.highlight 39ms RepoModel.GetById 2ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/webrick/httprequest.rb

http://github.com/agross/netopenspace
Ruby | 362 lines | 301 code | 43 blank | 18 comment | 36 complexity | 5825129ff75805e0f0366287e202a4af MD5 | raw file
  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 'timeout'
 12require 'uri'
 13
 14require 'webrick/httpversion'
 15require 'webrick/httpstatus'
 16require 'webrick/httputils'
 17require 'webrick/cookie'
 18
 19module 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      @header = HTTPUtils::parse_header(@raw_header.join)
246    end
247
248    def parse_uri(str, scheme="http")
249      if @config[:Escape8bitURI]
250        str = HTTPUtils::escape8bit(str)
251      end
252      str.sub!(%r{\A/+}o, '/')
253      uri = URI::parse(str)
254      return uri if uri.absolute?
255      if self["host"]
256        pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
257        host, port = *self['host'].scan(pattern)[0]
258      elsif @addr.size > 0
259        host, port = @addr[2], @addr[1]
260      else
261        host, port = @config[:ServerName], @config[:Port]
262      end
263      uri.scheme = scheme
264      uri.host = host
265      uri.port = port ? port.to_i : nil
266      return URI::parse(uri.to_s)
267    end
268
269    def read_body(socket, block)
270      return unless socket
271      if tc = self['transfer-encoding']
272        case tc
273        when /chunked/io then read_chunked(socket, block)
274        else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
275        end
276      elsif self['content-length'] || @remaining_size
277        @remaining_size ||= self['content-length'].to_i
278        while @remaining_size > 0 
279          sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
280          break unless buf = read_data(socket, sz)
281          @remaining_size -= buf.size
282          block.call(buf)
283        end
284        if @remaining_size > 0 && @socket.eof?
285          raise HTTPStatus::BadRequest, "invalid body size."
286        end
287      elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
288        raise HTTPStatus::LengthRequired
289      end
290      return @body
291    end
292
293    def read_chunk_size(socket)
294      line = read_line(socket)
295      if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
296        chunk_size = $1.hex
297        chunk_ext = $2
298        [ chunk_size, chunk_ext ]
299      else
300        raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
301      end
302    end
303
304    def read_chunked(socket, block)
305      chunk_size, = read_chunk_size(socket)
306      while chunk_size > 0
307        data = ""
308        while data.size < chunk_size
309          tmp = read_data(socket, chunk_size-data.size) # read chunk-data
310          break unless tmp
311          data << tmp
312        end
313        if data.nil? || data.size != chunk_size
314          raise BadRequest, "bad chunk data size."
315        end
316        read_line(socket)                    # skip CRLF
317        block.call(data)
318        chunk_size, = read_chunk_size(socket)
319      end
320      read_header(socket)                    # trailer + CRLF
321      @header.delete("transfer-encoding")
322      @remaining_size = 0
323    end
324
325    def _read_data(io, method, arg)
326      begin
327        timeout(@config[:RequestTimeout]){
328          return io.__send__(method, arg)
329        }
330      rescue Errno::ECONNRESET
331        return nil
332      rescue TimeoutError
333        raise HTTPStatus::RequestTimeout
334      end
335    end
336
337    def read_line(io)
338      _read_data(io, :gets, LF)
339    end
340
341    def read_data(io, size)
342      _read_data(io, :read, size)
343    end
344
345    def parse_query()
346      begin
347        if @request_method == "GET" || @request_method == "HEAD"
348          @query = HTTPUtils::parse_query(@query_string)
349        elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
350          @query = HTTPUtils::parse_query(body)
351        elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
352          boundary = HTTPUtils::dequote($1)
353          @query = HTTPUtils::parse_form_data(body, boundary)
354        else
355          @query = Hash.new
356        end
357      rescue => ex
358        raise HTTPStatus::BadRequest, ex.message
359      end
360    end
361  end
362end