File Download in PHP fails for Office Documents on some servers...

Hello,

I am using the following PHP code to fetch a file by FTP and then deliver it to the client.  This works perfectly on a Windows Server 2012 R2 IIS build (PHP 5.4), but does not on another of the same specification.  I am using the same browser (Chrome, contemporary) to view each server.

When served out from the VM that fails, PDFs do download and display correctly, but DOC, XLS and PPT files fail, as do DOCX, XLSX and PPTX, although the latter appear to be recoverable when opened in the relevant Microsoft Office application.

When I put up a simple TXT file, this works fine from the failing server, although if I uncomment the header('Content-Length: ' . filesize($file)); line, the file is truncated by the last three characters.  Now, it would be understandable if the DOCXs etc. were failing due to this, but when this header is not delivered, the TXT file serves in its entirety, whereas the DOCXs etc. still fail.

One possibility is that the issue lies within the IIS configuration, as this is one of the only variables that is not consistent between the builds.  I've even copied the precise same version of PHP, in case this was causing the issue.

It is also worth noting that the issue does not lie with the FTP connection, as the file can be echoed to the screen without error (including when the Content-Length header is sent).  It only fails when I attempt to stream the contents with the header data as a download.

Thanks for reading.  If you've got any pointers, I'd be grateful to hear them.

$conn_id = ftp_connect($user_access_result['details']['server']);
$login_result = ftp_login($conn_id, $user_access_result['details']['username'], $user_access_result['details']['password']);

if (!ftp_get($conn_id, $tmp_path."\\".$tmp_filename, $_GET['path']."\\".$_GET['name'], FTP_BINARY)) {
    echo "There was a problem\n";
}

// close the connection
ftp_close($conn_id);

$file = $tmp_path."\\".$tmp_filename;
$file_contents = file_get_contents ($file);


header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($_GET['name']).'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
//header('Content-Length: ' . filesize($file));

echo $file_contents;

unlink ($tmp_path."\\".$tmp_filename);

Open in new window

LVL 1
Anglian Learning Technical ServicesAsked:
Who is Participating?
 
Ray PaseurConnect With a Mentor Commented:
Any chance that the first three bytes of the file are a Byte-Order Mark?  Can you please post the first dozen or so bytes of the file you're testing?  Use "view source" to get them.  Thanks.
0
 
Julian HansenCommented:
but DOC, XLS and PPT files fail, as do DOCX, XLSX and PPTX, although the latter appear to be recoverable when opened in the relevant Microsoft Office application.
Does fail mean corrupted / short length?

Fail I interpret to mean - does not work at all - but if I read between the lines it seems the files are downloading they are just not complete or not opening by default or both?

Have you tried using readfile() instead of file_get_contents() / echo?
0
 
Anglian Learning Technical ServicesAuthor Commented:
Indeed, the document fails to load in the Office Application, suggesting it is corrupt, or incomplete.

I have tried the readfile() function, but the same happens, on this server.  As mentioned, it works on the other of the same build!
0
Making Bulk Changes to Active Directory

Watch this video to see how easy it is to make mass changes to Active Directory from an external text file without using complicated scripts.

 
Julian HansenCommented:
Is it always 3 bytes short or does it vary?
0
 
Anglian Learning Technical ServicesAuthor Commented:
Always three bytes with the single text file I've been testing with.
0
 
David Johnson, CD, MVPOwnerCommented:
you must use a binary transfer not an ascii transfer for all but text files
1
 
Julian HansenCommented:
Last 3 bytes?
What about the DOCX - also 3 bytes?
0
 
Anglian Learning Technical ServicesAuthor Commented:
It's the last three bytes that are being removed when the Content-Length header is used.  The text file I've been testing with simply contains the following:

Testing12345

Open in new window


This gets truncated to:

Testing12

Open in new window

.

The Excel document I've been trying with is attached, both the version from the server and the version that results once it's downloaded and doesn't open properly in the application.
test-excel---on-server.xlsx
test-excel---downloaded.xlsx
0
 
Ray PaseurCommented:
Not seeing a Byte-Order mark.  The file appears to be binary, not subject to rules of any character encoding.

Have you tried using strlen($file_contents) instead of filesize($file) ?
0
 
Ray PaseurCommented:
This worked correctly in Chrome.  It downloaded the file, and I could start Excel to use the spreadsheet.  However, Chrome did not launch Excel automatically.
<?php // demo/temp_bottishav.php
/**
 * https://www.experts-exchange.com/questions/28982110/File-Download-in-PHP-fails-for-Office-Documents-on-some-servers.html#a41881268
 */
error_reporting(E_ALL);

// FUNCTION TO FORCE A DOWNLOAD FROM A FILE
function force_download($url, $filename=NULL)
{
    // GET THE DOWNLOAD FILE NAME
    if (empty($filename)) $filename = basename($url);

    // GET LENGTH AND FILE RESOURCE POINTER
    $hdr = get_headers($url, TRUE);
    $len = trim($hdr['Content-Length']);
    $fpr = fopen($url,'rb');

    // ON SUCCESS
    if ($fpr)
    {
        // THESE HEADERS ARE USED ON ALL BROWSERS
        header("Content-Type: application-x/force-download");
        header("Content-Disposition: attachment; filename=$filename");
        header("Content-length: $len");
        header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

        // THIS HEADER MUST BE OMITTED FOR IE 6+
        if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
        {
            header("Cache-Control: no-cache, must-revalidate");
        }

        // THIS IS THE LAST HEADER
        header("Pragma: no-cache");

        // FLUSH THE HEADERS TO THE BROWSER
        flush();

        // WRITE THE FILE
        fpassthru($fpr);
    }

    // ERROR
    else
    {
        trigger_error("Unable to open $url", E_USER_ERROR);
    }
}

// THE TEST FILE
$url = 'https://filedb.experts-exchange.com/incoming/2016/11_w46/1126866/test-excel---on-server.xlsx';
force_download($url);

Open in new window

0
 
Anglian Learning Technical ServicesAuthor Commented:
Thank you, everyone for your contributions.  The above script results in the same outcome, so I'm now pretty convinced it must be something to do with the IIS configuration, or otherwise something outside of the scripting.  As originally stated, my script does work, on a different server, with the same webserver, version of PHP and Chrome client.  Very frustrating.

Any IIS-related ideas?
0
 
Julian HansenConnect With a Mentor Commented:
Not seeing a Byte-Order mark.  
It is there Ray - I think your guess is correct here.

First 16 bytes of test-excel---on-server.xlsx
50 4B 03 04 14 00 06 00 08 00 00 00 21 00 A4 53

Open in new window


The first 16 bytes of test-excel---downloaded.xlsx is as follows
EF BB BF 50 4B 03 04 14 00 06 00 08 00 00 00 21

Open in new window


EF BB BF
0
 
Julian HansenConnect With a Mentor Commented:
Suggestion - open your script file with a hex editor and make sure there is not a BOM at the front of it.
0
 
Anglian Learning Technical ServicesAuthor Commented:
It turns out that an include further up the page was yielding the inclusion of a Byte Order Mark, as suggested.  This has now been dealt with and the script works fine.  Thanks for the help - much appreciated.
0
 
Julian HansenCommented:
You are welcome.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.