Piotr Chabros
Piotr Chabros

Reputation: 475

PHP readfile() returning corrupted data

In my function I am saving an image decoded from a base64 string:

function saveImage(){
    //magic...
    define('UPLOAD_DIR', '../webroot/img/');
    $base64string = str_replace('data:image/png;base64,', '', $base64string);
    $base64string = str_replace(' ', '+', $base64string);
    $data = base64_decode($base64string);
    $id = uniqid();
    $file = UPLOAD_DIR.$id.'.png';
    $success = file_put_contents($file, $data);
}

The above function works properly and the images are saved and not corrupted in the specified folder.

In the next function I am now trying to force download the image to a user:

function getChart($uniqid= null){
    if($uniqid){
        $this->layout = null;
        header("Content-type: image/png");
        header("Content-Disposition:attachment;filename='".$uniqid.".png'");
        readfile('../webroot/img/'.$uniqid.'.png');
        exit;
    } else exit;
}

Image downloaded from the server is corrupted and cant be displayed. After opening the downloaded file in a text editor I noticed that a new line character is added at the very top. After deleting the character and saving the file it opens properly and is being displayed properly.

How can I fix this?

Upvotes: 0

Views: 2967

Answers (6)

Joe
Joe

Reputation: 2601

Just use ob_clean(); before readfile()

Upvotes: 0

Alexander Langanke
Alexander Langanke

Reputation: 71

I have had this problem several times. Here's the solution that I used:

Almost always it turned out that I had forgotten to turn off unicode BOM encoding in my download.php file which adds something to the front of the downloaded file and therefore corrupts it.

So the solution is to turn off unicode BOM in download.php.

Upvotes: 0

Develoger
Develoger

Reputation: 3998

EDIT (to force download):

function getChart($uniqid= null){
    if($uniqid){

        $image = $uniqid;

        header("Cache-Control: public");
        header("Content-Description: File Transfer");
        header("Content-Disposition: attachment; filename=" . $image);
        header("Content-Type: image/jpg");
        header("Content-Transfer-Encoding: binary");

        readfile($image);

    } else exit;
}

getChart("060620121945.jpg");

Try this (just to render image):

function getChart($uniqid= null){
    if($uniqid){

        $mime_type = "image/png";
        $content = file_get_contents('../webroot/img/'.$uniqid.'.png');
        $base64   = base64_encode($content); 
        return ('data:' . $mime_type . ';base64,' . $base64);

    } else exit;
}

Upvotes: 0

hakre
hakre

Reputation: 197564

What you describe can have multiple issues that are hidden until you actually open the downloaded file.

Instead make your code more robust and check pre-conditions, here if headers have been send already and to clean any possible existing output buffer and give error if that is not possible:

function getChart ($uniqid = null) {

    if (!$uniqid) exit;

    $this->layout = null;

    if (headers_sent()) throw new Exception('Headers sent.');
    while (ob_get_level() && ob_end_clean());
    if (ob_get_level()) throw new Exception('Buffering is still active.');

    header("Content-type: image/png");
    header("Content-Disposition:attachment;filename='".$uniqid.".png'");
    readfile('../webroot/img/'.$uniqid.'.png');

    exit;
}

Upvotes: 1

Vlad Balmos
Vlad Balmos

Reputation: 3412

check if you are outputing something before calling readfile:

// add ob_start() at the very top of your script
function getChart($uniqid= null){
    echo strlen(ob_get_clean()); die(); // if it's not 0 then you are definetly echoing something

    if($uniqid){
        $this->layout = null;
        header("Content-type: image/png");
        header("Content-Disposition:attachment;filename='".$uniqid.".png'");
        readfile('../webroot/img/'.$uniqid.'.png');
        exit;
    } else exit;
}

Upvotes: 0

Bud Damyanov
Bud Damyanov

Reputation: 31829

header("Content-length: $file_size")

This header tells the browser how large the file is. Some browser need it to be able to download the file properly. Anyway it's a good manner telling how big the file is. That way anyone who download the file can predict how long the download will take.

header("Content-type: $file_type")

This header tells the browser what kind of file it tries to download.

header("Content-Disposition: attachment; filename=$file_name");

This tells the browser to save this downloaded file under the specified name. If you don't send this header the browser will try to save the file using the script's name.

BUT you need to flush buffer, with flush(); or in your case with ob_flush(); right above first exit;

Upvotes: 1

Related Questions