A multipart form data parser, adapted from IOWA.
Usually, Rack::Request#POST takes care of calling this.
(Not documented)
# File lib/rack/utils.rb, line 549 549: def self.build_multipart(params, first = true) 550: if first 551: unless params.is_a?(Hash) 552: raise ArgumentError, "value must be a Hash" 553: end 554: 555: multipart = false 556: query = lambda { |value| 557: case value 558: when Array 559: value.each(&query) 560: when Hash 561: value.values.each(&query) 562: when UploadedFile 563: multipart = true 564: end 565: } 566: params.values.each(&query) 567: return nil unless multipart 568: end 569: 570: flattened_params = Hash.new 571: 572: params.each do |key, value| 573: k = first ? key.to_s : "[#{key}]" 574: 575: case value 576: when Array 577: value.map { |v| 578: build_multipart(v, false).each { |subkey, subvalue| 579: flattened_params["#{k}[]#{subkey}"] = subvalue 580: } 581: } 582: when Hash 583: build_multipart(value, false).each { |subkey, subvalue| 584: flattened_params[k + subkey] = subvalue 585: } 586: else 587: flattened_params[k] = value 588: end 589: end 590: 591: if first 592: flattened_params.map { |name, file| 593: if file.respond_to?(:original_filename) 594: ::File.open(file.path, "rb") do |f| 595: f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding) 596: "--\#{MULTIPART_BOUNDARY}\\r\nContent-Disposition: form-data; name=\"\#{name}\"; filename=\"\#{Utils.escape(file.original_filename)}\"\\r\nContent-Type: \#{file.content_type}\\r\nContent-Length: \#{::File.stat(file.path).size}\\r\n\\r\n\#{f.read}\\r\n" 597: end 598: else 599: "--\#{MULTIPART_BOUNDARY}\\r\nContent-Disposition: form-data; name=\"\#{name}\"\\r\n\\r\n\#{file}\\r\n" 600: end 601: }.join + "--#{MULTIPART_BOUNDARY}--\r" 602: else 603: flattened_params 604: end 605: end
(Not documented)
# File lib/rack/utils.rb, line 446 446: def self.parse_multipart(env) 447: unless env['CONTENT_TYPE'] =~ 448: %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n 449: nil 450: else 451: boundary = "--#{$1}" 452: 453: params = {} 454: buf = "" 455: content_length = env['CONTENT_LENGTH'].to_i 456: input = env['rack.input'] 457: input.rewind 458: 459: boundary_size = Utils.bytesize(boundary) + EOL.size 460: bufsize = 16384 461: 462: content_length -= boundary_size 463: 464: read_buffer = '' 465: 466: status = input.read(boundary_size, read_buffer) 467: raise EOFError, "bad content body" unless status == boundary + EOL 468: 469: rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n 470: 471: loop { 472: head = nil 473: body = '' 474: filename = content_type = name = nil 475: 476: until head && buf =~ rx 477: if !head && i = buf.index(EOL+EOL) 478: head = buf.slice!(0, i+2) # First \r\n 479: buf.slice!(0, 2) # Second \r\n 480: 481: filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1] 482: content_type = head[/Content-Type: (.*)#{EOL}/ni, 1] 483: name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1] 484: 485: if content_type || filename 486: body = Tempfile.new("RackMultipart") 487: body.binmode if body.respond_to?(:binmode) 488: end 489: 490: next 491: end 492: 493: # Save the read body part. 494: if head && (boundary_size+4 < buf.size) 495: body << buf.slice!(0, buf.size - (boundary_size+4)) 496: end 497: 498: c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer) 499: raise EOFError, "bad content body" if c.nil? || c.empty? 500: buf << c 501: content_length -= c.size 502: end 503: 504: # Save the rest. 505: if i = buf.index(rx) 506: body << buf.slice!(0, i) 507: buf.slice!(0, boundary_size+2) 508: 509: content_length = -1 if $1 == "--" 510: end 511: 512: if filename == "" 513: # filename is blank which means no file has been selected 514: data = nil 515: elsif filename 516: body.rewind 517: 518: # Take the basename of the upload's original filename. 519: # This handles the full Windows paths given by Internet Explorer 520: # (and perhaps other broken user agents) without affecting 521: # those which give the lone filename. 522: filename =~ /^(?:.*[:\\\/])?(.*)/m 523: filename = $1 524: 525: data = {:filename => filename, :type => content_type, 526: :name => name, :tempfile => body, :head => head} 527: elsif !filename && content_type 528: body.rewind 529: 530: # Generic multipart cases, not coming from a form 531: data = {:type => content_type, 532: :name => name, :tempfile => body, :head => head} 533: else 534: data = body 535: end 536: 537: Utils.normalize_params(params, name, data) unless data.nil? 538: 539: # break if we're at the end of a buffer, but not if it is the end of a field 540: break if (buf.empty? && $1 != EOL) || content_length == -1 541: } 542: 543: input.rewind 544: 545: params 546: end 547: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.