Generate header dynamically to display requested file - php

I have a folder with many files of different types. I need to display any requested file, so the header has to be adapted accordingly. What I tried is:
header( 'Content-Type: ' . filetype( 'files/' . $file ) );
readfile( 'files/' . $file );
It works for JPG, PNG and GIF files, but for some reason MP3 files already crash the browser, so I suppose it doesn't work for every file type. Is there something wrong with the approach, or the code? How would you do it? Thanks!

By the PHP Documentation, filetype() Returns the type of the file. Possible values are fifo, char, dir, block, link, file, socket and unknown.
What you want is the MIME type of the file. What you want is Fileinfo. For example:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
header("Content-Type: ".finfo_file($finfo, 'files/'.$file));
finfo_close($finfo);
readfile('files/'.$file);
You can also quickly try mime_content_type, if available, just to make sure there is nothing else wrong with the system. But this is not recommended because it is depricated.

You have to send the correct mime type and that can be tricky.
You can use the mime-content-type function, but that is a deprecated function.
$mimetype = mime_content_type ( $file );
header( 'Content-type: '. $mimetype );

use this for mp3 files
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);

Related

(PHP) Problem with filename after downloading it

While writing a PHP-Script, im stuck at an issue i cant resolve.
The PHP-Script consist in letting a user download a .mp4 file. The download works without any issues but the file downloaded can not be played.
Heres the code:
<?php
$filepath = "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header("Content-type: video/mp4");
header("Content-Disposition: attachment; filename=.$filename");
readfile($filename);
exit;
?>
After the .mp4 file gets downloaded, it cannot be played.
It looks like this:
The error message means that Windows Media Player cannot play back the file because probably the player doesnt support the codec. I already tried with VLC but it does not work either.
EDIT:
Comparing both file sizes, the downloaded file is only a couple bytes large instead of the 3,73 MB of the file on the server
Your code is not well-formed, you miss to escape double-quotes by adding single-quotes as I done here, please test my answer.
<?php
$filepath = "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header('Content-Type: video/mp4');
header('Content-Disposition: attachment; filename="' . $filename . '"');
readfile($filename);
exit;
?>
But I suggest a more complex way:
<?php
$filepath = $_SERVER['DOCUMENT_ROOT'] . "/www/servermedia/technounion.mp4";
$filename = basename($filepath);
header('Content-Type: video/mp4');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . sprintf("%u", filesize($filepath)));
set_time_limit(0);
$fh = fopen($filepath, "rb");
while (!feof($fh)) {
echo fgets($fh);
ob_flush();
flush();
}
fclose($fh);
exit;
?>
$_SERVER['DOCUMENT_ROOT'] is useful to get the full path from the server
set_time_limit(0) is useful to avoid any timeout during download
fgets() is useful for reading large files
ob_flush() and flush() assure that there is not other output in the buffer
I hope this helps.
Is the downloaded file the exact same filesize?
Does the content type exist in your webserver?
header("Content-Type: video/mp4"); Note capital 'T' for type.
This maybe worth testing with to see you can serve the file content inline:
http://www.phpmind.com/blog/2016/10/how-to-use-php-to-output-an-mp4-video/

readfile() function read the zip file instead of Downloading it (Zend)

I have to trigger a download of a zip file ( The Zip file is inside my data folder).
For this i am using the code,
$file = 'D:\php7\htdocs\Project\trunk\api\data\file.zip';
header('Content-Description: File Transfer');
header('Content-type: application/zip');
header('Content-disposition: attachment; filename=' . basename($file) );
readfile($file);`
This is working in core php as i expected. But when i am using the same code in the Zend prints a content like below,
PKYsVJ)~�� study.xlsPKYsVJs�����+
tutorial-point-Export.xlsPKYsVJn��� 8��Zabc.xlsP
In between the content i can see the name of all files in the zip. But it is not getting downloaded.
After i realised that this is not working i started searching about it and Found some solution from stack over flow
Try 1: Adding different header element and ob functions in every random lines
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $file_size);
ob_start();
ob_clean();
flush();
All these are tried from different stack overflow Question and answers and have the same result
Try 2:PHP is reading file instead of downloading . This question do not have any accepted answer (He was asking about the core php but i have the same issue with zend only) . I tried all of this but it was not working.
Try 3:Changing the .htaccess . After that i thought it was a problem with my .htaccess and found this answer for changing the .htaccess file.
<FilesMatch "\.(?i:zip)$">
ForceType application/octet-stream
Header set Content-Disposition attachment
</FilesMatch>
This also given me the same result.
Try 4:Using download functions in Zend . I have tried the all the zend functions in the answer of this question. But given me an empty output even the file was not read.
Try 5: Remove all the unwanted spaces before and after the php tag as per the answer
Is there any other way to trigger a download in ZF2 framework?
EDIT
Below is my exact function. This is GET(API) function,
public function getList(){
try{
//here i am getting the zip file name.
$exportFile = $this->getRequest()->getQuery('exportid','');
$file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
header('Content-Description: File Transfer');
header('Content-type: application/zip');
header('Content-disposition: attachment; filename=' . basename($file) );
readfile($file);
return new JsonModel(["status"=>"Success"]);
} catch(\Exception $e){
return new JsonModel(["status"=>"Failed"]);
}
}
There are two problems here:
your browser trying to open the file, instead of downloading it.
also, it is not opening the file correctly.
Both point to a Content-Type error. Verify that the Content-Type being received by the browser is correct (instead of being rewritten as, say, text/html).
If it is, change it to application/x-download. This might not work in Internet Explorer, which performs some aggressive Content-Type sniffing. You might try adding a nosniff directive.
Additionally, after a readfile (and you might be forced to return the file's contents instead of readfile()'ing - i.e., return file_get_contents($filename);), you should stop all output with return null;. ZIP file directory is at the very end, so if you attach a JSON message there, you risk the browser neither downloading the file, nor displaying it correctly.
As a last resort, you can go nuclear and do everything yourself. Extremely non-elegant, and all frameworks ought to provide an alternative, but just in case...
// Stop *all* buffering
while (ob_get_level()) {
ob_end_clean();
}
// Set headers using PHP functions instead of Response
header('Content-Type: application/x-download');
header('X-Content-Type-Options: nosniff');
header('Content-Length: ' . filesize($filename));
header('Content-Disposition: attachment; filename="whatever.zip"');
die(readfile($filename));
It's possible that some creative use of atexit handlers or destructor hooks might mess up even this last option, but I feel it's unlikely.
Based on this SO answer, you can try the following modification to your function.
public function getList(){
try{
//here i am getting the zip file name.
$exportFile = $this->getRequest()->getQuery('exportid','');
$file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
if (file_exists($file)) {
$response = new \Zend\Http\Response\Stream();
$response->setStream(fopen($file, 'r'));
$response->setStatusCode(200);
$response->setStreamName(basename($file));
$headers = new \Zend\Http\Headers();
$headers->addHeaders(array(
'Content-Description' => 'File Transfer',
'Content-Disposition' => 'attachment; filename="' . basename($file) .'"',
'Content-Type' => 'application/zip',
'Content-Length' => filesize($file)
));
$response->setHeaders($headers);
return $response;
//return new JsonModel(["status"=>"Success"]);
} else {
return new JsonModel(["status"=>"Failed. No such file in \"".$file."\""]);
}
} catch(\Exception $e){
return new JsonModel(["status"=>"Failed"]);
}
}
This worked for me!
ob_clean(); // Clear any previously written headers in the output buffer
$filepath = "some_file.zip";
$content_type = 'application/octet_stream';
$filetype = filetype($filepath);
$filename =$filepath;
if($filetype=='application/zip')
{
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
$fp = #fopen($filepath, 'rb');
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
{
header('Content-Type: '.$content_type);
header('Content-Disposition: attachment; filename="'.$filename.'"');
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(trim($filepath)));
}
else
{
header('Content-Type: '.$content_type);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary");
header('Expires: 0');
header('Pragma: no-cache');
header("Content-Length: ".filesize(trim($filepath)));
}
fpassthru($fp);
fclose($fp);
}
If you correct the capitalisation of the headers does it work? ie use Content-Disposition and Content-Type over Content-disposition and Content-type respectively?
Regardless, as standard debugging technique I would suggest using your browser dev tools to inspect the requests that are being made (inc headers) and comparing that to what ends up in your serverside code, and what is in the server side response and what ends up in the client. I would also validate this using a private-session (Incognito mode in Chrome etc) or a fresh profile / VM install just to eliminate anything else.
Also, why not use xsendfile and delegate the responsibility of sending the file to the web server so you aren't incurring the responsibility in your PHP code? You can do this with appropriate server configuration (sometimes through .htaccess, but in this day and age surely you have complete control anyway) and then simply setting the X-Sendfile header as per the example on the above link:
header("X-Sendfile: $path_to_somefile");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$somefile\"");
Because you are return JsonModel so your output will be a json with your message instead of buffering for downloading.
Edit: I notice that you was missing Content-Transfer-Encoding: Binary, tested on my os x - php5.6 env.
You should try this
public function getList(){
try{
//here i am getting the zip file name.
$exportFile = $this->getRequest()->getQuery('exportid','');
$file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
header('Content-Description: File Transfer');
header('Content-type: application/zip');
header('Content-disposition: attachment; filename=' . basename($file));
header("Content-Transfer-Encoding: Binary");
header("Content-length: " . filesize($file));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$file");
} catch(\Exception $e){
return new JsonModel(["status"=>"Failed"]);
}
}
Just remove your JSonModel on response.
You can try this for downloading the file instead of readfile();
Server side -
file_put_contents("file.zip", fopen("http://someurl/file.zip", 'r'));
Client side -
<button>download file</button>
download file

javascript - Default filename when saving from html2canvas or save as dialogue?

A friend of mine configured h2ml2canvas for me as I don't understand javascript. When saving using h2ml2canvas it generates a random filename e.g.
df0e604b2962492165eb8f2b31578171
Is there a way to specify a filename prefix? e.g. soccer then generate a random 3-4 digit number? Alternatively is there a way to open a save as dialogue instead of downloading an image on click? My download.php file.
<?php
$file = trim($_GET['path']);
// force user to download the image
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: image/png');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
unlink($file);
exit;
}
else {
echo "error not found";
}
?>
The filename in your case is actually generated (or not) by the PHP server-side, not the JavaScript you've quoted. When it returns the data to send back, it's including a Content-Disposition header, probably one that looks like this:
Content-Disposition: attachment
It's possible to suggest a filename to the browser by adding to that header:
Content-Disposition: attachment; filename=soccer123.xyz
In the PHP somewhere, you should find:
header("Content-Disposition", "attachment");
or similar. You can change it to:
header("Content-Disposition", "attachment; filename=soccer-" . rand(100,999) . ".xyz");
(Probably best to make the .xyz an appropriate extension for the type of image, e.g. .png or .jpg...)
Re your edit, you can replace:
header('Content-Disposition: attachment; filename='.basename($file));
with
header('Content-Disposition: attachment; filename=soccer-'.rand(100,999).'.xyz');
again you'll want a correct extension instead of .xyz.

serving .docx files through my webserver are corrupt when opened

i though i found the answer here:
Serving .docx files through Php
But i am still getting the error that the file is corrupt when trying to download and open a docx server via php
Maybe you can see something wrong with my code. The .doc works fine it is the docx that fail.
$parts = pathinfo($doc);
$docFile = $userDocRoot.$doc;
if ( !file_exists($docFile) ){
throw new Exception("Can not find ".$parts ['basename']." on server");
}
if ( $parts['extension'] == 'docx' ){
header('Content-type: application/vnd.openxmlformats- officedocument.wordprocessingml.document');
header('Content-Disposition: attachment; filename="'.$parts['basename'].'"');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
ob_clean();
flush();
readfile($docFile);
}else{
header('Content-type: application/msword');
header('Content-Disposition: attachment; filename="'.$parts['basename'].'"');
readfile($docFile);
}
The solution for me was to add
$fsize = filesize($docFile);
header("Content-Length: ".$fsize);
Thanks for everyones help
There were a few extra spaces in your code which would cause it to fail.
Try using this code:
$parts = pathinfo($doc);
$docFile = $userDocRoot . $doc;
if(!file_exists($docFile)){
throw new Exception('Can not find ' . $parts['basename'] . ' on server');
}
if($parts['extension'] == 'docx') {
header('Content-type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
header('Content-Disposition: attachment; filename="' . $parts['basename'] . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
ob_clean();
flush();
readfile($docFile);
} else {
header('Content-type: application/msword');
header('Content-Disposition: attachment; filename="' . $parts['basename'] . '"');
readfile($docFile);
}
If it still doesn't work, try commenting out the header and the readfile lines, then you will see if there are any errors.
Also, I suggest that you check the filenames against a whitelist, so that people can't download PHP files with passwords in them, etc.
I have just spent a while looking at why my DOCX files are being corrupted and stumbled across this... but I have also found the answer elsewhere...
$fsize = filesize($docFile);
header("Content-Length: ".$fsize);
This gave me the tools to look for... and the key is that filesize() needs the basename of the file to get an accurate file size!
Adapting my code:
header("Content-Length: ".filesize(basename($file)));
This now offers DOCX (I have set the Content-type to "application/vnd.openxmlformats-officedocument.wordprocessingml.document") as intended and I do not have to "repair" the document like others have reported... (I also found that repairing worked)
Here is a code that's working for me (after about 5 hours of messing around):
// headers to send your file
header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
header("Content-Length: " . filesize($original_file));
header('Content-Disposition: attachment; filename="' . $new_filename . '"');
ob_clean();
flush();
readfile($original_file);
exit;
I hope it helps :)
I had the same issue.
The reason was, that somewhere in my php-file two spaces were hidden.
Removing them fixed the issue.
Add "//" in front of the header and readfile-statements
Write echo "test"; after the readfile-statement.
Then look in the HTML source-code, if there are spaces in front of
the "test".

How to Automatically Start a Download in PHP?

What code do you need to add in PHP to automatically have the browser download a file to the local machine when a link is visited?
I am specifically thinking of functionality similar to that of download sites that prompt the user to save a file to disk once you click on the name of the software?
Send the following headers before outputting the file:
header("Content-Disposition: attachment; filename=\"" . basename($File) . "\"");
header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($File));
header("Connection: close");
#grom: Interesting about the 'application/octet-stream' MIME type. I wasn't aware of that, have always just used 'application/force-download' :)
Here is an example of sending back a pdf.
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Transfer-Encoding: binary');
readfile($filename);
#Swish I didn't find application/force-download content type to do anything different (tested in IE and Firefox). Is there a reason for not sending back the actual MIME type?
Also in the PHP manual Hayley Watson posted:
If you wish to force a file to be downloaded and saved, instead of being rendered, remember that there is no such MIME type as "application/force-download". The correct type to use in this situation is "application/octet-stream", and using anything else is merely relying on the fact that clients are supposed to ignore unrecognised MIME types and use "application/octet-stream" instead (reference: Sections 4.1.4 and 4.5.1 of RFC 2046).
Also according IANA there is no registered application/force-download type.
A clean example.
<?php
header('Content-Type: application/download');
header('Content-Disposition: attachment; filename="example.txt"');
header("Content-Length: " . filesize("example.txt"));
$fp = fopen("example.txt", "r");
fpassthru($fp);
fclose($fp);
?>
None of above worked for me!
Working on 2021 for WordPress and PHP:
<?php
$file = ABSPATH . 'pdf.pdf'; // Where ABSPATH is the absolute server path, not url
//echo $file; //Be sure you are echoing the absolute path and file name
$filename = 'Custom file name for the.pdf'; /* Note: Always use .pdf at the end. */
header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
header('Accept-Ranges: bytes');
#readfile($file);
Thanks to: https://qastack.mx/programming/4679756/show-a-pdf-files-in-users-browser-via-php-perl
my code works for txt,doc,docx,pdf,ppt,pptx,jpg,png,zip extensions and I think its better to use the actual MIME types explicitly.
$file_name = "a.txt";
// extracting the extension:
$ext = substr($file_name, strpos($file_name,'.')+1);
header('Content-disposition: attachment; filename='.$file_name);
if(strtolower($ext) == "txt")
{
header('Content-type: text/plain'); // works for txt only
}
else
{
header('Content-type: application/'.$ext); // works for all extensions except txt
}
readfile($decrypted_file_path);

Categories