i have created this application which creates a csv file from given list from DB and than allows user to download it! When i finished the job i came to realise that there is a security issues!
Here is the ajas i am using to make a request:
$.ajax({
type: "POST",
url:HTTPS + '/lib/model/data/ctlRates.php?export=1',
data: {
ext: ext,
filter: filter,
fileName: fileName
},
dataType: 'json',
success: function(data){
if(data['results'] == "success"){
console.log(data['results']);
var filename = data['filename'];
var extension = data['ext'];
window.location = HTTPS + '/lib/model/data/ctlRates.php download=1&filename='+filename+'&ext='+extension;
} else {
console.log(data['results']);
}
}
});
} else {
alert("Error: File name and extension must be provided");
}
});
And i imagine that most of you already sees the problem!
but here is the controller to which window.location is pointing:
if(isset($_GET['download']) && $_GET['download'] == 1){
$filePath = "/path/to/file/dir";
$name = $_GET['filename'];
$extension = $_GET['ext'];
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header('Content-Description: File Transfer');
header('Content-type: text/'.$extension.'');
header('Content-Disposition: attachment; filename="'.$name.".".$extension.'');
header("Content-Transfer-Encoding: binary");
header("Pragma: public");
ob_clean();
flush();
readfile($filePath.$name.".".$extension);
}
Started testing and came to realize that everyone can ender filename and extension they pleases and get files from that directory....Any ideas how to burry this hole?
And i imagine that most of you already sees the problem!
Not really - there are very few security issues i javascript - and this does not exhibit any of them - the problem is how you are procesing your data server-side. Do not attempt to server security holes with javascript changes.
If you want to restrict access to a specific directory tree...
if(isset($_GET['download']) && $_GET['download'] == 1){
$filePath = "/path/to/file/dir";
$fetchfrom = realpath($filepath . '/'
. /* basename( */ $name /* ) */
. "." . $extension);
if (substr($fetchfrom, 0, strlen($filepath))!=$filepath) {
header("HTTP/1.0 404 Not Found");
print "Directory traversal not allowed";
exit;
}
Related
My problem is: I am using a PHP script for zipping a folder and downloading it. But I am doing it by AJAX-jQuery code. So when I click on a button to generate a ZIP package, it won't work. But when I enabled IDM (Internet Download Manager), then it is downloaded. But after disabling the IDM ZIP file created but can not be downloaded. I have attached the AJAX response recorded on Chrome console.
This is my PHP function:
public function createZip($files, $zip_file_name) {
$valid_files = array();
if(is_array($files)) {
foreach($files as $file) {
if(file_exists($file)) {
$valid_files[] = $file;
}
}
}
if(count($valid_files > 0)){
$zip = new ZipArchive();
$zip_name = $zip_file_name.".zip";
if($zip->open($zip_name, ZIPARCHIVE::CREATE)!==TRUE){
$error .= "* Sorry ZIP creation failed at this time";
}
foreach($valid_files as $file){
$zip->addFile($file);
}
$zip->close();
if(file_exists($zip_name)){
// force to download the zip
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="'.$zip_name.'"');
readfile($zip_name);
// remove zip file from temp path
unlink($zip_name);
return "Files are successfully Archived !";
}
else{
return "ERROR in ZIP FIle Archive!";
}
} else {
return "No valid files to zip";
exit;
}
}
And this is my jQuery / AJAX code:
$(document).on('click', '.zip_dl', function(){
var click_id = $(this).attr('id');
//alert(click_id);
$.ajax({
url: 'includes/AjaxResponses.php',
data: {
folder_name : click_id,
action_type : "zip_file_download"
},
error: function() {
//$('#info').html('<p>An error has occurred</p>');
},
dataType: 'JSON',
success: function(response) {
},
type: 'GET'
});
});
I already found many questions here in SO about the theme, but any of them helps me with a specific problem.
I need to send a ajax request to PHP and then download a PDF file.
This is my Javascript
$('body').on("click",'.download-file', function(e) {
e.stopPropagation();
var id = $(this).attr('data-id');
$.ajax({
url: 'app/myPage/download',// The framework will redirect it to index.php and then to myPage.class and method dowload
data: id,
type: 'POST',
success: function (return) {
console.log(return);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log("Error... " + textStatus + " " + errorThrown);
}
});
});
Here is my PHP file:
class myPage{
public function download()
{
$id = $_POST['id'];
$filePath = $this->methodToGetFile($id);
$name = end(explode('/',$filePath));
$fp = fopen($filePath, 'rb');
header("Cache-Control: ");
header("Pragma: ");
header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($filePath));
header("Content-Disposition: attachment; filename='".$name."';");
header("Content-Transfer-Encoding: binary\n");
fpassthru($fp);
exit;
}
}
With this approach, I have the PDF "printed" in the console like
%PDF-1.5%����1 0 obj<</Type/Catalog/Pages 2 0 R/Lang(pt-BR) /StructTreeRoot 8 0 R/MarkInfo<</Marked true>>>> ....
So, I'd like to know what should I do to open this file and download it.
In the linked questions I see something about window.location, but I don't know how can I use it.
I've already tried change the PHP headers to try force the download, but no success with it. In these cases I just receive null in the javascript nothing happens. Here are the modified headers:
header('Content-Type: application/pdf');//tried with this option
//header('Content-Type: application/octet-stream');// also tried with this other option
header('Content-Disposition: attachment; filename=' . $name);
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: ' . filesize($filePath));
Are there some good approach to do this?
i don't think this approach to downloading a pdf will work. Javascript is sending the request, and php is sending the response. You want the response to go directly to the browser, not to javascript. You should change the download link to go directly to the download location. No ajax / javascript needed. Like this:
download
Solved.
i have a link for install ios app that work in safari.
now i want to hide link from users .but i'am not sure how to do this.
i used onclick for link :
function install() {
if (!isMobile()) {
alert('you can download from ios devices');
return;
}
var post_link = $(this).attr("target");
url = 'install.php?name='+post_link;
window.location.href = url;
}
and in install.php :
<?php
$name = $_GET['name'];
$path = 'itms-services://?action=download-manifest&url=https://acqshop.com/manifest/category/'.$name;
$mm_type="application/octet-stream";
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($path)) );
header('Content-Disposition: attachment; filename="'.basename($path).'"');
header("Content-Transfer-Encoding: binary\n");
readfile($path); // outputs the content of the file
exit();
?>
but it not work. what's my wrong.
you can see an example for onclick function to download ios app in this site: ipa.othman.tv/ipa/vi2.php
but i can't understant what's the functions in installvi2.php
(please see the page source)
thanks.
thanks to #Shynggys
i changed install.php to :
<?php
$name = $_GET['name'];
$path = 'itms-services://?action=download-manifest&url=https://acqshop.com/manifest/category/'.$name;
$mm_type="application/octet-stream";
header("Pragma: public");
header("Expires: 0");
header('Location:' .$path);
?>
Try to use absolute URL, not relative, and return 'false' from function 'install', it should look something like this:
function install() {
if (!isMobile()) {
alert('you can download from ios devices');
return;
}
var post_link = $(this).attr("target");
url = 'http://MyWebSite.kz/install.php?name='+post_link;
window.location.href = url;
return false;
}
Hope it helps
Trying to download a file which is dynamically generated by the the data posted. I have the results coming back. I'm just not sure how I can save the results into a proper file locally once the information (stream) is returned from the server.
Here is the ajax call:
function download_pdf(){
alert("made it" + result_array);
$.ajax({
type: "POST",
url: "report_generation/downlaod_pdf.php",
data: { result_array: result_array },
success: function(results){
alert(results);
},
error:
function(xmlHttpRequest, status, error){
alert(xmlHttpRequest + "| ajax failure: could not download haq report | " + status + " | error:" + error);
var xmlHttpRequestStr = "";
for(var x in xmlHttpRequest)
xmlHttpRequestStr = xmlHttpRequestStr + xmlHttpRequest[x];
alert(xmlHttpRequestStr);
}
});
}
This function is called from a hyperlink like so, and the success method is returning...
<a class="haq_button" href="javascript:download_pdf()"><span>Download Report As PDF</span></a>
UPDATE: Here is a snippet of code from the file generation in download_pdf
// set the headers, prevent caching
header("Pragma: public");
header("Expires: -1");
header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
header("Content-Disposition: attachment; filename=\"$file_name\"");
// set appropriate headers for attachment or streamed file
if ($is_attachment) {
header("Content-Disposition: attachment; filename=\"$output_file_name\"");
}
else {
header('Content-Disposition: inline;');
header('Content-Transfer-Encoding: binary');
}
// set the mime type based on extension, add yours if needed.
$ctype_default = "application/octet-stream";
$content_types = array(
"exe" => "application/octet-stream",
"pdf" => "application/pdf",
"zip" => "application/zip",
"mp3" => "audio/mpeg",
"mpg" => "video/mpeg",
"avi" => "video/x-msvideo",
);
$ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
header("Content-Type: " . $ctype);
//check if http_range is sent by browser (or download manager)
if(isset($_SERVER['HTTP_RANGE']))
{
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes')
{
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}
else
{
$range = '';
header('HTTP/1.1 416 Requested Range Not Satisfiable');
exit;
}
}
else
{
$range = '';
}
//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);
//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);
//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($file_size - 1))
{
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$file_size);
header('Content-Length: '.($seek_end - $seek_start + 1));
}
else
header("Content-Length: $file_size");
header('Accept-Ranges: bytes');
set_time_limit(0);
fseek($file, $seek_start);
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
if (connection_status()!=0)
{
#fclose($file);
exit;
}
}
// file save was a success
#fclose($file);
exit;
It's not posible. Only you can do is to open new browser window with that PDF url or change location of current window like that: window.location = 'report_generation/downlaod_pdf.php';
This is not possible using javascript/ajax. You have to use regural download link, or in your case form. If you want to have some error handling using javascript if pdf generation fails and still keep the same page open, you can submit into a hidden iframe.
You can create a dynamic form and submit it, it will open a new window/tab and if it fails you can show a message in that page. In anyway, the right way to handle and track an exception is not on the receiver, but sender (download_pdf.php)
To create a dynamic form:
var VerifyForm = document.createElement("form");
VerifyForm.target = "_blank"; //You must specify here your target
VerifyForm.method = "POST";
VerifyForm.action = "download_pdf.php";
var dataInput = document.createElement("input");
dataInput.type = "hidden";
dataInput.name = "mydata"; //var name
dataInput.value = value; //var value
VerifyForm.appendChild(dataInput); //Apend var to form, repeat for n
document.body.appendChild(VerifyForm);
VerifyForm.submit();
If you still try to it at your own way, is impossible, I recommend you read more about what means the capital "A" in "Ajax"
You can try to force the download of the file by adding these HTTP headers in the downlaod_pdf.php
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: public");
header("Content-Type: application/pdf");
header("Content-Disposition: attachment; filename=$your_file_name");
header("Content-Transfer-Encoding: binary");
readfile($your_file_content);
EDIT : and also add window.location.href = your_php_script_path in the js file
This question already has answers here:
PHP generate file for download then redirect
(11 answers)
Closed 3 years ago.
Page 1 links to page 2. Page 2 serves a download using the following code:
header("Content-disposition: attachment; filename= '$filename'");
header('Content-type: application/pdf');
readfile($file);
header("location: mainpage.php");
The result being the user "stays" on page 1 but is served a download.
How can I set things up, so that users remain on page 1 but it refreshes after the download is served.
I don't know javascript so I am hoping for a purely PHP solution.
In my opinion I wouldn't think you would necessarily need to refresh page1 at all. You should be able to force the download via a link within page1. See below:
Page1.php with a link
Download PDF
Page2.php
$filename = $_GET['pdf'] . '.pdf';
header('Content-type: application/pdf');
header("Content-disposition: attachment; filename= '$filename'");
header("location: $filename");
This will allow the download to start whilst you remain on page1.
Hope this is what you had in mind.
Didn't know of this before, but it's just one of the nice HTTP headers and most of us already know of it from HTML: Refresh.
Just add the following header call:
header('Refresh: 0; url=http://stackoverflow.com/');
You can check what the referer is by $_SERVER['HTTP_REFERER']. So you should be able to put this in you're page1.php:
if($_SERVER['HTTP_REFERER'] == page2.php) {
echo "<meta http-equiv=\"refresh\" content=\"0;url=http://www.yourdomain.com/page1.php\">";
exit();
}
This way, you check if your visitor is coming from page2.php, and if they are, you only parse a meta-tag which refresh the browser. When it is refreshed, it wouldn't refresh again, because the HTTP_REFERER is now page1.php.
No sure if you ever got this answered, but I had the same problem,
here is my solution
The AJAX jquery
$(function(){
$("#itemList").on("click", "a.downloadLink", function(){ //this binds a click event handler on the itemList container that will listen out for any a with class of downloadLink inside it being clicked
var link = $(this);
var item = link.parent();
var forId = item.data("itemid");
var started = new Date(); //the alternative to tracking time elapsed is to just use a simple counter you increment - "poll 5 times" etc. if your doing .5 second intervals, then 5 times = 2.5 seconds for example. Time elapsed may result in less polls, if a poll takes a long time to return, for example. Use whichever approach feels better.
var maxTime = 5000; //5 seconds
function poll(){
$.ajax({
type: "POST",
url: "Watergetstatus.php",
data: {FID: forId}, //this will be turned into a request for page1?forId=1&oldValue=2 - I expect it in this example to return a json-encoded response of {"changed":true|false, "newHtml":"replacementContent on success"}
datatype :'json',
success: function(data){
if (data.changed=true)
{
window.location.reload(true);
}
else {
var elapsed = (new Date())-started;
//window.location.reload(true);
if (elapsed <= maxTime) setTimeout(poll, 500); // Poll again in .5 seconds
//else you can assume the link didn't open / work / the database never changed etc - handle or ignore as needed
}
},
error: function() {
alert("borken"); //the request to the server bombed out; up to you if you want to simply re-queue for another try like above until the expiry time or if you want to show an error or just simply ignore it.
}
});
}
setTimeout(poll, 500); //wait 0.5 seconds before polling
});
});
The HTML had a large list of dynamic links
<div data-itemid="<?php echo $row_Files['FID']; ?> " >
<a class="downloadLink" href="PLC_FILES/WaterCheckOut.php?FID=<?php echo $row_Files['FID']; ?> " target=""><img src="PLCImages/download.fw.png"></a>
</div>
The Poling file watergetstatus.php
mysql_select_db($database_PLC, $PLC);
$query_files = "SELECT * FROM files WHERE FID = '{$_POST['FID']}'";
$files = mysql_query($query_files, $PLC) or die(mysql_error());
$row_files = mysql_fetch_assoc($files);
$totalRows_files = mysql_num_rows($files);
if($row_files['Status'] ==2)
{
$data= array("changed"=>true);
echo json_encode($data);
}
else
{
$data= array("changed"=>false);
echo json_encode($data);
}
THe download file link watercheckout.php
if ( $row_files['Status']==1 ) {
$file_path = $row_files['FileName'];;
$path_parts = pathinfo($file_path);
$file_name = $path_parts['basename'];
$file_ext = $path_parts['extension'];
$content_types = array(
"exe" => "application/octet-stream",
"zip" => "application/zip",
"mp3" => "audio/mpeg",
"mpg" => "video/mpeg",
"avi" => "video/x-msvideo",
);
$ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
$file = $row_files['FileName'];
$path = "Historical/".date('Y-m-d-His');
$newfile = $path."_".$file;
$today = date('Y-m-d H:i:s');
header('Content-disposition: attachment; filename='.$file);
header("Content-Type: " . $ctype);
header('Content-Length: ' . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Pragma: public');
mysql_select_db($database_PLC, $PLC);
mysql_query("UPDATE files SET Status = '2'");
ob_clean();
flush();
readfile($file);
rename($file, $newfile);
I have simplified my code and cut out alot, so it may be missing something, but this was the general frame work that I used and it works for me
Hope this helps someone, as there was not much out there
//----------------- TOP OF DOWNLOAD_PAGE.PHP ----------------------
$download_code = mysql_real_escape_string(urldecode($_GET['code']));
$download = mysql_real_escape_string(urldecode($_GET['download']));
$self = $_SERVER["PHP_SELF"]."?code=$download_code";
//refresh page after download...
echo"
<script type=\"text/javascript\">
function downloadRedirect(){
var redirect_url = \"$self\";
setTimeout(\"DoTheRedirect('\"+redirect_url+\"')\",
parseInt(0.5*1000));
}
function DoTheRedirect(url) { window.location=url; }
</script>
";
...
$filepath = "/var/www/vhosts/YOUR_DOMAIN.com/digital_downloads/";
if (isset($_GET['download'])) {
$file = $_GET['download'];
if (file_exists($filepath.$download) &&
is_readable($filepath.$download) && (preg_match('/\.zip$/',$download) || preg_match('/\.zipx$/',$download) )) {
...
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$download."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filepath.$download));
ob_end_flush();
readfile($filepath.$download);
}//end if file_exists
}//end if isset
//----------------- BOTTOM HALF OF DOWNLOAD_PAGE.PHP ----------------------
//link generated to download and call JS to refresh page
echo "Click to Download