module Merb module ControllerMixin NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze CRLF = "\r\n".freeze EOL = CRLF def parse_multipart(request,boundary) boundary = "--#{boundary}" paramhsh = MerbHash.new buf = "" content_length = @env['CONTENT_LENGTH'].to_i input = request input.binmode if defined? input.binmode boundary_size = boundary.size + EOL.size bufsize = 16384 content_length -= boundary_size status = input.read(boundary_size) raise EOFError, "bad content body" unless status == boundary + EOL rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/ loop { head = nil body = '' filename = content_type = name = nil until head && buf =~ rx if !head && i = buf.index("\r\n\r\n") head = buf.slice!(0, i+2) # First \r\n buf.slice!(0, 2) # Second \r\n filename = head[FILENAME_REGEX, 1] content_type = head[CONTENT_TYPE_REGEX, 1] name = head[NAME_REGEX, 1] if filename && !filename.empty? body = Tempfile.new(:Merb) body.binmode if defined? body.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) 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.empty? body.rewind data = { :filename => File.basename(filename), :content_type => content_type, :tempfile => body, :size => File.size(body) } else data = body end paramhsh = normalize_params(paramhsh,name,data) break if buf.empty? || content_length == -1 } paramhsh end def normalize_params(parms, key, val) case key when /(.+)\[(.+)\]\[\]$/ parms[$1] ||= MerbHash.new parms[$1] = normalize_params(parms[$1], "#{$2}[]", val) when /(.+)\[(.+)\]$/ parms[$1] ||= MerbHash.new parms[$1] = normalize_params(parms[$1], $2, val) when /(.+)\[\]$/ (parms[$1] ||= []) << val else parms[key] = val if val end parms end # parses a query string or the payload of a POST # request into the params hash. So for example: # /foo?bar=nik&post[title]=heya&post[body]=whatever # parses into: # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}} def query_parse(qs, d = '&;') m = proc {|_,o,n|o.u(n,&m)rescue([*o]< url}) return '' end # pass in a path to a file and this will set the # right headers and let mongrel do its thang and # serve the static file directly. def send_file(file, opts={}) opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts)) disposition = opts[:disposition].dup || 'attachment' disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}") headers.update( 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary', 'X-SENDFILE' => file ) return end # stream_file( { :filename => file_name, # :type => content_type, # :content_length => content_length }) do # @response.send_status(opts[:content_length]) # @response.send_header # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk| # @response.write chunk # end # end def stream_file(opts={}, &stream) opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts)) disposition = opts[:disposition].dup || 'attachment' disposition << %(; filename="#{opts[:filename]}") @response.headers.update( 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary', 'CONTENT-LENGTH' => opts[:content_length] ) stream end # This uses nginx X-Accel-Redirect header to send # a file directly from nginx. See the nginx wiki: # http://wiki.codemongers.com/NginxXSendfile def nginx_send_file(file) headers['X-Accel-Redirect'] = File.expand_path(file) return end # Sets a cookie to be included in the response. def set_cookie(name, value, expires) (headers['Set-Cookie'] ||='') << (Merb::Const::SET_COOKIE % [name.to_s, escape(value.to_s), expires.rfc2822]) end # Marks a cookie as deleted. The cookie is given an expires stamp in # the past. def delete_cookie(name) set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME) end # creates a random token like: # "b9a82e011694cc13a4249731b9e83cea" def make_token require 'digest/md5' Digest::MD5.hexdigest("#{inspect}#{Time.now}#{rand}") end def rand_uuid "%04x%04x-%04x-%04x-%04x-%06x%06x" % [ rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x1000000), rand(0x1000000), ] end def escape_xml(obj) obj.to_s.gsub(/[&<>"']/) { |s| Merb::Const::ESCAPE_TABLE[s] } end alias h escape_xml alias html_escape escape_xml # does url escaping def escape(s) Mongrel::HttpRequest.escape(s) end # does url unescaping def unescape(s) Mongrel::HttpRequest.unescape(s) end end end