php web site displays file instead of downloading

Viewed 211

I am running a php 8 website on linux web app service in Azure. When the user presses a button I want to create a file on the fly (read data from db) and download it. Instead it is displaying contents on the page. Here is the code

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Type: text/csv; charset=utf-8');
header("Content-Disposition: attachment; filename=geodata.csv");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize("geodata.csv"));

$file = fopen('php://output', 'w') 
            or die ("Unable to create file in write mode");
if (ob_get_contents()) ob_end_clean();
    fputcsv($output, array('name','loc','zip'), chr(6));
    $mrows = mysqli_query($myconnection, "SELECT name,loc,zip FROM geo_tbl WHERE sp = 'FL'");
    while ($mrow = mysqli_fetch_assoc($mrows)) {
        fputcsv($file, $mrow, chr(6));
    }
    fclose($file);
    readfile("geodata.csv");
    exit();

Thanks

3 Answers

I see several issues with the code:

  1. As Alan as already pointed out, you have duplicate Content-Type headers. If you are saving the file as an attachment, you can really use either one.

  2. You issue your die message after you have already set the Content-Type header for a csv file so the message would not be displayed correctly. You should therefore do the fopen statement before setting the content header.

  3. You are sending part of your output to variable $output instead of $file with the statement fputcsv($output, array('name','loc','zip'), chr(6)); and that variable does not seem to be defined.

  4. You are setting a Content-Length header with header('Content-Length: ' . filesize("geodata.csv")); but at this point you have no idea what the content length is.

  5. You should put double-quotes around your filename in the Content-Disposition header.

Although not errors, per se, you are retrieving the rows with fetch_assoc so $mrow will contain keys and values. You really only need to be using fetch_row. I also question your use of chr(6), which is an ACK character, as a column delimiter. Did you possibly mean chr(9), a TAB?

I believe the following is all you need:

<?php

//define (DELIMITER, chr(9)); // Shouldn't it be this?
define (DELIMITER, chr(6));

if (ob_get_contents()) {
    ob_end_clean();
}
$file = fopen('php://output', 'w') or die ("Unable to create file in write mode");

header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="geodata.csv"');
header('Pragma: no-cache');
header('Expires: 0');

fputcsv($file, array('name','loc','zip'), DELIMITER);
$mrows = mysqli_query($myconnection, "SELECT name,loc,zip FROM geo_tbl WHERE sp = 'FL'");
while ($mrow = mysqli_fetch_row($mrows)) {
    fputcsv($file, $mrow, DELIMITER);
}
fclose($file);

As others have said, there are several issues with your code.

But if you just remove the second content-type header and keep it as application/octet-stream, this should force a download.

However, your content-length will not be reported correctly. Perhaps read the contents into a variable, then take the length of that string as the content-length header.

Related