Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bulk_imports')
-rw-r--r--lib/bulk_imports/file_downloads/filename_fetch.rb46
-rw-r--r--lib/bulk_imports/file_downloads/validations.rb58
2 files changed, 104 insertions, 0 deletions
diff --git a/lib/bulk_imports/file_downloads/filename_fetch.rb b/lib/bulk_imports/file_downloads/filename_fetch.rb
new file mode 100644
index 00000000000..b6bb0fd8c81
--- /dev/null
+++ b/lib/bulk_imports/file_downloads/filename_fetch.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module FileDownloads
+ module FilenameFetch
+ REMOTE_FILENAME_PATTERN = %r{filename="(?<filename>[^"]+)"}.freeze
+ FILENAME_SIZE_LIMIT = 255 # chars before the extension
+
+ def raise_error(message)
+ raise NotImplementedError
+ end
+
+ private
+
+ # 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)
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def remote_filename
+ @remote_filename ||= begin
+ pattern = BulkImports::FileDownloads::FilenameFetch::REMOTE_FILENAME_PATTERN
+ name = response_headers['content-disposition'].to_s
+ .match(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
+
+ name = File.basename(name) # Ensures to remove path from the filename (../ for instance)
+ ensure_filename_size(name) # Ensures the filename is within the FILENAME_SIZE_LIMIT
+ end
+ rescue KeyError
+ raise_error 'Remote filename not provided in content-disposition header'
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def ensure_filename_size(filename)
+ limit = BulkImports::FileDownloads::FilenameFetch::FILENAME_SIZE_LIMIT
+ return filename if filename.length <= limit
+
+ extname = File.extname(filename)
+ basename = File.basename(filename, extname)[0, limit]
+ "#{basename}#{extname}"
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/file_downloads/validations.rb b/lib/bulk_imports/file_downloads/validations.rb
new file mode 100644
index 00000000000..ae94267a6e8
--- /dev/null
+++ b/lib/bulk_imports/file_downloads/validations.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module FileDownloads
+ module Validations
+ def raise_error(message)
+ raise NotImplementedError
+ end
+
+ def filepath
+ raise NotImplementedError
+ end
+
+ def file_size_limit
+ raise NotImplementedError
+ end
+
+ def response_headers
+ raise NotImplementedError
+ end
+
+ private
+
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_content_type
+ content_type = response_headers['content-type']
+
+ raise_error('Invalid content type') if content_type.blank? || allowed_content_types.exclude?(content_type)
+ end
+
+ def validate_symlink
+ return unless File.lstat(filepath).symlink?
+
+ File.delete(filepath)
+ raise_error 'Invalid downloaded file'
+ end
+
+ def validate_content_length
+ validate_size!(response_headers['content-length'])
+ end
+
+ def validate_size!(size)
+ if size.blank?
+ raise_error 'Missing content-length header'
+ elsif size.to_i > file_size_limit
+ raise_error format(
+ "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
+ end
+ end
+end