Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2016-12-24 03:59:40

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 985
Website

How to intercept file_download callback with a plugin

Dear Esteemed Textpattern Keepers,

My goal is to let capable browsers display PDF files managed by my Files tab in Textpattern, instead of forcing them to download.

Jakob pointed out that Texpattern sets headers in the output_file_download() function, that forces browsers to download files even if it could display them.

The PDF files are created by my editorial team, and they shouldn’t pose any security risk. But in principle, it should be possible for a plugin to do this, right?

I don’t know many plugins that intercept the file_download, and I’m mainly using smd_access_keys as an example.

So far, I can’t figure out how to do anything. Since the output_file_download() function doesn’t display anything in the browser, I don’t even know how to write a “Hello World”-type plugin triggered on file download.

Here’s what I have so far, in my ied_plugin_composer:

register_callback('pax_file_plugin', 'file_download');

function pax_file_plugin($evt, $stp) {
    return false;
}

I admit my PHP skills aren’t as good as my JavaScript, nunchuck, or bo staff skills, but I thought “return false” might interrupt the file download, just to show me that my function is being loaded by Textpattern.

Can anyone offer any guidance in this?

Thanks in advance!

Offline

#2 2016-12-24 13:23:20

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 8,681
Website

Re: How to intercept file_download callback with a plugin

Textpattern indeed raises a callback there, but if the callback completes, the download is served — irrespective of the return code. You therefore have two main avenues open to you.

Option one is to exit inside your plugin. If you do an exit() or a die() or maybe even a txp_die() (?) in your plugin code, Txp should halt in its tracks. So once you’ve set your own headers you can serve the file, i.e. copy a hunk of output_file_download() into your own code and hack it to do what you want, then quit.

Alternatively — and I don’t know if this will work — have your file download plugin set the global file_error to one of the status flags handled in the output_file_download() routine. Say, 403. If that’s set, the entire output part will be skipped and Txp will throw an error with the given code. Ordinarily not much use, but you can register a second callback to respond to the txp_die event with the error code as the step.

Thus, when the error is triggered you can intercept it, check the event is a valid file_download request and serve your download that way. You might get into scrapes such as the browser thinking it’s got a 403 and bailing out before you can get to it, or caches being given incorrect information, so it’s probably more hassle than the first method. I’d try that first, then use the second idea as fallback.

This is actually something I’d like to improve. Having the ability for people to manipulate the file download flow in a less hacky manner could be of immense benefit. btw, smd_remote_file also intercepts downloads (of sorts) so you might be able to glean something from that, but it’s been a long time since I looked at the plugin so it might be a bit bobbins.

Hope that helps.


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#3 2017-01-05 20:55:34

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 985
Website

Re: How to intercept file_download callback with a plugin

Thank you, Stef!

I had time to work on this yesterday and today, and now I’m even more confused.

Here’s what I tried:

function pax_file_plugin($evt, $stp) {
    exit();
}
function pax_file_plugin($evt, $stp) {
    die();
}
function pax_file_plugin($evt, $stp) {
    txp_die();
}

Each time, I paired the function with register_callback('pax_file_plugin', 'file_download');.

But none of those stopped the file download.

Taking a detour, I tried requesting a file via the HTML object element, like so:

<object data='<txp:file_download_link id="1"/>' type='application/pdf' width='100%' height='100%'>

  <!-- Fallback to iframe -->

  <iframe src='<txp:file_download_link id="1"/>' width='100%' height='100%' style='border: none;'>

    <!-- Fallback to link --> Please download the PDF to view it: <a href='<txp:file_download_link id="1"/>'>Download PDF</a>

  </iframe>

</object>

Using a browser with PDF display enabled, the above code works when I link to any PDF in the file system. But when I use any PDF file served by Textpattern (as above), the object markup displays nothing. The iframe fallback triggers an immediate download of the file as if I’m clicking on a file download link. If I remove the iframe fallback, nothing shows up at all.

The weird thing is, I use HTML’s audio element to play MP3 files served by Textpattern’s file download link tag, and it works fine: It counts each “play” as a download, but it doesn’t force the browser to download the file.

I’m afraid I didn’t understand your advice about using file_error in my plugin, and I feel like I’m at an impasse.

Offline

#4 2017-01-05 22:31:45

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 8,681
Website

Re: How to intercept file_download callback with a plugin

johnstephens wrote #303504:

Here’s what I tried [snip]

Oh, rats, that’s my theory blown out the water. Sorry for the bogus info. I was sure it would work. Hmmmm….


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#5 2017-01-06 01:49:15

maverick
Member
From: Southeastern Michigan, USA
Registered: 2005-01-14
Posts: 960
Website

Re: How to intercept file_download callback with a plugin

Is it reasonable to assume that etc’s plugin did not accomplish the goal?

(edited for grammar)

Last edited by maverick (2017-01-06 11:46:24)

Offline

#6 2017-01-06 03:00:04

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 985
Website

Re: How to intercept file_download callback with a plugin

@maverick, I’d love to know. Can you point me to the plugin you have in mind?

I don’t see any file_download-related plugins on etc’s website or Github account.

Offline

#7 2017-01-06 09:54:34

etc
Developer
Registered: 2010-11-11
Posts: 3,207
Website

Re: How to intercept file_download callback with a plugin

I have edited the plugin to reflect browser changes in applications treatment, it may still work. If someone wants it to be enhanced and “officially” released, please let me know.

Edit: it’s a shame, though, that one needs to rewrite a whole block of code just to append a header, there must be a better way.


etc_[ query | search | pagination | date | tree | cache ]

Offline

#8 2017-01-06 11:02:09

etc
Developer
Registered: 2010-11-11
Posts: 3,207
Website

Re: How to intercept file_download callback with a plugin

Bloke wrote #303403:

This is actually something I’d like to improve. Having the ability for people to manipulate the file download flow in a less hacky manner could be of immense benefit.

There are some easy options:

  1. Let plugins rewrite headers and return the control to output_file_download()
    • either via file_download callback, and checking afterwards (with headers_list() function) if relevant headers (say, Content-Disposition) are already set by plugins;
    • or introducing a new callback right after these headers are set in output_file_download().
  2. Pluginless: somehow add type and disposition attributes to <txp:file_download_link /> tag and pass them to output_file_download() in a secure way. Or even only type, and set Content-Disposition: inline if type is set and valid.

etc_[ query | search | pagination | date | tree | cache ]

Offline

#9 2017-01-06 11:07:27

colak
Admin
From: Cyprus
Registered: 2004-11-20
Posts: 7,296
Website

Re: How to intercept file_download callback with a plugin

etc wrote #303513:

Edit: it’s a shame, though, that one needs to rewrite a whole block of code just to append a header, there must be a better way.

+1. I guess the best way would be to have a preference on a per file or per file category basis deciding for a download or web browser read.

Alternatively (and probably impossible to do), view on web browser but also keep a track of download numbers.


Yiannis
——————————
neme.org | hblack.net | LABS | State Machines | Respbublika! | NeMe @ github

Offline

#10 2017-01-06 11:47:14

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 8,681
Website

Re: How to intercept file_download callback with a plugin

etc wrote #303516:

Let plugins rewrite headers and return the control to output_file_download()

Nice idea. Unless we could cleverly use the step, the plugin would be invoked for all downloads. So if you only wanted to affect certain types of file (for example all PDFs) you’d have to read the extension/mime type and skip anything you don’t care about.

As to whether it’s implemented using the existing callback or via a new one, I have no preference.

EDIT: are we expecting this to be used on a per-file-type basis? If so, setting the step to the file extension/mime type would be a nice way for plugins to only intercept files they are interested in.

somehow add type and disposition attributes to <txp:file_download_link /> tag and pass them to output_file_download() in a secure way. Or even only type, and set Content-Disposition: inline if type is set and valid.

Advantage: user control over how tags are interpreted, without needing to be able to code.

Disadvantage: security. Even passing the type might be a risk as it could be manipulated to serve a file as something else.

Either way, I think this needs looking into.

Last edited by Bloke (2017-01-06 11:49:18)


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

Board footer

Powered by FluxBB