Textpattern CMS support forum
You are not logged in. Register | Login | Help
- Topics: Active | Unanswered
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
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
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
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
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
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
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.
Offline
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:
- Let plugins rewrite headers and return the control to
output_file_download()
- either via
file_download
callback, and checking afterwards (withheaders_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()
.
- either via
- Pluginless: somehow add
type
anddisposition
attributes to<txp:file_download_link />
tag and pass them tooutput_file_download()
in a secure way. Or even onlytype
, and setContent-Disposition: inline
iftype
is set and valid.
Offline
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 | hblack.art | EMAP | A Sea change | Toolkit of Care
I do my best editing after I click on the submit button.
Offline
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
Re: How to intercept file_download callback with a plugin
johnstephens wrote #303509:
@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.
I linked the word “plugin” above, but you must not have seen it. etc linked below as well. It was an unofficial plugin posted in the thread discussing this from 2010/11.
Offline
Re: How to intercept file_download callback with a plugin
Bloke wrote #303518:
Disadvantage: security. Even passing the
type
might be a risk as it could be manipulated to serve a file as something else.
Valid point. We can not prevent bad guys from misusing Textpattern (say, by serving some evil js), but, at least, external sites shouldn’t be able to cheat with txp-served files. What are possible abuse scenarios?
Offline