JPEG data obtained from FileReader doesn't match data in file - javascript

I'm trying to select a local JPEG file in the web browser via the HTML5 FileReader so I can submit it to a server without reloading the page. All the mechanics are working and I think I'm transferring and saving the exact data that JavaScript gave me, but the result is an invalid JPEG file on the server. Here's the basic code that demonstrates the problem:
<form name="add_photos">
​<input type=​"file" name=​"photo" id=​"photo" /><br />
​<input type=​"button" value=​"Upload" onclick=​"upload_photo()​;​" />​
</form>
<script type="text/javascript">
function upload_photo() {
file = document.add_photos.photo.files[0];
if (file) {
fileReader = new FileReader();
fileReader.onload = upload_photo_ready;
fileReader.readAsBinaryString(file);
}
}
function upload_photo_ready(event) {
data = event.target.result;
// alert(data);
URL = "submit.php";
ajax = new XMLHttpRequest();
ajax.open("POST", URL, 1);
ajax.setRequestHeader("Ajax-Request", "1");
ajax.send(data);
}
</script>
Then my PHP script does this:
$data = file_get_contents("php://input");
$filename = "test.jpg";
file_put_contents($filename, $data);
$result = imagecreatefromjpeg($filename);
That last line throws a PHP error "test.jpg is not a valid JPEG file." If I download the data back to my Mac and try to open it in Preview, Preview says the file "may be damaged or use a file format that Preview doesn’t recognize."
If I open both the original file on my desktop and the uploaded file on the server in text editors to inspect their contents, they are almost but not quite the same. The original file starts like this:
ˇÿˇ‡JFIFˇ˛;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90
But the uploaded file starts like this:
ÿØÿàJFIFÿþ;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90
Interestingly, if I view the data in a JavaScript alert with the commented-out line above, it looks just like the uploaded file's data, so it seems as if the FileReader isn't giving the correct data at the very beginning, as opposed to a problem that is introduced while transferring or saving the data on the server. Can anyone explain this?
I'm using Safari 6 and I also tried Firefox 14.
UPDATE: I just figured out that if I skip the FileReader code and change ajax.send(data) to ajax.send(file), the image is transferred and saved correctly on the server. So my problem is basically solved, but I'll award the answer points to anyone who can explain why my original approach with readAsBinaryString didn't work.

Your problem lies with readAsBinaryString. This will transfer the binary data byte-for-byte into a string, so that you will send a text string to your PHP file. Now a text string always has an encoding; and when you use XmlHttpRequest to upload a string, by default it will use UTF-8.
So each character, which was originally supposed to represent one byte, will be encoded as UTF-8... which uses multiple bytes for each character with a code point above 127!
Your best best is to use readAsArrayBuffer instead of readAsBinaryString. This will avoid all the character set conversions (that are necessary when dealing with strings).

Related

Uploading files using js fileReader results in corrupted files

I am trying to upload a file using js file reader and AJAX to my server.
I used FileAPI and fileReader to read the file and convert it to string and then send it to the server via an AJAX request.
Here is my client side js code :
function upload() {
var file = document.getElementById('periodExcel').files[0];
if (file) {
console.log(file);
var reader = new FileReader();
reader.readAsText(file, file.type);
console.log(reader);
reader.onload = uploadAndRun;
}
}
function uploadAndRun(event) {
console.log(event);
var result = event.target.result;
$('#js').html(result);
var fileName = document.getElementById('periodExcel').files[0].name; //Should be 'picture.jpg'
$.post('./upload.php', {data: result, name: fileName}, function(result2){
$('#php').html(result2);
});
}
Here is the upload php script:
file_put_contents('upload/' . $_POST['name'], $_POST['data']);
it just write the file using php file_put_contents function.
My problem is that the uploaded file is corrupted and has a different size than the original file (it is larger).
I tried to use php file_get_contents function to read the same file and write it again using file_put_contents and the result file was fine and same as the original one.
I then tried to compare the two strings (the one that comes from the file reader and the one that comes from file_get_contents ) and compares the two strings using strcmp, that gives me that the string that come from the fileReader is larger than the one comes from file_get_contents.
So, what is the problem with my code and how to use the FileReader to upload file in this way while using readAsText function.
You are using the wrong collection in PHP. To access uploaded file stream use $_FILES.
See here:
http://php.net/manual/en/reserved.variables.files.php
and here: http://php.net/manual/en/features.file-upload.post-method.php
In short, PHP runtime takes care of reading the upload stream from the HTTP request, stores it locally in a temp folder and exposes the above map for you to access the temp file and possibly move it to another location (or do whatever else you need to do with it).

JavaScript - load PDF file from URL (cross domain) into variable

I would like to load PDF file from URL into JavaScript variable (this file is on another domain) and then print the base64 encoded string of that file.
This script allows me to browse file on my computer and then it prints base64 string into browser console:
<input id="inputFile" type="file" onchange="convertToBase64();" />
<script type="text/javascript">
function convertToBase64() {
//Read File
var selectedFile = document.getElementById("inputFile").files;
//Check File is not Empty
if (selectedFile.length > 0) {
// Select the very first file from list
var fileToLoad = selectedFile[0];
// FileReader function for read the file.
var fileReader = new FileReader();
var base64;
// Onload of file read the file content
fileReader.onload = function(fileLoadedEvent) {
base64 = fileLoadedEvent.target.result;
// Print data in console
console.log(base64);
};
// Convert data to base64
fileReader.readAsDataURL(fileToLoad);
}
}
</script>
I would like to completely remove the input button from this script and pass my file to variable var selectedFile from URL (for example: http://www.example.com/docs/document.pdf).
I'd need a help how to realize this, because I am not sure if XMLHttpRequest() works cross domain and scripts I've found with Ajax/jQuery method operated mainly with JSON file, which is something different that I need.
Thank you very much for help.
You cannot do this in normal browser-based JavaScript* if the other side (http://www.example.com in your case) doesn't allow cross-origin requests from your origin.
If the other side does let you do this, then yes, you'd use XMLHttpRequest (or jQuery's wrappers for it, such as ajax or get) to request the data and transform/display it as you see fit.
A fairly typical way to work around that if the other side doesn't is to use your own server in-between: Make the request to your server, have it make the request to the other side (server-side code doesn't have the Same Origin Policy blocks that browsers impose), and then have your server respond to your request with the data from the other server.
* "normal browser-based JavaScript" - e.g., without starting the browser with special flags that disable security, or getting people to install an extension, etc.

How to reconstruct a blob for an image from a binary string (client side hidden form field) in Google Apps Script

I am trying to use Google HTMLService to process a form from the client side. The tricky part is that I want to capture a pasted image (from clip-board) in the form, send it to the server side and save it in the DriveApp.
On the client side, I think I was able to capture the pasted image based on this page: http://joelb.me/blog/2011/code-snippet-accessing-clipboard-images-with-javascript/
And my client side code looks like this:
function pasteHandler(e) {
   // We need to check if event.clipboardData is supported (Chrome)
   if (e.clipboardData) {
      // Get the items from the clipboard
      var items = e.clipboardData.items;
      if (items) {
         // Loop through all items, looking for any kind of image
         for (var i = 0; i < items.length; i++) {
            if (items[i].type.indexOf("image") !== -1) {
               // We need to represent the image as a file,
               var blob = items[i].getAsFile();
               // and use a URL or webkitURL (whichever is available to the browser)
               // to create a temporary URL to the object
               var URLObj = window.URL || window.webkitURL;
               var source = URLObj.createObjectURL(blob);
var reader = new FileReader();
reader.onload = function(e) {
document.getElementById("summaryImage").value= reader.result;
}
reader.readAsBinaryString(blob);
               // The URL can then be used as the source of an image
               createImage(source);
            }
         }
      }
   // If we can't handle clipboard data directly (Firefox),
   // we need to read what was pasted from the contenteditable element
   }
}
So the change is only that I used a FileReader to read the blob and save it into a hidden field in the form.
<input type="hidden" name="summaryImage" id='summaryImage' value=''>
I think this part is working fine, as I could display the image in the client side and the hidden field is also populated with a byte stream.
What I am unsure about is how to pick up the value of the hidden field, now being a "Binary String" and save it as a proper PNG file in Google Drive and / or view it.
On the server side, I was able to save any uploaded files (using input file form element), but note the binary string passed by the hidden field.
I have tried things like below:
blob = form.summaryImage;
DriveApp.createFile('TRIFILE2.png', blob, 'image/png');
This does create a file, but cannot be viewed as png. If I open it in Google Drive, it shows as below. I suppose it knows this is a PNG file, but what's the correct way to convert it?
‰PNG
IHDR y 2 Þ R IDATx í]mLTYš~Øl€I ÃF ´šX ‰|Dc H(œ TÿàcÒÅPkuÂGÿ°è B'*ì: èHÖ =ÂàØà¸É”ýc¨ùa &–M75i„ ]0ÓR&’¢W£`RZ› ÅA·
LÒ`²¹›s?ªî­ïÂ[x©{ªÛÔ­óñž÷}žûž{îåT=i×JJ ÐWJ# Æ\ %9¥) þ!Åã£á ’¬Š“€f²
h¦$S’U€€
B¤™LIV * ‘f2%Y ¨ DšÉ”d ‚ i&S’U€€
B¤™LIV * ‘f2%Y ¨ DšÉ”d ‚ i&S’U€€
B¤™LIV * ‘f2%Y ¨ DšÉ”d ‚ i&S’U€€
B¤™LIV * ‘f2%Y ¨ DšÉ”d ‚ i&S’U€€
B¤™LIV * 1 a ú;^)N4 ®Sœ` %™’¬ T "ÍdJ²
PAˆ4“)É*## !ÒL¦$« „H3™’¬ T "ÍdJ²
PAˆ4“)É*## !ÒL¦$« „H3™’¬ T "ÍdJ²
PAˆ4“)É*## !ÒL¦$‹ pc -
I also tried Utilities.newBlob() function, but cannot figure out how to make a correct Blob from the binary string.
Don't use readAsBinaryString, instead use readAsDataURL, which gets a base64, and you won't have that mess of chars.
If you need the server side code you can follow my code and Sandy Good tutorial here:
Uploading Multiple Files to Google Drive with Google App Script

Javascript -> Download CSV file encoded in ISO-8859-1 / Latin1 / Windows-1252

I have hacked together a small tool to extract shipping data from Amazon CSV order data. it works so far. here is a simple version as JS Bin: http://output.jsbin.com/jarako
For printing stamps/shipping labels, I need a file for uploading to Deutsche Post and to other parcel services. I used a small function saveTextAsFile which i found on stackoverflow. Everything good so far. No wrong displayed special characters (äöüß...) in the output textarea or downloaded files.
All these german post / parcel services sites accept only latin1 / iso-8859-1 encoded files for upload. But my downloaded file is always utf-8. If i upload it, all special characters (äöüß...) go wrong.
How can i change this? I still searched a lot. I have tried i.e.:
Setting the charset of the tool to iso-8859-1:
<META http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
But the result is: Now I have wrong special characters still in the output textarea and in the downloaded file. If I upload it to the post site, I still get more wrong characters. Also if I check the encoding in CODA Editor it still says the downloaded file is UTF-8.
The saveTextAsFile function uses var textFileAsBlob = new Blob([textToWrite], {type:'text/plain'});. May be there is a ways to set the charset for download there!?
function saveTextAsFile()
{
var textToWrite = $('#dataOutput').val();
var textFileAsBlob = new Blob([textToWrite], {type:'text/plain'});
var fileNameToSaveAs = "Brief.txt";
var downloadLink = document.createElement("a");
downloadLink.download = fileNameToSaveAs;
downloadLink.innerHTML = "Download File";
if (window.webkitURL != null)
{
// Chrome allows the link to be clicked
// without actually adding it to the DOM.
downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
}
else
{
// Firefox requires the link to be added to the DOM
// before it can be clicked.
downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
downloadLink.onclick = destroyClickedElement;
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
}
downloadLink.click();
}
Anyhow, there have to be a way to download files in other encoding as the site uses itself. The Amazon site, where i download the CSV file from is UTF-8 encoded. But downloaded CSV file from there is Latin1 (iso-8859-1) if i check it in CODA...
SCROLL DOWN TO THE UPDATE for the real solution!
Because I got no answer, I have searched more and more. It looks like there is NO SOLUTION in Javascript. Every test download I'v made, which was generated in javascript was UTF-8 encoded. Looks like Javascript is only made for UNICODE / UTF-8 or an other encoding would (possibly) only apply if the data would be transported again using a former HTTP transport. But for a Javascript, which runs on the client no additional HTTP transport happens, because the data is still on the client..
I have helped me now with building a small PHP Script on my server, to which i send the Data via GET or POST request. It converters the encoding to latin1 / ISO-8859-1 and downloads it as file. This is a ISO-8859-1 file with correctly encoded special characters, which I can upload to the mentioned postal and parcel service sites and everything looks good.
latin-download.php: (It is VERY IMPORTANT to save the PHP file itself also in ISO-8859-1, to make it work!!)
<?php
$decoded_a = urldecode($_REQUEST["a"]);
$converted_to_latin = mb_convert_encoding($decoded_a,'ISO-8859-1', 'UTF-8');
$filename = $_REQUEST["filename"];
header('Content-Disposition: attachment; filename="'.$filename.'"; content-type: text/plain; charset=iso-8859-1;');
echo $converted_to_latin;
?>
in my javascript code i use:
<a id="downloadlink">Download File</a>
<script>
var mydata = "this is testdata containing äöüß";
document.getElementById("downloadlink").addEventListener("click", function() {
var mydataToSend = encodeURIComponent(mydata);
window.open("latin-download.php?a=" + mydataToSend + "&filename=letter-max.csv");
}, false);
</script>
for bigger amounts of data you have to switch from GET to POST...
UPDATE 08-Feb-2016
A half year later now i have found a solution in PURE JAVASCRIPT. Using inexorabletash/text-encoding. This is a polyfill for Encoding Living Standard. The standard includes decoding of old encodings like latin1 ("windows-1252"), but it forbids encoding into these old encoding types. So if you use the browser implemented window.TextEncoder function it does offer only UTF encoding. BUT, the polyfill solution offers a legacy mode, which does ALLOW also encoding into old encodings like latin1.
i use it like that:
<!DOCTYPE html>
<script>
// 'Copy' browser build in TextEncoder function to TextEncoderOrg (because it can NOT encode windows-1252, but so you can still use it as TextEncoderOrg() )
var TextEncoderOrg = window.TextEncoder;
// ... and deactivate it, to make sure only the polyfill encoder script that follows will be used
window.TextEncoder = null;
</script>
<script src="lib/encoding-indexes.js"></script> // needed to support encode to old encoding types
<script src="lib/encoding.js"></script> // encording polyfill
<script>
function download (content, filename, contentType) {
if(!contentType) contentType = 'application/octet-stream';
var a = document.createElement('a');
var blob = new Blob([content], {'type':contentType});
a.href = window.URL.createObjectURL(blob);
a.download = filename;
a.click();
}
var text = "Es wird ein schöner Tag!";
// Do the encoding
var encoded = new TextEncoder("windows-1252",{ NONSTANDARD_allowLegacyEncoding: true }).encode(text);
// Download 2 files to see the difference
download(encoded,"windows-1252-encoded-text.txt");
download(text,"utf-8-original-text.txt");
</script>
The encoding-indexes.js file is about 500kb big, because it contains all the encoding tables. Because i need only windows-1252 encoding, for my use i have deleted the other encodings in this file. so now there are only 632 byte left.
The problem is not the encoding but the fact that the special characters are displayed wrong in some applications, e.g. Microsoft Excel. UTF-8 is fine for displaying all special German characters. You can fix the problem by adding a Byte order mark (BOM) in front of the csv.
const BOM = "\uFEFF"
let csvData = BOM + csvData
const blob = new Blob([csvData], { type: "text/csv;charset=utf-8" });
Solution based on this github post
You cannot force a web server to send you data in a given encoding, only ask it politely. Your approach to just convert to the format you need is the right way to go.
If you wanted to avoid the PHP script, you may have luck specifying the encoding as a parameter when creating your Blob:
var textFileAsBlob = new Blob(textToWrite, {
type: 'text/plain;charset=ISO-8859-1',
encoding: "ISO-8859-1"
});
See Specifying blob encoding in Google Chrome for more details.

How to check if the file is having the same type as of its extension?

I want to build Javascript code which checks for the file type.In the web application which I am creating allows user to upload document files viz, doc, xls, ppt, docx, xlsx, pptx, txt, rar, zip, pdf, jpg, png, gif, jpeg, odt, but it should not allow other files. I can't check just extension name in file name. As user may change it.
I tried checking content-type but it is also getting changed everytime. Suggestions are appreciated.
In "modern" browsers (IE10+, Firefox 4+, Chrome 7+, Safari 6.0.2+ etc.), you could use the File/FileReader API to read the contents of the file and parse it client-side. E.g. (example, not production code):
var fileInput = /* Your <input type="file"> element */
fileInput.addEventListener("change", function(e) {
var file = e.currentTarget.files[0];
var reader = new FileReader();
reader.onload = fileLoaded;
reader.readAsArrayBuffer(file);
});
function fileLoaded(e)
{
var arrayBuffer = e.currentTarget.result;
// 32 indicates that we just want a look at the first 32 bytes of the buffer.
// If we don't specify a length, we get the entire buffer.
var bytes = new Uint8Array(arrayBuffer, 0, 32);
// Now we can check the content, comparing to whatever file signatures we
// have, e.g.:
if (bytes[0] == 0x50 &&
bytes[1] == 0x4b &&
bytes[2] == 0x03 &&
bytes[3] == 0x04)
{
// This is most likely docx, xlsx, pptx or other zip file.
}
}
http://jsfiddle.net/35XfG/
Note, however, that e.g. a .zip doesn't have to start with 50 4b 03 04. So, unless you spend quite a bit of time looking into different file signatures (or find some library that already did this), you're likely to be rejecting files that might actually be valid. Of course, it's also possible that it will give false positives.
False positives don't matter that much in this case, though - because this is only useful as a user friendly measure to check that the user isn't uploading files that will be rejected by the server anyway. The server should always validate what it ends up getting sent.
Of course, reading the entire file to look at the first few bytes isn't all that efficient either. :-) See Ray Nicholus' comment about that.
Unless you can actually parse the content and by the results tell whether the file is of a certain type, I don't see a good way of doing that with pure JS. You might want to consider to upload the file to the sever temporarily, and then perform the check on the server. The unix file command is a very useful tool for that. It does not rely on file extensions, but uses the file content to analyze the file type.

Categories

Resources