Several questions here very helpfully discuss downloading binary files using jQuery and Ajax. My files download but are corrupted. The ones I'm testing are MS docx type but they arrive with different contents and sizes to the original with BOM header bytes EF BB BF prefixed, which I guess means something somewhere in transit is trying to convert them to UTF8.
Test PHP code:
$id = json_decode(filter_input(INPUT_POST, 'downloadfile', FILTER_SANITIZE_STRING);
getFileData($pdo, $fileData, $id); // from database
$file = file_get_contents($fileData['file_location']);
header('Content-type: application/octet-stream');
header("Content-Transfer-Encoding: binary");
echo $file;
jQuery (I'm using v3.6.0):
$('#dodownload').on('click', function() {
$.ajax({
type: 'POST',
data: {'downloadfile': id},
xhrFields: {responseType: 'blob'},
url: '/php/ajax/fbdownload-ajax.php',
})
.done(function(blob, status, xhr){
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="testfile.docx";
link.click();
});
})
jQuery says blob is 'Blob {size: 14403, type: "application/octet-stream"', status is "success" and xhr.readyState is 4.
How do prevent the corruption to the file?
Related
I have a page where a user can download an image in the format he ask, the request is sent to a PHP script that produce the image and ... I want to serve to the user.
This is the JQUERY code sourced here on StackOverflow
$('[name ="download_img_ajax"]').click(function(e){
e.preventDefault()
var element = this
var formdata = new FormData(element.closest(".form_downIMG"))
formdata.append('download_img_ajax','true')
$(this).next("span.down_response").html('Preparazione file in corso...'),
$('.emailadr').hide(),
$.ajax({
type: 'POST',
url: '$target_post',
data: formdata,
cache: false,
contentType: false,
processData: false,
success: function(tornato) {
const blob = new Blob([tornato], {type: 'image/jpeg'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.jpg";
document.body.appendChild(a);
a.click();
},
})
})
The PHP script generate the file as a tmp file ($img), but I don't understand how return it as a correct AJAX response.
echo fread($img,filesize($img_path));
isn't working (the file isn't recognized as JPG file) even if the size is correct.
In a normal form I return the file this way:
header('Content-Transfer-Encoding: binary'); // For Gecko browsers mainly
header('Accept-Ranges: bytes'); // For download resume
header('Content-Length: ' . filesize($img_path)); // File size
header('Content-Encoding: none');
header('Content-Type: image/jpeg'); // Change this mime type if the file is not PDF
header('Content-Disposition: attachment; filename=' . $imgID); // Make the browser display the Save As dialog
readfile($img_path);
fclose($img);
PS: In JQ I use next, closest, etc. because I have many forms in the same dynamically generated by PHP.
I will use this kind of download only for file of ~700KB, leaving the bigger file on the old method of a submit form with target="_blank" and readfile in the PHP. Is safe?
If I want manage also error (example the PHP script can't serve the file) how I can handle it?
Thanks.
Solved with FileSaver.js ( https://github.com/eligrey/FileSaver.js )
var xhr = new XMLHttpRequest()
xhr.open('POST', '$target_post')
xhr.responseType = 'blob'
xhr.onload = function() {
saveAs(xhr.response, 'immagine.jpg');
}
xhr.send(formdata)
the PHP
echo fread($img,filesize($img_path));
I have some problems when I try to display multiple images (well it doesn't work for one image so multiple is impossible), and what I'm doing is with my function of AJAX to recover from my db, all the images location string that are in the table images. Then it calls another function called setImages() which receives those strings of the image locations and I use iterate over the strings (using jQuery's .each()) to trigger an AJAX request that calls a php script named image.php?img=[data]. data is the string containing the image location, so I have something like the code below:
The problem is that setImages() of my js, doesn't show the images
the PHP file:
<?php
$init="/var/Imagenes/cursos/";
$img=$_GET['img'];
$path=$init.$img;
echo $path;
//el path debe ser autores/ or cursos/
$name="azure";
/*
header("Cache-Control: no-cache, must-revalidate");
header("X-Sendfile: $path");
header("Content-Type: image/jpeg");
header("Content-Disposition: inline; filename='archivos'");
*/
//el nombre de la base de datos de la imagen
header("Cache-Control: no-cache, must-revalidate");
if((isset($path)&& !is_null($path))){
header("X-Sendfile: $path");
if(strpos( $img, ".png" )||strpos( $img, ".PNG" )){
header("Content-Type: image/PNG;base64");
}
elseif(strpos( $img, ".jpg" )||strpos( $img, ".JPG" )){
header("Content-Type: image/jpg;base64");
}
elseif(strpos( $img, ".jpeg" )||strpos( $img, ".JPEG" )){
header("Content-Type: image/jpeg;base64");
}
else{
return "error.jpg";
}
$newimg=rand(1000 , 9999 );
header("Content-Disposition: inline; fileimg= $newimg-$img");
exit();
}
else{
echo "no se pudo realizar la consulta";}
JS code:
functions listImgCursos works fine...
function listImgCursos(identificador) {
var resultado= $.ajax({
url: consultaBasica,
cache: false,
type: 'POST',
data : { action: "imgCursos"}
}).then(
function(data){// Success
var flagErrFound = false;
var nf404 = "" ;
$.each(data,
function(index,datos){
if((datos['id']===null)||(datos['img']=="")||(datos['img']==null)){
nf404 = datos['id'];
flagErrFound= true;
}//if close
}//function close
)//each close
if(flagErrFound===true){
error = {error: "CX02", msj: "Failed to get data from records.", data: nf404 };
return $.Deferred().reject(error);
}
else
return data;
},//function sucessful
function(){// fail
error = {error: "CX01", msj: "Failed to execute ajax"};
return $.Deferred().reject(error);
}//function fail
);//then;
resultado.done(
function (data){//success
setImages(data);
}//function DONE
);
resultado.fail(
function(e){//function fail
console.log(e.msj + " "+ e.error + ":" + e.data );
}//function FAIL)
);
}
function setImages(data){
$.each(data, function (index, datos) {
var temp="../classes/imagen.php?img="+encodeURIComponent(datos['img'])+"&t="+((new Date).getTime());
console.log(temp); // returns something like: ../classes/imagen.php?img=curso-2561.jpg&t=1489074434134
$.ajax({
url: temp,
type: "GET",
dataType: "image/jpg;base64",
async:true,
cache: false,
success: function(datai){
console.log(datai);
$('#pickimg').append('<img src="data:image/png;base64,' + datai + '" />');
},
fail: function(){
}
});
});
The problem is that setImages() of my js, doesn't show the images
This is because of multiple reasons:
The PHP code isn't actually returning the file contents. To do that, use a function like file_get_contents(),
readfile(), etc. Also, the string should be base-64 encoded so use base64_encode().
$newimg=rand(1000 , 9999 );
header("Content-Disposition: inline; fileimg= $newimg-$img");
echo base64_encode(file_get_contents($path));
exit();
This may be redundant with the first bullet, but the Syntax for header Content-Disposition only contains three directives: name, filename and filename*.1. So that fileimg directive is invalid. That header could include the filename directive but because a string is being returned it would be useless:
header("Content-Disposition: inline; filename=\"$newimg-$img\"");
In that sense it isn't really returning an image, so the headers for Content-Type are basically incorrect. Consequently, the AJAX call (using $.ajax()) should not specify the dataType (i.e. dataType: "image/jpg;base64" which wouldn't be dynamic anyway - for jpgs, pngs, etc). So remove that dataType option.
See a demonstratation of this with the modifications applied in this phpFiddle. Click the button labeled Run - F9 - and then when the frame loads, click the button labeled Update to trigger an AJAX call to the PHP script which will load a PNG image and append it to the element with id attribute "pickimg".
1https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives
I want use AJAX to ask PHP to read a file and return data. The problem is, PHP can read the file but AJAX cannot get the data.
$.ajax({
type: 'GET',
timeout : 1000,
url : '../tools/test2.php', //read file and echo
dataType : 'json',
cache : false,
beforeSend: function() {
$("#demo2").val('loading..')
},
success : function(data) {
$("#demo2").val('message:' + data) //this never can run
}
});
//test2.php
<?php
header("Content-type:text/html");
$fileName = "./testFile.txt";
$file = fopen($fileName, "r");
echo fgets($file);
fclose($file);
?>
You said:
dataType : 'json',
And then you said:
header("Content-type:text/html");
So jQuery tried to parse your HTML as JSON, failed and ran the error handler (which does nothing because you didn't write one) instead of the success handler.
Fix your dataType (you can just remove it so that jQuery will respect the Content-Type header) or rewrite the PHP so it returns JSON instead of HTML.
(If you are actually returning plain text (since you have a .txt file) then say Content-Type: text/plain).
I'm trying to write a method in a php class that will use ajax to execute a php function that will push a file back to the browser.
It seems like its trying to write the file to the modx log, getting a lot of binary garbage in there.
Here is the method:
public function pushDocuments($formdata){
$data = $formdata['formdata'];
$file = MODX_PROTECTED_STORAGE . $data['target'];
$file_name = basename($file);
if (file_exists($file)) {
header("Content-Disposition: attachment; filename=\"$file_name\"");
header("Content-Length: " . filesize($file));
header("Content-Type: application/octet-stream;");
readfile($file);
};
$output = array(
'status' => 'success',
'error_messages' => array(),
'success_messages' => array(),
);
$output = $this->modx->toJSON($output);
return $output;
}
and here is the jquery:
$('.btn-get-document').click(function(){
var target = $(this).attr('data-target');
var postdata = {"snippet":"DataSync", "function":"pushDocuments", "target": target}; // data object ~ not json!!
console.log('target = ' + target + postdata );
$.ajax({
type: "POST",
url: "processors/processor.ajax.generic/",
dataType : "json",
cache : false,
data: postdata, // posting object, not json
success: function(data){
if(data.status == 'success'){
console.log("SUCCESS status posting data");
}else if(data.status == 'error'){
console.log("error status posting data");
}
},
error: function(data){
console.log("FATAL: error posting data");
}
});
});
it's running through the scripts and giving a success in the console [because I am forcing success] but no file is prompted for download and the binary garbage shows up in the modx log
What am I doing wrong?
In order to download a file, you'd have to use JS to redirect to the file's location. You can't pull the file contents through AJAX and direct the browser to save those contents as a file.
You would need to structurally change your setup. For instance, your PHP script can verify the existence of the file to be downloaded, then send a link to JS in order to download the file. Something like this:
if ( file_exists( $file )) {
$success_message = array(
'file_url' => 'http://example.com/file/to/download.zip'
);
}
$output = array(
'status' => 'success',
'error_messages' => array(),
'success_messages' => $success_message
);
Then modify the "success" portion of your AJAX return like this:
success: function( data ) {
if ( data.status == 'success' ) {
location.href = data.success_messages.file_url;
} else if ( data.status == 'error' ) {
console.log( 'error status posting data' );
}
},
Since you're directing to a file, the browser window won't actually go anywhere, so long as the file's content-disposition is set to attachment. Typically this would happen if you directed to any file the browser didn't internally handle (like a ZIP file). If you want control over this so that it downloads all files (including things the browser may handle with plugins), you can direct to another PHP script that would send the appropriate headers and then send the file (similar to the way you're sending the headers and using readfile() in your example).
#sean-kimball,
You might want to extend MODX's class based processor instead:
https://github.com/modxcms/revolution/blob/master/core/model/modx/processors/browser/file/download.class.php
It does the download from any media source and also access checking if you want.
Its implementation on manager side is:
https://github.com/modxcms/revolution/blob/master/manager/assets/modext/widgets/system/modx.tree.directory.js#L553
Back to your case, these examples might bring you some ideas.
JS Example:
$.ajax({
type: "POST",
// read my note down below about connector file
url: "assets/components/mypackage/connectors/web.php",
dataType : "json",
cache : false,
data: {
action: 'mypath/to/processor/classfile'
}
success: function(data){
},
error: function(data){
console.log("FATAL: error posting data");
}
});
Processor example:
<?php
require_once MODX_CORE_PATH . 'model/modx/processors/browser/file/download.class.php';
class myDownloadProcessor extends modBrowserFileDownloadProcessor {
// override things in here
}
return 'myDownloadProcessor';
For this, I also suggest you to use MODX's index.php main file as the AJAX's connector so the $modx object in processor inherits the access permission as well.
http://www.virtudraft.com/blog/ajaxs-connector-file-using-modxs-main-index.php.html
I'm using JSONP to dynamically add data into my Android Phonegap app. Since Phonegap 2.5 (and upwards) allows application cache, I would like to use that. Only problem is that at this moment, my data is in a php-file. I read that data from a php-file cannot be cached by the cache manifest, so I'm thinking about changing it to js or something. Any idea how I would do this? I already tried a lot of tutorials on JSONP, but the only way I can get JSONP to work is in PHP. They are also quite vague about how I should save my data file (currently called home.php).
home.php
<?php echo $_GET["callback"] ?> (
[
{
"expo":"pit",
"datum":"05.06.2011 - 05.06.2016",
"img":"images/pit_home.jpg",
"link":"exp1_index.html"
},
{
"expo":"Space Odessy 2.0",
"datum":"17.02 - 19.05.2013",
"img":"images/so_home.jpg",
"link":"exp2_index.html"
}
]
);
script in index.html that calls data from home.php
<script type="text/javascript">
$.ajax({
type: 'GET',
jsonpCallback: 'jsonCallback',
contentType: 'application/json',
dataType: 'jsonp',
url: 'http://mllsdemode.be/Ex-cache/home.php',
success: function(json) {
var $home = $("#home");
$home.empty();
$.each(json, function(i, el) {
$home.append("<td><div class='dsc'>" + el.expo + "<br><em>" + el.datum + "</em></div></td>");
});
},
error: function() { alert("Error reading jsonP file"); }
});
</script>
Well, if I understood well, this is cached, you want it to be cached but not to develop? Then add some headers to develop!
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');
To easily forge your JSON, you can use Simple JSON for PHP, it allows you to build complex JSON/JSONP.