I'm at a loss with an annoying issue to do with exporting a report. Basically, a button is pressed and a report is generated server side using the following javascript:-
__callExportController(true, { op: 'build', type: exportType }, function(data) {
var outputURL = './reportinc/export_controller.php?op=output&filename=';
var reportFilename = data['filename'];
var reportTitle = data['title'];
if (reportFilename && reportTitle) {
var resultURL = outputURL + reportFilename + '&title=' + reportTitle;
/* Initiate the download dialog */
if (!$('#exportFrame').length) {
var hiddenIFrame = document.createElement('iframe');
hiddenIFrame.setAttribute('id','exportFrame');
document.body.appendChild(hiddenIFrame);
}
$('#exportFrame').attr('src', resultURL);
} else {
error('No filename or report title specified!');
}
});
The 'build' operation of the export controller builds the report to a temporary file on the server. If that succeeds, the 'output' operation is called to output that file to a hidden iframe in order to get the download prompt to the user. Internet Explorer 6/7 are the only browsers in use here.
This is the output handler on the server which the iframe will be requesting with the successfully built filename:-
/* Output handler */
case 'output':{
$filename = $_GET['filename'];
header('Content-Description: File Transfer');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Type: application/pdf");
/**
* NOTE: It appears this is required for some versions of adobe!
* http://www.acrobatusers.com/forums/aucbb/viewtopic.php?id=15400
*/
header("Cache-Control: private");
header("Pragma: cache");
header("Content-Disposition: attachment; filename=\"file.pdf\"");
header('Content-Length: ' . filesize($filename));
/* Flush the headers immediately for larger files */
ob_clean();
flush();
readfile($filename);
#unlink($filename);
}
The issue I'm having is: whilst this works fine once, the session appears to be destroyed after the first successful file download. That is, when the user navigates away to another page they appear to be generated a new session id. This also requires the user to have to 're-login' if basic authentication is in use with the next action they take.
The issue seems very intermittent and it seems to happen at times and not at other times.
Has anyone any ideas? Should I be adding more headers or something to prevent the users session from being destroyed?
if the top level domain of the iframe is not the equal the Ie will revert to the p3p protocol and deletes the session. adding a header will fix this issue.
http://weblogs.asp.net/coltk/archive/2010/10/13/session-lost-in-iframe-p3p-issue.aspx
could be your missing a session_name or session_start somewhere.
or, more likely, you’re users have cookies disabled. that way sessions are only valid for one site request
Related
I'm running the following code to download a file in the backround instead of just redirecting to the file in the browser, but I can't click on anything else on the page until the whole file is downloaded. Anything I can change in the code to fix that?
When clicking on a link this is loaded in a separate php file, so the user doesn't see where the file is fetched from ($filepath) and it's also protected by a session:
header("Cache-control: private");
header("Content-Type: application/octet-stream");
if ($download) {
header("Content-Disposition: attachment; filename=\"" . $title . "\"");
header('Content-Transfer-Encoding: binary');
} else {
header("Content-Disposition: inline; filename=\"" . $title . "\"");
}
// Disable caching
header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.
header('Expires: 0'); // Proxies.
readfile($filepath);
Edit // I still haven't been able to figure this out. Now I'm trying with this AJAX code but it still locks the whole page until the file is fully downloaded and it only loads in the background, not to the harddrive.
$('.download').on('click', function(e) {
e.preventDefault();
var self = $(this);
$.ajax({ type: 'POST',
url: self.attr('href'),
async: true,
success : function(response)
{
}
})
});
Disclaimer: all below is valid for the default file-based sessions. For other handlers it may not stay true.
It's sessions that block: as soon as one page has the session open no other page for the same session id would be served.
The solution would be to close the session as soon as you're done with it.
So put
session_write_close();
right before you started serving your file.
It would close the session for the page that serves that file and other pages can be served after that, even though the file is not downloaded fully yet.
References:
http://php.net/manual/en/function.session-write-close.php
I have a really weird problem regarding a small piece of code in a CodeIgniter application. Basically, there's a page with links to various PDF files. When a user clicks on the link, the request is parsed by PHP, an observer is notified, writing the click event in the database (activity log), and then the file is outputted by using readfile().
So far, so good. Tested it, it works like a charm. The PDF is outputted for download, and the event is written in the database as it should.
The problem comes when a user clicks on such link, then cancels the download and clicks on another link no later than 9-10 seconds. When that happens, the event is registered in the database twice.
I did triple check of the observers that record the event, but they appear to be fine. Besides, there's a similar function for a video links, only it redirects to another page instead of outputting the file directly, and it works just fine.
After a few hours of scratching my head, I figured there's an issue with the readfile() function, because, if I put a var_dump();die(); or anything that outputs some text before the download and force it to come as text, the download event is recorded only once.
Here's the code in question:
public function downloadPDF($id = NULL)
{
if (($id == NULL) OR (!$this->validateId($id))) {
// redirect with error
}
$item = // code for fetching the PDF properties from the DB
$this->notify('ActivityObserver'); // writes the download event in the DB
$file = '.' . urldecode($item['link']);
$size = filesize($file);
$name = urldecode(basename($file));
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header("Content-Disposition: attachment; filename=\"$name\"");
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));
readfile($file);
exit();
}
Tried to test it with different browsers, the behaviour is the same. All inspector tools show only 1 request being made on click.
What am I missing in this big ugly picture? Why could it sometimes write twice instead of only once?
Thanks for your time to read this wall of text.
I'm building a PHP application, one section of which will export an Excel file when a user submits the last of three pages of HTML forms. This takes a while to process, so on form submit I'm bringing up a "Please Wait" popup with JavaScript prior to processing beginning. The file is then created and downloaded on the users machine by setting the headers below
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $fileName . '.xls' .'"');
header('Cache-Control: max-age=0');
The problem I'm having, is that I then need to redirect the user back to the first page of the form. I can't echo a javascript location.href call, as the content-type has already been changed before this, so it never makes it to screen. Neither can I use a standard PHP header('Location: x') redirect for the same reason.
My question is, after diverting output to a file in the way above, how can I then either get the output back to screen to echo a JavaScript redirect, or redirect the user to a new page in some other way?
As always, any help is much appreciated.
James
Try this code
<?php
echo "<script>
window.location='page.php';
</script>";
?>
As described above there's no way to send new headers after the page has loaded, but there is a way to get the functionality I wanted.
The answer was to create an AJAX call, running in a setInterval loop which looks for a $_SESSION variable in the PHP. This session variable is set after the Excel file is created (where I was previously trying to place the redirect), causing the AJAX function to return success, and then perform a location.href redirect to the correct page.
the below code can do it. it can redirect you last page from where the download request is sent no need to echo content only readfile can do it so the page will automatically unload after download box appear.
$path is the path of your file to make user download
header("Content-type: application/vnd.ms-excel");
header('Content-Disposition: attachment; filename="'.$path.'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header("Cache-Control: public");
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
This is question about downloading files from server using php script. When user clicks on download link, it processed to download.php file, and using header it started downloading.
After downloading file there is one function in download.php file, which updates mysql database about file downloaded and deduct money from user's account. Everything works fine.
Now, the problem occurs when user have download manager installed in pc. Sometimes download started in browser and download manager both. So, at the end there are two download entries in database, And money deducted from user's account two times.
Question : Is there any way to start only one download at time? Or any other way to do this thing?
Download link I provide to user.
<a href='download.php?id=1'>Download Test Video</a>
The script I am using for downloading file. (download.php)
$file = "c:/test.avi"; // $file = $_GET['id'];
$title = "Test Video";
header("Pragma: public");
header('Content-disposition: attachment; filename='.$title);
header("Content-type: ".mime_content_type($file));
header("Content-Length: " . filesize($file) ."; ");
header('Content-Transfer-Encoding: binary');
ob_clean();
flush();
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
if (filesize($file) > $chunksize) {
$handle = fopen($file, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
}
fclose($handle);
} else {
readfile($file);
}
record_download('user id', 'file id');
Moving the comment down here for anyone interested.
The function that I had to come up with was a unique download link after a payment had been processed. These are the steps that were taken.
Process the Payment and capture IP address, file and path of the downloadable file - save this information to the database.
Once payment has been deducted successfully, trigger a function that generates a unique token, e.g: sha1(microtime().$transactionid), and save this to the database (note: please don't use microtime() in production, use a random string generator).
Using .htaccess we generated a download link, e.g.: http://domain.com/download/<token> the .htaccess contents:
RewriteRule ^download/([a-z0-9-]) /download.php?token=$1
Optional: If their IP matches what we have in the database, go ahead and allow the user to download the file. If it doesn't, we ask the user to log in so we can update their IP address and begin downloading.
Once you have the token, you can pretty much do any form of validation you would like from here, such as preventing multiple downloads by adding a column in the database download_downloaded INT(1) DEFAULT 0 where if it is set to 1, then it has been downloaded. I would suggest giving the user about a day before locking them out after downloading, just in case their file was corrupt in the process.
Any other additional items, such as download counter etc.
Finally use your code above after to start the download. I would have it structured a little differently though.
download.php
$token = $_GET['token'];
$allow_download = FALSE; // Can never be too careful..
...
//Database lookup to get the file ID
...
$file = "c:/test.avi"; // now from the database call
$title = "Test Video";
// Do any additional validation here
// returns TRUE or FALSE (Boolean) -- Custom function to query the database
$allow_download = check_if_downloadable('user_id', 'file_id', 'token_id');
record_download('user id', 'file id');
// After validation is complete, allow them to download
if ($allow_download === TRUE){
header("Pragma: public");
...
If a user lost their download link (as it has happened many times), we show their download link on their member home page once they have logged in, and they can start downloading again (if you allow it).
I hope this helps. I'm sorry that I can't give out some code examples at this time.
Most of this is just querying the database.
Sometime in our website , need a download link. When click this link then any type of file,image, pdf will be download.You can do this using a simple php script.
want to download a picture name “shafiq_photo.jpg” so parameter is file name “shafiq_photo.jpg”.
Then Create a php file name “download.php” which you use in above file.
<?php
$file = $_GET["file"];
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Type: application/force-download");
header('Content-Disposition: attachment; filename=' . urlencode(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);
exit;
}
?>
I would recommend to alter the logic:
Deduct money first.
Generate a random string. Save file and user info with it.
In the client profile dump a download link with the random identifier. I mean http://www.example.com/download.php?file=254fd1f5df4df2sd5fsd5f4sdfsd0fsdf5sd4fsdf5dfsdf
Onload complete remove the database entry.
But there is a security hole!
If two people start download that file with that link, then they can bypass payment for one.
But what if someone download that file and send it to other. So, that isn't so much problem. isn't it?
But there is a option attach CSRF token if you think you should really safe.
So, IE7 keeps logging my clients out if they try to download files from my website. The files are downloaded indirectly with a download script which uses something like:
private static function SendHeaders($theFileNameServer=NULL, $theFileNameClient, $theMimeType, $theSize)
{
$aBegin = 0;
$aEnd = $theSize;
// fix for IE catching or PHP bug issue
header('Pragma: public');
header('Expires: 0'); // set expiration time
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
// browser must download file from server instead of cache
// force download dialog
header('Content-Type: application/force-download');
header('Content-Type: application/octet-stream');
header('Content-Type: application/download');
// use the Content-Disposition header to supply a recommended filename and
// force the browser to display the save dialog.
header('Content-Disposition: attachment; filename='.$theFileNameClient.';');
/*
The Content-transfer-encoding header should be binary, since the file will be read
directly from the disk and the raw bytes passed to the downloading computer.
The Content-length header is useful to set for downloads. The browser will be able to
show a progress meter as a file downloads. The content-lenght can be determines by
filesize function returns the size of a file.
*/
header('Content-Transfer-Encoding: binary');
header('Content-Length:'.($aEnd-$aBegin));
self::FlushX();
}
private static function FlushX(){
// check that buffer is actually set before flushing
if (ob_get_length()){
#ob_flush();
#flush();
#ob_end_flush();
}
#ob_start();
}
So, it seems the cache headers are somehow confusing IE7 and making it dump all cookies or something.
Any workaround for this problem? My login system works great in Firefox, Chrome etc.