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

http://github.com/agross/netopenspace · Ruby · 391 lines · 333 code · 42 blank · 16 comment · 27 complexity · 273ebb1d96b3db5d74446a7005d2931c MD5 · raw file

  1. #
  2. # httputils.rb -- HTTPUtils Module
  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: httputils.rb,v 1.34 2003/06/05 21:34:08 gotoyuzo Exp $
  10. require 'socket'
  11. require 'tempfile'
  12. module WEBrick
  13. CR = "\x0d"
  14. LF = "\x0a"
  15. CRLF = "\x0d\x0a"
  16. module HTTPUtils
  17. def normalize_path(path)
  18. raise "abnormal path `#{path}'" if path[0] != ?/
  19. ret = path.dup
  20. ret.gsub!(%r{/+}o, '/') # // => /
  21. while ret.sub!(%r'/\.(?:/|\Z)', '/'); end # /. => /
  22. while ret.sub!(%r'/(?!\.\./)[^/]+/\.\.(?:/|\Z)', '/'); end # /foo/.. => /foo
  23. raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
  24. ret
  25. end
  26. module_function :normalize_path
  27. #####
  28. DefaultMimeTypes = {
  29. "ai" => "application/postscript",
  30. "asc" => "text/plain",
  31. "avi" => "video/x-msvideo",
  32. "bin" => "application/octet-stream",
  33. "bmp" => "image/bmp",
  34. "class" => "application/octet-stream",
  35. "cer" => "application/pkix-cert",
  36. "crl" => "application/pkix-crl",
  37. "crt" => "application/x-x509-ca-cert",
  38. #"crl" => "application/x-pkcs7-crl",
  39. "css" => "text/css",
  40. "dms" => "application/octet-stream",
  41. "doc" => "application/msword",
  42. "dvi" => "application/x-dvi",
  43. "eps" => "application/postscript",
  44. "etx" => "text/x-setext",
  45. "exe" => "application/octet-stream",
  46. "gif" => "image/gif",
  47. "htm" => "text/html",
  48. "html" => "text/html",
  49. "jpe" => "image/jpeg",
  50. "jpeg" => "image/jpeg",
  51. "jpg" => "image/jpeg",
  52. "lha" => "application/octet-stream",
  53. "lzh" => "application/octet-stream",
  54. "mov" => "video/quicktime",
  55. "mpe" => "video/mpeg",
  56. "mpeg" => "video/mpeg",
  57. "mpg" => "video/mpeg",
  58. "pbm" => "image/x-portable-bitmap",
  59. "pdf" => "application/pdf",
  60. "pgm" => "image/x-portable-graymap",
  61. "png" => "image/png",
  62. "pnm" => "image/x-portable-anymap",
  63. "ppm" => "image/x-portable-pixmap",
  64. "ppt" => "application/vnd.ms-powerpoint",
  65. "ps" => "application/postscript",
  66. "qt" => "video/quicktime",
  67. "ras" => "image/x-cmu-raster",
  68. "rb" => "text/plain",
  69. "rd" => "text/plain",
  70. "rtf" => "application/rtf",
  71. "sgm" => "text/sgml",
  72. "sgml" => "text/sgml",
  73. "tif" => "image/tiff",
  74. "tiff" => "image/tiff",
  75. "txt" => "text/plain",
  76. "xbm" => "image/x-xbitmap",
  77. "xls" => "application/vnd.ms-excel",
  78. "xml" => "text/xml",
  79. "xpm" => "image/x-xpixmap",
  80. "xwd" => "image/x-xwindowdump",
  81. "zip" => "application/zip",
  82. }
  83. # Load Apache compatible mime.types file.
  84. def load_mime_types(file)
  85. open(file){ |io|
  86. hash = Hash.new
  87. io.each{ |line|
  88. next if /^#/ =~ line
  89. line.chomp!
  90. mimetype, ext0 = line.split(/\s+/, 2)
  91. next unless ext0
  92. next if ext0.empty?
  93. ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
  94. }
  95. hash
  96. }
  97. end
  98. module_function :load_mime_types
  99. def mime_type(filename, mime_tab)
  100. suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
  101. suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
  102. mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
  103. end
  104. module_function :mime_type
  105. #####
  106. def parse_header(raw)
  107. header = Hash.new([].freeze)
  108. field = nil
  109. raw.each{|line|
  110. case line
  111. when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
  112. field, value = $1, $2
  113. field.downcase!
  114. header[field] = [] unless header.has_key?(field)
  115. header[field] << value
  116. when /^\s+(.*?)\s*\z/om
  117. value = $1
  118. unless field
  119. raise HTTPStatus::BadRequest, "bad header '#{line}'."
  120. end
  121. header[field][-1] << " " << value
  122. else
  123. raise HTTPStatus::BadRequest, "bad header '#{line}'."
  124. end
  125. }
  126. header.each{|key, values|
  127. values.each{|value|
  128. value.strip!
  129. value.gsub!(/\s+/, " ")
  130. }
  131. }
  132. header
  133. end
  134. module_function :parse_header
  135. def split_header_value(str)
  136. str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
  137. (?:,\s*|\Z)'xn).flatten
  138. end
  139. module_function :split_header_value
  140. def parse_range_header(ranges_specifier)
  141. if /^bytes=(.*)/ =~ ranges_specifier
  142. byte_range_set = split_header_value($1)
  143. byte_range_set.collect{|range_spec|
  144. case range_spec
  145. when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
  146. when /^(\d+)-/ then $1.to_i .. -1
  147. when /^-(\d+)/ then -($1.to_i) .. -1
  148. else return nil
  149. end
  150. }
  151. end
  152. end
  153. module_function :parse_range_header
  154. def parse_qvalues(value)
  155. tmp = []
  156. if value
  157. parts = value.split(/,\s*/)
  158. parts.each {|part|
  159. if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
  160. val = m[1]
  161. q = (m[2] or 1).to_f
  162. tmp.push([val, q])
  163. end
  164. }
  165. tmp = tmp.sort_by{|val, q| -q}
  166. tmp.collect!{|val, q| val}
  167. end
  168. return tmp
  169. end
  170. module_function :parse_qvalues
  171. #####
  172. def dequote(str)
  173. ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
  174. ret.gsub!(/\\(.)/, "\\1")
  175. ret
  176. end
  177. module_function :dequote
  178. def quote(str)
  179. '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
  180. end
  181. module_function :quote
  182. #####
  183. class FormData < String
  184. EmptyRawHeader = [].freeze
  185. EmptyHeader = {}.freeze
  186. attr_accessor :name, :filename, :next_data
  187. protected :next_data
  188. def initialize(*args)
  189. @name = @filename = @next_data = nil
  190. if args.empty?
  191. @raw_header = []
  192. @header = nil
  193. super("")
  194. else
  195. @raw_header = EmptyRawHeader
  196. @header = EmptyHeader
  197. super(args.shift)
  198. unless args.empty?
  199. @next_data = self.class.new(*args)
  200. end
  201. end
  202. end
  203. def [](*key)
  204. begin
  205. @header[key[0].downcase].join(", ")
  206. rescue StandardError, NameError
  207. super
  208. end
  209. end
  210. def <<(str)
  211. if @header
  212. super
  213. elsif str == CRLF
  214. @header = HTTPUtils::parse_header(@raw_header)
  215. if cd = self['content-disposition']
  216. if /\s+name="(.*?)"/ =~ cd then @name = $1 end
  217. if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
  218. end
  219. else
  220. @raw_header << str
  221. end
  222. self
  223. end
  224. def append_data(data)
  225. tmp = self
  226. while tmp
  227. unless tmp.next_data
  228. tmp.next_data = data
  229. break
  230. end
  231. tmp = tmp.next_data
  232. end
  233. self
  234. end
  235. def each_data
  236. tmp = self
  237. while tmp
  238. next_data = tmp.next_data
  239. yield(tmp)
  240. tmp = next_data
  241. end
  242. end
  243. def list
  244. ret = []
  245. each_data{|data|
  246. ret << data.to_s
  247. }
  248. ret
  249. end
  250. alias :to_ary :list
  251. def to_s
  252. String.new(self)
  253. end
  254. end
  255. def parse_query(str)
  256. query = Hash.new
  257. if str
  258. str.split(/[&;]/).each{|x|
  259. next if x.empty?
  260. key, val = x.split(/=/,2)
  261. key = unescape_form(key)
  262. val = unescape_form(val.to_s)
  263. val = FormData.new(val)
  264. val.name = key
  265. if query.has_key?(key)
  266. query[key].append_data(val)
  267. next
  268. end
  269. query[key] = val
  270. }
  271. end
  272. query
  273. end
  274. module_function :parse_query
  275. def parse_form_data(io, boundary)
  276. boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
  277. form_data = Hash.new
  278. return form_data unless io
  279. data = nil
  280. io.each{|line|
  281. if boundary_regexp =~ line
  282. if data
  283. data.chop!
  284. key = data.name
  285. if form_data.has_key?(key)
  286. form_data[key].append_data(data)
  287. else
  288. form_data[key] = data
  289. end
  290. end
  291. data = FormData.new
  292. next
  293. else
  294. if data
  295. data << line
  296. end
  297. end
  298. }
  299. return form_data
  300. end
  301. module_function :parse_form_data
  302. #####
  303. reserved = ';/?:@&=+$,'
  304. num = '0123456789'
  305. lowalpha = 'abcdefghijklmnopqrstuvwxyz'
  306. upalpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  307. mark = '-_.!~*\'()'
  308. unreserved = num + lowalpha + upalpha + mark
  309. control = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
  310. space = " "
  311. delims = '<>#%"'
  312. unwise = '{}|\\^[]`'
  313. nonascii = (0x80..0xff).collect{|c| c.chr }.join
  314. module_function
  315. def _make_regex(str) /([#{Regexp.escape(str)}])/n end
  316. def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end
  317. def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1[0] } end
  318. def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
  319. UNESCAPED = _make_regex(control+space+delims+unwise+nonascii)
  320. UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii)
  321. NONASCII = _make_regex(nonascii)
  322. ESCAPED = /%([0-9a-fA-F]{2})/
  323. UNESCAPED_PCHAR = _make_regex!(unreserved+":@&=+$,")
  324. def escape(str)
  325. _escape(str, UNESCAPED)
  326. end
  327. def unescape(str)
  328. _unescape(str, ESCAPED)
  329. end
  330. def escape_form(str)
  331. ret = _escape(str, UNESCAPED_FORM)
  332. ret.gsub!(/ /, "+")
  333. ret
  334. end
  335. def unescape_form(str)
  336. _unescape(str.gsub(/\+/, " "), ESCAPED)
  337. end
  338. def escape_path(str)
  339. result = ""
  340. str.scan(%r{/([^/]*)}).each{|i|
  341. result << "/" << _escape(i[0], UNESCAPED_PCHAR)
  342. }
  343. return result
  344. end
  345. def escape8bit(str)
  346. _escape(str, NONASCII)
  347. end
  348. end
  349. end