diff --git a/app/logical/media_file.rb b/app/logical/media_file.rb index 9d49f058b..e7a0c6ddf 100644 --- a/app/logical/media_file.rb +++ b/app/logical/media_file.rb @@ -105,6 +105,11 @@ class MediaFile dimensions.second end + # @return [Integer] the resolution of the file + def resolution + width * height + end + # @return [String] the MD5 hash of the file, as a hex string. def md5 Digest::MD5.file(file.path).hexdigest diff --git a/app/logical/post_replacement_processor.rb b/app/logical/post_replacement_processor.rb index 524d08ec1..4c0743770 100644 --- a/app/logical/post_replacement_processor.rb +++ b/app/logical/post_replacement_processor.rb @@ -18,6 +18,7 @@ class PostReplacementProcessor if media_file.md5 == post.md5 media_asset = post.media_asset else + MediaAsset.validate_media_file!(media_file, replacement.creator) media_asset = MediaAsset.upload!(media_file) end diff --git a/app/models/media_asset.rb b/app/models/media_asset.rb index 57650854b..661c70e72 100644 --- a/app/models/media_asset.rb +++ b/app/models/media_asset.rb @@ -6,6 +6,7 @@ class MediaAsset < ApplicationRecord FILE_TYPES = %w[jpg png gif mp4 webm swf zip] FILE_KEY_LENGTH = 9 VARIANTS = %i[preview 180x180 360x360 720x720 sample original] + MAX_FILE_SIZE = Danbooru.config.max_file_size.to_i MAX_VIDEO_DURATION = Danbooru.config.max_video_duration.to_i MAX_IMAGE_RESOLUTION = Danbooru.config.max_image_resolution MAX_IMAGE_WIDTH = Danbooru.config.max_image_width @@ -45,10 +46,7 @@ class MediaAsset < ApplicationRecord validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } }, if: :md5_changed? validates :file_ext, inclusion: { in: FILE_TYPES, message: "File is not an image or video" } - validates :file_size, numericality: { less_than_or_equal_to: Danbooru.config.max_file_size, message: ->(asset, _) { "too large (size: #{asset.file_size.to_formatted_s(:human_size)}; max size: #{Danbooru.config.max_file_size.to_formatted_s(:human_size)})" } } validates :file_key, length: { is: FILE_KEY_LENGTH }, uniqueness: true, if: :file_key_changed? - validate :validate_duration, on: :create - validate :validate_resolution, on: :create before_create :initialize_file_key @@ -248,8 +246,6 @@ class MediaAsset < ApplicationRecord def upload!(media_file, &block) media_file = MediaFile.open(media_file) unless media_file.is_a?(MediaFile) - raise Error, "File is corrupt" if media_file.is_corrupt? - media_asset = create!(file: media_file, status: :processing) yield media_asset if block_given? @@ -294,6 +290,24 @@ class MediaAsset < ApplicationRecord media_asset&.update!(status: :failed) raise end + + def validate_media_file!(media_file, uploader) + if !media_file.file_ext.to_s.in?(FILE_TYPES) + raise Error, "File is not an image or video" + elsif media_file.is_corrupt? + raise Error, "File is corrupt" + elsif media_file.file_size > MAX_FILE_SIZE + raise Error, "File size too large (size: #{media_file.file_size.to_formatted_s(:human_size)}; max size: #{MAX_FILE_SIZE.to_formatted_s(:human_size)})" + elsif media_file.resolution > MAX_IMAGE_RESOLUTION + raise Error, "Image resolution is too large (resolution: #{(media_file.resolution / 1_000_000.0).round(1)} megapixels (#{media_file.width}x#{media_file.height}); max: #{MAX_IMAGE_RESOLUTION / 1_000_000} megapixels)" + elsif media_file.width > MAX_IMAGE_WIDTH + raise Error, "Image width is too large (width: #{media_file.width}; max width: #{MAX_IMAGE_WIDTH})" + elsif media_file.height > MAX_IMAGE_HEIGHT + raise Error, "Image height is too large (height: #{media_file.height}; max height: #{MAX_IMAGE_HEIGHT})" + elsif media_file.duration.to_i > MAX_VIDEO_DURATION && !uploader.is_admin? + raise Error, "Duration must be less than #{MAX_VIDEO_DURATION} seconds" + end + end end def file=(file_or_path) @@ -397,27 +411,6 @@ class MediaAsset < ApplicationRecord end end - concerning :ValidationMethods do - def validate_resolution - resolution = image_width * image_height - - if resolution > MAX_IMAGE_RESOLUTION - errors.add(:base, "Image resolution is too large (resolution: #{(resolution / 1_000_000.0).round(1)} megapixels (#{image_width}x#{image_height}); max: #{MAX_IMAGE_RESOLUTION / 1_000_000} megapixels)") - elsif image_width > MAX_IMAGE_WIDTH - errors.add(:image_width, "is too large (width: #{image_width}; max width: #{MAX_IMAGE_WIDTH})") - elsif image_height > MAX_IMAGE_HEIGHT - errors.add(:image_height, "is too large (height: #{image_height}; max height: #{MAX_IMAGE_HEIGHT})") - end - end - - def validate_duration - return if CurrentUser.user.is_admin? - if duration.to_i > MAX_VIDEO_DURATION - errors.add(:base, "duration must be less than #{MAX_VIDEO_DURATION} seconds") - end - end - end - def self.generate_file_key loop do key = SecureRandom.send(:choose, [*"0".."9", *"A".."Z", *"a".."z"], FILE_KEY_LENGTH) # base62 diff --git a/app/models/upload_media_asset.rb b/app/models/upload_media_asset.rb index fb2cc5f2f..02f9481e1 100644 --- a/app/models/upload_media_asset.rb +++ b/app/models/upload_media_asset.rb @@ -101,6 +101,7 @@ class UploadMediaAsset < ApplicationRecord media_file = source_extractor.download_file!(source_url) end + MediaAsset.validate_media_file!(media_file, upload.uploader) MediaAsset.upload!(media_file) do |media_asset| update!(media_asset: media_asset) end