1

I have downloaded and added this very simple, one file, php web file explorer system(called Indexer) to my XAMPP server.

My XAMMP server is on my C: drive, but I want Indexer to display a directory on my G: drive. But when I change (what I think are) the right configuration variables, it doesn't work properly.

Here is the code I think is to do with the problem:

// configuration  
$Root = realpath("G:/test");  
$AllowDownload = TRUE;  
$WebServerPath = dirname("G:/test");

and later on in the code...

elseif ($AllowDownload) {  

        echo "<a href=\"http://".getenv("SERVER_NAME").$WebServerPath."/$rel_path".$item["filename"]."\">".$item["name"]."</a>";
    }

This is what happens: The script does correctly display the contents of the "test" directory on the G: drive, but when I click the filename, to download/view the file, the link is broken because the php constructs the link wrong (I suppose). The link looks like this: http://localhostg//[name of file].

Would you know how to solve this problem?

This script works perfectly if I change the configuration variables so it displays the contents of a relative subdirectory. And it also says $Root variable can be located outside the webserver root.

Also, even though clicking the link doesn't work, right-clicking and selecting "Save Target As" allows me to save/download the file.

(Feel free to ask if you need more information) :)

Jopper
  • 45
  • 4
  • 10

3 Answers3

5

Your web server can not see the files outside the DocRoot, so it can not serve the files via the browser with direct links. You need to print their contents into the browser with readfile() with the headers properly set.

To make this work, you need to change the configuration in indexer.php:

// this way it works with accentuated letters in Windows
$Root = utf8_decode("G:\test"); // define the directory the index should be created for (can also be located outside the webserver root)

$AllowDownload = TRUE; // enclose file items with the anchor-tag (only makes sense when the files are in the webserver root)
// you need to place download.php in the same directory as indexer.php
$WebServerPath = dirname($_SERVER['SCRIPT_NAME']) . "/download.php?path="; // path where the indexed files can be accessed via a http URL (only required when $AllowDownload is TRUE)

And you have to place a new file called download.php in the same directory as indexer.php, with this content:

<?php

// it must be the same as in indexer.php
$Root = utf8_decode("G:\test");

function checkFileIsInsideRootDirectory($path, $root_directory) {
    $realpath = realpath($path);

    if (!file_exists($realpath))
        die("File is not readable: " . $path);

    // detects insecure path with for example /../ in it
    if (strpos($realpath, $root_directory) === false || strpos($realpath, $root_directory) > 0)
        die("Download from outside of the specified root directory is not allowed!");
}

function forceDownload($path) {
    $realpath = realpath($path);

    if (!is_readable($realpath))
        die("File is not readable: " . $path);

    $savename = (basename($path));

    header("Pragmaes: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private", false);
    header("Content-type: application/force-download");
    header("Content-Transfer-Encoding: Binary");
    header("Content-length: " . filesize($path));
    header("Content-disposition: attachment; filename=\"$savename\"");

    readfile("$path");
    exit;
}

if (!isset($_GET['path']))
    die("Path not specified!");

$fullPath = $Root . $_GET['path'];

checkFileIsInsideRootDirectory($fullPath, $Root);

forceDownload($fullPath);
István Ujj-Mészáros
  • 3,228
  • 1
  • 27
  • 46
  • Thanks for answering! This sounds promising; where would I need to put this code (and would I need to change any variable names)? – Jopper Dec 08 '10 at 17:03
  • Edited my answer, now it works well with [`indexer`](http://tbmnet.de/tbmnet.php?content=php_indexer). – István Ujj-Mészáros Dec 08 '10 at 21:00
  • Thanks for your ongoing support:) Where should I put the code and what does it need to replace? – Jopper Dec 09 '10 at 12:56
  • @Jopper As I wrote, you need only to change the config parameters in indexer.php (in fact only `$WebServerPath` is changed to hack the download url), and you need to create a new php script which handles the downloads, and it must be named according to the `$webServerPath`'s value (download.php in this case). – István Ujj-Mészáros Dec 09 '10 at 13:02
  • It is also possible to combine the two files into one, but it seemed to be simpler this way (and I didn't want to modify indexer.php). – István Ujj-Mészáros Dec 09 '10 at 13:04
  • Ah yes, somehow I didn't notice your instructions above the code :P Thank you, I shall try your solution now... – Jopper Dec 09 '10 at 13:17
  • hmm...I've added the code to Indexer.php and created the new download.php file, but when I click a file to download, this is printed onscreen: "Download from outside of the specified root directory is not allowed!" Is is a server configuration issue (I'm using XAMPP), if so, what would I need to change/configure? :) – Jopper Dec 09 '10 at 13:32
  • Maybe your `$Root`'s value is not the same in the two script. If they are the same, then do `var_dump($realpath);` and `var_dump($root_directory);` in download.php's `checkFileIsInsideRootDirectory()` function, before the second if, and post the values (it is a security check to not allow download with path starting with `/../`, but you can completely bypass this function if you don't need it - just comment `checkFileIsInsideRootDirectory($fullPath, $Root);` at the end of the script). – István Ujj-Mészáros Dec 09 '10 at 13:40
  • It works properly when I comment out the function at the bottom of download.php, but if I add var_dump($realpath); and var_dump($root_directory); to where you said (but I don't know what you mean when you said "post the values"), this message is displayed: "string(18) "G:\test\clock.html" string(7) "G:/test" Download from outside of the specified root directory is not allowed!" Also, I have just noticed that using your script stops folders working; so now I can't navigate through folders, instead, it just stays on the same page. – Jopper Dec 09 '10 at 14:35
  • @Jopper Ok, you only need to change `$Root` from `G:/test` to `G:\test` with backslash, and it will work (the function compares that the realpath is starting with the defined root directory, and realpath uses backslashes in the path, so you need backslash in the config path as well)! – István Ujj-Mészáros Dec 09 '10 at 18:10
  • Well, no, I'm afraid :( When I just changed the $root (from download.php)to place a backslash (and not apply the suggestions from your last comment) this error appears when I click a file: "File is not readable: G: est/clock.html" and when I only change the $root to place a backslash (from indexer.php) three errors come up before I've even clicked anything: "scandir(G: est/) [function.scandir]: failed to open dir: No such file or directory in C:\XAMPP\htdocs\Implementation\indexerTest.php on line 82", "scandir() [function.scandir]: (errno 2):" and "Invalid argument supplied for foreach() in.." – Jopper Dec 13 '10 at 21:52
  • @Jopper I meant to only change the $Root's value in download.php (according to the var_dump()'s output). – István Ujj-Mészáros Dec 14 '10 at 06:54
  • Right, so at the moment, in indexer.php it has this: "$Root = utf8_decode("G:/test");" and in download.php it has this: "$Root = utf8_decode("G:\test");" and also in download.php it has "var_dump($realpath); var_dump($root_directory);" in between the first two if statements. But when I click on a file, this message is displayed: "File is not readable: G: est/clock.html". What am I doing wrong? :S /Sidenote: it shouldn't be a problem, but I've actually renamed both files to indexerTest.php and downloadTest.php. – Jopper Dec 14 '10 at 22:26
  • Also, I've uploaded the current source code of each file to snipt.org which might make this proccess easier. indexerTest.php - http://snipt.org/rklT/ | donwloadTest.php - http://snipt.org/rklmk/ Feel free to edit each one how you like :) – Jopper Dec 14 '10 at 22:28
  • Just read it again, probably a bit late, but using single quotes instead of double quotes should work, as backslash in double quotes is escaping the string hence `\t` is converted to a tab :) – István Ujj-Mészáros Nov 27 '15 at 00:01
3

You have to change your apache configuration. The problem is not the php script, the problem is the webserver (which is not able to serve files outside web root, unless you configure it to).

Try something like this in your apache configuration:

Alias /testalias "G:/test"
<Directory "G:/test">
  Options Indexes FollowSymLinks MultiViews ExecCGI
  AllowOverride All
  Order allow,deny
  Allow from all
</Directory>

This tells Apache to serve files from G:/test when you access http://localhost/testalias

Then change your script configuration like that:

$WebServerPath = dirname("testalias");

and it should work!

travelboy
  • 2,647
  • 3
  • 27
  • 37
  • I've found a file in the XAMPP Apache config (conf) folder called "httpd.conf". I suppose this is the right file, but do I just paste the code (from your first code box) into that file, or do I overwrite something? Also, do I need to change the name of "testalias" to the name of the folder the Indexer.php file is in? – Jopper Dec 09 '10 at 13:04
  • Just an advise: it will works fine except whether your intention is to keep your files protected (putting them out of webroot). – sdlins Jul 03 '18 at 03:13
2

Let's take a look at that script:

$Root = realpath("."); // define the directory the index should be created for (can also be located outside the webserver root)
$AllowDownload = TRUE; // enclose file items with the anchor-tag (only makes sense when the files are in the webserver root)
$WebServerPath = dirname(getenv("SCRIPT_NAME")); // path where the indexed files can be accessed via a http URL (only required when $AllowDownload is TRUE)

Notice "only makes sense when the files are in the webserver root" and "path where the indexed files can be accessed via a http URL". Which indicates that this script was not designed to be able to download files that are outside the web server root dir.

However, you could modify this script to be able to do that in the way that styu has noted in his answer. You could then send your changes to the author of the script.

BTW, I tested this on my own server.

d-_-b
  • 6,555
  • 5
  • 40
  • 58