mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
Merge branch 'site_file_rename'
This commit is contained in:
commit
ef1f448a40
7 changed files with 282 additions and 5 deletions
31
app/api.rb
31
app/api.rb
|
@ -76,6 +76,37 @@ post '/api/upload' do
|
|||
api_success 'your file(s) have been successfully uploaded'
|
||||
end
|
||||
|
||||
post '/api/rename' do
|
||||
require_api_credentials
|
||||
|
||||
api_error 400, 'missing_arguments', 'you must provide path and new_path' if params[:path].blank? || params[:new_path].blank?
|
||||
|
||||
path = current_site.scrubbed_path params[:path]
|
||||
new_path = current_site.scrubbed_path params[:new_path]
|
||||
|
||||
unless path.is_a?(String)
|
||||
api_error 400, 'bad_path', "#{path} is not a valid path, cancelled renaming"
|
||||
end
|
||||
|
||||
unless new_path.is_a?(String)
|
||||
api_error 400, 'bad_new_path', "#{new_path} is not a valid new_path, cancelled renaming"
|
||||
end
|
||||
|
||||
site_file = current_site.site_files.select {|sf| sf.path == path}.first
|
||||
|
||||
if site_file.nil?
|
||||
api_error 400, 'missing_file', "could not find #{path}"
|
||||
end
|
||||
|
||||
res = site_file.rename new_path
|
||||
|
||||
if res.first == true
|
||||
api_success "#{path} has been renamed to #{new_path}"
|
||||
else
|
||||
api_error 400, 'rename_error', res.last
|
||||
end
|
||||
end
|
||||
|
||||
post '/api/delete' do
|
||||
require_api_credentials
|
||||
|
||||
|
|
|
@ -166,6 +166,26 @@ post '/site_files/delete' do
|
|||
redirect "/dashboard#{dir_query}"
|
||||
end
|
||||
|
||||
post '/site_files/rename' do
|
||||
require_login
|
||||
path = HTMLEntities.new.decode params[:path]
|
||||
new_path = HTMLEntities.new.decode params[:new_path]
|
||||
site_file = current_site.site_files.select {|s| s.path == path}.first
|
||||
|
||||
res = site_file.rename new_path
|
||||
|
||||
if res.first == true
|
||||
flash[:success] = "Renamed #{path} to #{new_path}"
|
||||
else
|
||||
flash[:error] = "Failed to rename #{path} to #{new_path}: #{res.last}"
|
||||
end
|
||||
|
||||
dirname = Pathname(path).dirname
|
||||
dir_query = dirname.nil? || dirname.to_s == '.' ? '' : "?dir=#{Rack::Utils.escape dirname}"
|
||||
|
||||
redirect "/dashboard#{dir_query}"
|
||||
end
|
||||
|
||||
get '/site_files/:username.zip' do |username|
|
||||
require_login
|
||||
|
||||
|
|
|
@ -651,6 +651,14 @@ class Site < Sequel::Model
|
|||
false
|
||||
end
|
||||
|
||||
def self.valid_file_mime_type_and_ext?(mime_type, extname)
|
||||
unless (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/ || mime_type =~ /inode\/x-empty/) &&
|
||||
Site::VALID_EXTENSIONS.include?(extname.sub(/^./, '').downcase)
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def self.valid_file_type?(uploaded_file)
|
||||
mime_type = Magic.guess_file_mime_type uploaded_file[:tempfile].path
|
||||
extname = File.extname uploaded_file[:filename]
|
||||
|
@ -660,10 +668,7 @@ class Site < Sequel::Model
|
|||
# extname = uploaded_file[:filename]
|
||||
#end
|
||||
|
||||
unless (Site::VALID_MIME_TYPES.include?(mime_type) || mime_type =~ /text/ || mime_type =~ /inode\/x-empty/) &&
|
||||
Site::VALID_EXTENSIONS.include?(extname.sub(/^./, '').downcase)
|
||||
return false
|
||||
end
|
||||
return false unless valid_file_mime_type_and_ext?(mime_type, extname)
|
||||
|
||||
# clamdscan doesn't work on travis for testing
|
||||
return true if ENV['TRAVIS'] == 'true'
|
||||
|
@ -1162,7 +1167,14 @@ class Site < Sequel::Model
|
|||
clean << part if part != '..'
|
||||
end
|
||||
|
||||
clean.join '/'
|
||||
clean_path = clean.join '/'
|
||||
|
||||
# Scrub carriage garbage (everything below 32 bytes.. http://www.asciitable.com/)
|
||||
clean_path.each_codepoint do |c|
|
||||
raise ArgumentError, 'invalid character for filename' if c < 32
|
||||
end
|
||||
|
||||
clean_path
|
||||
end
|
||||
|
||||
def current_files_path(path='')
|
||||
|
|
|
@ -40,6 +40,55 @@ class SiteFile < Sequel::Model
|
|||
super
|
||||
end
|
||||
|
||||
def rename(new_path)
|
||||
current_path = self.path
|
||||
new_path = site.scrubbed_path new_path
|
||||
|
||||
if current_path == 'index.html'
|
||||
return false, 'cannot rename or move root index.html'
|
||||
end
|
||||
|
||||
if site.site_files.select {|sf| sf.path == new_path}.length > 0
|
||||
return false, "#{is_directory ? 'directory' : 'file'} already exists"
|
||||
end
|
||||
|
||||
unless is_directory
|
||||
mime_type = Magic.guess_file_mime_type site.files_path(self.path)
|
||||
extname = File.extname new_path
|
||||
|
||||
return false, 'unsupported file type' unless site.class.valid_file_mime_type_and_ext?(mime_type, extname)
|
||||
end
|
||||
|
||||
begin
|
||||
FileUtils.mv site.files_path(path), site.files_path(new_path)
|
||||
rescue Errno::ENOENT => e
|
||||
return false, 'destination directory does not exist' if e.message =~ /No such file or directory/i
|
||||
raise e
|
||||
rescue ArgumentError => e
|
||||
raise e unless e.message =~ /same file/
|
||||
end
|
||||
|
||||
DB.transaction do
|
||||
self.path = new_path
|
||||
self.save_changes
|
||||
site.purge_cache current_path
|
||||
site.purge_cache new_path
|
||||
|
||||
if is_directory
|
||||
site_files_in_dir = site.site_files.select {|sf| sf.path =~ /^#{current_path}\//}
|
||||
site_files_in_dir.each do |site_file|
|
||||
original_site_file_path = site_file.path
|
||||
site_file.path = site_file.path.gsub(/^#{current_path}\//, "#{new_path}\/")
|
||||
site_file.save_changes
|
||||
site.purge_cache original_site_file_path
|
||||
site.purge_cache site_file.path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true, nil
|
||||
end
|
||||
|
||||
def after_destroy
|
||||
super
|
||||
unless is_directory
|
||||
|
|
|
@ -241,6 +241,40 @@ describe 'api upload hash' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'api rename' do
|
||||
before do
|
||||
create_site
|
||||
basic_authorize @user, @pass
|
||||
post '/api/upload', {
|
||||
'testdir/test.jpg' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
}
|
||||
end
|
||||
|
||||
it 'succeeds' do
|
||||
post '/api/rename', path: 'testdir/test.jpg', new_path: 'testdir/test2.jpg'
|
||||
res[:result].must_equal 'success'
|
||||
end
|
||||
|
||||
it 'fails to overwrite index file' do
|
||||
post '/api/rename', path: 'testdir/test.jpg', new_path: 'index.html'
|
||||
res[:result].must_equal 'error'
|
||||
res[:error_type].must_equal 'rename_error'
|
||||
res[:message].must_equal 'file already exists'
|
||||
end
|
||||
|
||||
it 'fails to overwrite existing file' do
|
||||
post '/api/rename', path: 'testdir/test.jpg', new_path: 'not_found.html'
|
||||
res[:result].must_equal 'error'
|
||||
res[:error_type].must_equal 'rename_error'
|
||||
end
|
||||
|
||||
it 'succeeds with directory' do
|
||||
@site.create_directory 'derpiedir'
|
||||
post '/api/rename', path: 'derpiedir', new_path: 'notderpiedir'
|
||||
res[:result].must_equal 'success'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'api upload' do
|
||||
it 'fails with no auth' do
|
||||
post '/api/upload'
|
||||
|
|
|
@ -23,6 +23,107 @@ describe 'site_files' do
|
|||
ScreenshotWorker.jobs.clear
|
||||
end
|
||||
|
||||
describe 'rename' do
|
||||
before do
|
||||
PurgeCacheWorker.jobs.clear
|
||||
end
|
||||
|
||||
it 'renames in same path' do
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'files[]' => uploaded_file
|
||||
|
||||
@site.site_files.last.path.must_equal 'test.jpg'
|
||||
@site.site_files.last.rename('derp.jpg')
|
||||
@site.site_files.last.path.must_equal('derp.jpg')
|
||||
PurgeCacheWorker.jobs.first['args'].last.must_equal '/test.jpg'
|
||||
File.exist?(@site.files_path('derp.jpg')).must_equal true
|
||||
end
|
||||
|
||||
it 'fails for bad extension change' do
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'files[]' => uploaded_file
|
||||
|
||||
@site.site_files.last.path.must_equal 'test.jpg'
|
||||
res = @site.site_files.last.rename('dasharezone.exe')
|
||||
res.must_equal [false, 'unsupported file type']
|
||||
@site.site_files.last.path.must_equal('test.jpg')
|
||||
end
|
||||
|
||||
|
||||
it 'works for directory' do
|
||||
@site.create_directory 'dirone'
|
||||
@site.site_files.last.path.must_equal 'dirone'
|
||||
@site.site_files.last.is_directory.must_equal true
|
||||
res = @site.site_files.last.rename('dasharezone')
|
||||
res.must_equal [true, nil]
|
||||
@site.site_files.last.path.must_equal('dasharezone')
|
||||
@site.site_files.last.is_directory.must_equal true
|
||||
|
||||
PurgeCacheWorker.jobs.first['args'].last.must_equal 'dirone'
|
||||
PurgeCacheWorker.jobs.last['args'].last.must_equal 'dasharezone'
|
||||
end
|
||||
|
||||
it 'changes path of files and dirs within directory when changed' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
|
||||
)
|
||||
|
||||
@site.site_files.select {|s| s.path == 'test'}.first.rename('test2')
|
||||
@site.site_files.select {|sf| sf.path =~ /test2\/index.html/}.length.must_equal 1
|
||||
@site.site_files.select {|sf| sf.path =~ /test2\/test.jpg/}.length.must_equal 1
|
||||
@site.site_files.select {|sf| sf.path =~ /test\/test.jpg/}.length.must_equal 0
|
||||
|
||||
PurgeCacheWorker.jobs.collect {|p| p['args'].last}.must_equal ["/test/test.jpg", "/test/index.html", "/test/", "test", "test2", "test/test.jpg", "test2/test.jpg", "test/index.html", "test/", "test2/index.html", "test2/"]
|
||||
end
|
||||
|
||||
it 'doesnt wipe out existing file' do
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
)
|
||||
upload(
|
||||
'dir' => 'test',
|
||||
'files[]' => Rack::Test::UploadedFile.new('./tests/files/index.html', 'image/jpeg')
|
||||
)
|
||||
|
||||
res = @site.site_files.last.rename('test/test.jpg')
|
||||
res.must_equal [false, 'file already exists']
|
||||
end
|
||||
|
||||
it 'doesnt wipe out existing dir' do
|
||||
@site.create_directory 'dirone'
|
||||
@site.create_directory 'dirtwo'
|
||||
res = @site.site_files.last.rename 'dirone'
|
||||
res.must_equal [false, 'directory already exists']
|
||||
end
|
||||
|
||||
it 'refuses to move index.html' do
|
||||
res = @site.site_files.select {|sf| sf.path == 'index.html'}.first.rename('notindex.html')
|
||||
res.must_equal [false, 'cannot rename or move root index.html']
|
||||
end
|
||||
|
||||
it 'works with unicode characters' do
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'files[]' => uploaded_file
|
||||
@site.site_files.last.rename("HELL💩؋.jpg")
|
||||
@site.site_files.last.path.must_equal "HELL💩؋.jpg"
|
||||
end
|
||||
|
||||
it 'scrubs weird carriage return shit characters' do
|
||||
uploaded_file = Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')
|
||||
upload 'files[]' => uploaded_file
|
||||
proc {
|
||||
@site.site_files.last.rename("\r\n\t.jpg")
|
||||
}.must_raise ArgumentError
|
||||
@site.site_files.last.path.must_equal "test.jpg"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'delete' do
|
||||
before do
|
||||
PurgeCacheWorker.jobs.clear
|
||||
|
|
|
@ -153,6 +153,7 @@
|
|||
<a href="?dir=<%= Rack::Utils.escape file[:path] %>"><i class="fa fa-edit" title="Manage"></i> Manage</a>
|
||||
<% end %>
|
||||
<% if !file[:is_root_index] %>
|
||||
<a href="#" onclick="confirmFileRename($('#<%= Digest::SHA256.hexdigest file[:path] %>').text())"><i class="fa fa-file" title="Rename"></i> Rename</a>
|
||||
<a href="#" onclick="confirmFileDelete($('#<%= Digest::SHA256.hexdigest file[:path] %>').text())"><i class="fa fa-trash" title="Delete"></i> Delete</a>
|
||||
<% end %>
|
||||
<% if file[:is_directory] %>
|
||||
|
@ -186,6 +187,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal hide fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel" aria-hidden="true">
|
||||
<form method="post" action="/site_files/rename">
|
||||
<input type="hidden" value="<%= csrf_token %>" name="csrf_token">
|
||||
<input type="hidden" value="<%= @dir %>" name="dir">
|
||||
<input type="hidden" id="renamePathInput" name="path">
|
||||
|
||||
<div class="modal-header">
|
||||
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
||||
<h3 id="renameModalLabel">Rename / Move</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input id="renameNewPathInput" name="new_path" type="text">
|
||||
<p>Note: We will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
<button type="submit" class="btn-Action">Rename</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="site-actions" style="margin-bottom:25px">
|
||||
<% if !current_site.plan_feature(:no_file_restrictions) %>
|
||||
<a href="/site_files/allowed_types">Allowed file types</a> |
|
||||
|
@ -249,6 +272,13 @@
|
|||
<script src="/js/dropzone.min.js"></script>
|
||||
<script>
|
||||
|
||||
function confirmFileRename(path) {
|
||||
console.log(path)
|
||||
$('#renamePathInput').val(path.replace('/', ''));
|
||||
$('#renameNewPathInput').val(path.replace('/', ''));
|
||||
$('#renameModal').modal();
|
||||
}
|
||||
|
||||
function confirmFileDelete(name) {
|
||||
$('#deleteFileName').text(name.replace('/',''));
|
||||
$('#deleteConfirmModal').modal();
|
||||
|
|
Loading…
Add table
Reference in a new issue