/vendor/bundle/jruby/2.1/gems/rack-1.5.2/lib/rack/multipart/parser.rb

https://github.com/delowong/logstash · Ruby · 176 lines · 126 code · 41 blank · 9 comment · 35 complexity · 6be747d333b1ecc992aebdc4fbb1af6b MD5 · raw file

  1. require 'rack/utils'
  2. module Rack
  3. module Multipart
  4. class Parser
  5. BUFSIZE = 16384
  6. def initialize(env)
  7. @env = env
  8. end
  9. def parse
  10. return nil unless setup_parse
  11. fast_forward_to_first_boundary
  12. loop do
  13. head, filename, content_type, name, body =
  14. get_current_head_and_filename_and_content_type_and_name_and_body
  15. # Save the rest.
  16. if i = @buf.index(rx)
  17. body << @buf.slice!(0, i)
  18. @buf.slice!(0, @boundary_size+2)
  19. @content_length = -1 if $1 == "--"
  20. end
  21. filename, data = get_data(filename, body, content_type, name, head)
  22. Utils.normalize_params(@params, name, data) unless data.nil?
  23. # break if we're at the end of a buffer, but not if it is the end of a field
  24. break if (@buf.empty? && $1 != EOL) || @content_length == -1
  25. end
  26. @io.rewind
  27. @params.to_params_hash
  28. end
  29. private
  30. def setup_parse
  31. return false unless @env['CONTENT_TYPE'] =~ MULTIPART
  32. @boundary = "--#{$1}"
  33. @buf = ""
  34. @params = Utils::KeySpaceConstrainedParams.new
  35. @io = @env['rack.input']
  36. @io.rewind
  37. @boundary_size = Utils.bytesize(@boundary) + EOL.size
  38. if @content_length = @env['CONTENT_LENGTH']
  39. @content_length = @content_length.to_i
  40. @content_length -= @boundary_size
  41. end
  42. true
  43. end
  44. def full_boundary
  45. @boundary + EOL
  46. end
  47. def rx
  48. @rx ||= /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
  49. end
  50. def fast_forward_to_first_boundary
  51. loop do
  52. content = @io.read(BUFSIZE)
  53. raise EOFError, "bad content body" unless content
  54. @buf << content
  55. while @buf.gsub!(/\A([^\n]*\n)/, '')
  56. read_buffer = $1
  57. return if read_buffer == full_boundary
  58. end
  59. raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
  60. end
  61. end
  62. def get_current_head_and_filename_and_content_type_and_name_and_body
  63. head = nil
  64. body = ''
  65. filename = content_type = name = nil
  66. content = nil
  67. until head && @buf =~ rx
  68. if !head && i = @buf.index(EOL+EOL)
  69. head = @buf.slice!(0, i+2) # First \r\n
  70. @buf.slice!(0, 2) # Second \r\n
  71. content_type = head[MULTIPART_CONTENT_TYPE, 1]
  72. name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1]
  73. filename = get_filename(head)
  74. if filename
  75. body = Tempfile.new("RackMultipart")
  76. body.binmode if body.respond_to?(:binmode)
  77. end
  78. next
  79. end
  80. # Save the read body part.
  81. if head && (@boundary_size+4 < @buf.size)
  82. body << @buf.slice!(0, @buf.size - (@boundary_size+4))
  83. end
  84. content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
  85. raise EOFError, "bad content body" if content.nil? || content.empty?
  86. @buf << content
  87. @content_length -= content.size if @content_length
  88. end
  89. [head, filename, content_type, name, body]
  90. end
  91. def get_filename(head)
  92. filename = nil
  93. if head =~ RFC2183
  94. filename = Hash[head.scan(DISPPARM)]['filename']
  95. filename = $1 if filename and filename =~ /^"(.*)"$/
  96. elsif head =~ BROKEN_QUOTED
  97. filename = $1
  98. elsif head =~ BROKEN_UNQUOTED
  99. filename = $1
  100. end
  101. if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
  102. filename = Utils.unescape(filename)
  103. end
  104. if filename && filename !~ /\\[^\\"]/
  105. filename = filename.gsub(/\\(.)/, '\1')
  106. end
  107. filename
  108. end
  109. def get_data(filename, body, content_type, name, head)
  110. data = nil
  111. if filename == ""
  112. # filename is blank which means no file has been selected
  113. return data
  114. elsif filename
  115. body.rewind
  116. # Take the basename of the upload's original filename.
  117. # This handles the full Windows paths given by Internet Explorer
  118. # (and perhaps other broken user agents) without affecting
  119. # those which give the lone filename.
  120. filename = filename.split(/[\/\\]/).last
  121. data = {:filename => filename, :type => content_type,
  122. :name => name, :tempfile => body, :head => head}
  123. elsif !filename && content_type && body.is_a?(IO)
  124. body.rewind
  125. # Generic multipart cases, not coming from a form
  126. data = {:type => content_type,
  127. :name => name, :tempfile => body, :head => head}
  128. else
  129. data = body
  130. end
  131. [filename, data]
  132. end
  133. end
  134. end
  135. end