/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. require 'timeout'
  11. require 'uri'
  12. require 'webrick/httpversion'
  13. require 'webrick/httpstatus'
  14. require 'webrick/httputils'
  15. require 'webrick/cookie'
  16. module WEBrick
  17. class HTTPRequest
  18. BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
  19. BUFSIZE = 1024*4
  20. # Request line
  21. attr_reader :request_line
  22. attr_reader :request_method, :unparsed_uri, :http_version
  23. # Request-URI
  24. attr_reader :request_uri, :host, :port, :path
  25. attr_accessor :script_name, :path_info, :query_string
  26. # Header and entity body
  27. attr_reader :raw_header, :header, :cookies
  28. attr_reader :accept, :accept_charset
  29. attr_reader :accept_encoding, :accept_language
  30. # Misc
  31. attr_accessor :user
  32. attr_reader :addr, :peeraddr
  33. attr_reader :attributes
  34. attr_reader :keep_alive
  35. attr_reader :request_time
  36. def initialize(config)
  37. @config = config
  38. @logger = config[:Logger]
  39. @request_line = @request_method =
  40. @unparsed_uri = @http_version = nil
  41. @request_uri = @host = @port = @path = nil
  42. @script_name = @path_info = nil
  43. @query_string = nil
  44. @query = nil
  45. @form_data = nil
  46. @raw_header = Array.new
  47. @header = nil
  48. @cookies = []
  49. @accept = []
  50. @accept_charset = []
  51. @accept_encoding = []
  52. @accept_language = []
  53. @body = ""
  54. @addr = @peeraddr = nil
  55. @attributes = {}
  56. @user = nil
  57. @keep_alive = false
  58. @request_time = nil
  59. @remaining_size = nil
  60. @socket = nil
  61. end
  62. def parse(socket=nil)
  63. @socket = socket
  64. begin
  65. @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
  66. @addr = socket.respond_to?(:addr) ? socket.addr : []
  67. rescue Errno::ENOTCONN
  68. raise HTTPStatus::EOFError
  69. end
  70. read_request_line(socket)
  71. if @http_version.major > 0
  72. read_header(socket)
  73. @header['cookie'].each{|cookie|
  74. @cookies += Cookie::parse(cookie)
  75. }
  76. @accept = HTTPUtils.parse_qvalues(self['accept'])
  77. @accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
  78. @accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
  79. @accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
  80. end
  81. return if @request_method == "CONNECT"
  82. return if @unparsed_uri == "*"
  83. begin
  84. @request_uri = parse_uri(@unparsed_uri)
  85. @path = HTTPUtils::unescape(@request_uri.path)
  86. @path = HTTPUtils::normalize_path(@path)
  87. @host = @request_uri.host
  88. @port = @request_uri.port
  89. @query_string = @request_uri.query
  90. @script_name = ""
  91. @path_info = @path.dup
  92. rescue
  93. raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
  94. end
  95. if /close/io =~ self["connection"]
  96. @keep_alive = false
  97. elsif /keep-alive/io =~ self["connection"]
  98. @keep_alive = true
  99. elsif @http_version < "1.1"
  100. @keep_alive = false
  101. else
  102. @keep_alive = true
  103. end
  104. end
  105. def body(&block)
  106. block ||= Proc.new{|chunk| @body << chunk }
  107. read_body(@socket, block)
  108. @body.empty? ? nil : @body
  109. end
  110. def query
  111. unless @query
  112. parse_query()
  113. end
  114. @query
  115. end
  116. def content_length
  117. return Integer(self['content-length'])
  118. end
  119. def content_type
  120. return self['content-type']
  121. end
  122. def [](header_name)
  123. if @header
  124. value = @header[header_name.downcase]
  125. value.empty? ? nil : value.join(", ")
  126. end
  127. end
  128. def each
  129. @header.each{|k, v|
  130. value = @header[k]
  131. yield(k, value.empty? ? nil : value.join(", "))
  132. }
  133. end
  134. def keep_alive?
  135. @keep_alive
  136. end
  137. def to_s
  138. ret = @request_line.dup
  139. @raw_header.each{|line| ret << line }
  140. ret << CRLF
  141. ret << body if body
  142. ret
  143. end
  144. def fixup()
  145. begin
  146. body{|chunk| } # read remaining body
  147. rescue HTTPStatus::Error => ex
  148. @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
  149. @keep_alive = false
  150. rescue => ex
  151. @logger.error(ex)
  152. @keep_alive = false
  153. end
  154. end
  155. def meta_vars
  156. # This method provides the metavariables defined by the revision 3
  157. # of ``The WWW Common Gateway Interface Version 1.1''.
  158. # (http://Web.Golux.Com/coar/cgi/)
  159. meta = Hash.new
  160. cl = self["Content-Length"]
  161. ct = self["Content-Type"]
  162. meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
  163. meta["CONTENT_TYPE"] = ct.dup if ct
  164. meta["GATEWAY_INTERFACE"] = "CGI/1.1"
  165. meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
  166. #meta["PATH_TRANSLATED"] = nil # no plan to be provided
  167. meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
  168. meta["REMOTE_ADDR"] = @peeraddr[3]
  169. meta["REMOTE_HOST"] = @peeraddr[2]
  170. #meta["REMOTE_IDENT"] = nil # no plan to be provided
  171. meta["REMOTE_USER"] = @user
  172. meta["REQUEST_METHOD"] = @request_method.dup
  173. meta["REQUEST_URI"] = @request_uri.to_s
  174. meta["SCRIPT_NAME"] = @script_name.dup
  175. meta["SERVER_NAME"] = @host
  176. meta["SERVER_PORT"] = @port.to_s
  177. meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
  178. meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
  179. self.each{|key, val|
  180. next if /^content-type$/i =~ key
  181. next if /^content-length$/i =~ key
  182. name = "HTTP_" + key
  183. name.gsub!(/-/o, "_")
  184. name.upcase!
  185. meta[name] = val
  186. }
  187. meta
  188. end
  189. private
  190. def read_request_line(socket)
  191. @request_line = read_line(socket) if socket
  192. @request_time = Time.now
  193. raise HTTPStatus::EOFError unless @request_line
  194. if /^(\S+)\s+(\S+?)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
  195. @request_method = $1
  196. @unparsed_uri = $2
  197. @http_version = HTTPVersion.new($3 ? $3 : "0.9")
  198. else
  199. rl = @request_line.sub(/\x0d?\x0a\z/o, '')
  200. raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
  201. end
  202. end
  203. def read_header(socket)
  204. if socket
  205. while line = read_line(socket)
  206. break if /\A(#{CRLF}|#{LF})\z/om =~ line
  207. @raw_header << line
  208. end
  209. end
  210. @header = HTTPUtils::parse_header(@raw_header.join)
  211. end
  212. def parse_uri(str, scheme="http")
  213. if @config[:Escape8bitURI]
  214. str = HTTPUtils::escape8bit(str)
  215. end
  216. str.sub!(%r{\A/+}o, '/')
  217. uri = URI::parse(str)
  218. return uri if uri.absolute?
  219. if self["host"]
  220. pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
  221. host, port = *self['host'].scan(pattern)[0]
  222. elsif @addr.size > 0
  223. host, port = @addr[2], @addr[1]
  224. else
  225. host, port = @config[:ServerName], @config[:Port]
  226. end
  227. uri.scheme = scheme
  228. uri.host = host
  229. uri.port = port ? port.to_i : nil
  230. return URI::parse(uri.to_s)
  231. end
  232. def read_body(socket, block)
  233. return unless socket
  234. if tc = self['transfer-encoding']
  235. case tc
  236. when /chunked/io then read_chunked(socket, block)
  237. else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
  238. end
  239. elsif self['content-length'] || @remaining_size
  240. @remaining_size ||= self['content-length'].to_i
  241. while @remaining_size > 0
  242. sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
  243. break unless buf = read_data(socket, sz)
  244. @remaining_size -= buf.size
  245. block.call(buf)
  246. end
  247. if @remaining_size > 0 && @socket.eof?
  248. raise HTTPStatus::BadRequest, "invalid body size."
  249. end
  250. elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
  251. raise HTTPStatus::LengthRequired
  252. end
  253. return @body
  254. end
  255. def read_chunk_size(socket)
  256. line = read_line(socket)
  257. if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
  258. chunk_size = $1.hex
  259. chunk_ext = $2
  260. [ chunk_size, chunk_ext ]
  261. else
  262. raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
  263. end
  264. end
  265. def read_chunked(socket, block)
  266. chunk_size, = read_chunk_size(socket)
  267. while chunk_size > 0
  268. data = ""
  269. while data.size < chunk_size
  270. tmp = read_data(socket, chunk_size-data.size) # read chunk-data
  271. break unless tmp
  272. data << tmp
  273. end
  274. if data.nil? || data.size != chunk_size
  275. raise BadRequest, "bad chunk data size."
  276. end
  277. read_line(socket) # skip CRLF
  278. block.call(data)
  279. chunk_size, = read_chunk_size(socket)
  280. end
  281. read_header(socket) # trailer + CRLF
  282. @header.delete("transfer-encoding")
  283. @remaining_size = 0
  284. end
  285. def _read_data(io, method, arg)
  286. begin
  287. timeout(@config[:RequestTimeout]){
  288. return io.__send__(method, arg)
  289. }
  290. rescue Errno::ECONNRESET
  291. return nil
  292. rescue TimeoutError
  293. raise HTTPStatus::RequestTimeout
  294. end
  295. end
  296. def read_line(io)
  297. _read_data(io, :gets, LF)
  298. end
  299. def read_data(io, size)
  300. _read_data(io, :read, size)
  301. end
  302. def parse_query()
  303. begin
  304. if @request_method == "GET" || @request_method == "HEAD"
  305. @query = HTTPUtils::parse_query(@query_string)
  306. elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
  307. @query = HTTPUtils::parse_query(body)
  308. elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
  309. boundary = HTTPUtils::dequote($1)
  310. @query = HTTPUtils::parse_form_data(body, boundary)
  311. else
  312. @query = Hash.new
  313. end
  314. rescue => ex
  315. raise HTTPStatus::BadRequest, ex.message
  316. end
  317. end
  318. end
  319. end