3

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");
sootsnoot
  • 2,178
  • 3
  • 22
  • 27

1 Answers1

2

I'm not going to accept this as the answer to the question, because I still haven't found "the" encoding function that is expected to be used. But I did find this ancient Apache API documentation: http://pedrowa.weba.sk/docs/ApiDoc/apidoc_ap_unescape_url.html. It documents the retuen value of ap_unescape_url() as:

Returns 0 on success, BAD_REQUEST if a bad escape sequence is found, or NOT_FOUND if %2F (/) is found.

But of course in PHP, both urlencode() and rawurlencode() encode '/' as %2F.

So clearly, any full pathname used in an XSendFile header must NOT be urlencoded using either of these functions!!

My guess as to the "best" solution for me to use:

$encoded = str_replace('%2F', '/', rawurlencode($pathname));

I must admit I'm amazed that the XSendFile documentation does not mention this. And I'm even more amazed that the question has not gotten any answers here. Should I have posted this in a different Stack Exchange site?

sootsnoot
  • 2,178
  • 3
  • 22
  • 27