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

new(project) click to toggle source
# File lib/sproutcore/rack/filesystem.rb, line 22
def initialize(project)
  @project = project
  @ignore_directories = ['tmp', 'scripts', 'sproutcore'];
end

Public Instance Methods

call(env) click to toggle source

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
root_dir() click to toggle source
# 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

append_file(original_path,params) click to toggle source
# 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
append_to_file(dest_path,params) click to toggle source
# 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
blast_current_file(dest_path, params, body) click to toggle source
# 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
folder_contents(dir) click to toggle source
# 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
forbidden(body) click to toggle source
# 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
list_files(original_path) click to toggle source
# 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
make_directory(original_path) click to toggle source
# 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
not_found(path) click to toggle source
# 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
overwrite_file(current_file_path, params, body) click to toggle source

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
remove_path(original_path) click to toggle source

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
save_file(original_path,params) click to toggle source
# 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
save_to_file(dest_path,params) click to toggle source
# 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
send_file(original_path, env) click to toggle source
# 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
send_file_response(path, env) click to toggle source
# 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
success(msg) click to toggle source
# File lib/sproutcore/rack/filesystem.rb, line 294
def success(msg)
  [ 200, { 'Content-Type' => 'text/html' }, msg ]
end
touch_file(original_path) click to toggle source
# 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
with_modifiable_path(path) { |path| ... } click to toggle source
# 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
with_readable_path(path) { |path| ... } click to toggle source
# 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
with_sanitized_path(orig_path) { |join| ... } click to toggle source
# 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
with_tempfile_path(file_param) { |path| ... } click to toggle source
# 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