Update: I created an issue: https://issuetracker.google.com/issues/150675170
My web app worked well for a long time, but now suddenly the uploaded pdf files become corrupted. Below is a small reduced example that can be used to reproduce the issue.
Looking at the uploaded file content, it looks like the file content is treated as text, and several characters are replaced with EF BF BD
, which is the UTF-8 byte sequence for 'REPLACEMENT CHARACTER' (U+FFFD).
For example, first bytes of the original PDF file:
25 50 44 46 2D 31 2E 34 0A 25 E2 E3 CF D3 0A 31 39 | %PDF-1.4\n%âãÏÓ\n19
It got uploaded as:
25 50 44 46 2D 31 2E 34 0A 25 EF BF BD EF BF BD EF BF BD EF BF BD 0A 31 39 | %PDF-1.4\n%����\n19
I'm not sure where to report it, I only hope that a Google employee will see it and fix it.
In the meanwhile, perhaps somebody familiar with Google Apps Script has an idea for a workaround.
Below is the small reduced example - deploy, upload a binary file, go to Drive, find it under the "test" folder, download it, observe that it's broken.
HTML template, file name test_form.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form id="test-form">
<input type="file" id="test-file" name="test-file">
<button id="submit-button" type="submit">Upload</button>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
(function () {
'use strict';
$('#test-form').submit(function (e) {
e.preventDefault(); // prevent form from submitting
google.script.run
.withFailureHandler(fileUploadedFailure)
.withSuccessHandler(fileUploaded)
.uploadFilesFrame(this);
});
function fileUploaded(status) {
alert(status);
}
function fileUploadedFailure(error) {
alert('Failed: ' + error.message);
}
})();
</script>
</body>
</html>
Server code, file name Code.gs
:
var rootFolderName = "test";
function doGet(e) {
var template = HtmlService.createTemplateFromFile('test_form');
return template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function uploadFilesFrame(form) {
try {
var fileBlob = form['test-file'];
var rootFolder = DriveApp.getFoldersByName(rootFolderName);
if (rootFolder.hasNext()) {
rootFolder = rootFolder.next();
} else {
rootFolder = DriveApp.createFolder(rootFolderName);
}
var file = rootFolder.createFile(fileBlob);
return JSON.stringify({"status": 'ok', "msg": file.getId()});
} catch (error) {
return JSON.stringify({"status": 'error', "data": error.stack});
}
}
Update (Nov 2021): The issue has been resolved: https://issuetracker.google.com/issues/155109626
One workaround: base64-encode it on the client side, then base64-decode it on the server. Then the contents is not screwed up. Here's an example:
HTML template, file name test_form.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form id="test-form">
<input type="file" id="test-file" name="test-file">
<input type="hidden" id="test-file2" name="test-file2">
<input type="hidden" id="test-file-name" name="test-file-name">
<button id="submit-button" type="submit">Upload</button>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
(function () {
'use strict';
$('#test-form').submit(function (e) {
var thisForm = this;
e.preventDefault(); // prevent form from submitting
var reader = new FileReader();
reader.onload = function (event) {
var result = event.target.result;
var base64 = result.substr(result.indexOf(',') + 1);
$('#test-file2').val(base64);
var filename = $('#test-file').val().split('\\').pop();
$('#test-file-name').val(filename);
$('#test-file').prop('disabled', true);
google.script.run
.withFailureHandler(fileUploadedFailure)
.withSuccessHandler(fileUploaded)
.uploadFilesFrame(thisForm);
};
reader.onerror = function (event) {
alert("ERROR: " + event.target.error.code);
};
reader.readAsDataURL(document.getElementById('test-file').files[0]);
});
function fileUploaded(status) {
alert(status);
}
function fileUploadedFailure(error) {
alert('Failed: ' + error.message);
}
})();
</script>
</body>
</html>
Server code, file name Code.gs
:
var rootFolderName = "test";
function doGet(e) {
var template = HtmlService.createTemplateFromFile('test_form');
return template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function uploadFilesFrame(form) {
try {
var fileBlob = Utilities.newBlob(Utilities.base64Decode(form['test-file2']), 'application/octet-stream', form['test-file-name']);
var rootFolder = DriveApp.getFoldersByName(rootFolderName);
if (rootFolder.hasNext()) {
rootFolder = rootFolder.next();
} else {
rootFolder = DriveApp.createFolder(rootFolderName);
}
var file = rootFolder.createFile(fileBlob);
return JSON.stringify({"status": 'ok', "msg": file.getId()});
} catch (error) {
return JSON.stringify({"status": 'error', "data": error.stack});
}
}