views:

485

answers:

2

i'm builing this php script which displays a given folder 's children folders and files. Files ought to be downloaded.

In order to disclose as little information as possible, each item in the directory is a form with a submit button that contains the html markup. The send method is POST.

IF i click on a directory, it opens that directory and displays the content just fine. If i click on a file, it shows a download prompt and the file downloads just fine. But the click after that, i get to see such thing:

0 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
0000000000 65535 f
trailer
<</Size 34>>
startxref
116
%%EOF
HTTP/1.1 200 OK
Date: Mon, 31 Aug 2009 21:01:36 GMT
Server: Apache
X-Powered-By: PHP/5.2.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Keep-Alive: timeout=15, max=93
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

2822
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;

and the rest of the html code source of the display page is listed.

What is going on here?

UPDATE: my "force download" code:

function offerToDownloadFile($filename, $access_type='url') {


// converting url to local path so Apache can find the file.
// force download:
// required for IE, otherwise Content-disposition is ignored
    if (ini_get('zlib.output_compression'))
        ini_set('zlib.output_compression', 'Off');

    if($access_type === 'url') {
    // access type is via the file 's url
        $parsed_url = parse_url($filename);
        $fileinfo = pathinfo($filename);
        $parsed_url['extension'] = $fileinfo['extension'];
        $parsed_url['filename'] = $fileinfo['basename'];

        $parsed_url['localpath'] = LOCALROOT . $parsed_url['path'];
    }
    else {
    // access type is the local file path
        $fileinfo = pathinfo($filename);
        $parsed_url['localpath'] = $filename;
        $parsed_url['filename'] = basename($filename);
        $parsed_url['extension'] = $fileinfo['extension'];
    }


    // just in case there is a double slash created when joining document_root and path
    $parsed_url['localpath'] = preg_replace('/\/\//', '/', $parsed_url['localpath']);

    if (!file_exists($parsed_url['localpath'])) {
        die('File not found: ' . $parsed_url['localpath']);
    }
    $allowed_ext = array('ics','pdf', 'png', 'jpg', 'jpeg', 'zip', 'doc', 'xls', 'gif', 'exe', 'ppt');
    if (!in_array($parsed_url['extension'], $allowed_ext)) {
        die('This file type is forbidden.');
    }

    switch ($parsed_url['extension']) {
        case "ics": $ctype="text/calendar";
            break;
        case "pdf": $ctype = "application/pdf";
            break;
        case "exe": $ctype = "application/octet-stream";
            break;
        case "zip": $ctype = "application/zip";
            break;
        case "doc": $ctype = "application/msword";
            break;
        case "xls": $ctype = "application/vnd.ms-excel";
            break;
        case "ppt": $ctype = "application/vnd.ms-powerpoint";
            break;
        case "gif": $ctype = "image/gif";
            break;
        case "png": $ctype = "image/png";
            break;
        case "jpeg":
        case "jpg": $ctype = "image/jpg";
            break;
        default: $ctype = "application/force-download";
    }
    header("Pragma: public"); // required
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private", false); // required for certain browsers
    header("Content-Type: $ctype");
    header("Content-Disposition: attachment; filename=\"" . $parsed_url['filename'] . "\";");
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: " . filesize($parsed_url['localpath']));
    readfile($parsed_url['localpath']);
    clearstatcache();
    exit();

}
A: 

I couldn't tell you what all the garbage is there, but to avoid it, after you send the file through, call die().

nickf
i already have it, unfortunately.
pixeline
+2  A: 

By the looks of it your file download script may be setting the wrong Content-Length, so that the last few bytes of the file can spill over into the next request, causing all manners of brokenness.

What web server and OS are you using, and how are you invoking PHP? Because if it's Windows I'd have a suspicion that your PHP installation may somehow be writing to stdout as a text instead of a binary stream. This would cause each \n byte to be converted to a \r\n sequence, which would probably make most binary files unreadable as well as making the response body a bit longer than the Content-Length header said it was.

$parsed_url['localpath'] = LOCALROOT . $parsed_url['path'];

That seems rather dangerous. I would hope $filename were checked and validated to within an inch of its life before trying anything like that. (Filename validation is deceptively hard.)

bobince
the webserver+ OS:Server: Apache/2.0.63 (Unix) mod_ssl/2.0.63 OpenSSL/0.9.8e-fips-rhel5 mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635 PHP/5.2.8X-Powered-By: PHP/5.2.8as for your second remark, i previously check if file_exist(). Isn't that enough?
pixeline
... it seems commenting out the line calling filesize() solves the problem. could that be it ?
pixeline
Omitting the Content-Length would fall back to letting Apache handle the length itself (usually by wrapping your output in a chunked response), which would automatically get the length right. The question is why the length header would be wrong, if it's not a \n->\r\n problem (and it can't be that, if you're using a Unix server). Could filesize() be returning FALSE for some reason?
bobince
yes it's returning false,indeed. but the file gets downloaded right and is not corrupted, so the cause is probably at the server/php level.
pixeline