PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/webrick/httprequest.rb

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