4

I am trying to troubleshoot an issue I am having with downloading a "zip" file from a php script. It seems that when I download the file using the following code, the downloaded file has an extra 0A09 appended to the beginning of the file, causing winzip to throw a corruption error.

<?php
$pagePermissions = 7;
require_once ('util/check.php');
require_once ('util/file_manager.php');

$file_manager = new FileManager();

if ($_SERVER['REQUEST_METHOD'] == "GET") {
if (isset($_GET['q']) && $_GET['q'] == 'logout') {
    //require_once ('util/users.php');
    //$userdata = new Userdata();
    $userdata -> kill_session();
    header("Location: download.php");
    exit ;
}

if (isset($_GET['q']) && $_GET['q'] == 'fetch') {
    if (isset($_GET['name'])) {
        @apache_setenv('no-gzip', 1); 
        header("Content-length: " . filesize('upload/' . $_GET['name'])); 
        header('Content-type: application/zip');
        //header("Content-Disposition: attachment; filename=\"{$_GET['name']}\" ");
        header("Content-Disposition: attachment; filename={$_GET['name']}");
        header('Content-Transfer-Encoding: binary');

        readfile('upload/' . $_GET['name']);
        exit();
    }
}

}
?>

Any help would be greatly appreciated, the file downloads fine through a direct link, the appended 2 bytes to the beginning of the file occurs only thorough this code. Thanks in advance

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
jimmyjambles
  • 1,631
  • 2
  • 20
  • 34
  • 2
    I hope you're not using that in a production environment, it allows a visitor to fetch _any_ file in your server that PHP has access to. – igorw Nov 07 '11 at 19:40
  • 1
    Not related to your bug, but... filter your input! Else I can provide ?name=../../../etc/passwd on the URL. – Alex Howansky Nov 07 '11 at 19:41
  • ```readfile()``` will append no of bytes read from file at the end. – Sathvik Jun 30 '21 at 13:27

6 Answers6

25

Remove the last ?> and check that your opening tag is on the very first line, at the very first character of your scripts. PHP files do not have to end with end tags. The reason why your downloaded files contain a (or more) \r\n is because PHP will directly echo (output) anything outside of <?php ?>. Usually, if you script does not echo HTML, you will omit the closing PHP tag as it is not mandatory and, IMO, yields more trouble than anything else.

** Edit **

If you read the PHP manual for readfile, you have a useful example, pretty much the code you have in your question, less two lines of code :

@apache_setenv('no-gzip', 1); 
header("Content-length: " . filesize('upload/' . $_GET['name'])); 
header('Content-type: application/zip');
//header("Content-Disposition: attachment; filename=\"{$_GET['name']}\" ");
header("Content-Disposition: attachment; filename={$_GET['name']}");
header('Content-Transfer-Encoding: binary');

// add these two lines
ob_clean();   // discard any data in the output buffer (if possible)
flush();      // flush headers (if possible)

readfile('upload/' . $_GET['name']);
exit();

If you still have a problem after that, then the problem might not be with your PHP code.

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • 3
    Also make sure the ` – Ilmari Karonen Nov 07 '11 at 19:53
  • It sounds like a white space issue, but since the readfile is before the closing ?> would white space after the readfile really be the issue? For sending binary files, I prefer to use X-Sendfile if you have access to the needed apache module. Using x-sendfile also adds some security to issues like lack of GET sanitizing calling files outside the apache web root. – Alice Wonder Nov 07 '11 at 19:56
  • yes. The function `readfile` output the file and then returns. Then if there's extra characters after the closing tags at the end of the script, everything will be echoed before closing the HTTP connection. – Yanick Rochon Nov 07 '11 at 20:04
  • But, in this question, the problem seems to come from the opening PHP tag since the extra `\r\n` is at the beginning. – Yanick Rochon Nov 07 '11 at 20:06
  • Thanks Yanick, I checked through my files and there is no whitespace to be found either at the beginning or the end of any included files. The exit() call after readfile() should take care of anything else on the same page. Any other ideas? – jimmyjambles Nov 07 '11 at 20:42
  • Thanks a lot Yanick, I suppose you were right the first time, but in the absence of actually locating the extra characters those two lines seemed to do the trick. – jimmyjambles Nov 08 '11 at 03:32
  • 2
    Awesome, ob_clean(); did it for me. – Jacob Mouka Mar 31 '18 at 21:13
  • I'm using Apache 2.4.48. I have detected that when pushing a file to the client via readfile(), if the code seats inside a require_once() include, three bytes are added to the file. These extra bytes obviously come from the buffer, as emptying it solves the issue. I have taken the time to make sure I am not adding any extra output but just a single require_once(). This could be a bug, in any case the buffer flushing helps it. – Daniel J. Sep 25 '21 at 14:47
1

Sorry for late reply.....

i don't know i am right until you vote this.....

edit your code as :

ob_start(""); 
//instead of ob_start(); with out a null callback 

and

ob_end_clean();  //at the end , Note : "important" add instead of ob_end_flush()

ie;

ob_start("");
//header           
ob_end_clean();
andrewsi
  • 10,807
  • 132
  • 35
  • 51
Nifty
  • 174
  • 5
0

I had a similar problem in Joomla (2.5), using readfile to pass back Excel (.xls) files to the user.

I also noticed that the text and xml files also had some code inserted at the begining, but nearly ignored it because xml & text readers tended to open the files still.

I decided to try Yanick's suggestions (rather than playing with server compression options), simply flushing the buffer before readfile:

ob_clean();   // discard any data in the output buffer (if possible)
flush();      // flush headers (if possible)

Hey presto, it worked. I'm suggesting this as an alternative answer: to highlight the root cause, show it can fix a Joomla issue and because I had a mixture of binary and text returns.

Just to add (apologies if this is obvious!) - the mime type setting worked fine:

$document = JFactory::getDocument();
$document->setMimeEncoding($mimetype);

I did not even need to set 'Content-Transfer-Encoding: binary' when the mime type was to application/octet-stream.

From Orbonia
  • 616
  • 5
  • 16
0

I had same problem So I used this headers and I got my solution.

    $filename = ABSPATH.'/wp-content/summary/user_content/'.trim($file);
    header('Pragma: public');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Content-Description: File Transfer');
    header('Content-Type: text/text');
    header('Content-Disposition: attachment; filename="'.$file.'"');
    header('Content-Transfer-Encoding: binary');
    header('Cache-Control: max-age=0');
    readfile($filename);
    exit;
0

I had a similar issue with blank space at the start of an image file.

I suspect my issue was caused by blank space before opening

What worked for me was:

@ob_start('');  //@ supresses a warning  
//header entries
ob_end_clean();
ob_clean();
readfile($file);
0

I ran into a similar issue today related to readfile(). It turns out my php.ini file has output compression enabled and that was messing up the flash module trying to retrieve the file. (I guess it couldn't handle it.)

All I had to do was the turn off the compression in the php.ini file:

zlib.output_compression = off

Or alternatively, in your script:

<?php ini_set('zlib.output_compression', 'Off'); ?> 

Just want to share this in case someone else was having trouble receiving files from a readfile() output.

tchen
  • 2,212
  • 1
  • 16
  • 18