javascriptthunderbird-addonthunderbird-webextensions

How to execute / access local file from Thunderbird WebExtension?


I like to write a Thunderbird AddOn that encrypts stuff. For this, I already extracted all data from the compose window. Now I have to save this into files and run a local executable for encryption. But I found no way to save the files and execute an executable on the local machine. How can I do that?

I found the File and Directory Entries API documentation, but it seems to not work. I always get undefined while trying to get the object with this code:

var filesystem = FileSystemEntry.filesystem;
console.log(filesystem); // --> undefined

At least, is there a working AddOn that I can examine to find out how this is working and maybe what permissions I have to request in the manifest.json?

NOTE: Must work cross-platform (Windows and Linux).


Solution

  • The answer is, that WebExtensions are currently not able to execute local files. Also, saving to some local folder on the disk is also not possible.

    Instead, you need to add some WebExtension Experiment to your project and there use the legacy APIs. There you can use the IOUtils and FileUtils extensions to reach your goal:

    Execute a file:

    In your background JS file:

    var ret = await browser.experiment.execute("/usr/bin/executable", [ "-v" ]);
    

    In the experiment you can execute like this:

    var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
    var { FileUtils }       = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
    var { XPCOMUtils }      = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
    
    XPCOMUtils.defineLazyGlobalGetters(this, ["IOUtils");
    
    async execute(executable, arrParams) {
      var fileExists = await IOUtils.exists(executable);
      if (!fileExists) {
        Services.wm.getMostRecentWindow("mail:3pane")
          .alert("Executable [" + executable + "] not found!");
        return false;
      }
      var progPath = new FileUtils.File(executable);
    
      let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
      process.init(progPath);
      process.startHidden = false;
      process.noShell = true;
      process.run(true, arrParams, arrParams.length);
      return true;
    },
    

    Save an attachment to disk:

    In your backround JS file you can do like this:

    var f = messenger.compose.getAttachmentFile(attachment.id)
    var blob = await f.arrayBuffer();
    var t = await browser.experiment.writeFileBinary(tempFile, blob);
    

    In the experiment you can then write the file like this:

    async writeFileBinary(filename, data) {
      // first we need to convert the arrayBuffer to some Uint8Array
      var uint8 = new Uint8Array(data);
      uint8.reduce((binary, uint8) => binary + uint8.toString(2), "");
      // then we can save it
      var ret = await IOUtils.write(filename, uint8);
      return ret;
    },
    

    IOUtils documentation:

    https://searchfox.org/mozilla-central/source/dom/chrome-webidl/IOUtils.webidl

    FileUtils documentation:

    https://searchfox.org/mozilla-central/source/toolkit/modules/FileUtils.jsm