PageRenderTime 39ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Languages/Ruby/Tests/Libraries/rack-1.1.0/lib/rack/utils.rb

http://github.com/IronLanguages/main
Ruby | 648 lines | 548 code | 63 blank | 37 comment | 44 complexity | c5c1b1865a4e12db2155840c68edae62 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. # -*- encoding: binary -*-
  2. require 'fileutils'
  3. require 'set'
  4. require 'tempfile'
  5. module Rack
  6. # Rack::Utils contains a grab-bag of useful methods for writing web
  7. # applications adopted from all kinds of Ruby libraries.
  8. module Utils
  9. # Performs URI escaping so that you can construct proper
  10. # query strings faster. Use this rather than the cgi.rb
  11. # version since it's faster. (Stolen from Camping).
  12. def escape(s)
  13. s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
  14. '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
  15. }.tr(' ', '+')
  16. end
  17. module_function :escape
  18. # Unescapes a URI escaped string. (Stolen from Camping).
  19. def unescape(s)
  20. s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
  21. [$1.delete('%')].pack('H*')
  22. }
  23. end
  24. module_function :unescape
  25. DEFAULT_SEP = /[&;] */n
  26. # Stolen from Mongrel, with some small modifications:
  27. # Parses a query string by breaking it up at the '&'
  28. # and ';' characters. You can also use this to parse
  29. # cookies by changing the characters used in the second
  30. # parameter (which defaults to '&;').
  31. def parse_query(qs, d = nil)
  32. params = {}
  33. (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
  34. k, v = p.split('=', 2).map { |x| unescape(x) }
  35. if v =~ /^("|')(.*)\1$/
  36. v = $2.gsub('\\'+$1, $1)
  37. end
  38. if cur = params[k]
  39. if cur.class == Array
  40. params[k] << v
  41. else
  42. params[k] = [cur, v]
  43. end
  44. else
  45. params[k] = v
  46. end
  47. end
  48. return params
  49. end
  50. module_function :parse_query
  51. def parse_nested_query(qs, d = nil)
  52. params = {}
  53. (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
  54. k, v = unescape(p).split('=', 2)
  55. normalize_params(params, k, v)
  56. end
  57. return params
  58. end
  59. module_function :parse_nested_query
  60. def normalize_params(params, name, v = nil)
  61. if v and v =~ /^("|')(.*)\1$/
  62. v = $2.gsub('\\'+$1, $1)
  63. end
  64. name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
  65. k = $1 || ''
  66. after = $' || ''
  67. return if k.empty?
  68. if after == ""
  69. params[k] = v
  70. elsif after == "[]"
  71. params[k] ||= []
  72. raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
  73. params[k] << v
  74. elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
  75. child_key = $1
  76. params[k] ||= []
  77. raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
  78. if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
  79. normalize_params(params[k].last, child_key, v)
  80. else
  81. params[k] << normalize_params({}, child_key, v)
  82. end
  83. else
  84. params[k] ||= {}
  85. raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
  86. params[k] = normalize_params(params[k], after, v)
  87. end
  88. return params
  89. end
  90. module_function :normalize_params
  91. def build_query(params)
  92. params.map { |k, v|
  93. if v.class == Array
  94. build_query(v.map { |x| [k, x] })
  95. else
  96. "#{escape(k)}=#{escape(v)}"
  97. end
  98. }.join("&")
  99. end
  100. module_function :build_query
  101. def build_nested_query(value, prefix = nil)
  102. case value
  103. when Array
  104. value.map { |v|
  105. build_nested_query(v, "#{prefix}[]")
  106. }.join("&")
  107. when Hash
  108. value.map { |k, v|
  109. build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
  110. }.join("&")
  111. when String
  112. raise ArgumentError, "value must be a Hash" if prefix.nil?
  113. "#{prefix}=#{escape(value)}"
  114. else
  115. prefix
  116. end
  117. end
  118. module_function :build_nested_query
  119. # Escape ampersands, brackets and quotes to their HTML/XML entities.
  120. def escape_html(string)
  121. string.to_s.gsub("&", "&amp;").
  122. gsub("<", "&lt;").
  123. gsub(">", "&gt;").
  124. gsub("'", "&#39;").
  125. gsub('"', "&quot;")
  126. end
  127. module_function :escape_html
  128. def select_best_encoding(available_encodings, accept_encoding)
  129. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  130. expanded_accept_encoding =
  131. accept_encoding.map { |m, q|
  132. if m == "*"
  133. (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
  134. else
  135. [[m, q]]
  136. end
  137. }.inject([]) { |mem, list|
  138. mem + list
  139. }
  140. encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
  141. unless encoding_candidates.include?("identity")
  142. encoding_candidates.push("identity")
  143. end
  144. expanded_accept_encoding.find_all { |m, q|
  145. q == 0.0
  146. }.each { |m, _|
  147. encoding_candidates.delete(m)
  148. }
  149. return (encoding_candidates & available_encodings)[0]
  150. end
  151. module_function :select_best_encoding
  152. def set_cookie_header!(header, key, value)
  153. case value
  154. when Hash
  155. domain = "; domain=" + value[:domain] if value[:domain]
  156. path = "; path=" + value[:path] if value[:path]
  157. # According to RFC 2109, we need dashes here.
  158. # N.B.: cgi.rb uses spaces...
  159. expires = "; expires=" + value[:expires].clone.gmtime.
  160. strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
  161. secure = "; secure" if value[:secure]
  162. httponly = "; HttpOnly" if value[:httponly]
  163. value = value[:value]
  164. end
  165. value = [value] unless Array === value
  166. cookie = escape(key) + "=" +
  167. value.map { |v| escape v }.join("&") +
  168. "#{domain}#{path}#{expires}#{secure}#{httponly}"
  169. case header["Set-Cookie"]
  170. when nil, ''
  171. header["Set-Cookie"] = cookie
  172. when String
  173. header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
  174. when Array
  175. header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
  176. end
  177. nil
  178. end
  179. module_function :set_cookie_header!
  180. def delete_cookie_header!(header, key, value = {})
  181. case header["Set-Cookie"]
  182. when nil, ''
  183. cookies = []
  184. when String
  185. cookies = header["Set-Cookie"].split("\n")
  186. when Array
  187. cookies = header["Set-Cookie"]
  188. end
  189. cookies.reject! { |cookie|
  190. cookie =~ /\A#{escape(key)}=/
  191. }
  192. header["Set-Cookie"] = cookies.join("\n")
  193. set_cookie_header!(header, key,
  194. {:value => '', :path => nil, :domain => nil,
  195. :expires => Time.at(0) }.merge(value))
  196. nil
  197. end
  198. module_function :delete_cookie_header!
  199. # Return the bytesize of String; uses String#length under Ruby 1.8 and
  200. # String#bytesize under 1.9.
  201. if ''.respond_to?(:bytesize)
  202. def bytesize(string)
  203. string.bytesize
  204. end
  205. else
  206. def bytesize(string)
  207. string.size
  208. end
  209. end
  210. module_function :bytesize
  211. # Context allows the use of a compatible middleware at different points
  212. # in a request handling stack. A compatible middleware must define
  213. # #context which should take the arguments env and app. The first of which
  214. # would be the request environment. The second of which would be the rack
  215. # application that the request would be forwarded to.
  216. class Context
  217. attr_reader :for, :app
  218. def initialize(app_f, app_r)
  219. raise 'running context does not respond to #context' unless app_f.respond_to? :context
  220. @for, @app = app_f, app_r
  221. end
  222. def call(env)
  223. @for.context(env, @app)
  224. end
  225. def recontext(app)
  226. self.class.new(@for, app)
  227. end
  228. def context(env, app=@app)
  229. recontext(app).call(env)
  230. end
  231. end
  232. # A case-insensitive Hash that preserves the original case of a
  233. # header when set.
  234. class HeaderHash < Hash
  235. def self.new(hash={})
  236. HeaderHash === hash ? hash : super(hash)
  237. end
  238. def initialize(hash={})
  239. super()
  240. @names = {}
  241. hash.each { |k, v| self[k] = v }
  242. end
  243. def each
  244. super do |k, v|
  245. yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
  246. end
  247. end
  248. def to_hash
  249. inject({}) do |hash, (k,v)|
  250. if v.respond_to? :to_ary
  251. hash[k] = v.to_ary.join("\n")
  252. else
  253. hash[k] = v
  254. end
  255. hash
  256. end
  257. end
  258. def [](k)
  259. super(@names[k]) if @names[k]
  260. super(@names[k.downcase])
  261. end
  262. def []=(k, v)
  263. delete k
  264. @names[k] = @names[k.downcase] = k
  265. super k, v
  266. end
  267. def delete(k)
  268. canonical = k.downcase
  269. result = super @names.delete(canonical)
  270. @names.delete_if { |name,| name.downcase == canonical }
  271. result
  272. end
  273. def include?(k)
  274. @names.include?(k) || @names.include?(k.downcase)
  275. end
  276. alias_method :has_key?, :include?
  277. alias_method :member?, :include?
  278. alias_method :key?, :include?
  279. def merge!(other)
  280. other.each { |k, v| self[k] = v }
  281. self
  282. end
  283. def merge(other)
  284. hash = dup
  285. hash.merge! other
  286. end
  287. def replace(other)
  288. clear
  289. other.each { |k, v| self[k] = v }
  290. self
  291. end
  292. end
  293. # Every standard HTTP code mapped to the appropriate message.
  294. # Generated with:
  295. # curl -s http://www.iana.org/assignments/http-status-codes | \
  296. # ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
  297. # puts " #{m[1]} => \x27#{m[2].strip}x27,"'
  298. HTTP_STATUS_CODES = {
  299. 100 => 'Continue',
  300. 101 => 'Switching Protocols',
  301. 102 => 'Processing',
  302. 200 => 'OK',
  303. 201 => 'Created',
  304. 202 => 'Accepted',
  305. 203 => 'Non-Authoritative Information',
  306. 204 => 'No Content',
  307. 205 => 'Reset Content',
  308. 206 => 'Partial Content',
  309. 207 => 'Multi-Status',
  310. 226 => 'IM Used',
  311. 300 => 'Multiple Choices',
  312. 301 => 'Moved Permanently',
  313. 302 => 'Found',
  314. 303 => 'See Other',
  315. 304 => 'Not Modified',
  316. 305 => 'Use Proxy',
  317. 306 => 'Reserved',
  318. 307 => 'Temporary Redirect',
  319. 400 => 'Bad Request',
  320. 401 => 'Unauthorized',
  321. 402 => 'Payment Required',
  322. 403 => 'Forbidden',
  323. 404 => 'Not Found',
  324. 405 => 'Method Not Allowed',
  325. 406 => 'Not Acceptable',
  326. 407 => 'Proxy Authentication Required',
  327. 408 => 'Request Timeout',
  328. 409 => 'Conflict',
  329. 410 => 'Gone',
  330. 411 => 'Length Required',
  331. 412 => 'Precondition Failed',
  332. 413 => 'Request Entity Too Large',
  333. 414 => 'Request-URI Too Long',
  334. 415 => 'Unsupported Media Type',
  335. 416 => 'Requested Range Not Satisfiable',
  336. 417 => 'Expectation Failed',
  337. 422 => 'Unprocessable Entity',
  338. 423 => 'Locked',
  339. 424 => 'Failed Dependency',
  340. 426 => 'Upgrade Required',
  341. 500 => 'Internal Server Error',
  342. 501 => 'Not Implemented',
  343. 502 => 'Bad Gateway',
  344. 503 => 'Service Unavailable',
  345. 504 => 'Gateway Timeout',
  346. 505 => 'HTTP Version Not Supported',
  347. 506 => 'Variant Also Negotiates',
  348. 507 => 'Insufficient Storage',
  349. 510 => 'Not Extended',
  350. }
  351. # Responses with HTTP status codes that should not have an entity body
  352. STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
  353. SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)|
  354. hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code
  355. hash
  356. }
  357. def status_code(status)
  358. if status.is_a?(Symbol)
  359. SYMBOL_TO_STATUS_CODE[status] || 500
  360. else
  361. status.to_i
  362. end
  363. end
  364. module_function :status_code
  365. # A multipart form data parser, adapted from IOWA.
  366. #
  367. # Usually, Rack::Request#POST takes care of calling this.
  368. module Multipart
  369. class UploadedFile
  370. # The filename, *not* including the path, of the "uploaded" file
  371. attr_reader :original_filename
  372. # The content type of the "uploaded" file
  373. attr_accessor :content_type
  374. def initialize(path, content_type = "text/plain", binary = false)
  375. raise "#{path} file does not exist" unless ::File.exist?(path)
  376. @content_type = content_type
  377. @original_filename = ::File.basename(path)
  378. @tempfile = Tempfile.new(@original_filename)
  379. @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
  380. @tempfile.binmode if binary
  381. FileUtils.copy_file(path, @tempfile.path)
  382. end
  383. def path
  384. @tempfile.path
  385. end
  386. alias_method :local_path, :path
  387. def method_missing(method_name, *args, &block) #:nodoc:
  388. @tempfile.__send__(method_name, *args, &block)
  389. end
  390. end
  391. EOL = "\r\n"
  392. MULTIPART_BOUNDARY = "AaB03x"
  393. def self.parse_multipart(env)
  394. unless env['CONTENT_TYPE'] =~
  395. %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
  396. nil
  397. else
  398. boundary = "--#{$1}"
  399. params = {}
  400. buf = ""
  401. content_length = env['CONTENT_LENGTH'].to_i
  402. input = env['rack.input']
  403. input.rewind
  404. boundary_size = Utils.bytesize(boundary) + EOL.size
  405. bufsize = 16384
  406. content_length -= boundary_size
  407. read_buffer = ''
  408. status = input.read(boundary_size, read_buffer)
  409. raise EOFError, "bad content body" unless status == boundary + EOL
  410. rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
  411. loop {
  412. head = nil
  413. body = ''
  414. filename = content_type = name = nil
  415. until head && buf =~ rx
  416. if !head && i = buf.index(EOL+EOL)
  417. head = buf.slice!(0, i+2) # First \r\n
  418. buf.slice!(0, 2) # Second \r\n
  419. token = /[^\s()<>,;:\\"\/\[\]?=]+/
  420. condisp = /Content-Disposition:\s*#{token}\s*/i
  421. dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
  422. rfc2183 = /^#{condisp}(#{dispparm})+$/i
  423. broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
  424. broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
  425. if head =~ rfc2183
  426. filename = Hash[head.scan(dispparm)]['filename']
  427. filename = $1 if filename and filename =~ /^"(.*)"$/
  428. elsif head =~ broken_quoted
  429. filename = $1
  430. elsif head =~ broken_unquoted
  431. filename = $1
  432. end
  433. if filename && filename !~ /\\[^\\"]/
  434. filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
  435. end
  436. content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
  437. name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
  438. if filename
  439. body = Tempfile.new("RackMultipart")
  440. body.binmode if body.respond_to?(:binmode)
  441. end
  442. next
  443. end
  444. # Save the read body part.
  445. if head && (boundary_size+4 < buf.size)
  446. body << buf.slice!(0, buf.size - (boundary_size+4))
  447. end
  448. c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
  449. raise EOFError, "bad content body" if c.nil? || c.empty?
  450. buf << c
  451. content_length -= c.size
  452. end
  453. # Save the rest.
  454. if i = buf.index(rx)
  455. body << buf.slice!(0, i)
  456. buf.slice!(0, boundary_size+2)
  457. content_length = -1 if $1 == "--"
  458. end
  459. if filename == ""
  460. # filename is blank which means no file has been selected
  461. data = nil
  462. elsif filename
  463. body.rewind
  464. # Take the basename of the upload's original filename.
  465. # This handles the full Windows paths given by Internet Explorer
  466. # (and perhaps other broken user agents) without affecting
  467. # those which give the lone filename.
  468. filename = filename.split(/[\/\\]/).last
  469. data = {:filename => filename, :type => content_type,
  470. :name => name, :tempfile => body, :head => head}
  471. elsif !filename && content_type
  472. body.rewind
  473. # Generic multipart cases, not coming from a form
  474. data = {:type => content_type,
  475. :name => name, :tempfile => body, :head => head}
  476. else
  477. data = body
  478. end
  479. Utils.normalize_params(params, name, data) unless data.nil?
  480. # break if we're at the end of a buffer, but not if it is the end of a field
  481. break if (buf.empty? && $1 != EOL) || content_length == -1
  482. }
  483. input.rewind
  484. params
  485. end
  486. end
  487. def self.build_multipart(params, first = true)
  488. if first
  489. unless params.is_a?(Hash)
  490. raise ArgumentError, "value must be a Hash"
  491. end
  492. multipart = false
  493. query = lambda { |value|
  494. case value
  495. when Array
  496. value.each(&query)
  497. when Hash
  498. value.values.each(&query)
  499. when UploadedFile
  500. multipart = true
  501. end
  502. }
  503. params.values.each(&query)
  504. return nil unless multipart
  505. end
  506. flattened_params = Hash.new
  507. params.each do |key, value|
  508. k = first ? key.to_s : "[#{key}]"
  509. case value
  510. when Array
  511. value.map { |v|
  512. build_multipart(v, false).each { |subkey, subvalue|
  513. flattened_params["#{k}[]#{subkey}"] = subvalue
  514. }
  515. }
  516. when Hash
  517. build_multipart(value, false).each { |subkey, subvalue|
  518. flattened_params[k + subkey] = subvalue
  519. }
  520. else
  521. flattened_params[k] = value
  522. end
  523. end
  524. if first
  525. flattened_params.map { |name, file|
  526. if file.respond_to?(:original_filename)
  527. ::File.open(file.path, "rb") do |f|
  528. f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
  529. <<-EOF
  530. --#{MULTIPART_BOUNDARY}\r
  531. Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
  532. Content-Type: #{file.content_type}\r
  533. Content-Length: #{::File.stat(file.path).size}\r
  534. \r
  535. #{f.read}\r
  536. EOF
  537. end
  538. else
  539. <<-EOF
  540. --#{MULTIPART_BOUNDARY}\r
  541. Content-Disposition: form-data; name="#{name}"\r
  542. \r
  543. #{file}\r
  544. EOF
  545. end
  546. }.join + "--#{MULTIPART_BOUNDARY}--\r"
  547. else
  548. flattened_params
  549. end
  550. end
  551. end
  552. end
  553. end