3

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});
  }
}

TheMaster
  • 45,448
  • 6
  • 62
  • 85
Paul
  • 6,061
  • 6
  • 39
  • 70

2 Answers2

3

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});
  }
}
Paul
  • 6,061
  • 6
  • 39
  • 70
0

Since you prevent the default behavior for submission how about trying it this way:

I'm actually hesitating telling you this right now because I suspect you know more about it than I do. But in an effort to be helpful and possibly look stupid I suggest that you try it this way.

<!DOCTYPE html>
<html>
<head>
</head>
<body>

<form>
    <input type="file" id="test-file" name="test-file">
    <input type="button" value="Submit" onClick="this.parentNode" />
</form>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
    function uploadFile(form) {
      google.script.run
      .withFailureHandler(fileUploadedFailure)
      .withSuccessHandler(fileUploaded)
      .uploadFilesFrame(form);
    }
    function fileUploaded(status) {
      alert(status);
    }
    function fileUploadedFailure(error) {
       alert('Failed: ' + error.message);
     }

</script>
</body>
</html>
Cooper
  • 59,616
  • 6
  • 23
  • 54