It seems like googling for xsendfile issues produces a number of hits that are contradictory/outdated.
In the interest of full disclosure, I'm taking about xsendfile 1.0 beta, documented at https://tn123.org/mod_xsendfile/beta/ (this site doesn't often show up in search results, just the one without the /beta does). I'm using it with apache 2.4 and php 5.4.34 on both Linux and Windows. Besides being the latest version, I need to use the beta version because only the beta site has Windows binaries built with VC9 for apache 2.4.
I made the mistake of reading the documentation, where the description of the file name value in the header says:
The value (file name) given by the header is assmumed to be url-encoded, i.e. unescaping/url-decoding will be performed. See XSendFileUnescape. If you happen to store files using already url-encoded file names, you must "double" encode the names... %20 -> %2520
And the description of XSendFIleUnescape says:
Setting XSendFileUnescape off will restore the pre-1.0 behavior of using the raw header value, instead of trying to unescape/url-decode first.
The documentation about relative paths makes it pretty clear that the file name on the X-SendFile
header should be a full pathname. So I carefully ran my pathnames through php's urlencode
function.
The end result for me was invariably a server internal error (500 status code) on both Linux and Windows. When I had my XSendFilePath directive in server config
context, which the documentation says is allowed, I got nothing more specific in my error log. But when I (eventually) moved that directive to Directory
context, I graduated to getting this in my error log:
(404)Unknown error: [client 127.0.0.1:20742] xsendfile: bad file name encoding
Eventually, out of desperation, I said "screw the documentation", and removed the urlencode
on the pathname. And suddenly it started working perfectly (both Windows and Linux)!!!
I don't have any pathnames with non-ASCII characters in them, so I'm all set. But I do wonder what sort of encoding is supposed to be applied to allow non-ASCII characters to work. If you google xsendfile: bad file name encoding
, you'll find the following source code at https://github.com/nmaier/mod_xsendfile/blob/master/mod_xsendfile.c where that message string is produced by taking the true branch of:
rv = ap_unescape_url(file);
if (rv != OK) {
But I can't find a good description or source code for ap_unescape_url()
. Unless the source on github is outdated, that function objects to the simple %-encoding that PHP's urlencode()
function performs. As a wild guess I tried calling ap_escape_url()
, but it's not defined in PHP. So that leaves the question of what encoding is supposed to be applied to the pathname parameter in the X-SendFile
header??
One More Observation/Question
The description of XSendFile
using "apache internals" to send the file might make you think that it would construct a Content-Type
header from the filename extension using mod_mime
. But in fact it doesn't, and the examples show an explicit header()
call for Content-Type
. So my follow-on is what is the "right" way to construct that header from the pathname being passed to X-SendFile
, such that it is guaranteed to match what mod_mime
would do if we weren't using X-SendFile
? The best I could come up with is the following code using PHP's fileinfo
extension - but as far as I know there's no particular reason to expect that it would actually match what apache does when given a url for a filename.
$finfo = new finfo(FILEINFO_MIME);
$mime_info = $finfo->file($pathname);
if (! strlen($mime_info)) {
$mime_info = 'application/octet-stream; charset=binary';
}
$basename = basename($pathname);
$encoded = "$pathname";
header("Content-Type: $mime_info");
header("Content-Disposition: attachment; filename=\"$basename\"");
header("X-SendFile: $encoded");