0

I'm trying to send a file to the user using xsendfile within the code igniter framework.

It is all installed correctly, my problem is that it only seems to work from the route, even though every page comes from index.php anyway.

This is my function:

function _output_file($name, $root_location, $public_location = FALSE)
{
    if (function_exists('apache_get_modules') && in_array('mod_xsendfile', apache_get_modules())) {
        header ('Content-Description: File Transfer');
        header ('Content-Type: application/octet-stream');
        if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
            header ('Content-Disposition: attachment; filename='.urlencode($name));
        } else {
            header ('Content-Disposition: attachment; filename="'.$name.'"');
        }
        //86400 is one day
        header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
        header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header ('Pragma: public');
        header ('X-Sendfile: '.$root_location);
        exit;
    } else {
        redirect(site_url($public_location));
    }
}

If I place this at the top of my index.php and load the root it works fine, but if I try to access it from domain.com/controller/function it returns a 404 error.

It is definitely using the index.php file since if I replace the function call with die("test"); this displays to the screen.

I believe it's something to do with what permissions xsendfile has to access the file, but since it's working from the root index.php I would have thought it would have complete permissions, presumably it's based on what the request url is, which I find strange.

So.... does anyone have any suggestions as to how I can get xsendfile to work through codeigniter, from a url such as "domain.com/files/get/12"?

John Mellor
  • 2,351
  • 8
  • 45
  • 79

4 Answers4

0

XSendFile has some specific security-related path quirks... and depending on your server configuration, these issues can sometimes occur over HTTPS even when HTTP seems to be working correctly.

If you run into mysterious 404s while using mod_xsendfile and you can confirm that the files being served really do exist, then you probably need to configure XSendFilePath in your Apache confs.

Add the following to the appropriate conf (httpd.conf, ssl.conf, httpd-ssl.conf, etc) and/or inside the appropriate VirtualHost declaration (if using vhosts)...

XSendFilePath /Absolute/Path/To/Your/Working/Directory/

Note: You cannot add this to an .htaccess file. It must be placed into an Apache conf.

Generally, xsendfile will try to figure out the working directory automatically, but sometimes it can't. This directive tells it explicitly what directory (or directories) should be accessible through xsendfile. Chances are that a mysterious 404 means that your directory isn't passing the whitelist check for some reason. This will fix that.

And don't forget to reboot apache after you change the config.

Matt van Andel
  • 636
  • 7
  • 13
-1

Prefixing a method name with an underscore makes it inaccessible through the URL.

From the documentation:

Private Functions

In some cases you may want certain functions hidden from public access. To make a function private, simply add an underscore as the name prefix and it will not be served via a URL request. For example, if you were to have a function like this:

private function _utility()
{
    // some code
}

Trying to access it via the URL, like this, will not work: example.com/index.php/blog/_utility/

birderic
  • 3,745
  • 1
  • 23
  • 36
  • I do realise this, this is another function which is called e.g. I have a function called "get" within a class called "files" to access through domain.com/files/get which then calls this function. Sorry I should have mentioned that, I thought it would be obvious – John Mellor Sep 21 '11 at 13:24
  • When you said it would work from `index.php` but not from a controller through the URL I figured it was an access issue. Could you pretty please remove the downvote? – birderic Sep 26 '11 at 14:20
  • Hi my vote is locked unless the answer is edited :) (edit: that's not me being mean, that's the error SO gives!) – John Mellor Sep 26 '11 at 17:07
-1

It seems this answer never got a reponse, in the end I just created a file in my root called "getfile.php", it's not perfect but it gets the job done for now, here it is for anyone that may find it useful.

<?php
define('BASEPATH', 'just done to stop direct access being disallowed');

function show_getfile_error()
{
    echo 'You do not have permission to download this file, if you think this is a mistake please get in contact.';
    exit;
}

include('applications/config/database.php');
$mysqli = new mysqli($db['default']['hostname'], $db['default']['username'], $db['default']['password'], $db['default']['database']);
if(!preg_match('%^[0-9]+$%', $_GET['key']))
{
    show_getfile_error();
}
else
{
    $query = mysqli_query($mysqli, 'SELECT * FROM getfiles WHERE getfile_key = '.(int)$_GET['key']);

    $result = mysqli_fetch_array($query, MYSQLI_ASSOC);

    if(!$result || $result['getfile_ip'] != $_SERVER['REMOTE_ADDR'])
    {
        show_getfile_error();
    }

    header ('Content-Description: File Transfer');
    header ('Content-Type: application/octet-stream');
    if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
        header ('Content-Disposition: attachment; filename='.urlencode($result['getfile_name']));
    } else {
        header ('Content-Disposition: attachment; filename="'.$result['getfile_name'].'"');
    }
    //86400 is one day
    header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
    header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header ('Pragma: public');
    header ('X-Sendfile: '.$result['getfile_location']);
}
?>
John Mellor
  • 2,351
  • 8
  • 45
  • 79
-1

I have come across the 404 error today. If the path you pass to the header contains any components that lie outside of the root folder that index.php is in, then you get a 404. Make sure the path is relative to index.php, not an absolute path.

Jason
  • 4,411
  • 7
  • 40
  • 53
  • This isn't correct. xsendfile requires that all paths that are passed to it be absolute system paths. xsendfile will then handle serving the file directly through apache. – Matt van Andel Jun 01 '16 at 16:42
  • Nope. I had `base_dir` restrictions on the site, and `mod_xsendfile` would return a 404 if *any* part of the path it was provided with sat outside of the restricted root folder. It *did* however work with relative paths from the `cwd`, which is by default the directory containing the index.php entry point of the application. Absolute paths may work with some system setups, but installing a plugin that allows streaming from any path the web user has read access to on the server seems a bit of an unnecessary security risk. – Jason Jun 03 '16 at 12:18