Safari 10 on Mac not uploading file in XHR - javascript

I'm using XHR to upload an image to an external server which has CORS enabled.
everything works fine in Chrome, Firefox and IE.
But using Safari, server response with mime type error. saying the file type is 'application/octet-stream' while it should be 'image/*'.
After I disabled mime type checking, safari can upload file but its all 0b empty file.
anyone knows why?
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://up-z1.qiniu.com/', true);
var formData;
formData = new FormData();
formData.append('key', file.name);
formData.append('token', acessToken);
formData.append('file', file);
xhr.onreadystatechange = function (response) {
if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != "") {
callback(true,null);
} else if (xhr.status != 200 && xhr.responseText) {
callback(false,null);
}
};
xhr.send(formData);

So according to another stackoverflow question, I cant remember which one
Safari has a bug that when other browsers do file.toBlob(), safari will do file.toString(). So the workaround would be write your own file to blob function and upload that blob.
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://up-z1.qiniu.com/', true);
var formData;
formData = new FormData();
formData.append('key', file.name);
formData.append('token', acessToken);
formData.append('file', fileToBlob(file));
xhr.onreadystatechange = function (response) {
if (xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != "") {
callback(true,null);
} else if (xhr.status != 200 && xhr.responseText) {
callback(false,null);
}
};
xhr.send(formData);

This is a very weird one. I came to the same conclusion as +arslan2012 .
For anyone stuck on the fileToBlob function he refers to, here's what I am using
const readFileContents = function(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = function(evt) {
resolve(evt.target.result)
};
reader.onerror = function() {
reader.abort()
reject(new DOMException("Unable to read the file."))
}
reader.readAsArrayBuffer(file);
})
}

Related

Internet explorer throws TypeMismatchError at POST upload request with large body

I have few files (~30Mb every) that needs convert to base64 and upload for server. After uploading part of files IE11 throws TypeMismatchError. File content is a base64 string that it is not encoding problem. Panel of network requests do not contents it, request fails before sending. Another browsers are working without errors. How to fix it?
function post(url, data, timeout) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = (result) => {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
resolve(xhr.responseText);
}
};
xhr.onerror = function (event) {
reject(event);
};
xhr.timeout = timeout;
xhr.send(data);
});
}
function handleFileSelect() {
post('/upload', LARGE_FILE_DATA_BASE64).catch(error => {
// Throws TypeMismatchError error after few uploads.
});
}
Google says about this similar problem only here: https://helperbyte.com/questions/276626/jquery-deferrer-typemismatcherror-when-you-bulk-load-data-cant-find-what-this-might-mean

XHR formdata ionic application

I have a weird act with xhr -> form-data:
I develop an Ionic application that complied to Android & IOS.
I'm trying to upload a file using blob and formdata xhr and the act is:
IOS - works great.
Android - works great in some Android versions, but devices such as LG & Xiaomi aren't response at all.
here is my code:
const uploadInvoice = async(data, type = "expenses", file, cb) => {
if (type === "incomes") {
delete data.budget;
}
data.invoicedate = data.date;
data.title = encodeURI(data.title);
const formData = new FormData();
const extention = file.name.split(".").pop();
const newFileModified = new File(
[file],
`${Date.now().toString()}.${extention}`
);
formData.append("file", newFileModified);
let xhr = new XMLHttpRequest();
xhr.open("POST", `${BASE_URL}/api/management/file`);
Object.keys(data).map(key => {
xhr.setRequestHeader(key, data[key]);
});
xhr.setRequestHeader("client", "app");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
//if complete
if (xhr.status === 200) {
return cb(true);
} else {
return cb(false); //otherwise, some other code was returned
}
} else {
return cb(false);
}
};
xhr.send(formData);
};
note that I dispatch http request (and not https) but I have the right permissions to upload an http request.
I went over the internet to find a solution without success :-/
thanks!!

Create an HTTP request after btoa() is complete in javascript

I am trying to send a file as a base64 string to a server. The server requires that the data is sent as an HTTP 'POST' with the information is JSON. However, in my code, the request is sent before the string is converted. Please advise on what error I am making in my code.
This is the function which converts the file
function handleFileSelect(evt) {
var files = evt.files;
var file = files[0];
var string = '';
if (files && file)
{
var reader = new FileReader();
reader.onload = function(readerEvt)
{
var binaryString = readerEvt.target.result;
string = btoa(binaryString);
};
reader.readAsBinaryString(file);
}
create_HTTP_request(string);};
This is my HTTP request function(s)
function create_HTTP_request(string)
{
runTask('task_required_by_server', {'file' : string}, format_data);
};
function runTask(task_name, inputs, callback){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
console.log('Request Successful!');
callback(xmlhttp.responseText);
console.log(xmlhttp.responseText);
}
else if(xmlhttp.readyState == 4 && xmlhttp.status == 500)
{
console.log(xmlhttp.responseText);
}
else {
console.log(xmlhttp.responseText);
}
}
xmlhttp.open("POST", '/api/v1/jobs', true);
xmlhttp.setRequestHeader('Content-Type', 'application/json');
xmlhttp.send(JSON.stringify({name: task_name, input: inputs}));
};
I am new to Javascript. I basically want to call my create_HTTP_request function only after the btoa() function is complete. Thanks for your time!

How to download and then upload the file?

Tried to use the following code, but it doesn't work properly:
// download the file first
var req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
// upload the file
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", req.responseText.length);
req.sendAsBinary(req.responseText); // What should I pass here?
if (req.status != 200) return '';
return req.responseText;
sendAsBinary is firefox function.
Upd. Also I've tried to upload that as part of the form:
var response = req.responseText;
var formData = new FormData();
formData.append("file", response);
req.open("POST", "http://mysite.com/upload", false);
req.send(formData);
But still not full data is received by the server.
Finally I've used the approach with temp file:
var downloadCompleted = false;
// download the file first
var persist = Components.classes["#mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(Components.interfaces.nsIWebBrowserPersist);
// get OS temp folder
var file = Components.classes["#mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsIFile);
file.append("temp.ext");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
var fURI = Services.io.newURI(url,null,null);
const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
persist.progressListener = {
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
},
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
downloadCompleted = true; // file has been downloaded
}
}
}
persist.saveURI(fURI, null, null, null, "", file);
var thread = Components.classes["#mozilla.org/thread-manager;1"]
.getService(Components.interfaces.nsIThreadManager)
.currentThread;
while (!downloadCompleted) // emulate synchronous request, not recommended approach
thread.processNextEvent(true);
// upload the file
var stream = Components.classes["#mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
stream.init(file, 0x04 | 0x08, 0644, 0x04); // file is an nsIFile instance
// try to determine the MIME type of the file
var mimeType = "text/plain";
try {
var mimeService = Components.classes["#mozilla.org/mime;1"]
.getService(Components.interfaces.nsIMIMEService);
mimeType = mimeService.getTypeFromFile(file); // file is an nsIFile instance
}
catch(e) { /* just use text/plain */ }
var req = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
req.open('POST', "http://mysite.com/upload", false);
req.setRequestHeader('Content-Type', mimeType);
req.send(stream);
// delete the file
file.remove(false);
You need to store the responseText in an intermediate variable before reusing the req object.
// download the file first
var req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
var response = req.responseText;
// upload the file
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", response.length);
req.sendAsBinary(response);
if (req.status != 200) return '';
return req.responseText;
Update
Per the MDN page Using XMLHttpRequest, it looks like the above code won't work. Following is the proper way to get the binary response. In the end, you will have an array of unsigned integers which you could send back to the server and convert to binary. I think.
//req.responseType is only defined for FF6+
req.responseType = "arraybuffer";
req.send(null);
//req.response is for FF6+, req.mozResponseArrayBuffer is for FF < 6
var buffer = req.mozResponseArrayBuffer || req.response;
if (buffer) {
var byteArray = new Uint8Array(buffer);
}
Update 2
To submit the byteArray to a server, I would try something like the following untested, almost guaranteed not to work code.
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", byteArray.length);
//if this doesn't work, try byteArray.buffer
//if byteArray.buffer works, try skipping 'var byteArray = new Uint8Array(buffer);' altogether and just sending the buffer directly
req.send(byteArray);
Update 3
Could Using XMLHttpRequest from JavaScript modules / XPCOM components have anything to do with your issue?

BlobBuilder ruins binary data

I have a problem with BlobBuilder (Chrome11)
I try to obtain an image from server with XHR request. Then i try to save it to local FS with BlobBuilder / FileWriter. Every example on the internet is about working with text/plain mime type and these examples work fine. But when i try to write binary data obtained with XHR, file size becomes about 1.5-2 times bigger than the original file size. And it cannot be viewed in Picasa / Eye Of Gnome.
var xhr = new XMLHttpRequest();
var photoOrigUrl = 'http://www.google.ru/images/nav_logo72.png';
xhr.open('GET', photoOrigUrl, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var contentType = xhr.getResponseHeader('Content-type');
fsLink.root.getFile('nav_logo72.png', {'create': true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var BlobBuilderObj = new (window.BlobBuilder || window.WebKitBlobBuilder)();
BlobBuilderObj.append(xhr.responseText);
fileWriter.write(BlobBuilderObj.getBlob(contentType));
}, function(resultError) {
console.log('writing file to file system failed ( code ' + resultError.code + ')');
});
});
}
}
xhr.send();
fsLink exists, this is extension.
The problem is that BlobBuilder.append(xhr.responseText) is detecting its argument as a UTF-8 string, which is what XHR returns, and not binary data, which is what it really is. There's a couple of tricks to get the BlobBuilder reading it as binary data instead of string data:
var xhr = new XMLHttpRequest();
var photoOrigUrl = 'http://www.google.ru/images/nav_logo72.png';
xhr.open('GET', photoOrigUrl, true);
// CHANGE 1: This stops the browser from parsing the data as UTF-8:
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var contentType = xhr.getResponseHeader('Content-type');
fsLink.root.getFile('nav_logo72.png', {'create': true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
// CHANGE 2: convert string object into a binary object
var byteArray = new Uint8Array(xhr.response.length);
for (var i = 0; i < xhr.response.length; i++) {
byteArray[i] = xhr.response.charCodeAt(i) & 0xff;
}
var BlobBuilderObj = new (window.BlobBuilder || window.WebKitBlobBuilder)();
// CHANGE 3: Pass the BlobBuilder an ArrayBuffer instead of a string
BlobBuilderObj.append(byteArray.buffer);
// CHANGE 4: not sure if it's needed, but keep only the necessary
// part of the Internet Media Type string
fileWriter.write(BlobBuilderObj.getBlob(contentType.split(";")[0]));
}, function(resultError) {
console.log('writing file to file system failed ( code ' + resultError.code + ')');
});
});
}
}
xhr.send();
This gave me a file with the same length as what xhr.getResponseHeader('Content-Length') suggests it should have been.
You can use xhr.responseType='arraybuffer' though:
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var bb = new BlobBuilder();
bb.append(this.response); // Note: not xhr.responseText
var blob = bb.getBlob('image/png');
...
}
};
xhr.send();
I think Stoive is spot on but I want to point out that instead of BlobBuilder there is now Blob constructor available that will do the trick
var b = new Blob([byteArray.buffer], {'type': 'application/type'});
I think this is more in keeping with current standards. Thanks much Stoive, very helpful.
Btw XHR2 sets a better way for implementing my task:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.google.ru/images/nav_logo72.png', true);
xhr.responseType = 'blob';
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// xhr.responseBlob is needed blob data
}
}
xhr.send();
The only disappointment is that this is still a bug in Chrome: http://code.google.com/p/chromium/issues/detail?id=52486
XMLHttpRequest cannot load http://www.google.ru/images/nav_logo72.png. Origin file:// is not allowed by Access-Control-Allow-Origin.

Categories

Resources