ruby-on-railsgoogle-cloud-storagerails-activestorage

Force content disposition on active storage object with google cloud storage


Expected result

Force a file download even if the file is displayable in browser like an image.

Problem

I'm having troubles with using rails_blob_url(file, disposition: 'attachment') or file.url(disposition: 'attachment') which is probably the same? The response headers of the file are always content-disposition: inline; filename="image.png"; filename*=UTF-8''image.png

On my local dev machine (using local file storage) it works perfectly fine also for images - everything gets a forced download. On my staging env using google cloud storage it seems as if disposition attachment gets ignored. There is no metadata set with disposition on the GCS file so as the documentation states it should use the querystring provided (response-content-disposition & response-content-type). All files are private so we are using a signed_url.

I created a method to debug - basically using the same methods as active storage does internally..

def download_url
  disposition = "attachment"
  service = @file.service
  expires_in = 300

  if service.name == :google
    gcs_config = Rails.application.config.active_storage.service_configurations['google']
    storage = Google::Cloud::Storage.new(
      project_id: gcs_config['project'],
      credentials: gcs_config['credentials']
    )
    bucket = storage.bucket(gcs_config['bucket'])
    blob = bucket.file(@file.key, skip_lookup: true)

    content_disposition_formatted = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: @file.filename.sanitized)
    args = {
      expires: expires_in,
      query: {
        "response-content-disposition" => content_disposition_formatted,
        "response-content-type" => @file.content_type
      }
    }

    logger.info "X"*80
    logger.info @file.filename
    logger.info "X"*80
    logger.info args.inspect
    logger.info "X"*80

    blob.signed_url(**args)
  else
    rails_blob_url(@file, disposition: disposition, expires_in: expires_in)
  end
end

Any ideas how to force this? It should be possible - right?!


Solution

  • there is an activestorage bug for gcs when using direct upload in activestorage/lib/active_storage/service/gcs_service.rb#headers_for_direct_upload. it sets the content disposition metadata to inline and currently the only way to get the query params for content disposition working is to clear the content disposition metadata.

    here is a bushhack method for clearing it:

    def clear_content_disposition
      return unless file.service.name == :google
    
      gcs_config = Rails.application.config.active_storage.service_configurations['google']
      storage = Google::Cloud::Storage.new(
        project_id: gcs_config['project'],
        credentials: gcs_config['credentials']
      )
      bucket = storage.bucket(gcs_config['bucket'])
      blob = bucket.file(file.key, skip_lookup: true)
      blob.content_disposition = nil
    end