474: def self.parse_multipart(env)
475: unless env['CONTENT_TYPE'] =~
476: %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
477: nil
478: else
479: boundary = "--#{$1}"
480:
481: params = {}
482: buf = ""
483: content_length = env['CONTENT_LENGTH'].to_i
484: input = env['rack.input']
485: input.rewind
486:
487: boundary_size = Utils.bytesize(boundary) + EOL.size
488: bufsize = 16384
489:
490: content_length -= boundary_size
491:
492: read_buffer = ''
493:
494: status = input.read(boundary_size, read_buffer)
495: raise EOFError, "bad content body" unless status == boundary + EOL
496:
497: rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
498:
499: loop {
500: head = nil
501: body = ''
502: filename = content_type = name = nil
503:
504: until head && buf =~ rx
505: if !head && i = buf.index(EOL+EOL)
506: head = buf.slice!(0, i+2)
507: buf.slice!(0, 2)
508:
509: token = /[^\s()<>,;:\\"\/\[\]?=]+/
510: condisp = /Content-Disposition:\s*#{token}\s*/i
511: dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
512:
513: rfc2183 = /^#{condisp}(#{dispparm})+$/i
514: broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
515: broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
516:
517: if head =~ rfc2183
518: filename = Hash[head.scan(dispparm)]['filename']
519: filename = $1 if filename and filename =~ /^"(.*)"$/
520: elsif head =~ broken_quoted
521: filename = $1
522: elsif head =~ broken_unquoted
523: filename = $1
524: end
525:
526: if filename && filename !~ /\\[^\\"]/
527: filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
528: end
529:
530: content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
531: name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
532:
533: if filename
534: body = Tempfile.new("RackMultipart")
535: body.binmode if body.respond_to?(:binmode)
536: end
537:
538: next
539: end
540:
541:
542: if head && (boundary_size+4 < buf.size)
543: body << buf.slice!(0, buf.size - (boundary_size+4))
544: end
545:
546: c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
547: raise EOFError, "bad content body" if c.nil? || c.empty?
548: buf << c
549: content_length -= c.size
550: end
551:
552:
553: if i = buf.index(rx)
554: body << buf.slice!(0, i)
555: buf.slice!(0, boundary_size+2)
556:
557: content_length = -1 if $1 == "--"
558: end
559:
560: if filename == ""
561:
562: data = nil
563: elsif filename
564: body.rewind
565:
566:
567:
568:
569:
570: filename = filename.split(/[\/\\]/).last
571:
572: data = {:filename => filename, :type => content_type,
573: :name => name, :tempfile => body, :head => head}
574: elsif !filename && content_type
575: body.rewind
576:
577:
578: data = {:type => content_type,
579: :name => name, :tempfile => body, :head => head}
580: else
581: data = body
582: end
583:
584: Utils.normalize_params(params, name, data) unless data.nil?
585:
586:
587: break if (buf.empty? && $1 != EOL) || content_length == -1
588: }
589:
590: input.rewind
591:
592: params
593: end
594: end