How to add an Attachment to a Gmail Message with a Chrome Extension

If you are building a chrome extension for Gmail and want to add a feature that automatically adds an attachment to emails - in this blog post I show how to do that.

The problem

Adding an attachment to a Gmail message would be straightforward if Google Chrome allowed to change the value of file input elements to be changed with JavaScript like this:

document.querySelector( "input[ type = 'file' ]" ).value = FILE_BLOB;

But the problem is that for security reasons that is not possible. Otherwise websites would be able to read sensitive files like passwords or private data from your computer without your permission. Here is an example of an input element with type "file"


Clicking this will execute this: document.querySelector( "input[ type = 'file' ]" ).value = "/root/passwords.csv";

If you click the button above - you will see this message in the console:

At first glance it seems like there is nothing we can do about this, but there is a workaround :)

The solution

Although setting the value property on input[ type = 'file' ] elements is prohibited, nothing stops us from setting the value property on a div element and making Gmail believe that that is an input element.

(function () {
  function wait(time) {
    return new Promise(function (resolve) {
      setTimeout(resolve, time);
    });
  }
  function change_default_create_element(doc) {
    var old_create_element = doc.createElement;
    doc.createElement = function () {
      if (should_replace_input_elements && arguments[0] === "input") {
        console.log("attaching file");
        var result = doc.createElement("div");
        return result;
      }
      return old_create_element.apply(this, arguments);
    };
  }
  function simulate_change_event_on_real_input(input) {
    var event = document.createEvent("HTMLEvents");
    event.initEvent("change", false, true);
    input.dispatchEvent(event);
  }
  function simulate_change_event_on_fake_input(fake_input_element) {
    var data_blob = new Blob(["example"], { type: "text/plain" });
    data_blob.lastModifiedDate = new Date();
    data_blob.name = "example.txt";
    fake_input_element.files = [data_blob];
    var event = document.createEvent("HTMLEvents");
    event.initEvent("change", false, true);
    fake_input_element.dispatchEvent(event, { bubbles: true });
  }
  async function attach_example_file() {
    change_default_create_element(document);
    var attach_files_button = document.querySelector("div.a1");
    var input = document.querySelector("div.wG[ command = 'Files' ]+input");
    should_replace_input_elements = true;
    dispatch_change_event_on_real_input(input);
    await wait(1000);
    var fake_input_element = document.querySelector("div.wG[ command = 'Files' ]+div");
    should_replace_input_elements = false;
    dispatch_change_event_on_fake_input(fake_input_element);
  }
  attach_example_file();
})();

If you go to Gmail, click on the compose button, and execute the above code in the console - A file "example.txt" will be attached to the letter.

Conclusion

The method of attaching files described in this post is not only applicable to Gmail. Any website that has a file input can be reverse engineered and have this method applied to it.