Class Index [+]

Quicksearch

Rack::Utils::Multipart

A multipart form data parser, adapted from IOWA.

Usually, Rack::Request#POST takes care of calling this.

Constants

EOL
MULTIPART_BOUNDARY

Public Class Methods

build_multipart(params, first = true) click to toggle source
     # File lib/rack/utils.rb, line 596
596:       def self.build_multipart(params, first = true)
597:         if first
598:           unless params.is_a?(Hash)
599:             raise ArgumentError, "value must be a Hash"
600:           end
601: 
602:           multipart = false
603:           query = lambda { |value|
604:             case value
605:             when Array
606:               value.each(&query)
607:             when Hash
608:               value.values.each(&query)
609:             when UploadedFile
610:               multipart = true
611:             end
612:           }
613:           params.values.each(&query)
614:           return nil unless multipart
615:         end
616: 
617:         flattened_params = Hash.new
618: 
619:         params.each do |key, value|
620:           k = first ? key.to_s : "[#{key}]"
621: 
622:           case value
623:           when Array
624:             value.map { |v|
625:               build_multipart(v, false).each { |subkey, subvalue|
626:                 flattened_params["#{k}[]#{subkey}"] = subvalue
627:               }
628:             }
629:           when Hash
630:             build_multipart(value, false).each { |subkey, subvalue|
631:               flattened_params[k + subkey] = subvalue
632:             }
633:           else
634:             flattened_params[k] = value
635:           end
636:         end
637: 
638:         if first
639:           flattened_params.map { |name, file|
640:             if file.respond_to?(:original_filename)
641:               ::File.open(file.path, "rb") do |f|
642:                 f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
643: --#{MULTIPART_BOUNDARY}\rContent-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\rContent-Type: #{file.content_type}\rContent-Length: #{::File.stat(file.path).size}\r\r#{f.read}\r
644:               end
645:             else
646: --#{MULTIPART_BOUNDARY}\rContent-Disposition: form-data; name="#{name}"\r\r#{file}\r
647:             end
648:           }.join + "--#{MULTIPART_BOUNDARY}--\r"
649:         else
650:           flattened_params
651:         end
652:       end
parse_multipart(env) click to toggle source
     # File lib/rack/utils.rb, line 474
474:       def self.parse_multipart(env)
475:         unless env['CONTENT_TYPE'] =~
476:             %\Amultipart/.*boundary=\"?([^\";,]+)\"?|
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}|--)/
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*/
511:                 dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
512: 
513:                 rfc2183 = /^#{condisp}(#{dispparm})+$/
514:                 broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/
515:                 broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/
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}/i, 1]
531:                 name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/i, 1] || head[/Content-ID:\s*([^#{EOL}]*)/i, 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

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.