diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-02 21:07:38 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-02 21:07:38 +0000 |
commit | 9d54184f308893338967b18874dedebf38acf89e (patch) | |
tree | 100e32c6d4b34deac52d9e98a083361d89804b50 /app/uploaders | |
parent | d5b5f5e6e1474d5526add9033c9754b8e395841f (diff) | |
download | gitlab-ce-9d54184f308893338967b18874dedebf38acf89e.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/uploaders')
-rw-r--r-- | app/uploaders/avatar_uploader.rb | 3 | ||||
-rw-r--r-- | app/uploaders/favicon_uploader.rb | 4 | ||||
-rw-r--r-- | app/uploaders/upload_type_check.rb | 98 |
3 files changed, 105 insertions, 0 deletions
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index d42c9dbedf4..b79a5deb9c0 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -5,6 +5,9 @@ class AvatarUploader < GitlabUploader include RecordsUploads::Concern include ObjectStorage::Concern prepend ObjectStorage::Extension::RecordsUploads + include UploadTypeCheck::Concern + + check_upload_type extensions: AvatarUploader::SAFE_IMAGE_EXT def exists? model.avatar.file && model.avatar.file.present? diff --git a/app/uploaders/favicon_uploader.rb b/app/uploaders/favicon_uploader.rb index a0b275b56a9..f393fdf0d84 100644 --- a/app/uploaders/favicon_uploader.rb +++ b/app/uploaders/favicon_uploader.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class FaviconUploader < AttachmentUploader + include UploadTypeCheck::Concern + EXTENSION_WHITELIST = %w[png ico].freeze + check_upload_type extensions: EXTENSION_WHITELIST + def extension_whitelist EXTENSION_WHITELIST end diff --git a/app/uploaders/upload_type_check.rb b/app/uploaders/upload_type_check.rb new file mode 100644 index 00000000000..2837b001660 --- /dev/null +++ b/app/uploaders/upload_type_check.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# Ensure that uploaded files are what they say they are for security and +# handling purposes. The checks are not 100% reliable so we err on the side of +# caution and allow by default, and deny when we're confident of a fail state. +# +# Include this concern, then call `check_upload_type` to check all +# uploads. Attach a `mime_type` or `extensions` parameter to only check +# specific upload types. Both parameters will be normalized to a MIME type and +# checked against the inferred MIME type of the upload content and filename +# extension. +# +# class YourUploader +# include UploadTypeCheck::Concern +# check_upload_type mime_types: ['image/png', /image\/jpe?g/] +# +# # or... +# +# check_upload_type extensions: ['png', 'jpg', 'jpeg'] +# end +# +# The mime_types parameter can accept `NilClass`, `String`, `Regexp`, +# `Array[String, Regexp]`. This matches the CarrierWave `extension_whitelist` +# and `content_type_whitelist` family of behavior. +# +# The extensions parameter can accept `NilClass`, `String`, `Array[String]`. +module UploadTypeCheck + module Concern + extend ActiveSupport::Concern + + class_methods do + def check_upload_type(mime_types: nil, extensions: nil) + define_method :check_upload_type_callback do |file| + magic_file = MagicFile.new(file.to_file) + + # Map file extensions back to mime types. + if extensions + mime_types = Array(mime_types) + + Array(extensions).map { |e| MimeMagic::EXTENSIONS[e] } + end + + if mime_types.nil? || magic_file.matches_mime_types?(mime_types) + check_content_matches_extension!(magic_file) + end + end + before :cache, :check_upload_type_callback + end + end + + def check_content_matches_extension!(magic_file) + return if magic_file.ambiguous_type? + + if magic_file.magic_type != magic_file.ext_type + raise CarrierWave::IntegrityError, 'Content type does not match file extension' + end + end + end + + # Convenience class to wrap MagicMime objects. + class MagicFile + attr_reader :file + + def initialize(file) + @file = file + end + + def magic_type + @magic_type ||= MimeMagic.by_magic(file) + end + + def ext_type + @ext_type ||= MimeMagic.by_path(file.path) + end + + def magic_type_type + magic_type&.type + end + + def ext_type_type + ext_type&.type + end + + def matches_mime_types?(mime_types) + Array(mime_types).any? do |mt| + magic_type_type =~ /\A#{mt}\z/ || ext_type_type =~ /\A#{mt}\z/ + end + end + + # - Both types unknown or text/plain. + # - Ambiguous magic type with text extension. Plain text file. + # - Text magic type with ambiguous extension. TeX file missing extension. + def ambiguous_type? + (ext_type.to_s.blank? && magic_type.to_s.blank?) || + (magic_type.to_s.blank? && ext_type_type == 'text/plain') || + (ext_type.to_s.blank? && magic_type_type == 'text/plain') + end + end +end |