diff --git a/app.rb b/app.rb index 43c623c8..67ae8f57 100644 --- a/app.rb +++ b/app.rb @@ -566,7 +566,9 @@ post '/api/upload' do params.each do |k,v| next unless v.is_a?(Hash) && v[:tempfile] - files << {filename: k.to_s, tempfile: v[:tempfile]} + filename = k.to_s + api_error('bad_filename', "#{filename} is not a valid filename, files not uploaded") unless Site.valid_filename? filename + files << {filename: filename, tempfile: v[:tempfile]} end api_error 'missing_files', 'you must provide files to upload' if files.empty? @@ -592,6 +594,36 @@ post '/api/upload' do api_success 'your file(s) have been successfully uploaded' end +post '/api/:delete' do + require_api_credentials + + api_error 'missing_filenames', 'you must provide files to delete' if params[:filenames].nil? || params[:filenames].empty? + + filenames = [] + + params[:filenames].each do |filename| + unless filename.is_a?(String) && Site.valid_filename?(filename) + api_error 'bad_filename', "#{filename} is not a valid filename, canceled all deletes" + end + + if !current_site.file_exists?(filename) + api_error 'missing_files', "#{filename} was not found on your site, canceled all deletes" + end + + if filename == 'index.html' + api_error 'cannot_delete_index', 'you cannot delete your index.html file, canceled all deletes' + end + + filenames << filename + end + + filenames.each do |filename| + current_site.delete_file(filename) + end + + api_success 'files have been deleted' +end + # Catch-all for missing api calls get '/api/:name' do diff --git a/models/site.rb b/models/site.rb index 9bfb2bc0..0687e371 100644 --- a/models/site.rb +++ b/models/site.rb @@ -125,6 +125,15 @@ class Site < Sequel::Model } end + def self.valid_filename?(filename) + return false if sanitize_filename(filename) != filename + true + end + + def self.sanitize_filename(filename) + filename.gsub(/[^a-zA-Z0-9_\-.]/, '') + end + def self.valid_file_type?(uploaded_file) mime_type = Magic.guess_file_mime_type uploaded_file[:tempfile].path @@ -169,8 +178,9 @@ class Site < Sequel::Model begin FileUtils.rm file_path(filename) rescue Errno::ENOENT - # File was probably already deleted + return false end + true end def move_files_from(oldusername) diff --git a/tests/api_tests.rb b/tests/api_tests.rb index 56c1a59b..696500df 100644 --- a/tests/api_tests.rb +++ b/tests/api_tests.rb @@ -7,13 +7,66 @@ def app Sinatra::Application end -describe 'api upload' do - def create_site - site_attr = Fabricate.attributes_for :site - @site = Site.create site_attr - @user = site_attr[:username] - @pass = site_attr[:password] +def create_site + site_attr = Fabricate.attributes_for :site + @site = Site.create site_attr + @user = site_attr[:username] + @pass = site_attr[:password] +end + +describe 'api delete' do + it 'fails with no or bad auth' do + post '/api/delete', filenames: ['hi.html'] + res[:error_type].must_equal 'invalid_auth' + create_site + basic_authorize 'derp', 'fake' + post '/api/delete', filenames: ['hi.html'] + res[:error_type].must_equal 'invalid_auth' end + + it 'fails with missing filename argument' do + create_site + basic_authorize @user, @pass + post '/api/delete' + res[:error_type].must_equal 'missing_filenames' + end + + it 'fails to delete index.html' do + create_site + basic_authorize @user, @pass + post '/api/delete', filenames: ['index.html'] + res[:error_type].must_equal 'cannot_delete_index' + end + + it 'fails with bad filename' do + create_site + basic_authorize @user, @pass + @site.store_file 't$st.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') + post '/api/delete', filenames: ['t$st.jpg'] + res[:error_type].must_equal 'bad_filename' + end + + it 'fails with missing files' do + create_site + basic_authorize @user, @pass + @site.store_file 'test.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') + post '/api/delete', filenames: ['doesntexist.jpg'] + res[:error_type].must_equal 'missing_files' + end + + it 'succeeds with valid filenames' do + create_site + basic_authorize @user, @pass + @site.store_file 'test.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') + @site.store_file 'test2.jpg', Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') + post '/api/delete', filenames: ['test.jpg', 'test2.jpg'] + res[:result].must_equal 'success' + site_file_exists?('test.jpg').must_equal false + site_file_exists?('test2.jpg').must_equal false + end +end + +describe 'api upload' do it 'fails with no auth' do post '/api/upload' @@ -34,6 +87,15 @@ describe 'api upload' do res[:error_type].must_equal 'missing_files' end + it 'fails for invalid filenames' do + create_site + basic_authorize @user, @pass + post '/api/upload', { + '../lol.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg') + } + res[:error_type].must_equal 'bad_filename' + end + it 'fails for invalid files' do create_site basic_authorize @user, @pass