# File lib/rack/utils.rb, line 469
469:       def self.parse_multipart(env)
470:         unless env['CONTENT_TYPE'] =~
471:             %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
472:           nil
473:         else
474:           boundary = "--#{$1}"
475: 
476:           params = {}
477:           buf = ""
478:           content_length = env['CONTENT_LENGTH'].to_i
479:           input = env['rack.input']
480:           input.rewind
481: 
482:           boundary_size = Utils.bytesize(boundary) + EOL.size
483:           bufsize = 16384
484: 
485:           content_length -= boundary_size
486: 
487:           read_buffer = ''
488: 
489:           status = input.read(boundary_size, read_buffer)
490:           raise EOFError, "bad content body"  unless status == boundary + EOL
491: 
492:           rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
493: 
494:           loop {
495:             head = nil
496:             body = ''
497:             filename = content_type = name = nil
498: 
499:             until head && buf =~ rx
500:               if !head && i = buf.index(EOL+EOL)
501:                 head = buf.slice!(0, i+2) # First \r\n
502:                 buf.slice!(0, 2)          # Second \r\n
503: 
504:                 token = /[^\s()<>,;:\\"\/\[\]?=]+/
505:                 condisp = /Content-Disposition:\s*#{token}\s*/i
506:                 dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
507: 
508:                 rfc2183 = /^#{condisp}(#{dispparm})+$/i
509:                 broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
510:                 broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
511: 
512:                 if head =~ rfc2183
513:                   filename = Hash[head.scan(dispparm)]['filename']
514:                   filename = $1 if filename and filename =~ /^"(.*)"$/
515:                 elsif head =~ broken_quoted
516:                   filename = $1
517:                 elsif head =~ broken_unquoted
518:                   filename = $1
519:                 end
520: 
521:                 if filename && filename !~ /\\[^\\"]/
522:                   filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
523:                 end
524: 
525:                 content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
526:                 name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
527: 
528:                 if filename
529:                   body = Tempfile.new("RackMultipart")
530:                   body.binmode  if body.respond_to?(:binmode)
531:                 end
532: 
533:                 next
534:               end
535: 
536:               # Save the read body part.
537:               if head && (boundary_size+4 < buf.size)
538:                 body << buf.slice!(0, buf.size - (boundary_size+4))
539:               end
540: 
541:               c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
542:               raise EOFError, "bad content body"  if c.nil? || c.empty?
543:               buf << c
544:               content_length -= c.size
545:             end
546: 
547:             # Save the rest.
548:             if i = buf.index(rx)
549:               body << buf.slice!(0, i)
550:               buf.slice!(0, boundary_size+2)
551: 
552:               content_length = -1  if $1 == "--"
553:             end
554: 
555:             if filename == ""
556:               # filename is blank which means no file has been selected
557:               data = nil
558:             elsif filename
559:               body.rewind
560: 
561:               # Take the basename of the upload's original filename.
562:               # This handles the full Windows paths given by Internet Explorer
563:               # (and perhaps other broken user agents) without affecting
564:               # those which give the lone filename.
565:               filename = filename.split(/[\/\\]/).last
566: 
567:               data = {:filename => filename, :type => content_type,
568:                       :name => name, :tempfile => body, :head => head}
569:             elsif !filename && content_type
570:               body.rewind
571: 
572:               # Generic multipart cases, not coming from a form
573:               data = {:type => content_type,
574:                       :name => name, :tempfile => body, :head => head}
575:             else
576:               data = body
577:             end
578: 
579:             Utils.normalize_params(params, name, data) unless data.nil?
580: 
581:             # break if we're at the end of a buffer, but not if it is the end of a field
582:             break if (buf.empty? && $1 != EOL) || content_length == -1
583:           }
584: 
585:           input.rewind
586: 
587:           params
588:         end
589:       end