Revised issue motivation

We would like to allow for sending redirect responses, instead of direct file responses, when responding to requests handled by the Media Entity Download module. This change would help from multiple perspectives by:

  • improving performance regarding external and Drupal-based caching systems;
  • allowing for external file storage (as the original motivation for this issue).

Original issue motivation

When files are not served from the local Drupal installation's files directory, the file_exists in the controller will fail and cause the exception to be thrown. It seems like it would be nice to have a configuration setting to allow external file storage. If this were checked, it would still make sure the file entity exists, but bypass the file_exists check and return a TrustedRedirectResponse object instead of the BinaryFileResponse object.

I will work on a patch for this, but if anyone has a different solution, I would love to hear about it.

My situation is that I have .htaccess rules forwarding requests to files from my local development environment or from test environments to production. This means I can't test or show the functionality until it gets to production. Once on production, I believe it should work without the external file storage configuration turned on. This could also make sense with CDN integration.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tnathanjames created an issue. See original summary.

tnathanjames’s picture

I have added a simple configuration form to allow external file storage and updated the controller to use that setting to determine if a redirect should be returned instead of the file. The patch is attached.

tnathanjames’s picture

Status: Active » Needs review
kevinquillen’s picture

Assigned: Unassigned » kevinquillen
tnathanjames’s picture

I did a bit of cleanup on my patch. Here is the new version.

michael_wojcik’s picture

Title: Allow compatibility with CDN / external file storage » Allow redirect responses for download requests
Issue summary: View changes
StatusFileSize
new6.28 KB

I've refactored the patch written by @tnathanjames in #5, to rename the config being created so that users can decide to use redirects vs. file responses in general, since this has many potential use cases and doesn't just apply to external file storage. For examples, using redirects instead of file responses can have many benefits related to performance (re: caching) and security (re: private file access control).

michael_wojcik’s picture

StatusFileSize
new2.44 KB

Including interdiff for changes from #5 to #6.

rbayliss’s picture

This patch is looking really good, but in order to get the performance benefits (cacheability of the redirect), the redirect would need to be a 301, but TrustedRedirectResponse defaults to a 302. Does it make sense to make the redirect code be configurable here? One way to do it would be to have "redirect_download" be an integer, where 0 = no redirect, and it can be set to 301 or 302.

michael_wojcik’s picture

Re-rolled patch to include redirect configuration options (301, 302, or 307). Thanks, @rbayliss!

webflo’s picture

Lets add it to 8.x-2.x first and then backport to 8.x-1.x if still needed?

chop’s picture

Rerolled and adapted for 8.x-2.x

Need this for Drupal 8.4+ Media in core.

Status: Needs review » Needs work
damienmckenna’s picture

Issue tags: +Security

Just tagging this with "security" as it affects content security.

nicrodgers’s picture

Assigned: kevinquillen » Unassigned
Status: Needs work » Needs review

updating status, and assignee based on the last 9 comments.

nicrodgers’s picture

Status: Needs review » Needs work

needs work as tests in #11 failed.

guedressel’s picture

Frankly: I don't like this approach.

To me redirects only really make sense for external media sources. Security wise @vitalie's approach is good and integrates access control for "private://" files. Thinking of custom filename suggestion in forced-download situations (content-disposition: attachment): HTTP redirects to "public://" files would prevent any control of HTTP response header from Drupal/PHP since those files are delivered solely by the HTTP daemon - hence would prevent such features from being implemented.

What do you think of my more general approach to handle needs of external media sources in 3012528?

bkosborne’s picture

Remember that the original goal of this module was to provide a stable URL for a file so that editors can link to it without worrying about it changing when files are replaced.

If we keep that goal in mind, then always redirecting to the file URL makes the most sense to me. Editors just need an easy way to link to the vanity path and Drupal handles the redirect to the actual file when someone requests the path.

But now I'm a bit conflicted. If a redirect is done for something like an image or a PDF, then it's possible that by default a user's browser may open that URL directly in the browser. That would expose the actual file URL to the browser, which means that a visitor may copy and paste that link to and give it to someone else... which is what we don't want.

I don't know how much I should really be concerned about that.

Perhaps the bigger concern should be: Do we really want to be bootstrapping Drupal to proxy the download of public file assets? I think the performance implications of doing so are great enough that we shouldn't. Imagine a swath of visitors with very slow internet connections, and they all request to download a large file using this method. Now PHP is stuck handling this slow I/O instead of the web server. This is of course unavoidable for private files.

bkosborne’s picture

In the mean time while this issue is fleshed out more, I created a small contrib module that redirects the canonical path for file-based media entities to the actual file source URL: Media Entity File Redirect.

chop’s picture

I agree with @bkosborne above:

Remember that the original goal of this module was to provide a stable URL for a file so that editors can link to it without worrying about it changing when files are replaced.

Redirects to a managed file, that is uploaded for a Media entity, come with problems. Particularly around caching on High Availability architectures where Varnish, Cloudflare and other reverse-proxy caches are used. Cachability metadata is not set and/or returned by this module's download route either which I'm planning to address in another issue.

The main question you need to think about is "what happens if the File changes?"
Will your redirect still be cached at the Edge, or anywhere else between the Origin and the Visitor, as pointing to the old file?
If it is cached, will it be cleared from cache because the redirect response returned appropriate cache metadata (ie. cache tags, contexts, max age, etc.)

There are already patterns provided by other modules that can do a redirect from a Media entity to its file quite well. I'd point to the Rabbit Hole module, in tandem with the Tokens module:

- https://dgo.to/rabbit_hole
- https://dgo.to/token

Using these you set-up your Media type with Rabbit Hole so any request for a Media of that type is redirected (HTTP 301), using tokens, to the File URI of the attached file.

Here's the config export:

config/default/rabbit_hole.behavior_settings.media_type_document.yml

uuid: b4c6dbf4-5660-4c86-88bd-0bfa5ad0238a
langcode: en
status: true
dependencies:
  config:
    - media.type.document
id: media_type_document
action: page_redirect
allow_override: 1
redirect: '[media:field_document:entity:url]'
redirect_code: 301

FWIW I think the focus of this module is not really related to doing redirects.

bkosborne’s picture

Issue summary: View changes

Removing the security concern from the issue motivation, since that is being addressed in #2986486: Check entity access instead of global permission in download controller

The main question you need to think about is "what happens if the File changes?"
Will your redirect still be cached at the Edge, or anywhere else between the Origin and the Visitor, as pointing to the old file?
If it is cached, will it be cleared from cache because the redirect response returned appropriate cache metadata (ie. cache tags, contexts, max age, etc.)

These concerns around caching is not justified - This is easily solvable by adding the proper cache metadata to the redirect response. My alternate module Media Entity File Redirect does this. If the file on a "Document" media entity is replaced, the redirect response will be cache invalidated correctly.

clarentina’s picture

Rerolled and adapted for 8.x-2.x

Need this for Drupal 8.8+ Media in core.

gisle’s picture

Status: Needs work » Needs review

Changing status to request review of patch in #22.

Status: Needs review » Needs work

The last submitted patch, 22: media_entity_download-allow_download_redirect_responses-2925406-21-d8.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

steven jones made their first commit to this issue’s fork.

steven jones’s picture

Status: Needs work » Needs review

I've opened a MR that takes the patch from #22 and then fixes a couple of bugs.

Seems to work though!

dave reid made their first commit to this issue’s fork.

berdir’s picture

Status: Needs review » Needs work
dave reid’s picture

Title: Allow redirect responses for download requests » Allow redirect responses for download requests (and automatically redirect external file URLs like s3fs)
dave reid’s picture

Status: Needs work » Needs review

I think this is ready for re-review.

man-1982 made their first commit to this issue’s fork.

man-1982’s picture

Status: Needs review » Reviewed & tested by the community

tested this approach with Stage File proxy module, and it works perfectly. Made some minor changes to @dave reid PR.
Overall all works perfectly.
Using this patch for couple our Acquia site factory projects

Have some questions about tests. What kind tests do we need to cover this functionality? And how we can emulate remote file download for BrowserTestBase?
i have only one idea is save dummy file in the project repo and and use it permanent link in test module for download file.

thanks

man-1982’s picture

Status: Reviewed & tested by the community » Needs review