Module: Rack::Utils::Multipart
- Defined in:
- lib/rack/utils.rb
Overview
A multipart form data parser, adapted from IOWA.
Usually, Rack::Request#POST takes care of calling this.
Defined Under Namespace
Classes: UploadedFile
Constant Summary collapse
- EOL =
"\r\n"
- MULTIPART_BOUNDARY =
"AaB03x"
- MULTIPART =
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
- TOKEN =
/[^\s()<>,;:\\"\/\[\]?=]+/
- CONDISP =
/Content-Disposition:\s*#{TOKEN}\s*/i
- DISPPARM =
/;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})*/
- RFC2183 =
/^#{CONDISP}(#{DISPPARM})+$/i
- BROKEN_QUOTED =
/^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
- BROKEN_UNQUOTED =
/^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
- MULTIPART_CONTENT_TYPE =
/Content-Type: (.*)#{EOL}/ni
- MULTIPART_CONTENT_DISPOSITION =
/Content-Disposition:.*\s+name="?([^\";]*)"?/ni
- MULTIPART_CONTENT_ID =
/Content-ID:\s*([^#{EOL}]*)/ni
Class Method Summary collapse
Class Method Details
.build_multipart(params, first = true) ⇒ Object
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
# File 'lib/rack/utils.rb', line 627 def self.build_multipart(params, first = true) if first unless params.is_a?(Hash) raise ArgumentError, "value must be a Hash" end multipart = false query = lambda { |value| case value when Array value.each(&query) when Hash value.values.each(&query) when UploadedFile multipart = true end } params.values.each(&query) return nil unless multipart end flattened_params = Hash.new params.each do |key, value| k = first ? key.to_s : "[#{key}]" case value when Array value.map { |v| build_multipart(v, false).each { |subkey, subvalue| flattened_params["#{k}[]#{subkey}"] = subvalue } } when Hash build_multipart(value, false).each { |subkey, subvalue| flattened_params[k + subkey] = subvalue } else flattened_params[k] = value end end if first flattened_params.map { |name, file| if file.respond_to?(:original_filename) ::File.open(file.path, "rb") do |f| f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding) <<-EOF --#{MULTIPART_BOUNDARY}\r Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r Content-Type: #{file.content_type}\r Content-Length: #{::File.stat(file.path).size}\r \r #{f.read}\r EOF end else <<-EOF --#{MULTIPART_BOUNDARY}\r Content-Disposition: form-data; name="#{name}"\r \r #{file}\r EOF end }.join + "--#{MULTIPART_BOUNDARY}--\r" else flattened_params end end |
.parse_multipart(env) ⇒ Object
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 |
# File 'lib/rack/utils.rb', line 512 def self.parse_multipart(env) unless env['CONTENT_TYPE'] =~ MULTIPART nil else boundary = "--#{$1}" params = {} buf = "" content_length = env['CONTENT_LENGTH'].to_i input = env['rack.input'] input.rewind boundary_size = Utils.bytesize(boundary) + EOL.size bufsize = 16384 content_length -= boundary_size read_buffer = nil loop do read_buffer = input.gets break if read_buffer == boundary + EOL end rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n loop { head = nil body = '' filename = content_type = name = nil until head && buf =~ rx if !head && i = buf.index(EOL+EOL) head = buf.slice!(0, i+2) # First \r\n buf.slice!(0, 2) # Second \r\n if head =~ RFC2183 filename = Hash[head.scan(DISPPARM)]['filename'] filename = $1 if filename and filename =~ /^"(.*)"$/ elsif head =~ BROKEN_QUOTED filename = $1 elsif head =~ BROKEN_UNQUOTED filename = $1 end if filename && filename !~ /\\[^\\"]/ filename = Utils.unescape(filename).gsub(/\\(.)/, '\1') end content_type = head[MULTIPART_CONTENT_TYPE, 1] name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1] if filename body = Tempfile.new("RackMultipart") body.binmode if body.respond_to?(:binmode) end next end # Save the read body part. if head && (boundary_size+4 < buf.size) body << buf.slice!(0, buf.size - (boundary_size+4)) end c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer) raise EOFError, "bad content body" if c.nil? || c.empty? buf << c content_length -= c.size end # Save the rest. if i = buf.index(rx) body << buf.slice!(0, i) buf.slice!(0, boundary_size+2) content_length = -1 if $1 == "--" end if filename == "" # filename is blank which means no file has been selected data = nil elsif filename body.rewind # Take the basename of the upload's original filename. # This handles the full Windows paths given by Internet Explorer # (and perhaps other broken user agents) without affecting # those which give the lone filename. filename = filename.split(/[\/\\]/).last data = {:filename => filename, :type => content_type, :name => name, :tempfile => body, :head => head} elsif !filename && content_type && body.is_a?(IO) body.rewind # Generic multipart cases, not coming from a form data = {:type => content_type, :name => name, :tempfile => body, :head => head} else data = body end Utils.normalize_params(params, name, data) unless data.nil? # break if we're at the end of a buffer, but not if it is the end of a field break if (buf.empty? && $1 != EOL) || content_length == -1 } input.rewind params end end |