Upload an image file using the request object - javascript

In the Firefox extension I am currently developing, I've been trying for days now to upload an image file to a server using the request module of the Firefox Add-on SDK.
I managed to upload a text file but I cannot upload any other kind of files.
My final goal is to upload a screenshot so I really need to be able to upload an image.
Here is my current code:
params= file.read("C:\\FullPath\h3nf5v2c.png", "b");
//multipart form data
boundary = "---------------------------132611019532525";
var snapShotUpload = Request({
url : "https://myurl.com",
headers : {
"Referer" : "https://myurl.com",
"Content-Type" : "multipart/form-data; boundary=" + boundary,
},
content : "--" + boundary + "\r\n" + "Content-Disposition: form-data; name='Upload_FileName'; filename='h3nf5v2c.png'\r\nContent-Type: application/octet-stream\r\n\r\n" + params + "\r\n--" + boundary + "--\r\n",
onComplete: function(response) {
console.log(response.text);
}
});
console.log("request built");
snapShotUpload.post();
The uploaded image is corrupted and I can't read it.
So my question is:
How do I post an image using the request module of the Firefox Add-on SDK?

Thank you Wladimir,
I didn't actually modify the Request module but simply used an XMLHttpRequest instead.
Here is the code I used if anybody is interested:
function sendImage(file){
fName = "h3nf5v2c.png";
// prepare the MIME POST data
var boundaryString = '---------------------------132611019532525';
var boundary = '--' + boundaryString;
var requestbody = boundary + '\r\n'
+ 'Content-Disposition: form-data; name="Upload_FileName"; filename="'
+ fName + '"' + '\r\n'
+ 'Content-Type: image/png' + '\r\n'
+ '\r\n'
+ file.read()
+ '\r\n'
+ boundary + '--\r\n';
// Send
var http_request = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
http_request.onreadystatechange = function() {
if (http_request.readyState == 4 && http_request.status == 200) {
console.log(http_request.responseText);
}
};
http_request.open('POST', 'http://myUrl.com', true);
http_request.setRequestHeader("Referer", "http://myUrl.com");
http_request.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundaryString);
//http_request.setRequestHeader("Connection", "close");
http_request.setRequestHeader("Content-length", requestbody.length);
http_request.sendAsBinary(requestbody);
}
The file parameter of the function is a file object from the file module.
The sendAsBinary function at the end is very important because I'm working client side only and I had to simulate an insert via a form.
If you have access to the server side, sending a base64 encoded version of the image and decoding it server-side is probably simpler.

Unfortunately, the answer is: you cannot. The request module is mainly meant to transmit url-encoded data so anything that isn't a string is assumed to be an object containing key-value pairs that need to be encoded. So sending a string is your only option if you need to send multipart content yet XMLHttpRequest will always encode strings as UTF-8 which will produce rather undesirable results when you are trying to send raw binary data (such as an image data).
It would be much easier to use a FormData object but even if you create one - the request module won't pass it to XMLHttpRequest. There is no simple solution short of modifying the request module (file packages/addon-kit/lib/request.js of the Add-on SDK). Find this line:
let data = stringify(content);
Changing it into the following code should work:
let {Ci} = require("chrome");
let data = content;
if (!(content instanceof Ci.nsIDOMFormData))
data = stringify(content);
This makes sure that FormData objects aren't changed.
Note that you might not have FormData and File defined in your main.js. If that's the case "stealing" them from any JavaScript module should work:
let {Cu} = require("chrome");
var {FormData, File} = Cu.import("resource://gre/modules/Services.jsm")

I think there is another way to post an image on your server. You can use the base64 encoding.
I never test it but I found this subject on the web which explains how to convert the image. I hope this can help you.

What can simplify your life is using FormData and XMLHttpRequests together as in this example.
function uploadFiles(url, files) {
var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) {
formData.append(file.name, file);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
uploadFiles('/server', this.files);
}, false);
source: http://www.html5rocks.com/en/tutorials/file/xhr2/

Related

How to automatically save a file received from server onto client [duplicate]

I have a javascript app that sends ajax POST requests to a certain URL. Response might be a JSON string or it might be a file (as an attachment). I can easily detect Content-Type and Content-Disposition in my ajax call, but once I detect that the response contains a file, how do I offer the client to download it? I've read a number of similar threads here but none of them provide the answer I'm looking for.
Please, please, please do not post answers suggesting that I shouldn't use ajax for this or that I should redirect the browser, because none of this is an option. Using a plain HTML form is also not an option. What I do need is to show a download dialog to the client. Can this be done and how?
Don't give up so quickly, because this can be done (in modern browsers) using parts of the FileAPI:
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params, true));
Or if using jQuery.ajax:
$.ajax({
type: "POST",
url: url,
data: params,
xhrFields: {
responseType: 'blob' // to avoid binary data being mangled on charset conversion
},
success: function(blob, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
});
Create a form, use the POST method, submit the form - there's no need for an iframe. When the server page responds to the request, write a response header for the mime type of the file, and it will present a download dialog - I've done this a number of times.
You want content-type of application/download - just search for how to provide a download for whatever language you're using.
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it.
Here we need to carefully set few things at the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
What server-side language are you using? In my app I can easily download a file from an AJAX call by setting the correct headers in PHP's response:
Setting headers server-side
header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);
header("Content-type: " . $mimeType);
// $strFileName is, of course, the filename of the file being downloaded.
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));
// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;
This will in fact 'redirect' the browser to this download page, but as #ahren alread said in his comment, it won't navigate away from the current page.
It's all about setting the correct headers so I'm sure you'll find a suitable solution for the server-side language you're using if it's not PHP.
Handling the response client side
Assuming you already know how to make an AJAX call, on the client side you execute an AJAX request to the server. The server then generates a link from where this file can be downloaded, e.g. the 'forward' URL where you want to point to.
For example, the server responds with:
{
status: 1, // ok
// unique one-time download token, not required of course
message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}
When processing the response, you inject an iframe in your body and set the iframe's SRC to the URL you just received like this (using jQuery for the ease of this example):
$("body").append("<iframe src='" + data.message +
"' style='display: none;' ></iframe>");
If you've set the correct headers as shown above, the iframe will force a download dialog without navigating the browser away from the current page.
Note
Extra addition in relation to your question; I think it's best to always return JSON when requesting stuff with AJAX technology. After you've received the JSON response, you can then decide client-side what to do with it. Maybe, for example, later on you want the user to click a download link to the URL instead of forcing the download directly, in your current setup you would have to update both client and server-side to do so.
Here is how I got this working
https://stackoverflow.com/a/27563953/2845977
$.ajax({
url: '<URL_TO_FILE>',
success: function(data) {
var blob=new Blob([data]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
link.click();
}
});
Updated answer using download.js
$.ajax({
url: '<URL_TO_FILE>',
success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
For those looking for a solution from an Angular perspective, this worked for me:
$http.post(
'url',
{},
{responseType: 'arraybuffer'}
).then(function (response) {
var headers = response.headers();
var blob = new Blob([response.data],{type:headers['content-type']});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "Filename";
link.click();
});
For those looking for a more modern approach, you can use the fetch API. The following code shows how to download a spreadsheet file.
fetch(url, {
body: JSON.stringify(data),
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = "file.xlsx";
document.body.appendChild(a);
a.click();
})
I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.
Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following [link][1].
Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.
I see you've already found out a solution, however I just wanted to add some information which may help someone trying to achieve the same thing with big POST requests.
I had the same issue a couple of weeks ago, indeed it isn't possible to achieve a "clean" download through AJAX, the Filament Group created a jQuery plugin which works exactly how you've already found out, it is called jQuery File Download however there is a downside to this technique.
If you're sending big requests through AJAX (say files +1MB) it will negatively impact responsiveness. In slow Internet connections you'll have to wait a lot until the request is sent and also wait for the file to download. It isn't like an instant "click" => "popup" => "download start". It's more like "click" => "wait until data is sent" => "wait for response" => "download start" which makes it appear the file double its size because you'll have to wait for the request to be sent through AJAX and get it back as a downloadable file.
If you're working with small file sizes <1MB you won't notice this. But as I discovered in my own app, for bigger file sizes it is almost unbearable.
My app allow users to export images dynamically generated, these images are sent through POST requests in base64 format to the server (it is the only possible way), then processed and sent back to users in form of .png, .jpg files, base64 strings for images +1MB are huge, this force users to wait more than necessary for the file to start downloading. In slow Internet connections it can be really annoying.
My solution for this was to temporary write the file to the server, once it is ready, dynamically generate a link to the file in form of a button which changes between "Please wait..." and "Download" states and at the same time, print the base64 image in a preview popup window so users can "right-click" and save it. This makes all the waiting time more bearable for users, and also speed things up.
Update Sep 30, 2014:
Months have passed since I posted this, finally I've found a better approach to speed things up when working with big base64 strings. I now store base64 strings into the database (using longtext or longblog fields), then I pass its record ID through the jQuery File Download, finally on the download script file I query the database using this ID to pull the base64 string and pass it through the download function.
Download Script Example:
<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64 = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>
I know this is way beyond what the OP asked, however I felt it would be good to update my answer with my findings. When I was searching for solutions to my problem, I read lots of "Download from AJAX POST data" threads which didn't give me the answer I was looking for, I hope this information helps someone looking to achieve something like this.
Here is my solution using a temporary hidden form.
//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();
//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});
//Make it part of the document and submit
$('body').append(form);
form.submit();
//Clean up
form.remove();
Note that I massively use JQuery but you can do the same with native JS.
I want to point out some difficulties that arise when using the technique in the accepted answer, i.e. using a form post:
You can't set headers on the request. If your authentication schema involves headers, a Json-Web-Token passed in the Authorization header, you'll have to find other way to send it, for example as a query parameter.
You can't really tell when the request has finished. Well, you can use a cookie that gets set on response, as done by jquery.fileDownload, but it's FAR from perfect. It won't work for concurrent requests and it will break if a response never arrives.
If the server responds with a error, the user will be redirected to the error page.
You can only use the content types supported by a form. Which means you can't use JSON.
I ended up using the method of saving the file on S3 and sending a pre-signed URL to get the file.
As others have stated, you can create and submit a form to download via a POST request. However, you don't have to do this manually.
One really simple library for doing exactly this is jquery.redirect. It provides an API similar to the standard jQuery.post method:
$.redirect(url, [values, [method, [target]]])
This is a 3 years old question but I had the same problem today. I looked your edited solution but I think that it can sacrifice the performance because it has to make a double request. So if anyone needs another solution that doesn't imply to call the service twice then this is the way I did it:
<form id="export-csv-form" method="POST" action="/the/path/to/file">
<input type="hidden" name="anyValueToPassTheServer" value="">
</form>
This form is just used to call the service and avoid to use a window.location(). After that you just simply have to make a form submit from jquery in order to call the service and get the file. It's pretty simple but this way you can make a download using a POST. I now that this could be easier if the service you're calling is a GET, but that's not my case.
I used this FileSaver.js. In my case with csv files, i did this (in coffescript):
$.ajax
url: "url-to-server"
data: "data-to-send"
success: (csvData)->
blob = new Blob([csvData], { type: 'text/csv' })
saveAs(blob, "filename.csv")
I think for most complicated case, the data must be processed properly. Under the hood FileSaver.js implement the same approach of the answer of Jonathan Amend.
see: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
it'll return a blob as a response, which can then be put into filesaver
Here is my solution, gathered from different sources:
Server side implementation :
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
// Set headers
response.setHeader("content-disposition", "attachment; filename =" + fileName);
response.setContentType(contentType);
// Copy file to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
try (InputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, servletOutputStream);
} finally {
servletOutputStream.flush();
Utils.closeQuitely(servletOutputStream);
fileToDownload = null;
}
Client side implementation (using jquery):
$.ajax({
type: 'POST',
contentType: 'application/json',
url: <download file url>,
data: JSON.stringify(postObject),
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
},
success: function(message, textStatus, response) {
var header = response.getResponseHeader('Content-Disposition');
var fileName = header.split("=")[1];
var blob = new Blob([message]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
}
});
Below is my solution for downloading multiple files depending on some list which consists of some ids and looking up in database, files will be determined and ready for download - if those exist.
I am calling C# MVC action for each file using Ajax.
And Yes, like others said, it is possible to do it in jQuery Ajax.
I did it with Ajax success and I am always sending response 200.
So, this is the key:
success: function (data, textStatus, xhr) {
And this is my code:
var i = 0;
var max = 0;
function DownloadMultipleFiles() {
if ($(".dataTables_scrollBody>tr.selected").length > 0) {
var list = [];
showPreloader();
$(".dataTables_scrollBody>tr.selected").each(function (e) {
var element = $(this);
var orderid = element.data("orderid");
var iscustom = element.data("iscustom");
var orderlineid = element.data("orderlineid");
var folderPath = "";
var fileName = "";
list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
});
i = 0;
max = list.length;
DownloadFile(list);
}
}
Then calling:
function DownloadFile(list) {
$.ajax({
url: '#Url.Action("OpenFile","OrderLines")',
type: "post",
data: list[i],
xhrFields: {
responseType: 'blob'
},
beforeSend: function (xhr) {
xhr.setRequestHeader("RequestVerificationToken",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
success: function (data, textStatus, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
else {
getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
}
i = i + 1;
if (i < max) {
DownloadFile(list);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
},
complete: function () {
if(i===max)
hidePreloader();
}
});
}
C# MVC:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
{
byte[] file = null;
try
{
if (model != null)
{
//code for getting file from api - part is missing here as not important for this example
file = apiHandler.Get<byte[]>(downloadApiUrl, token);
var contentDispositionHeader = new System.Net.Mime.ContentDisposition
{
Inline = true,
FileName = fileName
};
// Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
Response.Headers.Add("Content-Type", "application/pdf");
Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
Response.Headers.Add("Content-Transfer-Encoding", "binary");
Response.Headers.Add("Content-Length", file.Length.ToString());
}
}
catch (Exception ex)
{
this.logger.LogError(ex, "Error getting pdf", null);
return Ok();
}
return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
As long as you return response 200, success in Ajax can work with it, you can check if file actually exist or not as the line below in this case would be false and you can inform user about that:
if (disposition && disposition.indexOf('attachment') !== -1) {
To get Jonathan Amends answer to work in Edge I made the following changes:
var blob = typeof File === 'function'
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
to this
var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
I would rather have posted this as a comment but I don't have enough reputation for that
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
If response is an Array Buffer, try this under onsuccess event in Ajax:
if (event.data instanceof ArrayBuffer) {
var binary = '';
var bytes = new Uint8Array(event.data);
for (var i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
$("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
return;
}
where event.data is response received in success function of xhr event.
I needed a similar solution to #alain-cruz's one, but in nuxt/vue with multiple downloads. I know browsers block multiple file downloads, and I also have API which returns a set of csv formatted data.I was going to use JSZip at first but I needed IE support so here is my solution. If anyone can help me improve this that would be great, but it's working for me so far.
API returns:
data : {
body: {
fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
fileTwo: ""col1", "col2"..."
}
}
page.vue:
<template>
<b-link #click.prevent="handleFileExport">Export<b-link>
</template>
export default = {
data() {
return {
fileNames: ['fileOne', 'fileTwo'],
}
},
computed: {
...mapState({
fileOne: (state) => state.exportFile.fileOne,
fileTwo: (state) => state.exportFile.fileTwo,
}),
},
method: {
handleExport() {
//exportFileAction in store/exportFile needs to return promise
this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
.then(async (response) => {
const downloadPrep = this.fileNames.map(async (fileName) => {
// using lodash to get computed data by the file name
const currentData = await _.get(this, `${fileName}`);
const currentFileName = fileName;
return { currentData, currentFileName };
});
const response = await Promise.all(downloadPrep);
return response;
})
.then(async (data) => {
data.forEach(({ currentData, currentFileName }) => {
this.forceFileDownload(currentData, currentFileName);
});
})
.catch(console.error);
},
forceFileDownload(data, fileName) {
const url = window.URL
.createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${fileName}.csv`);
document.body.appendChild(link);
link.click();
},
}
I used Naren Yellavula's solution and got it working with few changes to the script, after trying several other solutions using jquery. But, jquery will not download a zip file properly. I can't unzip the file after download.
In my use case, I have to upload a zip file, which is unzipped in the Servlet, files are processed and zipped again before the zip file is downloaded to the client. This is what you need to do on client side.
$('#fileUpBtn').click(function (e){
e.preventDefault();
var file = $('#fileUpload')[0].files[0];
var formdata = new FormData();
formdata.append('file', file);
// Use XMLHttpRequest instead of Jquery $ajax to download zip files
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4 && xhttp.status === 200) {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
a.download = "modified_" + file.name;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
}
};
xhttp.open("POST", "<URL to Servlet>", true);
xhttp.responseType = 'blob';
xhttp.send(formdata);
});
<div class="form-group">
<label id="fileUpLabel" for="fileUpload"></label>
<input type="file" class="form-control" id="fileUpload" name="file" accept="" required/>
</div>
<button class="btn" type="submit" id="fileUpBtn"></button>

Can't properly send Request Body on the node.js version of XMLHttpRequest when doing POST request

I'm trying to make a PSOT request to the Microsoft Oauth2.0 token URL using NodeJS and the XMLHttpRequest library found here. However, I am having the problem, that I just can't send a proper Request body alongside with the Request. I have already tried using FormData from here, I tried URLSearchParams, and I tried making it a String in the way we know it from our Adress bar in GET Requests. Below you can see my Code from when I tried doing it in a GET URL form, and in the part that I made a commend, you can see my attempts of using FormData instead.
var xhr = new XMLHttpRequest();
xhr.open("POST", 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
/*var data = new FormData();
//var data = new URLSearchParams();
data.append('client_id', clientId);
data.append("grant_type", "authorization_code");
data.append("scope", "openid email profile");
data.append("code", code);
data.append("redirect_uri", "http://" + req.headers.host + req.url);
data.append("client_secret", secret);
Error message on this one: TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of FormData
Same message with URLSearchParams, except it received an instance of URLSearchParams
*/
var data = 'client_id=' + clientId;
data += '&grant_type=authorization_code';
data += '&scope=openid%20email%20profile';
data += '&code=' + code;
data += '&redirect_uri=' + encodeURIComponent("http://" + req.headers.host + req.url);
data += '&client_secret=' + secret;
//This one gives me an error message from Microsoft: {"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: ratherNotSay\r\nCorrelation ID: ratherNotSay\r\nTimestamp: 2020-02-10 10:37:36Z","error_codes":[900144],"timestamp":"2020-02-10 10:37:36Z","trace_id":"ratherNotSay","correlation_id":"ratherNotSay","error_uri":"https://login.microsoftonline.com/error?code=900144"}
//This must mean that the request body can not have been submitted in the right way.
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
console.log(xhr.status + "\n" + xhr.responseText + "\n");
}
};
xhr.send(data);
You can transform a URLSearchParams instance into a query string, like the one that you build manually, by using the toString() method.
I don't know if the Content-Type header is set to application/x-www-form-urlencoded by default in the node-XMLHTTPRequest version, but it couldn't hurt to set it manually.
const xhr = new XMLHttpRequest();
xhr.open("POST", 'https://login.microsoftonline.com/common/oauth2/v2.0/token');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
const data = new URLSearchParams();
data.append('client_id', clientId);
data.append("grant_type", "authorization_code");
data.append("scope", "openid email profile");
data.append("code", code);
data.append("redirect_uri", "http://" + req.headers.host + req.url);
data.append("client_secret", secret);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
console.log(xhr.status + "\n" + xhr.responseText + "\n");
}
};
xhr.send(data.toString());

Javascript binary string concatenation on form-data in XMLHttpRequest

I'm writing a Chrome extension, and need to build a custom Form data to upload a Zip file (I can't use a real HTML form) to a server (not mine).
I found a way to do it here -
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms/Sending_forms_through_JavaScript (last section - Dealing with binary data).
It works fine with clear text files, but I want to send Zip which is binary.
It seems that JavaScript can't handle concatenation of binary and non binary, and adds some 0Xc2 chars to my file )-:
I also found a solution at http://footle.org/2007/07/31/binary-multipart-posts-in-javascript/,
but it uses Components.classes["#mozilla.org/io/string-input-stream;1"], which is not usable in Chrome extension.
How can I concatenate a binary zip file with a string and upload it to a server?
function sendData(zipBinary) {
var XHR = new XMLHttpRequest();
var boundary = "----WebKitFormBoundary3n9vu9ZOkCPW4HAw";
var prefix = "";
var postfix = "";
prefix += "--" + boundary + "\r\n";
prefix += 'content-disposition: form-data; '
+ 'name="' + 'UploadedFile' + '"; '
+ 'filename="' + 'hello.zip' + '"\r\n';
prefix += 'Content-Type: ' + 'application/x-zip-compressed' + '\r\n';
prefix += '\r\n';
postfix += '\r\n';
// Once we are done, we "close" the body's request
postfix += "--" + boundary + "--";
postfix += '\r\n';
XHR.addEventListener('load', function(event) {
alert('Yeah! Data sent and response loaded.');
});
XHR.addEventListener('error', function(event) {
alert('Oups! Something goes wrong.');
});
XHR.open('POST', 'https://example.com');
XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);
XHR.send(prefix + zipBinary + postfix); // <----
}
Have you read the section about FormData in the article that you've linked? Because that is the best solution for sending files via the XMLHttpRequest API. The advantage of this API over string concatenation is that files can be uploaded in a streamed fashion, instead of having to be entirely being buffered in memory before being sent off.
Assuming that zipBinary is a Blob or File object, you can upload a file as follows:
function sendData(zipBinary) {
var xhr = new XMLHttpRequest();
var fd = new Formdata();
fd.append('hello.zip', zipBinary);
xhr.onload = function() {
// Request completed! Use xhr.status and/or xhr.responseText to
// check the server's response status code and response body.
};
xhr.onerror = function() {
// Aw. Network error.
};
xhr.open('POST', 'https://example.com/');
xhr.send(fd);
}
If zipBinary is not a Blob or File object, but a string of binary data, then you can convert it to a Blob with a specified MIME-type as follows:
function sendData(zipBinary) {
var zipBinaryBytes = new Uint8Array(zipBinary.length);
for (var i = 0; i < zipBinary.length; ++i) {
zipBinaryBytes[i] = zipBinary.charCodeAt(i);
}
zipBinary = new Blob([zipBinaryBytes], { type: 'application/zip' });
// rest of code as I suggested above...
(note: application/zip is the official MIME-type for zip files, not application/x-zip-compressed)
If you are an expert of the HTTP protocol and really want to write the HTTP request body fully by hand, then you need to send a typed array instead of a string, because the XMLHttpRequest API will encode a regular string as UTF-8 before passing off the data to the server. This can be done using the TextEncoder API since Chrome 38+, applied to your code as follows:
// Was: XHR.send(prefix + zipBinary + postfix);
// New
XHR.send(new TextEncoder().encode(prefix + zipBinary + postfix));
Note: You rarely need to construct the request body by hand. Unless you know what you're doing, having to manually construct the request body is usually an indicator that you are doing it wrong.

Share canvas on Facebook

I have a site where the user can design smartphone cases. At one point the user should be able to share the design on Facebook, including the design. I have the object as well as a , with the 'style' set as the data URI of the canvas.
The code for custom image while sharing is:
How would I go to share it with my image, as it's a data URI.
Thanks
UPDATE:
I now have the canvas saved down on the server, linked correctly. Although, I can't seem to be able to edit the "href" of the link Facebook read the thumbnail picture from.
I tried:
var fblink = document.getElementById("facebook_share");
fblink.href="http://example.com/image.png";
AND
fblink.setAttribute("href", "http://example.com/image.png");
None seem to work. The 'fblink' object is correct as I can read the 'rel' etc.
I personally used canvas.toDataURL() which generates a base64 encoded URL of your canvas content.
After that I decoded the URL using the following command Base64Binary.decode(encodedPng)
Once you have your decoded image you can put it in a form and send all that through an XMLHttpRequest object as specified in the code below:
// Random boundary defined to separate element in form
var boundary = '----ThisIsTheBoundary1234567890';
// this is the multipart/form-data boundary we'll use
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="source"; filename="' + filename + '"\r\n';
formData += 'Content-Type: ' + mimeType + '\r\n\r\n';
// let's encode our image file
for ( var i = 0; i < imageData.length; ++i ) {
formData += String.fromCharCode( imageData[ i ] & 0xff );
}
formData += '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="message"\r\nContent-Type: text/html; charset=utf-8\r\n\r\n';
formData += message + '\r\n'
formData += '--' + boundary + '--\r\n';
// Create a POST XML Http Request
var xhr = new XMLHttpRequest();
xhr.open( 'POST', 'https://graph.facebook.com/me/photos?access_token=' + authToken, true );
// Call back function if POST request succeed or fail
xhr.onload = xhr.onerror = function() {
if ( !(xhr.responseText.split('"')[1] == "error") ) {
// If there is no error we redirect the user to the FB post she/he just created
var userID = xhr.responseText.split('"')[7].split('_')[0];
var postID = xhr.responseText.split('"')[7].split('_')[1];
w = window.open('https://www.facebook.com/'+userID+'/posts/'+postID,
'width=1235,height=530');
}
else {
alert("Erreur: "+xhr.responseText);
}
};
xhr.setRequestHeader( "Content-Type", "multipart/form-data; boundary=" + boundary );
// Attach the data to the request as binary
xhr.sendAsBinary( formData );
You can see a full working example on my Github project in the file maskToFb.html

post xml as a binary file using javascript

I'm trying to write something that can post xml, as a binary file to an external URL (which I have no control over) in JavaScript.
I have YUI3 available to me. And probably jQuery if I needed it.
Any ideas? Everything I've looked at seems to be about receiving xml, rather than posting it.
Edit: The external url is an advertising bot, the xml essentially describes what sort of ad I want to get back.
I'm forced to post as binary. I've tested using-
<form enctype="multipart/form-data" action="http://something.com" method="post">
<input name="anything" type="file">something</file>
<input type="submit">
</form>
and that works. I just need to implement in js.
Edit #2
My solution (couldn't get it formatted properly)-
var AdManager = {
getRandomBoundary : function(){
var today = new Date;
return '---' + today.getTime();
},
fetch : function(){
var boundary = this.getRandomBoundary();
var xhr = new XMLHttpRequest;
var CRLF = "\r\n";
xhr.open('POST', 'http://url.com', true);
xhr.onreadystatechange = function(){
if (xhr.readyState === 4)
{
//Parse xml(badly)
var splitter = xhr.responseText.split('<responsecontent>');
var allAds = '';
for (var i= 1; i< splitter.length; i++)
{
var tempAd = splitter[i].split('</responsecontent>');
allAds += tempAd[0];
}
//Html is returned encoded, so decode.
jQuery('#results').html(jQuery("<div/>").html(allAds).text());
}
};
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
var mimeReq = "--" + boundary + CRLF;
mimeReq += 'Content-Disposition: form-data; name="arbitrary"; filename="arbitrary.xml"' + CRLF;
mimeReq += 'Content-Type: application/octet-stream' + CRLF + CRLF;
mimeReq += '<?xml version=\"1.0\" encoding=\"utf-8\"?><adrequestpacket responsemarkup=\"wml\" test=\"0\" xmlns=...'+ CRLF;
mimeReq += "--" + boundary + "--" + CRLF;
xhr.send(mimeReq);
}
};
`
I think I understand what your asking, but if I'm totally on the wrong track, the below may appear a little patronising, so apologies in advance...
If all you want to do is send an XML file to a known URL via AJAX its fairly simple in javascript with no lovelies like jQuery etc. I am assuming you have already generated the XML file and have it stored as string variable somewhere.
The below code is a bit messy and fairly basic, but hopefully it should point you in the right direction. There are probably better ways of fetching an AJAX object if you search for them, but this is a method I have used for ages and never really have any problems with.
You will need to write some code to parse the server response to determine whether data was accepted or not - see comments in code for where you would do this. The ajaxObj.status and ajaxObj.responseText properties will be your friends here.
function postXMLToServer (serverURL, xmlStr) {
// Declare some variables
var activeXModes, i, ajaxObj, aSync, contentType;
// Set this to false to perform the request synchronously (i.e. execution will block until request has completed)
aSync = true;
// 'application/octet-stream' is treated as raw binary data by any sensible server.
// It might make more sense to use 'text/xml' or some variant depending on your application
contentType = 'application/octet-stream';
// Fetch an AJAX object
activeXModes = ["Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
if (window.ActiveXObject) { // Try ActiveX (for IE)
for (i = 0; i < activeXModes.length; i++) {
try {
ajaxObj = new ActiveXObject(activeXModes[i]);
} catch (e) {}
}
} else if (window.XMLHttpRequest) { // For Mozilla, Safari etc
ajaxObj = new XMLHttpRequest();
} else { // No AJAX
alert('AJAX is not supported in your browser');
return;
}
// Open connection to server
ajaxObj.open('POST',serverURL,aSync);
// Set some request headers - you might be able to get away with not doing this, but it
// should be considered good practice, especially when doing POST requests
ajaxObj.setRequestHeader('Content-Type',contentType);
ajaxObj.setRequestHeader('Content-Length',xmlStr.length);
// Set a callback for asynchronous requests (not called if aSync = false)
ajaxObj.onreadystatechange = function () {
if (ajaxObj.readyState == 4) {
// parse the server response here
}
};
// Send the request
ajaxObj.send(xmlStr);
// if aSync = false, parse the server response here
}
// Example of how to use the function
var myXMLStr = '<?xml version="1.0" encoding="iso-8859-1" ?>\n<toplevel>\n<lowerlevel anattribute="a value">An inner value</lowerlevel>\n</toplevel>';
var myURL = 'http://sub.domain.tld/path/to/document.ext?getparameter=somevalue';
postXMLToServer(myURL,myXMLStr);
It's not entirely clear what you want. Everything in the computer is represented in binary. So when you post an XML document over to http://something.com, the it's the binary representation of the characters in the XML file that is being transmitted.

Categories