# File lib/rack/utils.rb, line 474
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) # First \r\n
507:                 buf.slice!(0, 2)          # Second \r\n
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:               # Save the read body part.
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:             # Save the rest.
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:               # filename is blank which means no file has been selected
562:               data = nil
563:             elsif filename
564:               body.rewind
565: 
566:               # Take the basename of the upload's original filename.
567:               # This handles the full Windows paths given by Internet Explorer
568:               # (and perhaps other broken user agents) without affecting
569:               # those which give the lone filename.
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:               # Generic multipart cases, not coming from a form
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:             # break if we're at the end of a buffer, but not if it is the end of a field
587:             break if (buf.empty? && $1 != EOL) || content_length == -1
588:           }
589: 
590:           input.rewind
591: 
592:           params
593:         end
594:       end