utils.rb 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. # -*- encoding: binary -*-
  2. require 'fileutils'
  3. require 'set'
  4. require 'tempfile'
  5. require 'rack/multipart'
  6. major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
  7. if major == 1 && minor < 9
  8. require 'rack/backports/uri/common_18'
  9. elsif major == 1 && minor == 9 && patch < 3
  10. require 'rack/backports/uri/common_192'
  11. else
  12. require 'uri/common'
  13. end
  14. module Rack
  15. # Rack::Utils contains a grab-bag of useful methods for writing web
  16. # applications adopted from all kinds of Ruby libraries.
  17. module Utils
  18. # URI escapes. (CGI style space to +)
  19. def escape(s)
  20. URI.encode_www_form_component(s)
  21. end
  22. module_function :escape
  23. # Like URI escaping, but with %20 instead of +. Strictly speaking this is
  24. # true URI escaping.
  25. def escape_path(s)
  26. escape(s).gsub('+', '%20')
  27. end
  28. module_function :escape_path
  29. # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
  30. # target encoding of the string returned, and it defaults to UTF-8
  31. if defined?(::Encoding)
  32. def unescape(s, encoding = Encoding::UTF_8)
  33. URI.decode_www_form_component(s, encoding)
  34. end
  35. else
  36. def unescape(s, encoding = nil)
  37. URI.decode_www_form_component(s, encoding)
  38. end
  39. end
  40. module_function :unescape
  41. DEFAULT_SEP = /[&;] */n
  42. class << self
  43. attr_accessor :key_space_limit
  44. end
  45. # The default number of bytes to allow parameter keys to take up.
  46. # This helps prevent a rogue client from flooding a Request.
  47. self.key_space_limit = 65536
  48. # Stolen from Mongrel, with some small modifications:
  49. # Parses a query string by breaking it up at the '&'
  50. # and ';' characters. You can also use this to parse
  51. # cookies by changing the characters used in the second
  52. # parameter (which defaults to '&;').
  53. def parse_query(qs, d = nil)
  54. params = KeySpaceConstrainedParams.new
  55. (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
  56. k, v = p.split('=', 2).map { |x| unescape(x) }
  57. if cur = params[k]
  58. if cur.class == Array
  59. params[k] << v
  60. else
  61. params[k] = [cur, v]
  62. end
  63. else
  64. params[k] = v
  65. end
  66. end
  67. return params.to_params_hash
  68. end
  69. module_function :parse_query
  70. def parse_nested_query(qs, d = nil)
  71. params = KeySpaceConstrainedParams.new
  72. (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
  73. k, v = p.split('=', 2).map { |s| unescape(s) }
  74. normalize_params(params, k, v)
  75. end
  76. return params.to_params_hash
  77. end
  78. module_function :parse_nested_query
  79. def normalize_params(params, name, v = nil)
  80. name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
  81. k = $1 || ''
  82. after = $' || ''
  83. return if k.empty?
  84. if after == ""
  85. params[k] = v
  86. elsif after == "[]"
  87. params[k] ||= []
  88. raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
  89. params[k] << v
  90. elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
  91. child_key = $1
  92. params[k] ||= []
  93. raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
  94. if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
  95. normalize_params(params[k].last, child_key, v)
  96. else
  97. params[k] << normalize_params(params.class.new, child_key, v)
  98. end
  99. else
  100. params[k] ||= params.class.new
  101. raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
  102. params[k] = normalize_params(params[k], after, v)
  103. end
  104. return params
  105. end
  106. module_function :normalize_params
  107. def params_hash_type?(obj)
  108. obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
  109. end
  110. module_function :params_hash_type?
  111. def build_query(params)
  112. params.map { |k, v|
  113. if v.class == Array
  114. build_query(v.map { |x| [k, x] })
  115. else
  116. v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
  117. end
  118. }.join("&")
  119. end
  120. module_function :build_query
  121. def build_nested_query(value, prefix = nil)
  122. case value
  123. when Array
  124. value.map { |v|
  125. build_nested_query(v, "#{prefix}[]")
  126. }.join("&")
  127. when Hash
  128. value.map { |k, v|
  129. build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
  130. }.join("&")
  131. when String
  132. raise ArgumentError, "value must be a Hash" if prefix.nil?
  133. "#{prefix}=#{escape(value)}"
  134. else
  135. prefix
  136. end
  137. end
  138. module_function :build_nested_query
  139. ESCAPE_HTML = {
  140. "&" => "&amp;",
  141. "<" => "&lt;",
  142. ">" => "&gt;",
  143. "'" => "&#x27;",
  144. '"' => "&quot;",
  145. "/" => "&#x2F;"
  146. }
  147. if //.respond_to?(:encoding)
  148. ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
  149. else
  150. # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
  151. # TODO doesn't apply to jruby, so a better condition above might be preferable?
  152. ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
  153. end
  154. # Escape ampersands, brackets and quotes to their HTML/XML entities.
  155. def escape_html(string)
  156. string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
  157. end
  158. module_function :escape_html
  159. def select_best_encoding(available_encodings, accept_encoding)
  160. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  161. expanded_accept_encoding =
  162. accept_encoding.map { |m, q|
  163. if m == "*"
  164. (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
  165. else
  166. [[m, q]]
  167. end
  168. }.inject([]) { |mem, list|
  169. mem + list
  170. }
  171. encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
  172. unless encoding_candidates.include?("identity")
  173. encoding_candidates.push("identity")
  174. end
  175. expanded_accept_encoding.find_all { |m, q|
  176. q == 0.0
  177. }.each { |m, _|
  178. encoding_candidates.delete(m)
  179. }
  180. return (encoding_candidates & available_encodings)[0]
  181. end
  182. module_function :select_best_encoding
  183. def set_cookie_header!(header, key, value)
  184. case value
  185. when Hash
  186. domain = "; domain=" + value[:domain] if value[:domain]
  187. path = "; path=" + value[:path] if value[:path]
  188. # According to RFC 2109, we need dashes here.
  189. # N.B.: cgi.rb uses spaces...
  190. expires = "; expires=" +
  191. rfc2822(value[:expires].clone.gmtime) if value[:expires]
  192. secure = "; secure" if value[:secure]
  193. httponly = "; HttpOnly" if value[:httponly]
  194. value = value[:value]
  195. end
  196. value = [value] unless Array === value
  197. cookie = escape(key) + "=" +
  198. value.map { |v| escape v }.join("&") +
  199. "#{domain}#{path}#{expires}#{secure}#{httponly}"
  200. case header["Set-Cookie"]
  201. when nil, ''
  202. header["Set-Cookie"] = cookie
  203. when String
  204. header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
  205. when Array
  206. header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
  207. end
  208. nil
  209. end
  210. module_function :set_cookie_header!
  211. def delete_cookie_header!(header, key, value = {})
  212. case header["Set-Cookie"]
  213. when nil, ''
  214. cookies = []
  215. when String
  216. cookies = header["Set-Cookie"].split("\n")
  217. when Array
  218. cookies = header["Set-Cookie"]
  219. end
  220. cookies.reject! { |cookie|
  221. if value[:domain]
  222. cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
  223. elsif value[:path]
  224. cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
  225. else
  226. cookie =~ /\A#{escape(key)}=/
  227. end
  228. }
  229. header["Set-Cookie"] = cookies.join("\n")
  230. set_cookie_header!(header, key,
  231. {:value => '', :path => nil, :domain => nil,
  232. :expires => Time.at(0) }.merge(value))
  233. nil
  234. end
  235. module_function :delete_cookie_header!
  236. # Return the bytesize of String; uses String#size under Ruby 1.8 and
  237. # String#bytesize under 1.9.
  238. if ''.respond_to?(:bytesize)
  239. def bytesize(string)
  240. string.bytesize
  241. end
  242. else
  243. def bytesize(string)
  244. string.size
  245. end
  246. end
  247. module_function :bytesize
  248. # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
  249. # of '% %b %Y'.
  250. # It assumes that the time is in GMT to comply to the RFC 2109.
  251. #
  252. # NOTE: I'm not sure the RFC says it requires GMT, but is ambigous enough
  253. # that I'm certain someone implemented only that option.
  254. # Do not use %a and %b from Time.strptime, it would use localized names for
  255. # weekday and month.
  256. #
  257. def rfc2822(time)
  258. wday = Time::RFC2822_DAY_NAME[time.wday]
  259. mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
  260. time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
  261. end
  262. module_function :rfc2822
  263. # Parses the "Range:" header, if present, into an array of Range objects.
  264. # Returns nil if the header is missing or syntactically invalid.
  265. # Returns an empty array if none of the ranges are satisfiable.
  266. def byte_ranges(env, size)
  267. # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
  268. http_range = env['HTTP_RANGE']
  269. return nil unless http_range
  270. ranges = []
  271. http_range.split(/,\s*/).each do |range_spec|
  272. matches = range_spec.match(/bytes=(\d*)-(\d*)/)
  273. return nil unless matches
  274. r0,r1 = matches[1], matches[2]
  275. if r0.empty?
  276. return nil if r1.empty?
  277. # suffix-byte-range-spec, represents trailing suffix of file
  278. r0 = [size - r1.to_i, 0].max
  279. r1 = size - 1
  280. else
  281. r0 = r0.to_i
  282. if r1.empty?
  283. r1 = size - 1
  284. else
  285. r1 = r1.to_i
  286. return nil if r1 < r0 # backwards range is syntactically invalid
  287. r1 = size-1 if r1 >= size
  288. end
  289. end
  290. ranges << (r0..r1) if r0 <= r1
  291. end
  292. ranges
  293. end
  294. module_function :byte_ranges
  295. # Context allows the use of a compatible middleware at different points
  296. # in a request handling stack. A compatible middleware must define
  297. # #context which should take the arguments env and app. The first of which
  298. # would be the request environment. The second of which would be the rack
  299. # application that the request would be forwarded to.
  300. class Context
  301. attr_reader :for, :app
  302. def initialize(app_f, app_r)
  303. raise 'running context does not respond to #context' unless app_f.respond_to? :context
  304. @for, @app = app_f, app_r
  305. end
  306. def call(env)
  307. @for.context(env, @app)
  308. end
  309. def recontext(app)
  310. self.class.new(@for, app)
  311. end
  312. def context(env, app=@app)
  313. recontext(app).call(env)
  314. end
  315. end
  316. # A case-insensitive Hash that preserves the original case of a
  317. # header when set.
  318. class HeaderHash < Hash
  319. def self.new(hash={})
  320. HeaderHash === hash ? hash : super(hash)
  321. end
  322. def initialize(hash={})
  323. super()
  324. @names = {}
  325. hash.each { |k, v| self[k] = v }
  326. end
  327. def each
  328. super do |k, v|
  329. yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
  330. end
  331. end
  332. def to_hash
  333. hash = {}
  334. each { |k,v| hash[k] = v }
  335. hash
  336. end
  337. def [](k)
  338. super(k) || super(@names[k.downcase])
  339. end
  340. def []=(k, v)
  341. canonical = k.downcase
  342. delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
  343. @names[k] = @names[canonical] = k
  344. super k, v
  345. end
  346. def delete(k)
  347. canonical = k.downcase
  348. result = super @names.delete(canonical)
  349. @names.delete_if { |name,| name.downcase == canonical }
  350. result
  351. end
  352. def include?(k)
  353. @names.include?(k) || @names.include?(k.downcase)
  354. end
  355. alias_method :has_key?, :include?
  356. alias_method :member?, :include?
  357. alias_method :key?, :include?
  358. def merge!(other)
  359. other.each { |k, v| self[k] = v }
  360. self
  361. end
  362. def merge(other)
  363. hash = dup
  364. hash.merge! other
  365. end
  366. def replace(other)
  367. clear
  368. other.each { |k, v| self[k] = v }
  369. self
  370. end
  371. end
  372. class KeySpaceConstrainedParams
  373. def initialize(limit = Utils.key_space_limit)
  374. @limit = limit
  375. @size = 0
  376. @params = {}
  377. end
  378. def [](key)
  379. @params[key]
  380. end
  381. def []=(key, value)
  382. @size += key.size unless @params.key?(key)
  383. raise RangeError, 'exceeded available parameter key space' if @size > @limit
  384. @params[key] = value
  385. end
  386. def key?(key)
  387. @params.key?(key)
  388. end
  389. def to_params_hash
  390. hash = @params
  391. hash.keys.each do |key|
  392. value = hash[key]
  393. if value.kind_of?(self.class)
  394. hash[key] = value.to_params_hash
  395. elsif value.kind_of?(Array)
  396. value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
  397. end
  398. end
  399. hash
  400. end
  401. end
  402. # Every standard HTTP code mapped to the appropriate message.
  403. # Generated with:
  404. # curl -s http://www.iana.org/assignments/http-status-codes | \
  405. # ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
  406. # puts " #{m[1]} => \x27#{m[2].strip}x27,"'
  407. HTTP_STATUS_CODES = {
  408. 100 => 'Continue',
  409. 101 => 'Switching Protocols',
  410. 102 => 'Processing',
  411. 200 => 'OK',
  412. 201 => 'Created',
  413. 202 => 'Accepted',
  414. 203 => 'Non-Authoritative Information',
  415. 204 => 'No Content',
  416. 205 => 'Reset Content',
  417. 206 => 'Partial Content',
  418. 207 => 'Multi-Status',
  419. 226 => 'IM Used',
  420. 300 => 'Multiple Choices',
  421. 301 => 'Moved Permanently',
  422. 302 => 'Found',
  423. 303 => 'See Other',
  424. 304 => 'Not Modified',
  425. 305 => 'Use Proxy',
  426. 306 => 'Reserved',
  427. 307 => 'Temporary Redirect',
  428. 400 => 'Bad Request',
  429. 401 => 'Unauthorized',
  430. 402 => 'Payment Required',
  431. 403 => 'Forbidden',
  432. 404 => 'Not Found',
  433. 405 => 'Method Not Allowed',
  434. 406 => 'Not Acceptable',
  435. 407 => 'Proxy Authentication Required',
  436. 408 => 'Request Timeout',
  437. 409 => 'Conflict',
  438. 410 => 'Gone',
  439. 411 => 'Length Required',
  440. 412 => 'Precondition Failed',
  441. 413 => 'Request Entity Too Large',
  442. 414 => 'Request-URI Too Long',
  443. 415 => 'Unsupported Media Type',
  444. 416 => 'Requested Range Not Satisfiable',
  445. 417 => 'Expectation Failed',
  446. 418 => "I'm a Teapot",
  447. 422 => 'Unprocessable Entity',
  448. 423 => 'Locked',
  449. 424 => 'Failed Dependency',
  450. 426 => 'Upgrade Required',
  451. 500 => 'Internal Server Error',
  452. 501 => 'Not Implemented',
  453. 502 => 'Bad Gateway',
  454. 503 => 'Service Unavailable',
  455. 504 => 'Gateway Timeout',
  456. 505 => 'HTTP Version Not Supported',
  457. 506 => 'Variant Also Negotiates',
  458. 507 => 'Insufficient Storage',
  459. 510 => 'Not Extended',
  460. }
  461. # Responses with HTTP status codes that should not have an entity body
  462. STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
  463. SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
  464. [message.downcase.gsub(/\s|-/, '_').to_sym, code]
  465. }.flatten]
  466. def status_code(status)
  467. if status.is_a?(Symbol)
  468. SYMBOL_TO_STATUS_CODE[status] || 500
  469. else
  470. status.to_i
  471. end
  472. end
  473. module_function :status_code
  474. Multipart = Rack::Multipart
  475. end
  476. end