PageRenderTime 31ms CodeModel.GetById 10ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

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