68

I have a CSV file on my server. If a user clicks on a link it should download, but instead it opens up in my browser window.

My code looks as follows

<a href="files/csv/example/example.csv">
    Click here to download an example of the "CSV" file
</a>

It's a normal webserver where I have all of my development work on.

I tried something like:

<a href="files/csv/example/csv.php">
    Click here to download an example of the "CSV" file
</a>

Now the contents of my csv.php file:

header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=example.csv');
header('Pragma: no-cache');

Now my issue is it's downloading, but not my CSV file. It creates a new file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Elitmiar
  • 35,072
  • 73
  • 180
  • 229

10 Answers10

115

.htaccess Solution

To brute force all CSV files on your server to download, add in your .htaccess file:

AddType application/octet-stream csv

PHP Solution

header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=example.csv');
header('Pragma: no-cache');
readfile("/path/to/yourfile.csv");
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
robjmills
  • 18,438
  • 15
  • 77
  • 121
  • 4
    Why not just change the Content-Type line to `Content-Type: application/octet-stream` ? It seems a bit redundant to do it in an .htaccess when you're overriding it anyway. – Powerlord Oct 22 '09 at 21:07
  • that was the inital answer to the question, the question then changed so the second part was the answer to that. – robjmills Oct 28 '09 at 08:34
  • This doesn't work in all browsers, with all filetypes. For example, it won't work on the latest (Aug 2013) version of Chrome with PDF files and perhaps csv files too. See my answer for how to get this working in a more reliable way. – Chris Harrison Aug 05 '13 at 14:09
  • Great Answer! I would suggest changing `'Content-Type: application/csv'` to `'Content-Type: application/octet-stream'` as suggested by @Powerlord. It will be a more well-rounded answer and will help others who are not just interested in having their CSVs downloaded. Just a suggestion! Thanks! – Govind Rai Sep 25 '16 at 06:39
  • Can you please tell me how, if it is a `zip` file?? – Prifulnath Feb 23 '17 at 08:54
  • FYI the standard HTTP `Content-Type` header for CSV is `text/csv`, not `application/csv`. – Marco Demaio Jul 02 '19 at 10:54
29

Or you can do this using HTML5. Simply with

<a href="example.csv" download>download not open it</a>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AtanuCSE
  • 8,832
  • 14
  • 74
  • 112
18

This cannot be done reliably, since it's up to the browser to decide what to do with an URL it's been asked to retrieve.

You can suggest to the browser that it should offer to "save to disk" right away by sending a Content-disposition header:

header("Content-disposition: attachment");

I'm not sure how well this is supported by various browsers. The alternative is to send a Content-type of application/octet-stream, but that is a hack (you're basically telling the browser "I'm not telling you what kind of file this is" and depending on the fact that most browsers will then offer a download dialog) and allegedly causes problems with Internet Explorer.

Read more about this in the Web Authoring FAQ.

Edit You've already switched to a PHP file to deliver the data - which is necessary to set the Content-disposition header (unless there are some arcane Apache settings that can also do this). Now all that's left to do is for that PHP file to read the contents of the CSV file and print them - the filename=example.csv in the header only suggests to the client browser what name to use for the file, it does not actually fetch the data from the file on the server.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
  • 5
    Using "Content-disposition: attachment" has worked consistently for us in all FF versions, IE6 and IE7. – Steve Claridge Sep 23 '09 at 12:31
  • Typically you'll only run into issues with content-disposition if you have a default action for downloaded files of a type set for your browser, otherwise browsers will typically follow the recommendation (for more modern versions, ymmv with old/outdated browser versions). – DavidScherer Nov 21 '18 at 17:28
13

Here is a more browser-safe solution:

    $fp = @fopen($yourfile, 'rb');

    if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
{
    header('Content-Type: "application/octet-stream"');
    header('Content-Disposition: attachment; filename="yourname.file"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header("Content-Transfer-Encoding: binary");
    header('Pragma: public');
    header("Content-Length: ".filesize($yourfile));
}
else
{
    header('Content-Type: "application/octet-stream"');
    header('Content-Disposition: attachment; filename="yourname.file"');
    header("Content-Transfer-Encoding: binary");
    header('Expires: 0');
    header('Pragma: no-cache');
    header("Content-Length: ".filesize($yourfile));
}

fpassthru($fp);
fclose($fp);
Franz
  • 11,353
  • 8
  • 48
  • 70
8

Configure your server to send the file with the media type application/octet-stream.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
2

This means that your browser can handle this file type.

If you don't like it, the easiest method would be offering ZIP files. Everyone can handle ZIP files, and they are downloadable by default.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
StampedeXV
  • 2,715
  • 2
  • 24
  • 40
2

Nice clean solution:

<?php
    header('Content-Type: application/download');
    header('Content-Disposition: attachment; filename="example.csv"');
    header("Content-Length: " . filesize("example.csv"));

    $fp = fopen("example.csv", "r");
    fpassthru($fp);
    fclose($fp);
?>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vdbuilder
  • 12,254
  • 2
  • 25
  • 29
  • 1
    What makes it nice and clean? – Peter Mortensen Mar 28 '18 at 20:14
  • 1
    I would just use readfile() From the docs `If you just want to dump the contents of a file to the output buffer, without first modifying it or seeking to a particular offset, you may want to use the readfile(), which saves you the fopen() call. ` – DavidScherer Nov 21 '18 at 17:31
1

A previous answer on this page describes how to use .htaccess to force all files of a certain type to download. However, the solution does not work with all file types across all browsers. This is a more reliable way:

<FilesMatch "\.(?i:csv)$">
  ForceType application/octet-stream
  Header set Content-Disposition attachment
</FilesMatch>

You might need to flush your browser cache to see this working correctly.

Chris Harrison
  • 5,512
  • 3
  • 28
  • 36
  • If you change the contenttype your pc will not longer know that this is a csv file, and your default csv programm is not offered for opening this file. – Radon8472 Mar 04 '22 at 10:46
1

If you are doing it with your application itself... I hope this code helps.

HTML

In href -- you have to add download_file.php along with your URL:

<a class="download" href="'/download_file.php?fileSource='+http://www.google.com/logo_small.png" target="_blank" title="YourTitle">

PHP

/* Here is the Download.php file to force download stuff */

<?php
    $fullPath = $_GET['fileSource'];
    if($fullPath) {
        $fsize = filesize($fullPath);
        $path_parts = pathinfo($fullPath);
        $ext = strtolower($path_parts["extension"]);

        switch ($ext) {
            case "pdf":
                header("Content-Disposition: attachment; filename=\"" . $path_parts["basename"]."\""); // Use 'attachment' to force a download
                header("Content-type: application/pdf"); // Add here more headers for diff. extensions
                break;

            default;
                header("Content-type: application/octet-stream");
                header("Content-Disposition: filename=\"" . $path_parts["basename"]."\"");
        }

        if($fsize) { // Checking if file size exist
            header("Content-length: $fsize");
        }
        readfile($fullPath);
        exit;
    }
?>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hussain
  • 432
  • 5
  • 7
  • 7
    This model could create a major security flaw. An attacker can use it to download your source file! Use it with caution!!!! – The Quantum Physicist Aug 16 '15 at 18:37
  • 2
    This looks like some old download script I've written years ago and you're right a hacker could try to find the location of the file. Input validation is also difficult this way. A few days ago I re-published an article with a second examples that is using a database with a hash ID to obtain the file name. It's not a perfect solution, but it points into the right redirection: http://www.web-development-blog.com/archives/php-download-file-script/ – Olaf Feb 04 '16 at 07:12
  • 1
    Talking about Null byte injection? – Didix Mar 28 '18 at 20:18
  • 1
    Despite the need for suggested additions for security, this really is the best approach IMO. I've 8used something like this for years to allow visitors to either view or download PDF file product documentation, and now that most browsers will "play" (rather than download) an MP3, MP4, or other media file, this is the most reliable way of ensuring a download. It also makes it easy to add logging capability, so you can easily see how many people have downloaded, when they did it, and even log IP addresses. Too bad this didn't receive more up-votes! @Olaf I think I've used yours before! – Randy Mar 19 '19 at 15:50
0

To force download you may use Content-Type: application/octet-stream header, which is supported by most browsers:

function downloadFile($filePath)
{
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
    header('Content-Length: ' . filesize($filePath));
    readfile($filePath);
}

A BETTER WAY

Downloading files this way is not the best idea especially for large files. PHP will require extra CPU / Memory to read and output file contents and when dealing with large files may reach time / memory limits.

A better way would be to use PHP to authenticate and grant access to a file, and actual file serving should be delegated to a web server using X-SENDFILE method (requires some web server configuration):

After configuring web server to handle X-SENDFILE, just replace readfile($filePath) with header('X-SENDFILE: ' . $filePath) and web server will take care of file serving, which will require less resources than using PHP readfile.

(For Nginx use X-Accel-Redirect header instead of X-SENDFILE)

Note: If you end up downloading empty files, it means you didn't configure your web server to handle X-SENDFILE header. Check the links above to see how to correctly configure your web server.

Tarquin
  • 482
  • 2
  • 6
  • 21
Dima L.
  • 3,443
  • 33
  • 30