class SC::Rack::Filesystem
Sends and modifies files in the local file system. Uses Rack::Rack::File to transfer the file, which makes this work with rack/sendfile and other middleware. Some code originally adapted from Rack::Rack::File.
Public Class Methods
# File lib/sproutcore/rack/filesystem.rb, line 22 def initialize(project) @project = project @ignore_directories = ['tmp', 'scripts', 'sproutcore']; end
Public Instance Methods
Accepts the following commands
GET /foo/bar/blah
-
send blah back to the client
POST /foo/bar/blah with no “action” body parameter
-
also sends contents of blah back to the client (defeats caching)
POST to /foo/bar/blah with “action=save” and “file” body parameters
-
create a file named “blah” with contents of file OR overwrite “blah” with contents of file; create /foo/bar if needed
POST to /foo/bar/blah with “action=touch” body parameter
-
create an empty file named “blah” OR update the timestamp of blah; create /foo/bar if needed
POST to /foo/bar/blah with “action=makedir” body parameter
-
create a directory named “blah” and create parent directories if needed
POST to /foo/bar/blah with “action=append” and “file” body parameters
-
append the contents of the file body parameter to the blah file; return error if “blah” doesn't exist
POST to /foo/bar/blah with “action=remove” body parameter
-
delete a file or directory named “blah”; will fail if directory is not empty
# File lib/sproutcore/rack/filesystem.rb, line 61 def call(env) regex = /^\/sproutcore\/fs/ rqust = ::Rack::Request.new(env) params = rqust.params body = rqust.body.read() path = env["PATH_INFO"] return [404, {}, "not found"] unless path =~ regex path = path.sub regex, '' # remove path prefix action = params['action'] #Missing: #need a way to rename/move files and folders case action when nil #sends the contents of the file send_file(path, env) when 'list' #returns folder structure list_files(path) when 'save' #doesn't do anything useful save_file(path,params) when 'overwrite' #overwrites file's contents with whatever is sent overwrite_file(path,params,body) when 'append' #does nothing append_file(path,params) when 'touch' #creates a file touch_file(path) overwrite_file(path,params,body) if body when 'mkdir' #creates a folder make_directory(path) when 'remove' #removes the file remove_path(path) else forbidden("Unknown action #{params['action']}") end rescue StandardError return forbidden("Cannot #{action} #{path} due to #{$!.message}") end
# File lib/sproutcore/rack/filesystem.rb, line 27 def root_dir unless @root_dir @root_dir = @project.project_root end return @root_dir end
Private Instance Methods
# File lib/sproutcore/rack/filesystem.rb, line 158 def append_file(original_path,params) with_sanitized_path(original_path) do |sanitized_path| with_modifiable_path(sanitized_path) do |dest_path| append_to_file(dest_path,params) success("Appended to #{dest_path}") end end end
# File lib/sproutcore/rack/filesystem.rb, line 221 def append_to_file(dest_path,params) with_tempfile_path(params['file']) do |tempfile_path| ::Rack::File.open(dest_path,'a') do |dest_file| ::Rack::File.open(tempfile_path,'r') do |source_file| FileUtils.copy_stream(source_file,dest_file) end end end end
# File lib/sproutcore/rack/filesystem.rb, line 212 def blast_current_file(dest_path, params, body) return not_found(dest_path) unless File.exist?(dest_path) return forbidden("Cannot modify #{dest_path}") unless File.writable?(dest_path) return forbidden("No content body for #{dest_path}") unless body File.open(dest_path, 'w') do |f| f.puts(body) end end
# File lib/sproutcore/rack/filesystem.rb, line 111 def folder_contents(dir) results = [] Dir.new(dir).each do |path| name = path path = dir + path sc_path = dir.gsub(root_dir,"") if FileTest.directory?(path) if not (File.basename(path)[0] == ?. or @ignore_directories.include?(File.basename(path))) results<< {:type => :Dir, :dir => sc_path, :name =>name, :contents=> folder_contents(path+"/"), :id => @id+=1} end else #just a regular file results<< {:type => :File, :dir => sc_path, :name => name, :id => @id+=1 } end end return results end
# File lib/sproutcore/rack/filesystem.rb, line 285 def forbidden(body) body += "\n" unless body =~ /\n$/ [403, { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s}, [body] ] end
# File lib/sproutcore/rack/filesystem.rb, line 102 def list_files(original_path) results = [] @id = 0 with_sanitized_path(original_path) do |root_path| results = folder_contents(root_path) end success(results.to_json) end
# File lib/sproutcore/rack/filesystem.rb, line 176 def make_directory(original_path) with_sanitized_path(original_path) do |sanitized_path| with_modifiable_path(sanitized_path) do |dest_path| FileUtils.mkdir(dest_path) # with_modifiable_path call takes care of any parent directories success("Created directory #{original_path}") end end end
# File lib/sproutcore/rack/filesystem.rb, line 276 def not_found(path) body = "Path not found: #{path}\n" [404, { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s}, [body] ] end
This method will blast the current file
@param current_file_path | the path of the current file @param params | the contents of the file
# File lib/sproutcore/rack/filesystem.rb, line 134 def overwrite_file(current_file_path, params, body) with_sanitized_path(current_file_path) do |file_path| blast_current_file(file_path, params, body) success("Saved your changes to #{file_path}") end end
will raise SystemCallError if the path to be removed is a non-empty directory
# File lib/sproutcore/rack/filesystem.rb, line 189 def remove_path(original_path) with_sanitized_path(original_path) do |destroy_path| return not_found(original_path) unless File.exist?(destroy_path) return forbidden( "Cannot modify #{destroy_path}" ) unless File.writable?(destroy_path) if File.directory?(destroy_path) Dir.rmdir(destroy_path) success("Removed directory #{destroy_path}") else File.delete(destroy_path) success("Removed file #{destroy_path}") end end end
# File lib/sproutcore/rack/filesystem.rb, line 149 def save_file(original_path,params) with_sanitized_path(original_path) do |sanitized_path| with_modifiable_path(sanitized_path) do |dest_path| save_to_file(dest_path,params) success("Saved #{dest_path}") end end end
# File lib/sproutcore/rack/filesystem.rb, line 205 def save_to_file(dest_path,params) with_tempfile_path(params['file']) do |tempfile_path| puts "moving #{tempfile_path} to #{dest_path}" FileUtils.mv(tempfile_path,dest_path) end end
# File lib/sproutcore/rack/filesystem.rb, line 141 def send_file(original_path, env) with_sanitized_path(original_path) do |sanitized_path| with_readable_path(sanitized_path) do |readable_path| send_file_response(readable_path, env) end end end
# File lib/sproutcore/rack/filesystem.rb, line 238 def send_file_response(path, env) if size = File.size?(path) # use Rack::File so streaming works and for max compatibility with # other handlers # TODO we could make this work with Rack::Sendfile pretty easily if # the server supports it body = ::Rack::File::new(root_dir) body.path = path body.serving(env) body else # file does not provide size info via stat, so we have to read it # into memory body = [File.read(path)] size = ::Rack::Utils.bytesize(body.first) end [200, { "Last-Modified" => File.mtime(path).httpdate, "Content-Type" => ::Rack::Mime.mime_type(File.extname(path), 'text/plain'), "Content-Length" => size.to_s }, body] end
# File lib/sproutcore/rack/filesystem.rb, line 294 def success(msg) [ 200, { 'Content-Type' => 'text/html' }, msg ] end
# File lib/sproutcore/rack/filesystem.rb, line 167 def touch_file(original_path) with_sanitized_path(original_path) do |sanitized_path| with_modifiable_path(sanitized_path) do |dest_path| FileUtils.touch(dest_path) success("Touched #{original_path}") end end end
# File lib/sproutcore/rack/filesystem.rb, line 298 def with_modifiable_path(path) # can't use File.dirname here as it only recognizes Unix separators path_parts = path.split(::File::SEPARATOR) dir_name = File.join(path_parts[0..-2]) begin FileUtils.mkdir_p(dir_name) rescue Errno::EACCES return forbidden( "Cannot create directory #{dir_name} due to #{$!.message}" ) end return forbidden( "Cannot write to directory #{dir_name}" ) unless File.writable?(dir_name) if File.file?(path) && !File.writable?(path) return forbidden("Cannot write to file #{path}") end yield path end
# File lib/sproutcore/rack/filesystem.rb, line 263 def with_readable_path(path) return not_found(path) unless File.file?(path) return forbidden("Cannot read #{path}") unless File.readable?(path) yield path end
# File lib/sproutcore/rack/filesystem.rb, line 269 def with_sanitized_path(orig_path) path = ::Rack::Utils.unescape(orig_path) return forbidden("Illegal path #{path}") if path.include?("..") yield File.join(root_dir, path) end
# File lib/sproutcore/rack/filesystem.rb, line 231 def with_tempfile_path(file_param) return forbidden("Did not receive a file") unless file_param tempfile = file_param[:tempfile] return forbidden("File was not uploaded") unless tempfile yield tempfile.path end