How to download a file through a custom POST request with CasperJS - javascript

I am writing a crawler and needs to download file generated after a form request using POST.
I have successfully used this.download(url,'POST',Params) for regular forms.
One of the sites has many fields using the same name, thus preventing me from using the regular download method.
After trying a lot of things, I tried with $.ajax() and __utils.sendAJAX() to process the form like this:
response = this.evaluate(function(){
url=...
params = $('form#theirForm').serialize();
data = __utils__.sendAJAX(url, 'POST', params,false,{contentType:"application/x-www-form-urlencoded"});
return __utils__.encode(data);
});
function decode_base64(s) { var e={},i,k,v=[],r='',w=String.fromCharCode; var n=[[65,91],[97,123],[48,58],[43,44],[47,48]]; for(z in n){for(i=n[z][0];i<n[z][1];i++){v.push(w(i));}} for(i=0;i<64;i++){e[v[i]]=i;} for(i=0;i<s.length;i+=72){ var b=0,c,x,l=0,o=s.substring(i,i+72); for(x=0;x<o.length;x++){ c=e[o.charAt(x)];b=(b<<6)+c;l+=6; while(l>=8){r+=w((b>>>(l-=8))%256);} } } return r; }
casper.then(function() {
utils.dump(response);
fs.write("test.zip",decode_base64(response),'w');
});
The codes returns me base64 data which I convert and store in a test.zip file.
But I juste can't uncompress it, says it is corrupted.
I dump the data of a correct zip file =>
PK^C^D^T^#^H^#^H^#<F4><89><96>F^#^#^#^#^#^#^#^#^#^#^#^#?^#^#^#fourniture denr<E9>es alimentaires - dietetique infantile\CCAP.pdf<AC><BC>^ET\K<D3><F7>;^D<B7><U+0B81>^#<C1><99>^Y^F'^D<B7><E0>^D^ON<90><E0><EE><EE><EE>^Dwww'^P<9C>^D^H<EE>^^܂<C3>%'<CF>9<E7><C9><F7><U+07B5><BE>7<F7>f^SVOzf
Compared it with the first line of my file :
PK^C^D^T^#^H^#^H^#)_^M^#^#^#^#^#^#^#^#^#^#^#^#^#b^#^#^#fourniture denr<FD>es alimentaires - dietetique infantile\Bordereau de prix dietetique infantile.xlsx<FD>zuT<FD>I<FD><FD><FD><FD>^^4hp^M^D^M^R^H<FD>.<FD><FD>}p<FD>3<FD>kpw<FD>#pw^M<FD><FD>^R4<FD>Gv<FD>~<FD>[<FD><FD><FD><FD><FD>
Anyone has an idea of what could have gone wrong?
I have tried so many things (encoding tools, encoding settings, dumping from the chrome console to get pure base64, etc.)
I don't understand why it is related to latin-1 or utf8 encoding, since a website asks me to select which encoding to use. Tried both.

casper.download() happily accepts a serialized form instead of an object, so you can still use it. You just have to serialize the form in the page context beforehand:
var formData = casper.evaluate(function(){
return $('form#theirForm').serialize();
});
var url;
casper.download(url, targetFile, 'POST', params);
The only problem might be, that another mimeType is used: "text/plain; charset=x-user-defined".
In that case, you will have to recreate the whole cascade of functions that go into casper.download():
var url;
var response = casper.evaluate(function(url){
var params = $('form#theirForm').serialize();
var data = __utils__.sendAJAX(url, 'POST', params, false);
return __utils__.encode(data);
}, url);
var cu = require('clientutils');
fs.write("test.zip", cu.decode(response), 'wb');
"application/x-www-form-urlencoded" is used by default for __utils__.sendAJAX().

Related

Save Multiple Canvas Created Images to Server via AJAX with JavaScript

I am working on a form set for a client. In a nutshell:
The forms are filled out by my client’s customers by selecting different options on each form.
Each form can have multiple instances, depending on the customer.
At the end of the process, the customer can opt to either sign one or all the forms digitally or decline to sign them digitally and at the end of the process the forms are printed out and signed manually.
To accomplish this, I’ve created a signature plugin written in jQuery. Once the customer fills out the forms, they are presented each form separately. To sign the form digitally they simply tap (click) the signature block, a dialog with a canvas element appears, they sign the form and save it, the signature appears in the form, and they move on to the next form.
Here is the portion of the code that stores the completed signature and adds the image to the form:
$.sig = {
signatures: {},
}
function signatureSave() {
var canvas = document.getElementById("sigcanvas"),
dataURL = canvas.toDataURL("image/png");
document.getElementById($.sig.target).src = dataURL;
$.sig.signatures[$.sig.target].url = dataURL;
$.sig.signatures[$.sig.target].hasSignature = true;
};
The function is only called if the signature is saved, if there is no signature, the $.sig.signatures[$.sig.target].hasSignature remains false and the system skips the object.
This all works as intended, almost.
My problem lies in the process used to save the form information. If the customer does not sign any forms digitally the form information is simply saved and the forms are printed out, no need to save any signatures.
If the customer signs at least one form, though, the signatures must be sent to the server using the FormData() object.
I’ve used the FormData object in other projects for the client successfully, but only when the customer uploads one or more images to the browser using the input file element. It’s a pretty simple process because the resulting images have a img.file property that I send to the server.
Not so with a canvas object. All I get is the .src property, an any attempt to use anything from the resulting .png image that is created in the function above results in either a “cannot use a blob” or some other error.
Now I know if I have a single image, I can send it using AJAX with the following:
$.ajax({
type: "POST",
url: "script.php",
data: {
imgBase64: dataURL
}
})
Problem is that I am sending from one to x number of signatures.
Edit: I forgot to add this in. This is the function that is supposed to create the FormData object used to send the signatures to the server (and where my problem lies):
function getUploadData() {
var upl = new FormData();
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
var im = new Image();
im.src = u.url;
upl.append(u.target, im, u.target + '.png');
}
})
return upl;
}
I've tried all the tricks and nothing is working. The var im = new Image(); as well as the following line are just my latest ill fated attempt.
Picture perfect would be the ability to save the image information in the $.sig.signatures object so I can simply loop through any signatures that are signed, add them as elements of the FormData object, and then send the FormData object as the data for the AJAX call. As stated before, I use this method in other projects and works fine.
Does anyone know a way to do this?
Please note:
The server-side AJAX processor functions correctly.
The signature process works correctly (customer signs canvas, signature is displayed, signature information is stored).
All I need is how to send multiple images created using the canvas element in a FormData object to the server.
I know the answer is staring me right in the face, but I am just not getting it. Any hints or suggestions would be greatly appreciated!
Edit: Just a note. I've searched the entire afternoon for this and have found entries that either deal with sending multiple files using FormData and AJAX - but the files are uploaded to the browser (not created using Canvas), or single files sent to the server that are created using Canvas, but nothing about sending multiple files sent using FormData and AJAX that are created using Canvas. Oje!
As stated, the answer was staring me in the face, but I didn't see it because was looking behind the wrong door. FormData has nothing to do with it (Homer Dope Slap!).
Since I already have the data stored in $.sig.signature for each signature, I just need to send the information to the server as the data in the AJAX function. I updated my function above as shown:
function getUploadData() {
var upl = {};
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
upl[e] = u.url;
}
})
return upl;
}
Since the form information is sent as JSON I just add the signature info to the object that contains the form information, JSON.stringify it and send it on its way. This should work because the information retrieved above are strings.
Server side will look something like this:
$info = json_decode( $_POST['info'] );
// Various validation routines and checks
foreach( $info->signatures as $sig=>$data ):
$data = str_replace('data:image/png;base64,', '', $data);
$data = str_replace(' ', '+', $data);
$img = base64_decode($data);
// Do some processing, file naming, database saving and other general dodads
$success = file_put_contents( $file, $img );
endforeach;
The above function is still concept, I am reworking some of the code but this should work.
Credit is given to this post for opening my eyes:
post sending base64 image with ajaxpost sending base64 image with ajax
So question answered and yeah, I deserve a dope slap, but all comes out right in the end.
CAVEAT: Works like a charm.

How can I send a file from a URL (not a file upload) to the backend with javascript?

I would like to have a button that when clicked gets a file (in this case a dynamically generated PDF) from a (predefined) URL on the same domain, and sends it to a (php) backend to be saved.
I am guessing that the best way to do this is to somehow load the file returned by the URL into a javascript variable, base64 encode it and send that to the backend with an ajax POST. Then on the backend I would base64 decode it and save it as a regular file.
Is this the right approach, or is there a better way to do it?
If this is the right approach, the part I am not sure how to do is getting the file from the URL into a variable. Once it's there, I guess I can use btoa() to base64 encode it. The other thing I am not 100% sure about is whether that will be compatible with base64_decode() in PHP for when I decode it?
Update
You say, the URL is predefined, and you don't know how to get in into a javascript variable. I guess, the URL is defined in the backend (PHP). So you could simply set/inject it in the client side code (javascript) with PHP.
Don't post the URL back from the client to the server, as there seems to be no need for that, and the URL could easily be changed by the user (security issue)!!!
So, maybe a better way would be to keep the URL on the server side, and inject it in the client page using php, without posting it back to the server.
Original answer
This answer is only appropriate if the client generates the URL and you have a secure way to verify it, as every client side input has to be treated as potential harmful user input.
In my opinion this is the absolutely right approach. Yes, you should base64 encode your variable. You could put the base64 encoded string in a json object and post this json object via ajax, or post it as plain text in your post body. Make sure to verify this as a client input!
I see no reason why btoa()/base64_decode() should not work. Base64 is platform independent.
I figured it out. It's a mix of vanilla and jQuery because the vanilla is from this article and the project I am working in already has jQuery available for $.ajax to make the POST to the backend easier.
var oReq = new XMLHttpRequest();
var fileUrl = '[URL from PHP]';
oReq.open("GET", fileUrl, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
let arrayBuffer = oReq.response; // Note: not oReq.responseText
if (arrayBuffer) {
let binaryText;
let byteArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteArray.byteLength; i++) {
binaryText+=String.fromCharCode( byteArray[ i ] );
}
$.ajax({
type: "POST",
url: "/path/to/backend-upload",
data: {"fileData":btoa(binaryText)},
success: function(resultData){
alert("File Uploaded");
}
});
}
};
oReq.send(null);
and in the PHP backend:
file_put_contents("/path/to/destination", base64_decode($_POST['fileData']));

Does fetch support multiple file upload natively?

Summary
I am trying to set my FormData properly using javascript.
I need to be able to upload jpg/png, but I might need to upload some other file types pdf/csv in the future using fetch.
Expected
I expect it to append the data to the form
Error
Working
This snippet is working fine:
const formData = new FormData(document.querySelector('form'));
formData.append("extraField", "This is some extra data, testing");
return fetch('http://localhost:8080/api/upload/multi', {
method: 'POST',
body: formData,
});
Not working
const formData = new FormData();
const input = document.querySelector('input[type="file"]');
formData.append('files', input.files);
Question
Does fetch support multiple file upload natively?
If you want multiples file, you can use this
var input = document.querySelector('input[type="file"]')
var data = new FormData()
for (const file of input.files) {
data.append('files',file,file.name)
}
fetch('http://localhost:8080/api/upload/multi', {
method: 'POST',
body: data
})
The issue with your code is in the lineformData.append('files', input.files);
Instead of that, you should upload each file running a loop with unique keys, like this
const fileList = document.querySelector('input[type="file"]').files;
for(var i=0;i<fileList.length;i++) {
formData.append('file'+i, fileList.item(i));
}
I have created a simple error fiddle here with your code. You can check its' submitted post data here, where you can see that no file has been uploaded.
At the bottom of the page you can find
.
I have corrected the fiddle here with the fix. You can check its'post data from the server, where it shows the details of the two files that I uploaded.
I mentioned this on a similar question: I had the same problem, but with a PHP backend. The unique formData keys work, but I found that the classic HTML notation worked just fine and simply results in an array on the server.
formData.append('file[]', data[i]);
I like that a lot better, since I can use the same methods to process this as with a classic <input type="file" multiple />.

Mailgun in .xsjs

is there a way to send an email via Mailgun with html page as its content that is longer than ~2000 characters?
I have this code, that works perfectly for short html as I believe it is sent in URL address:
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
req.headers.set('Content-Type', encodeURIComponent("application/x-www-form-urlencoded"));
req.parameters.set("domain", "mailgundomain.com");
req.parameters.set("from", "me#mailgundomain.com");
req.parameters.set("to", 'to#email.com');
req.parameters.set("subject", "subject");
req.parameters.set("html", obj); //email content
In the code above I receive the file and save it to 'org' variable and then send it to mail. What I need is to probably get my "too large" .html file to the body and then show it as a content of the email. As you probably can see, I'm quite new in .xsjs so the more detailed answer the better. If you need any more info, feel free to ask. Thank you.
Edit1: I should add that when I try to send a larger file, the response I get is "414 Request-URI Too Large".
EDIT
This seems to be the right approach, jointly figured out by the OP and myself:
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
// request headers
req.headers.set('Content-Type', "application/x-www-form-urlencoded");
// request URL parameters
req.parameters.set("domain", "mailgundomain.com");
req.parameters.set("from", "me#mailgundomain.com");
req.parameters.set("to", 'to#email.com');
req.parameters.set("subject", "subject");
// request body
req.setBody(encodeURIComponent(message));
The $.web.WebRequest class sends everything you set in the .parameters collection as an URL parameter, even if the request method is POST. This is perfectly all-right, POST requests may have URL parameters. However, URLs are length-limited, as you have noticed.
The body of a POST request is not length-limited, but you have to do the proper content encoding on your own. The body of a application/x-www-form-urlencoded type request follows the same rules as the URL - key=value pairs separated by & characters.
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
req.headers.set('Content-Type', "application/x-www-form-urlencoded");
var message = {
domain: "mailgundomain.com",
from: "me#mailgundomain.com",
to: "to#email.com",
subject: "subject",
html: obj
};
req.setBody(urlEncode(message));
where urlEncodedFormat() is a little helper function:
function urlEncode(obj) {
return Object.keys(obj).map(function (key) {
return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
}).join("&");
}
Turning objects into an URL-encoded string is a pretty common operation. It's likely that one of the libraries you use already contains a function that does that.
While the above function is is probably correct (there might be edge cases with undefined or null values), it's preferable not to use a hand-rolled variant. Spend some time looking for the right function in your libraries.
Maybe WebRequest already does the right thing on its own, I have no way to test it, though. Try setting the message object as the body directly:
req.setBody(message);

Opening a JSON file from javascript

I have a C# functoin in my MVC application that returns a JSON representation of a csv file. I am trying to catch that in javascript and open the file. Basically I want the browser do its thing and let the user decide if he want to open it, save it or cancel. I am unable to get that popup to ask me to open the file. Below are the functions
C# Function
[HttpPost]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
}
This is the javascript where I am making the ajax call to the function
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
$.ajax({
url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv",
type: 'Post',
success: function (result) {
window.open(result.url);
}
});
});
I know I am missing something. Can someone please help.
EDIT
After reading through all the potential answers and comments, this is what I am trying to achieve. So if my code is all horribly wrong please let me know.
I have a grid and I have an export to excel button. I have a method that converts the data i want into comma delimited text in javascript itself. I need to present this to the user as a downloadable csv file. For this I was creating the File object using the controller method. The previous incarnation was a Get method and I faced limitations due to querystring length restrictions. So I tried converting it to a POST method and it is not working.
This is the previous version of the code that works for smaller amounts of data
Javascript
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
window.location.href = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
});
C# Function
[HttpGet]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName);
}
Hope this now gives you all more context. Basically I needed to convert my GET method to a POST method and use it from Javascript.
If you use ajax, you're expected to handle the result in code. But you can't trigger a file download (directly) that way.
Instead, create a (hidden) form and post it to a (hidden) iframe (by giving the iframe a name and specifying that as the target of the form), making sure that the response specifies the header Content-Disposition: attachment. That will trigger the browser to offer to save the file. Optionally in the header you can suggest a filename for the file by adding ; filename="fname.ext" to the header value. E.g. Content-Disposition: attachment; filename="fname.ext".
The client-side looks something like this:
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
var frame = $('iframe[name="formreceiver"]');
if (!frame.length) {
frame = $('<iframe name="formreceiver"></iframe>').appendTo(document.body).css("display", "none");
}
var form = $("#senderform");
if (!form.length) {
form = $('<form id="senderform"></form>').appendTo(document.body);
}
form = form[0]; // Raw DOM element rather than jQuery wrapper
form.target = "formreceiver";
form.action = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
form.method = "POST";
form.submit();
});
The server side is just a standard form response with the Content-Disposition header.
I've used this technique for years, with browsers from IE6 onward. My usual setup already has the iframe in the markup (rather than creating it as above), but other than that this is basically what I do.
Note that it's important you're doing this in response to a button click by the user. If you weren't, there would be popup-blocker issues.
You can't save binary files using ajax - it's restricted due to security reasons. If you'd like the user to save the file - return a binary stream from your controller as you post the data back in JSON format (ie if the user wants to save it).
I've done a similar thing here: How to properly create and download a dynamically generated binary *.xlsx file on .net MVC4?
Maybe save it as a json file instead and load in the current page's DOM. Although I though I am confused, don't you just have a JSON response containing a URL to your CSV file is which is just that a CSV file, not a JSON representation of CSV?
$("#btnExport").click(function (event) {
event.preventDefault();
var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
$.ajax({
url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.json",
type: 'Post',
success: function (result) {
var myDummyElement = $('<div>'); //dummy div so you can call load
myDummyElement .load('result.url #myJson');
}
});
});
<div id="myJson"></div>
[HttpPost]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
//change fileName to be a .json extension
fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
}

Categories

Resources