Merge pull request #8999 from colinux/fix-export-attachment-filenames

ETQ instructeur, ne plante pas un export s'il contient des PJ avec des noms de fichiers invalides
This commit is contained in:
Colin Darie 2023-05-04 13:35:39 +00:00 committed by GitHub
commit da178e354d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 7 deletions

View file

@ -8,7 +8,7 @@ module DownloadManager
def initialize(attachments, destination)
@attachments = attachments
@destination = destination
@destination = Pathname.new(destination)
end
def download_all
@ -28,26 +28,40 @@ module DownloadManager
# can't be used with typhoeus, otherwise block is closed before the request is run by hydra
def download_one(attachment:, path_in_download_dir:, http_client:)
attachment_path = File.join(destination, path_in_download_dir)
attachment_dir = File.dirname(attachment_path)
path = Pathname.new(path_in_download_dir)
attachment_path = destination.join(path.dirname, sanitize_filename(path.basename.to_s))
attachment_path.dirname.mkpath # defensive, do not write in undefined dir
FileUtils.mkdir_p(attachment_dir) if !Dir.exist?(attachment_dir) # defensive, do not write in undefined dir
if attachment.is_a?(ActiveStorage::FakeAttachment)
File.write(attachment_path, attachment.file.read, mode: 'wb')
attachment_path.write(attachment.file.read, mode: 'wb')
else
request = Typhoeus::Request.new(attachment.url)
request.on_complete do |response|
if response.success?
File.open(attachment_path, mode: "wb") do |fd|
attachment_path.open(mode: "wb") do |fd|
fd.write(response.body)
end
else
File.delete(attachment_path) if File.exist?(attachment_path) # -> case of retries failed, must cleanup partialy downloaded file
attachment_path.delete if attachment_path.exist? # -> case of retries failed, must cleanup partialy downloaded file
on_error.call(attachment, path_in_download_dir, response.code)
end
end
http_client.queue(request)
end
end
private
def sanitize_filename(original_filename)
filename = ActiveStorage::Filename.new(original_filename).sanitized
return filename if filename.bytesize <= 255
ext = File.extname(filename)
basename = File.basename(filename, ext).byteslice(0, 255 - ext.bytesize)
basename + ext
end
end
end

View file

@ -40,5 +40,38 @@ describe DownloadManager::ParallelDownloadQueue do
# expect(downloadable_manager.errors).to have_key(destination)
end
end
context 'with a destination filename too long' do
let(:destination) { 'a' * 252 + '.txt' }
it 'limit the file path to 255 bytes' do
target = File.join(download_to_dir, 'a' * 251 + '.txt')
expect { subject }.to change { File.exist?(target) }
attachment.file.rewind
expect(attachment.file.read).to eq(File.read(target))
end
end
context 'with filename containing unsafe characters for storage' do
let(:destination) { "file:éà\u{202e} K.txt" }
it 'sanitize the problematic chars' do
target = File.join(download_to_dir, 'file-éà- K.txt')
expect { subject }.to change { File.exist?(target) }
attachment.file.rewind
expect(attachment.file.read).to eq(File.read(target))
end
context 'with a destination tree' do
let(:destination) { 'subdir/file.txt' }
it 'preserves the destination tree' do
target = File.join(download_to_dir, 'subdir/file.txt')
expect { subject }.to change { File.exist?(target) }
attachment.file.rewind
expect(attachment.file.read).to eq(File.read(target))
end
end
end
end
end