PHP readfile() causing corrupt file downloads - php

I am using php script to provide download from my website after a requisite javascript timer this php script is included which causes the download. But the downloaded file is corrupt no matter whatever I try. Can anyone help me point out where am I going wrong.
This is my code
<?php
include "db.php";
$id = htmlspecialchars($_GET['id']);
$error = false;
$conn = mysql_connect(DB_HOST,DB_USER,DB_PASSWORD);
if(!($conn)) echo "Failed To Connect To The Database!";
else{
if(mysql_select_db(DB_NAME,$conn)){
$qry = "SELECT Link FROM downloads WHERE ID=$id";
try{
$result = mysql_query($qry);
if(mysql_num_rows($result)==1){
while($rows = mysql_fetch_array($result)){
$f=$rows['Link'];
}
//pathinfo returns an array of information
$path = pathinfo($f);
//basename say the filename+extension
$n = $path['basename'];
//NOW comes the action, this statement would say that WHATEVER output given by the script is given in form of an octet-stream, or else to make it easy an application or downloadable
header('Content-type: application/octet-stream');
header('Content-Length: ' . filesize($f));
//This would be the one to rename the file
header('Content-Disposition: attachment; filename='.$n.'');
//Finally it reads the file and prepare the output
readfile($f);
exit();
}else $error = true;
}catch(Exception $e){
$error = true;
}
if($error)
{
header("Status: 404 Not Found");
}
}
}
?>

This helped me in case of more output buffers was opened.
//NOW comes the action, this statement would say that WHATEVER output given by the script is given in form of an octet-stream, or else to make it easy an application or downloadable
header('Content-type: application/octet-stream');
header('Content-Length: ' . filesize($f));
//This would be the one to rename the file
header('Content-Disposition: attachment; filename='.$n.'');
//clean all levels of output buffering
while (ob_get_level()) {
ob_end_clean();
}
readfile($f);
exit();

First of all, as some people pointed out on the comments, remove all spaces before the opening PHP tag (<?php) on the first line and that should do the trick (unless this file is included or required by some other file).
When you print anything on the screen, even a single space, your server will send the headers along with the content to be printed (in the case, your blank spaces). To prevent this from happening, you can:
a) not print anything before you're done writing the headers;
b) run an ob_start() as the first thing in your script, write stuff, edit your headers and then ob_flush() and ob_clean() whenever you want your content to be sent to the user's browser.
In b), even if you successfully write your headers without getting an error, the spaces will corrupt your binary file. You should only be writing your binary content, not a few spaces with the binary content.
The ob_ prefix stands for Output Buffer. When calling ob_start(), you tell your application that everything you output (echo, printf, etc) should be held in memory until you explicitly tell it to 'go' (ob_flush()) to the client. That way, you hold the output along with the headers, and when you are done writing them, they will be sent just fine along with the content.

ob_start();//add this to the beginning of your code
if (file_exists($filepath) && is_readable($filepath) ) {
header('Content-Description: File Transfer');
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=$files");
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header("content-length=".filesize($filepath));
header("Content-Transfer-Encoding: binary");
/*add while (ob_get_level()) {
ob_end_clean();
} before readfile()*/
while (ob_get_level()) {
ob_end_clean();
}
flush();
readfile($filepath);

Related

writing not required html to a text file

I am writing my output to the text file and then downloading it using php but issue is that it is saving the output but it is also saving the whole structure of HTML into the textfile also. I don't know why its happening tried to solve it but did'nt figure out how.
I want to save output from fwrite($fh, $formatted_url."\n");
Below is my code:
function get_m3u8_video_segment($url,$portnum=80,$from,$to)
{
$file_name="urlscan.txt";
$fh = fopen($file_name, 'w') or die("Unable to open file!");
for ($x = $from; $x <= $to; $x++)
{
$formatted_url="{$url}:{$portnum}/s-{$x}.m3u8";
//echo "URL is: $formatted_url <br>";
//$contents = file_get_contents($formatted_url);
$contents = get_web_page( $formatted_url );
if ((strpos($contents, 'not found') !== false)||(strpos($contents, 'EXTM3U') !== false))
{
echo" $formatted_url<br>";
fwrite($fh, $formatted_url."\n");
}
}
//header download
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
header("Content-Type: application/force-download");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/plain");
}
get_m3u8_video_segment($url,$portnum,$from,$to);
}
If there is other HTML content elsewhere in your PHP script then this will also be outputted as it normally is, except in this case it will become part of the downloaded file. If you don't want that to happen then you have to stop your script with an exit(); command after you have output the content you actually want. In your script, it looks like you can probably do this just after the call to the function. (But if you have already output some HTML before this, you'll need to alter your script more substantially.)
N.B. I'm surprised you aren't getting a warning about headers being already sent? That normally happens if you try to set headers after you've already echoed some content. Check your log files. Normally you are supposed to output the headers first.
Also, unless you are wanting to keep it for some other purpose, there is no use in saving anything to urlscan.txt - it is not playing any part in your download process. And it would get overwritten every time this script is executed anyway. The headers will cause the browser to treat the output contents (i.e. anything which the PHP script sends to the regular output) as a text file - but this is not the same file as the text file on your server's disk, and its contents can be different.
You happen to be outputting similar content (via echo" $formatted_url<br>";) as you are adding to the urlscan file (via fwrite($fh, $formatted_url."\n");) and I think this may be confusing you into thinking that you're outputting the contents of urlscan.txt, but you aren't - your PHP headers are telling the browser to treat the output of your script (which would normally just go onto the browser window as a HTML page) as a file - but it's a) a new file, and b) actually isn't a file at all until it reaches the browser, it's just a response to a HTTP request. The browser turns it into a file on the client machine because of how it interprets the headers.
Another thing: the content you output needs to be in text format, not HTML, so you need to change the <br> in your echo to a \n.
Lastly, you're outputting the content-type header twice, which is nonsense. A HTTP request or response can only have one content type. In this case, text/plain is the valid MIME type, the other one is not real.
Taking into account all of the above, your code would probably be better written as:
function get_m3u8_video_segment($url, $portnum=80, $from, $to)
{
//header download
$file_name="urlscan.txt";
header("Content-Disposition: attachment; filename=\"" . $file_name . "\"");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/plain");
for ($x = $from; $x <= $to; $x++)
{
$formatted_url="{$url}:{$portnum}/s-{$x}.m3u8";
$contents = get_web_page( $formatted_url );
if ((strpos($contents, 'not found') !== false)||(strpos($contents, 'EXTM3U') !== false))
{
echo" $formatted_url\n";
}
}
}
get_m3u8_video_segment($url, $portnum, $from, $to);
exit();

View file stored in database using php on browser

I'm trying to view files (i.e: excel sheets/pdf/images) on browser that are stored in database.
I already wrote a code for downloading the files from the database and it is working but I want to display it in the browser.
Here is the code:
<?php require_once('Connections/databasestudents.php'); ?>
<?php
$id = $_GET['id']; // ID of entry you wish to view. To use this enter "view.php?id=x" where x is the entry you wish to view.
$query = "SELECT fileContent, filetype FROM file where id = $id"; //Find the file, pull the filecontents and the filetype
$result = MYSQL_QUERY($query); // run the query
if($row=mysql_fetch_row($result)) // pull the first row of the result into an array(there will only be one)
{
$data = $row[0]; // First bit is the data
$type = $row[1]; // second is the filename
Header( "Content-type: $type"); // Send the header of the approptiate file type, if it's' a image you want it to show as one :)
print $data; // Send the data.
}
else // the id was invalid
{
echo "invalid id";
}
?>
What happens is that view.php is downloaded and nothing is viewed.
Any suggestions?
According to your code, $row[1] is "the filename". The Content type header should contain the content type instead, i.e. the file mime type, for example:
header('Content-type: application/pdf');
If you want to add a filename:
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename='.$row[1]);
print $data;
Be sure $data is the content of the file, something you can take from readfile() for example.
Read more on the manual: http://php.net/manual/en/function.readfile.php
Keep in mind that while PDF and images are easily viewable by a browser, I think Excel needs some ad hoc plugin for that.
A more complete example right out of the manual, to get you a more thorough idea (not all those headers are necessary, and you should change others accordingly to your code):
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');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;

Force download file after query php mvc

I got stuck with the code left by the first programmer of the current webpage that I'm on. He create his own custom framework.
My problem is that I have created a function for file export cause there is a feature wherein I should export the query to textfile so client/user download it automatically via forcing the browser to download the generated file: heres the code of my force download.
Note: I have already search with others forums as well as at Stackoverflow but I can't find an answer. This is my current code:
if(!defined('APPLICATION_RUNNING')){
header("HTTP/1.0 404 Not Found");
die('acces denied');
}
if(defined('ANNOUNCE')){
echo "\n<!-- loaded: ".__FILE__." -->\n";
}
if(!class_exists('APP_Ajax')){
class APP_Ajax extends APP_Base_Ajax
{
function textfiles()
{
global $apptemplate, $appform, $appdb;
if(!$appform->form_valid($this->post['formid'])) // if not b\valid form
{
json_return_error(5);
}
else
{
if(!empty($this->post['form'])) //if the post of form is not empty
{
$e = array();
}
else
{
json_return_error(6);//alert if form is empty
}
if(empty($this->post['form']['rowid']))
{
json_return_error(252); // if rowid is empty then raise error
}
else
{
$rets = $this->post['form']['rowid'];
$ids = explode(',',$rets);
foreach ($ids as $kids)
{
$sql = pg_query("SELECT * from tbl_patient WHERE id='$kids'");
$info = pg_fetch_assoc($sql);
$text[] = ucwords($info['firstname']." ".$info['mi']." ".$info['lastname']).",".$info['medicalrecordnumber'].",".$info['referraldate'];
}
$output = implode( "\r\n" , $text );
$randfile = rand(12345689,999999999)."_".date('m-d-Y');
$f = fopen("templates/default/exports/".$randfile.".txt", "w");
fwrite($f, $output);
fclose($f);
$file_name = ABS_PATH.'templates/default/exports/'.$randfile.'.txt';
header('Pragma: public'); // required
header('Expires: 0');// no cache
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Last-Modified: '.gmdate ('D, d M Y H:i:s', filemtime ($file_name)).' GMT');
header('Cache-Control: private',false);
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="'.basename($file_name).'"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.filesize($file_name));
header('Connection: close');
readfile($file_name);// push it out
}
}
}
}
The code above produce no error, the file where created on the path I create but
the problem is that "No save as" or something, the browser do not download a file. When I use httpfox to view the headers, etc, etc, the result of the query is displayed on httpfox content tab, so nothings wrong with the code except that I want the browser to show the Download file(Save as dialog) so the user can save it to there Hard drives..
Note 2#: When I create a download link like Download, when I click the download link, the page returns me to the main page and no downloads at all.
Please give me a script or advice on how to work on this stuff. Thank you in advance.
Tell me if I'm missing something here.
Oops forgot to tell you, the website I'm on it do not allow external access to file for security that's why I can't link the export file it to other page for download. :)

Sending mp3 file through PHP to be used with the audio tag of HTML5 fails in the middle of the mp3

I'm trying to send my mp3 files through a PHP script in order to hide the path to the file in the HTML. Everything works perfectly fine except half way into the mp3 chrome gives me a GET [URL] error. And the rest of the mp3 is just blank. The audio tag thinks it's still reading the file, but there is no sound.
This is how I'm sending the mp3 file from php:
if (file_exists($filename)) {
header("Content-Transfer-Encoding: binary");
header("Content-Type: audio/mpeg");
header('Content-length: ' . filesize($filename));
header('Content-Disposition: inline; filename="' . $filename . '"');
header('X-Pad: avoid browser bug');
header('Cache-Control: no-cache');
readfile($filename);
exit;
}
else {
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found', true, 404);
echo "no file";
}
Edit: I added the Etag header. It seems to make things better. The problem still occurs but not as often. :-/
Edit2: I have noticed that the request headers sent to my PHP file is different from the headers sent directly to the mp3 file. Actually there's a HTTP_RANGE request sent to the mp3 file, but this range request is missing when I try to fetch things from PHP. (This is in Chrome). Any idea why this might be ?
I know this is an old post but I was able to get this working with two files and index.php and example.php.
this is example.php that's checking a databse for a hash and a used field to know if the file has been used before
<?php
if(isset($_GET['hash'])){
$mysqli = new mysqli('host_here','user_here','password_here','database_here');
$result = $mysqli->query("select * from hashes where hash = '{$_GET['hash']}' limit 1");
$row = $result->fetch_array();
}
if(isset($row['used']) && $row['used'] == 0){
$mysqli->query("update hashes set used=1 where hash = '{$_GET['hash']}'");
$filename = "example.mp3";
header("Content-Transfer-Encoding: binary");
header("Content-Type: audio/mpeg");
header('Content-length: ' . filesize($filename));
//If this is a secret filename then don't include it.
header('Content-Disposition: inline');
//Otherwise you can add it like so, in order to give the download a filename
//header('Content-Disposition: inline;filename='.$filename);
header('Cache-Control: no-cache');
readfile($filename);
exit;
}
elseif(isset($row['used']) && $row['used'] == 1){
die("you can't just download this dude");
}
and here is index.php with the audio tag that will allow playing but not downloading of the mp3.
<html>
<body>
<?php
$mysqli = new mysqli('localhost','root','','test_mp3');
$rand = mt_rand();
$hash = md5($rand);
$result = $mysqli->query("insert into hashes (hash,used) values('$hash',0)");
?>
<audio controls>
<source src="example.php?hash=<?=$hash?>" type="audio/mpeg">
</audio>
</body>
</html>
there is a database table with just a hash and a used field for this example to work

temp. download links (with codeigniter)

I was wondering how I could start generating temporarily download links based on files from a protected directory (e.g. /downloads/). These links need to be valid until someone used it 5 times or so or after a week or so, after that the link shouldn't be accessible anymore.
Any help would be appreciated.
One clever solution I've stumbled upon lately if you're using apache (or lighty) is to use mod_xsendfile (http://tn123.ath.cx/mod_xsendfile/), an apache module that uses a header to determine which file to deliver to the user.
It's very simple to install (see link above), and afterward, just include these lines in your .htaccess file:
XSendFile on
XSendFileAllowAbove on
Then in your php code, do something like this when you want the user to receive the file:
header('X-Sendfile: /var/notwebroot/files/secretfile.zip')
Apache will intercept any response with an X-Sendfile header, and instead of sending whatever content you output (you may as well return a blank page), apache will deliver the file.
This takes out all the pain of dealing with mimetypes, chunking, and miscellaneous headers.
Use a database. Every time a file is downloaded the database would be updated, as soon as a certain file has reached it's limit it can be either removed or it's access could be denied. For example:
$data = $this->some_model->get_file_info($id_of_current_file);
if ( $data->max_downloads <= 5 )
{
// Allow access to the file
}
I generally keep files outside of the website directory structure for security and request like so:
function retrive_file($file_hash)
{
$this->_redirect();
$this->db->where('file_hash', $file_hash);
$query = $this->db->get('file_uploads');
if($query->num_rows() > 0)
{
$file_info = $query->row();
if($file_info->protect == 1){
$this->_checklogin();
}
$filesize = filesize($file_info->file_path . $file_info->file_name);
$file = fopen($file_info->file_path . $file_info->file_name, "r");
// Generate the server headers
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
{
header('Content-Type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$file_info->file_name.'"');
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);
}
else
{
header('Content-Type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$file_info->file_name.'"');
header("Content-Transfer-Encoding: binary");
header('Expires: 0');
header('Pragma: no-cache');
header("Content-Length: ".$filesize);
}
if($file)
{
while(!feof($file)){
set_time_limit(0);
echo fread($file, $filesize);
flush();
ob_flush();
}
}
fclose($file);
}
}
It would be pretty trivial to add byte/request counting to this.

Categories