0

Following a chat in #amo-editors, I was wondering if the following are possible from a Firefox Addon:

  1. Opening a local XPI for reading
  2. Listing all files in above XPI with their sizes
  3. Reading selected files
erosman
  • 7,094
  • 7
  • 27
  • 46

1 Answers1

1

Absolutely possible.

1 + 2) Have to use nsIZipReader to read the xpi. This gives you all files within it.

3) To read contents you have to use zip readers getInputStream function and then wrap it in stream instance, then read it with argument of entry.realSize as read on stream takes characters to read.

MDN :: nsIZipWriter

MDN :: nsIZipReader


edit: i was curious. i think i got it. heres example of how to make it dump contents of a zip (list all files within). see the console.log(entryPointer) that spits out the "zip path". it also reads the contents of the files.

var zr = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader);
Cu.import('resource://gre/modules/osfile.jsm');
Cu.import('resource://gre/modules/FileUtils.jsm');

var reusableStreamInstance = Cc['@mozilla.org/scriptableinputstream;1'].createInstance(Ci.nsIScriptableInputStream);

//var pathExtFolder = OS.Path.join(OS.Constants.Path.profileDir, 'extensions');
var pathToXpiToRead = OS.Path.join(OS.Constants.Path.profileDir, 'extensions', 'PortableTester@jetpack.xpi');
var nsiFileXpi = new FileUtils.File(pathToXpiToRead);

//Services.ww.activeWindow.alert(pathToXpiToRead);

try {
  zr.open(nsiFileXpi); //if file dne it throws here
  var entries = zr.findEntries('*');
  while (entries.hasMore()) {
    var entryPointer = entries.getNext(); //just a string of "zip path" (this means path to file in zip, and it uses forward slashes remember)
    var entry = zr.getEntry(entryPointer); // should return true on `entry instanceof Ci.nsIZipEntry`
    console.log('entryPointer', entryPointer);
    /* CONSOLE OUTPUT
     * "entryPointer" "bootstrap.js" Scratchpad/1:18
     */
    console.info('entry', entry);
    /* CONSOLE OUTPUT
     * "entry" XPCWrappedNative_NoHelper { QueryInterface: QueryInterface(), compression: Getter, size: Getter, realSize: Getter, CRC32: Getter, isDirectory: Getter, lastModifiedTime: Getter, isSynthetic: Getter, permissions: Getter, compression: 8 } Scratchpad/1:19
     */
    if (!entry.isDirectory) {
        var inputStream = zr.getInputStream(entryPointer);
        reusableStreamInstance.init(inputStream);
        var fileContents = reusableStreamInstance.read(entry.realSize);
        console.log('contenst of file=', fileContents);
    } else {
        console.log('is directory, no stream to read');
    }
  }
} catch (ex) {
  console.warn('exception occured = ', ex);
  if (ex.name == 'NS_ERROR_FILE_NOT_FOUND') {
    Services.ww.activeWindow.alert('XPI at path does not exist!\n\nPath = ' + pathToXpiToRead);
  }
} finally {
  zr.close();
  console.log('zr closed');
  //Cu.forceGC(); //im not sure shoud i do this here?
}

I'm not sure if I should do a Cu.forceGC() in the finally, maybe @nmaier can advise us on that.

I'm also not sure if I handled reading the input stream properly, it works, but i dont know memory wise. I did .read(entry.realSize) first time doing this.

variable viewer on entry: variable viewer on <code>entry</code>

Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • "I'm not sure if I should do a Cu.forceGC() in the finally, maybe @nmaier can advise us on that." Don't use `.forceGC()` if you don't have to. Not sure why you'd want to do that anyway?! Anyway, you'll get my +1 once you remove the unrelated bits that don't even attempt to answer the question like "I haven't read from zip but i have written text directly to a zip..."... – nmaier Jul 12 '14 at 08:47
  • The way I'm reusing the stream instance is that ok? Should I do a `reusableStreamInstance.close()` in the `finally{}`? I forgot about that I just got carried away with coding and appending to this post :P I took it out – Noitidart Jul 12 '14 at 08:52
  • Also is there a better way to do my exception test for if not found flag? – Noitidart Jul 12 '14 at 08:54
  • Actually, answers are supposed to, well, answer questions, not ask new ones?! :p Anyway: You shouldn't reuse streams (stream wrappers); that kinda violates the contract, even though it works in this instance. You should close the inputStream (or scriptable stream wrapper around it) from `zr.getInputStream()`. `zr.open()` should be outside of the try-finally block, but in another (outer) try-block. And so on. Might be a good idea to http://codereview.stackexchange.com/ – nmaier Jul 12 '14 at 09:12
  • Haha it's just that I think this answer could be better. I'm learning while helping others. I can get things to work but I don't know how things are going on under the hood and if they break. I'll post this code there thanks man. – Noitidart Jul 12 '14 at 09:16
  • Thank you Noitidart. I gather this is a code example form something else. What does `'PortableTester@jetpack.xpi'` doing? Also is the directory listing recursive? – erosman Jul 12 '14 at 17:59
  • It's just the name of an xpi of an add-on i have installed, change it to the name of any add-on xpi file in your extensions folder. To figure out your extensions folder go to scratchpad and paste this: `alert(OS.Path.join(OS.Constants.Path.profileDir, 'extensions'))` – Noitidart Jul 12 '14 at 22:33
  • See [CodeReviewer :: Improvements to nsiZipReader and nsIScriptableInputStream](http://codereview.stackexchange.com/a/56824/49106) for implementation by @nmaier it's very interesting and the `try-catch` is done properly, because if `zr.open` does not open then it will throw error on the `zr.close` and my snippet above doesn't catch that. The input stream is also properly used. It also uses fewer interfaces, like no need for `FileUtils.jsm` and `OS.File.jsm`. Has some bonus on exception logic too, real nice. – Noitidart Jul 12 '14 at 23:08