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)
502: buf.slice!(0, 2)
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:
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:
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:
557: data = nil
558: elsif filename
559: body.rewind
560:
561:
562:
563:
564:
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:
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:
582: break if (buf.empty? && $1 != EOL) || content_length == -1
583: }
584:
585: input.rewind
586:
587: params
588: end
589: end