diff options
Diffstat (limited to 'activestorage/lib/active_storage')
7 files changed, 113 insertions, 86 deletions
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb index 7eb93b5e16..384e6ebfa6 100644 --- a/activestorage/lib/active_storage/engine.rb +++ b/activestorage/lib/active_storage/engine.rb @@ -20,12 +20,15 @@ module ActiveStorage config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ] config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ] config.active_storage.paths = ActiveSupport::OrderedOptions.new + config.active_storage.queues = ActiveSupport::OrderedOptions.new config.active_storage.variable_content_types = %w( image/png image/gif image/jpg image/jpeg + image/pjpeg + image/tiff image/vnd.adobe.photoshop image/vnd.microsoft.icon ) @@ -39,6 +42,19 @@ module ActiveStorage text/xml application/xml application/xhtml+xml + application/mathml+xml + text/cache-manifest + ) + + config.active_storage.content_types_allowed_inline = %w( + image/png + image/gif + image/jpg + image/jpeg + image/tiff + image/vnd.adobe.photoshop + image/vnd.microsoft.icon + application/pdf ) config.eager_load_namespaces << ActiveStorage @@ -46,7 +62,6 @@ module ActiveStorage initializer "active_storage.configs" do config.after_initialize do |app| ActiveStorage.logger = app.config.active_storage.logger || Rails.logger - ActiveStorage.queue = app.config.active_storage.queue ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick ActiveStorage.previewers = app.config.active_storage.previewers || [] ActiveStorage.analyzers = app.config.active_storage.analyzers || [] @@ -56,6 +71,8 @@ module ActiveStorage ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || [] ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || [] ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes + ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || [] + ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream" end end @@ -100,6 +117,20 @@ module ActiveStorage end end + initializer "active_storage.queues" do + config.after_initialize do |app| + if queue = app.config.active_storage.queue + ActiveSupport::Deprecation.warn \ + "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \ + "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead." + + ActiveStorage.queues = { purge: queue, analysis: queue } + else + ActiveStorage.queues = app.config.active_storage.queues || {} + end + end + end + initializer "active_storage.reflection" do ActiveSupport.on_load(:active_record) do include Reflection::ActiveRecordExtensions diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb index 54ba08fb87..c18fccbb1d 100644 --- a/activestorage/lib/active_storage/service.rb +++ b/activestorage/lib/active_storage/service.rb @@ -62,10 +62,16 @@ module ActiveStorage # Upload the +io+ to the +key+ specified. If a +checksum+ is provided, the service will # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, **options) raise NotImplementedError end + # Update metadata for the file identified by +key+ in the service. + # Override in subclasses only if the service needs to store specific + # metadata that has to be updated upon identification. + def update_metadata(key, **metadata) + end + # Return the content of the file at the +key+. def download(key) raise NotImplementedError diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index 8de3889cb5..17cecd891c 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -17,7 +17,7 @@ module ActiveStorage @container = container end - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, **) instrument :upload, key: key, checksum: checksum do handle_errors do blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum) @@ -51,12 +51,10 @@ module ActiveStorage def delete(key) instrument :delete, key: key do - begin - blobs.delete_blob(container, key) - rescue Azure::Core::Http::HTTPError => e - raise unless e.type == "BlobNotFound" - # Ignore files already deleted - end + blobs.delete_blob(container, key) + rescue Azure::Core::Http::HTTPError => e + raise unless e.type == "BlobNotFound" + # Ignore files already deleted end end diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb index 52f3a3df16..67892d43b2 100644 --- a/activestorage/lib/active_storage/service/disk_service.rb +++ b/activestorage/lib/active_storage/service/disk_service.rb @@ -15,7 +15,7 @@ module ActiveStorage @root = root end - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, **) instrument :upload, key: key, checksum: checksum do IO.copy_stream(io, make_path_for(key)) ensure_integrity_of(key, checksum) if checksum @@ -29,35 +29,29 @@ module ActiveStorage end else instrument :download, key: key do - begin - File.binread path_for(key) - rescue Errno::ENOENT - raise ActiveStorage::FileNotFoundError - end + File.binread path_for(key) + rescue Errno::ENOENT + raise ActiveStorage::FileNotFoundError end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - begin - File.open(path_for(key), "rb") do |file| - file.seek range.begin - file.read range.size - end - rescue Errno::ENOENT - raise ActiveStorage::FileNotFoundError + File.open(path_for(key), "rb") do |file| + file.seek range.begin + file.read range.size end + rescue Errno::ENOENT + raise ActiveStorage::FileNotFoundError end end def delete(key) instrument :delete, key: key do - begin - File.delete path_for(key) - rescue Errno::ENOENT - # Ignore files already deleted - end + File.delete path_for(key) + rescue Errno::ENOENT + # Ignore files already deleted end end @@ -79,17 +73,23 @@ module ActiveStorage def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key: key do |payload| - verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) - - generated_url = - url_helpers.rails_disk_service_url( - verified_key_with_expiration, - host: current_host, - filename: filename, - disposition: content_disposition_with(type: disposition, filename: filename), + content_disposition = content_disposition_with(type: disposition, filename: filename) + verified_key_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + disposition: content_disposition, content_type: content_type - ) + }, + { expires_in: expires_in, + purpose: :blob_key } + ) + generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration, + host: current_host, + disposition: content_disposition, + content_type: content_type, + filename: filename + ) payload[:url] = generated_url generated_url diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index 18c0f14cfc..9c20ed1d10 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -11,19 +11,16 @@ module ActiveStorage @config = config end - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil) instrument :upload, key: key, checksum: checksum do - begin - # The official GCS client library doesn't allow us to create a file with no Content-Type metadata. - # We need the file we create to have no Content-Type so we can control it via the response-content-type - # param in signed URLs. Workaround: let the GCS client create the file with an inferred - # Content-Type (usually "application/octet-stream") then clear it. - bucket.create_file(io, key, md5: checksum).update do |file| - file.content_type = nil - end - rescue Google::Cloud::InvalidArgumentError - raise ActiveStorage::IntegrityError - end + # GCS's signed URLs don't include params such as response-content-type response-content_disposition + # in the signature, which means an attacker can modify them and bypass our effort to force these to + # binary and attachment when the file's content type requires it. The only way to force them is to + # store them as object's metadata. + content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename + bucket.create_file(io, key, md5: checksum, content_type: content_type, content_disposition: content_disposition) + rescue Google::Cloud::InvalidArgumentError + raise ActiveStorage::IntegrityError end end @@ -34,43 +31,44 @@ module ActiveStorage end else instrument :download, key: key do - begin - file_for(key).download.string - rescue Google::Cloud::NotFoundError - raise ActiveStorage::FileNotFoundError - end + file_for(key).download.string + rescue Google::Cloud::NotFoundError + raise ActiveStorage::FileNotFoundError + end + end + end + + def update_metadata(key, content_type:, disposition: nil, filename: nil) + instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do + file_for(key).update do |file| + file.content_type = content_type + file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - begin - file_for(key).download(range: range).string - rescue Google::Cloud::NotFoundError - raise ActiveStorage::FileNotFoundError - end + file_for(key).download(range: range).string + rescue Google::Cloud::NotFoundError + raise ActiveStorage::FileNotFoundError end end def delete(key) instrument :delete, key: key do - begin - file_for(key).delete - rescue Google::Cloud::NotFoundError - # Ignore files already deleted - end + file_for(key).delete + rescue Google::Cloud::NotFoundError + # Ignore files already deleted end end def delete_prefixed(prefix) instrument :delete_prefixed, prefix: prefix do bucket.files(prefix: prefix).all do |file| - begin - file.delete - rescue Google::Cloud::NotFoundError - # Ignore concurrently-deleted files - end + file.delete + rescue Google::Cloud::NotFoundError + # Ignore concurrently-deleted files end end end diff --git a/activestorage/lib/active_storage/service/mirror_service.rb b/activestorage/lib/active_storage/service/mirror_service.rb index 6002ef5a00..75274f81b3 100644 --- a/activestorage/lib/active_storage/service/mirror_service.rb +++ b/activestorage/lib/active_storage/service/mirror_service.rb @@ -24,9 +24,9 @@ module ActiveStorage # Upload the +io+ to the +key+ specified to all services. If a +checksum+ is provided, all services will # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, **options) each_service.collect do |service| - service.upload key, io.tap(&:rewind), checksum: checksum + service.upload key, io.tap(&:rewind), checksum: checksum, **options end end diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index 89a9e54158..382920ef61 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -16,13 +16,11 @@ module ActiveStorage @upload_options = upload end - def upload(key, io, checksum: nil) + def upload(key, io, checksum: nil, **) instrument :upload, key: key, checksum: checksum do - begin - object_for(key).put(upload_options.merge(body: io, content_md5: checksum)) - rescue Aws::S3::Errors::BadDigest - raise ActiveStorage::IntegrityError - end + object_for(key).put(upload_options.merge(body: io, content_md5: checksum)) + rescue Aws::S3::Errors::BadDigest + raise ActiveStorage::IntegrityError end end @@ -33,22 +31,18 @@ module ActiveStorage end else instrument :download, key: key do - begin - object_for(key).get.body.string.force_encoding(Encoding::BINARY) - rescue Aws::S3::Errors::NoSuchKey - raise ActiveStorage::FileNotFoundError - end + object_for(key).get.body.string.force_encoding(Encoding::BINARY) + rescue Aws::S3::Errors::NoSuchKey + raise ActiveStorage::FileNotFoundError end end end def download_chunk(key, range) instrument :download_chunk, key: key, range: range do - begin - object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY) - rescue Aws::S3::Errors::NoSuchKey - raise ActiveStorage::FileNotFoundError - end + object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY) + rescue Aws::S3::Errors::NoSuchKey + raise ActiveStorage::FileNotFoundError end end |