I am asking this because my requests keep getting "Content Length Mismatch" on files larger than 4Mb even though the content length and the file size is exact match. If I remove the Content Length header, everything works fine.
Is there an advantage or necessity of using Content Length in the headers for inline content (not attachment)?
[EDIT] Code:
public function actionViewFile($id) {
$model = File::model()->findByPk($id);
if(!$model) {
throw new CHttpException(404, "File not found");
}
$data = file_get_contents($this->storageDir.'/'.$model->fileName);
header('Content-type: '.$model->mime_type);
header('Content-length: '.$model->size); //I have tried calling `mb_strlen($data);`
echo $data;
Yii::app()->end(); //I have tried calling `die;`
}
The framework I am using is Yii, which doesn't really matter for the context of this problem.
The Content-Length entity-header field indicates the size of the entity-body, in decimal number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a GET.
Try using filesize() as per the following:
public function actionViewFile($id) {
$model = File::model()->findByPk($id);
if(!$model) {
throw new CHttpException(404, "File not found");
}
$filepath= $this->storageDir.'/'.$model->fileName;
$data = file_get_contents($filepath);
header('Content-type: '.$model->mime_type);
header("Cache-Control: no-cache, must-revalidate");
header('Content-Length: ' . sprintf('%u', filesize($filepath)))
echo $data;
return Yii::app()->end(); //I have tried calling `die;`
}
Related
Below there is some code extracted from a file browser written in PHP.
Actually the file browsing works and all the rest too but, after a recent Google Chrome update, downloads wouldn't work only on that browser (I've not tested firefox anyway).
I've searched elsewhere, but found nothing that could help. The Interesting thing, is that the download works fine in IE, and worked before the recent update on chrome.
Maybe there are some relevant changes in standards that I should know of?
Below the relevant portion of code.
global $_ERROR;
global $_START_TIME;
// If user click the download link
if(isset($_GET['filename']))
{
// The directory of downloadable files
// This directory should be unaccessible from web
$file_dir="C:/Directory/".$this->location->getDir(false, true, false, 0);
// Replace the slash and backslash character with empty string
// The slash and backslash character can be dangerous
$file_name=str_replace("/", "", $_GET['filename']);
$file_name=str_replace("\\", "", $file_name);
// If the requested file exists
if(file_exists($file_dir.$file_name))
{
// Get the file size
$file_size=filesize($file_dir.$file_name);
// Open the file
$fh=fopen($file_dir.$file_name, "r");
// Download speed in KB/s
$speed=2048;
// Initialize the range of bytes to be transferred
$start=0;
$end=$file_size-1;
// Check HTTP_RANGE variable
if(isset($_SERVER['HTTP_RANGE']) && preg_match('/^bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $arr))
{
// Starting byte
$start=$arr[1];
if($arr[2])
{
// Ending byte
$end=$arr[2];
}
}
// Check if starting and ending byte is valid
if($start>$end || $start>=$file_size)
{
header("HTTP/1.1 416 Requested Range Not Satisfiable");
header("Content-Length: 0");
}
else
{
// For the first time download
if($start==0 && $end==$file_size)
{
// Send HTTP OK header
header("HTTP/1.1 200 OK");
}
else
{
// For resume download
// Send Partial Content header
header("HTTP/1.1 206 Partial Content");
// Send Content-Range header
header("Content-Range: bytes ".$start."-".$end."/".$file_size);
}
// Bytes left
$left=$end-$start+1;
// Send the other headers
header("Content-Type: application/octet-stream ");
header("Accept-Ranges: bytes");
// Content length should be the bytes left
header("Content-Length: ".$left);
header("Content-Disposition: attachment; filename=".$file_name);
// Read file from the given starting bytes
fseek($fh, $start);
// Loop while there are bytes left
while($left>0)
{
// Bytes to be transferred
// according to the defined speed
$bytes=$speed*1024;
// Read file per size
echo fread($fh, $bytes);
// Flush the content to client
flush();
// Substract bytes left with the tranferred bytes
$left-=$bytes;
// Delay for 1 second
sleep(1);
}
}
fclose($fh);
}
else
{
// If the requested file is not exist
// Display error message
$_ERROR = "File not found!";
}
}
This relevant portion of code will fix my own answer, this was trivial, a simple error never noticed until recent updates of one browser:
Modify
// For the first time download
if($start==0 && $end==$file_size)
{
// Send HTTP OK header
header("HTTP/1.1 200 OK");
}
into
// For the first time download
if($start==0 && ($end+1)==$file_size)
{
// Send HTTP OK header
header("HTTP/1.1 200 OK");
}
UPDATE:
I used another solution to write my data into a file. It seems that I can't echo data while AJAX is waiting for a response. So I now use fwrite.
$fileHandle = '';
$fileHandle = fopen("export.txt","w");
fwrite($fileHandle, $export);
Original:
Hi there,
maybe my logic is wrong.
I make an AJAX call to get data from another URL.
That worked so far.
But now I want to add an file export also.
$handler = new MyHandler();
// Step 1: get data from URL
$dataAjax = $handler->getData($_POST['data']);
// Step 2: write the data into a text file to provide a download
$handler->writeToText($dataAjax);
echo json_encode($dataAjax);
Now the console shows me a "parserError" because my JSON data contains also the string I wanted to write into the file. That's bad and unwanted.
This below is just a test how I want to write my data into a txt file:
function writeToText($data)
{
header("Content-type: text/plain");
header("Content-Disposition: attachment; filename=export.txt");
header("Pragma: no-cache");
header("Expires: 0");
$title = "";
$title .= "Name,Quantity,Model,Price,Weight,Status"."\n";
echo $title;
}
That is how the error looks like:
{
"readyState": 4,
"responseText": "Name,Quantity,Model,Price,Weight,Status\n[{\"domain\":\"Text\",\"name\":\"Banana\}]",
"status": 200,
"statusText": "OK"
}
parsererror
i have a zip file and user can only access the file once they complete their payment and i am using paypal payment gateway,so i have applied the condition that the download link will only be visible to user once they have completed their transaction,but my requirement id this that user must not be able to open this zip file through url,but once they complete their transaction then the user can download the file from the link given by me 1.e
Download Zip, i have hosted my website on godaddy.
<?php
session_start();
if(isset($_SESSION['item_name']) && $_SESSION['item_name']!=''){
if($_SESSION['item_name']=='Android Apps'){
?>
Download Zip
<?php }
} else{
header('Location: abc.com/rrr/form1.html');
}
?>
use the following methods in your code. this way you don't need to redirect the user to the real url of zip file. you can serve the zip from any php script e.g. your payment confirmation page can redirect to a script with a random token which expires after first download/time-
//call this method for sending download file.
function sendDL($filename)
{
header("Content-length:".filesize($filename));
header('Content-Type: application/x-gzip'); // ZIP file
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="download.gz"');
header('Content-Transfer-Encoding: binary');
ob_end_clean(); //output buffer cleanup
_readfileChunked($filename);
}
function _readfileChunked($filename, $retbytes=true) {
$chunksize = 1*(1024*1024); // how many bytes per chunk
$buffer = '';
$cnt =0;
// $handle = fopen($filename, 'rb');
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
consider these like utility methods. from any script which doesn't push any other out (ie. no echo) the first method can be used for sending a file to user. call that like this
<?php
$dlToken = $_GET['dltoken'];
$filename = '/path/to/secret.file';
//Check if this dlToken is in database/memcache or not. and if yes its expired or not.
if($yes)
{
sendDL($filename);
}
else
{
sendNack();
}
function sendNack()
{
echo '___DATA_NOT_FOUND___'; //NOTICE this is the only echo in this script. and it means we are not sending the file after all.
//header("HTTP/1.0 404 Not Found");
exit();
}
//put the two methods there
function sendDL($filename)
{
//...
}
function _readfileChunked($filename, $retbytes=true)
{
//...
}
?>
At the page/script where you give the download link. generate a random unique token. you can use uniqid or mt_rand or both. save this in database along with a timestamp value (which you can use in download script mentioned above to check if the token has expired). create a download url with that token as something like following
download.php?file=test.zip&token=the_unique_token×tamp=unix_timestamp
For better or worse, I am storing binary information in a database table and am having a problem retrieving it. Each BLOB has a newline prepended to it upon retrieval, at least, I believe it's upon retrieval, as the binary object in the table is exactly the same size as the source file.
I've searched for a similar problem to mine, and the closest I have found is this However, I am using PDO instead of mysql_* and I have checked for empty lines prior to the opening
Here's the retrieval function stored in a separate file that I'm including in my test:
(in raw.php):
function return_raw_rawid($raw_id) {
$data = array();
$aggregate_data = array();
$sql = "SELECT * FROM `raw` WHERE `raw_id` = :rawid";
try {
$db_obj = dbCore::getInstance();
$query = $db_obj->dbh->prepare($sql);
$query->bindValue(':rawid', $raw_id);
if ($query->execute()) {
while($results = $query->fetch(PDO::FETCH_ASSOC)) {
$data['raw_id'] = $results['raw_id'];
$data['filename'] = $results['filename'];
$data['mime_type'] = $results['mime_type'];
$data['file_size'] = $results['file_size'];
$data['file_data'] = $results['file_data'];
$data['test_id'] = $results['test_id'];
$data['user_id'] = $results['user_id'];
$data['time'] = date('Y-m-d H:i:s', $results['time']);
$aggregate_data[] = $data;
} // while
} // if
$query->closeCursor();
return $aggregate_data;
} catch (PDOException $ex) {
$errors[] = $ex;
} // catch
}
Here's the code I'm testing it with in a separate file:
<?php
include 'core/init.php'; // Contains protect_page() and includes for return_raw_rawid
protect_page();
$blob_id = 20;
$blob = return_raw_rawid($blob_id);
$data = ltrim($blob[0]['file_data']);
$name = ltrim($blob[0]['filename']);
$size = ltrim($blob[0]['file_size']);
$type = ltrim($blob[0]['mime_type']);
header("Content-type: $type");
header("Content-length: $size");
header("Content-disposition: attachment; filename=$name");
header("Content-Description: PHP Generated Data");
echo $data;
When I load this page in my browser, it will prompt me to download the file identified by blob_id and has the correct filename and type. However, upon downloading it and opening in ghex, I see that the first byte is '0A' Using cmp original_file downloaded_file I determine that the only difference is this first byte. Googling led me to the ltrim() function that I've (perhaps too) liberally applied above.
I can't tell for sure if this problem is not being caused during upload, though as I said before, I don't believe it is since the "file_size" value in phpmyadmin is exactly the same as the source file. I'm not sure if the use of the aggregate_data array in the retrieval function could be to blame or what.
Any help is greatly appreciated!
Are you sure those 4 header lines are being properly executed? 0x0A is the newline char. You could have a newline in your core/init.php triggering output, and the headers are never executed. With display_errors/error_reporting off, you'd never see the warnings about "headers not sent - output started at line X...".
When I generate an export to a CSV file, I have a tab that is inserted at the beginning.
I don't understand why.
Here is my code :
public function hookExportAll($params){
header('Content-Type: application/csv');
header('Content-Disposition: inline; filename="clients.csv"');
header('Cache-Control: private, max-age=0, must-revalidate');
//mysql request
$sql = "..."
$clientlist = Db::getInstance()->ExecuteS($sql);
//separator recovered the post of the form
$filter['sep']=Tools::getValue('sep');
//; or , or \t
$entete = array('email','nom','prenom','anniversaire','newsletter','adresse','pays','téléphone');
echo $this->getcsvline($entete, $sep);
foreach($clientlist AS $client){
echo $this->getcsvline($client, $sep);
}
}
private function getcsvline($list, $sep="\t"){
return implode($sep, $list)."\r\n";
}
A little help?
Finally, Ivar Bonsaksen must be right. A tab must have been generated in the CMS Prestashop before a . With no time to dwell on this case, I consider this issue as resolved.
Thank you all for your support, see you soon =)