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.