require 'gem_plugin' require 'mongrel' require 'digest/sha1' class CryptedDownload < GemPlugin::Plugin "/handlers" include Mongrel::HttpHandlerPlugin def process(request, response) query = Mongrel::HttpRequest.query_parse(request.params['QUERY_STRING']) cookies = Mongrel::HttpRequest.query_parse(request.params['HTTP_COOKIE']) secret_string = cookies.keys.first query['path'] = CGI::unescape(query['path']) if secret_string.nil? or query['token'].nil? or query['timestamp'].nil? or query['path'].nil? response.start(500){} elsif query['timestamp'].to_i < Time.now.to_i response.start(408){} elsif query['token'] == Digest::SHA1.hexdigest("#{secret_string}#{query['path']}#{query['timestamp']}").to_s send_file(File.expand_path("." + query['path'].decrypt(:symmetric, :key => secret_string) + query['file-name']), response) else response.start(403){} end end def self.generate(file_name, path, uri_prefix, request) secret_string = request.cookies.keys.first path = path.encrypt(:symmetric, :key => secret_string) timestamp = 1.minute.from_now.to_i.to_s token = Digest::SHA1.hexdigest(secret_string + path + timestamp) path = CGI::escape(path) return "#{uri_prefix}/?token=#{token}&path=#{path}&file-name=#{file_name}×tamp=#{timestamp}" end private # Sends the contents of a file back to the user. def send_file(path, response) # first we setup the headers and status then we do a very fast send on the socket directly file_status = File.stat(path) response.status = 200 # Set the last modified times as well and etag for all files response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate # Calculated the same as apache, not sure how well the works on win32 response.header[Mongrel::Const::ETAG] = Mongrel::Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino] #set the content type to something generic for now response.header[Mongrel::Const::CONTENT_TYPE] = @default_content_type #set the content disposition and filename response.header['Content-Disposition'] = "attachment; filename=\"#{File.basename(path)}\"" # send a status with out content length response.send_status(file_status.size) response.send_header response.send_file(path) end end