Skip to content

Commit

Permalink
Merge pull request #322 from igorkasyanchuk/321-content_type-validati…
Browse files Browse the repository at this point in the history
…on-may-be-broken-for-applicationx-ole-storage

[Validator] Detect attachable content_type like Rails, plus rewind the io after using it (#321)
  • Loading branch information
Mth0158 authored Dec 11, 2024
2 parents a463823 + 2ae166e commit 963d60e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 16 deletions.
2 changes: 1 addition & 1 deletion lib/active_storage_validations/content_type_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def authorized_content_types_from_options(record)
end

def set_attachable_cached_values(attachable)
@attachable_content_type = attachable_content_type(attachable)
@attachable_content_type = attachable_content_type_rails_like(attachable)
@attachable_filename = attachable_filename(attachable).to_s
end

Expand Down
68 changes: 55 additions & 13 deletions lib/active_storage_validations/shared/asv_attachable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,59 @@ def attachable_content_type(attachable)
full_attachable_content_type(attachable) && full_attachable_content_type(attachable).downcase.split(/[;,\s]/, 2).first
end

# Retrieve the content_type from attachable using the same logic as Rails
# ActiveStorage::Blob::Identifiable#identify_content_type
def attachable_content_type_rails_like(attachable)
Marcel::MimeType.for(
attachable_io(attachable, max_byte_size: 4.kilobytes),
name: attachable_filename(attachable).to_s,
declared_type: full_attachable_content_type(attachable)
)
end

# Retrieve the io from attachable.
def attachable_io(attachable)
def attachable_io(attachable, max_byte_size: nil)
io = case attachable
when ActiveStorage::Blob
(max_byte_size && supports_blob_download_chunk?) ? attachable.download_chunk(0...max_byte_size) : attachable.download
when ActionDispatch::Http::UploadedFile
max_byte_size ? attachable.read(max_byte_size) : attachable.read
when Rack::Test::UploadedFile
max_byte_size ? attachable.read(max_byte_size) : attachable.read
when String
blob = ActiveStorage::Blob.find_signed!(attachable)
(max_byte_size && supports_blob_download_chunk?) ? blob.download_chunk(0...max_byte_size) : blob.download
when Hash
max_byte_size ? attachable[:io].read(max_byte_size) : attachable[:io].read
when File
raise_rails_like_error(attachable) unless supports_file_attachment?
max_byte_size ? attachable.read(max_byte_size) : attachable.read
when Pathname
raise_rails_like_error(attachable) unless supports_pathname_attachment?
max_byte_size ? attachable.read(max_byte_size) : attachable.read
else
raise_rails_like_error(attachable)
end

rewind_attachable_io(attachable)
io
end

# Rewind the io attachable.
def rewind_attachable_io(attachable)
case attachable
when ActiveStorage::Blob
attachable.download
when ActionDispatch::Http::UploadedFile
attachable.read
when Rack::Test::UploadedFile
attachable.read
when String
blob = ActiveStorage::Blob.find_signed!(attachable)
blob.download
when ActiveStorage::Blob, String
# nothing to do
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
attachable.rewind
when Hash
attachable[:io].read
attachable[:io].rewind
when File
supports_file_attachment? ? attachable : raise_rails_like_error(attachable)
raise_rails_like_error(attachable) unless supports_file_attachment?
attachable.rewind
when Pathname
supports_pathname_attachment? ? attachable.read : raise_rails_like_error(attachable)
raise_rails_like_error(attachable) unless supports_pathname_attachment?
File.open(attachable) { |f| f.rewind }
else
raise_rails_like_error(attachable)
end
Expand Down Expand Up @@ -128,6 +163,13 @@ def supports_file_attachment?
end
alias :supports_pathname_attachment? :supports_file_attachment?

# Check if the current Rails version supports ActiveStorage::Blob#download_chunk
#
# https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md#rails-700alpha1-september-15-2021
def supports_blob_download_chunk?
Rails.gem_version >= Gem::Version.new('7.0.0.alpha1')
end

# Retrieve the content_type from the file name only
def marcel_content_type_from_filename(attachable)
Marcel::MimeType.for(name: attachable_filename(attachable).to_s)
Expand Down
10 changes: 8 additions & 2 deletions test/dummy/app/models/content_type_spoof_detector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ContentTypeSpoofDetector < ApplicationRecord
{ media: 'video', type: :wmv },
{ media: 'video', type: :mov },
{ media: 'video', type: :mkv },
{ media: 'video', type: :ogv },
# { media: 'video', type: :ogv }, => issue with content_type validator, handled below
{ media: 'video', type: :webm },
# Audio
{ media: 'audio', type: :mp3 },
Expand Down Expand Up @@ -65,10 +65,16 @@ class ContentTypeSpoofDetector < ApplicationRecord
{ media: 'application', type: :'7z' },
{ media: 'application', type: :rar },
{ media: 'application', type: :gz },
{ media: 'application', type: :tar }
# { media: 'application', type: :tar }, => issue with content_type validator, handled below
].each do |content_type|
has_one_attached :"#{content_type[:media]}_#{content_type[:type]}"
validates :"#{content_type[:media]}_#{content_type[:type]}",
content_type: { with: content_type[:type], spoofing_protection: true }

# Issues with content_type validator
has_one_attached :video_ogv
validates :video_ogv, content_type: { with: 'video/theora', spoofing_protection: true }
has_one_attached :application_tar
validates :application_tar, content_type: { in: ['application/x-tar', 'application/x-gtar'], spoofing_protection: true }
end
end

0 comments on commit 963d60e

Please sign in to comment.