diff options
Diffstat (limited to 'app/services/bulk_imports/file_download_service.rb')
-rw-r--r-- | app/services/bulk_imports/file_download_service.rb | 83 |
1 files changed, 14 insertions, 69 deletions
diff --git a/app/services/bulk_imports/file_download_service.rb b/app/services/bulk_imports/file_download_service.rb index a2c8ba5b1cd..45f1350df92 100644 --- a/app/services/bulk_imports/file_download_service.rb +++ b/app/services/bulk_imports/file_download_service.rb @@ -10,10 +10,11 @@ # @param filename [String] Name of the file to download, if known. Use remote filename if none given. module BulkImports class FileDownloadService + include ::BulkImports::FileDownloads::FilenameFetch + include ::BulkImports::FileDownloads::Validations + ServiceError = Class.new(StandardError) - REMOTE_FILENAME_PATTERN = %r{filename="(?<filename>[^"]+)"}.freeze - FILENAME_SIZE_LIMIT = 255 # chars before the extension DEFAULT_FILE_SIZE_LIMIT = 5.gigabytes DEFAULT_ALLOWED_CONTENT_TYPES = %w(application/gzip application/octet-stream).freeze @@ -74,6 +75,10 @@ module BulkImports raise e end + def raise_error(message) + raise ServiceError, message + end + def http_client @http_client ||= BulkImports::Clients::HTTP.new( url: configuration.url, @@ -85,24 +90,20 @@ module BulkImports ::Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services? end - def headers - @headers ||= http_client.head(relative_url).headers - end - - def validate_filepath - Gitlab::Utils.check_path_traversal!(filepath) + def response_headers + @response_headers ||= http_client.head(relative_url).headers end def validate_tmpdir Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir]) end - def validate_symlink - if File.lstat(filepath).symlink? - File.delete(filepath) + def filepath + @filepath ||= File.join(@tmpdir, filename) + end - raise(ServiceError, 'Invalid downloaded file') - end + def filename + @filename.presence || remote_filename end def validate_url @@ -113,61 +114,5 @@ module BulkImports schemes: %w(http https) ) end - - def validate_content_length - validate_size!(headers['content-length']) - end - - def validate_size!(size) - if size.blank? - raise ServiceError, 'Missing content-length header' - elsif size.to_i > file_size_limit - raise ServiceError, "File size %{size} exceeds limit of %{limit}" % { - size: ActiveSupport::NumberHelper.number_to_human_size(size), - limit: ActiveSupport::NumberHelper.number_to_human_size(file_size_limit) - } - end - end - - def validate_content_type - content_type = headers['content-type'] - - raise(ServiceError, 'Invalid content type') if content_type.blank? || allowed_content_types.exclude?(content_type) - end - - def filepath - @filepath ||= File.join(@tmpdir, filename) - end - - def filename - @filename.presence || remote_filename - end - - # Fetch the remote filename information from the request content-disposition header - # - Raises if the filename does not exist - # - If the filename is longer then 255 chars truncate it - # to be a total of 255 chars (with the extension) - def remote_filename - @remote_filename ||= - headers['content-disposition'].to_s - .match(REMOTE_FILENAME_PATTERN) # matches the filename pattern - .then { |match| match&.named_captures || {} } # ensures the match is a hash - .fetch('filename') # fetches the 'filename' key or raise KeyError - .then(&File.method(:basename)) # Ensures to remove path from the filename (../ for instance) - .then(&method(:ensure_filename_size)) # Ensures the filename is within the FILENAME_SIZE_LIMIT - rescue KeyError - raise ServiceError, 'Remote filename not provided in content-disposition header' - end - - def ensure_filename_size(filename) - if filename.length <= FILENAME_SIZE_LIMIT - filename - else - extname = File.extname(filename) - basename = File.basename(filename, extname)[0, FILENAME_SIZE_LIMIT] - - "#{basename}#{extname}" - end - end end end |