diff options
| author | Guilhem Moulin <guilhem@fripost.org> | 2015-03-16 02:05:26 +0100 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@fripost.org> | 2015-03-16 02:57:15 +0100 | 
| commit | 3750346a8e8736c726e6ad8fcb8ad82d81975fbc (patch) | |
| tree | 9fdce67616fee40662009f5e2ed70c632f2a133b | |
| parent | 64a74af9c1bc79cdc1aaeb6e160eedf80aaf97dd (diff) | |
XUL extension
| l--------- | xul-ext/COPYING | 1 | ||||
| -rw-r--r-- | xul-ext/Makefile | 11 | ||||
| -rw-r--r-- | xul-ext/chrome.manifest | 3 | ||||
| -rw-r--r-- | xul-ext/chrome/content/icevault.js | 223 | ||||
| -rw-r--r-- | xul-ext/chrome/content/icevault.xul | 31 | ||||
| -rw-r--r-- | xul-ext/chrome/locale/en-US/messages | 0 | ||||
| -rw-r--r-- | xul-ext/chrome/locale/en-US/messages.dtd | 0 | ||||
| -rw-r--r-- | xul-ext/install.rdf | 42 | 
8 files changed, 311 insertions, 0 deletions
| diff --git a/xul-ext/COPYING b/xul-ext/COPYING new file mode 120000 index 0000000..012065c --- /dev/null +++ b/xul-ext/COPYING @@ -0,0 +1 @@ +../COPYING
\ No newline at end of file diff --git a/xul-ext/Makefile b/xul-ext/Makefile new file mode 100644 index 0000000..d44f648 --- /dev/null +++ b/xul-ext/Makefile @@ -0,0 +1,11 @@ +XPI_NAME:=$(shell sed -nr '/.*<em:id>([^<]+)<\/em:id>.*/ {s//..\/\1.xpi/p;q}' install.rdf) + +all: $(XPI_NAME) + +$(XPI_NAME): chrome.manifest install.rdf COPYING $(shell find chrome/ -type f) +	zip $@ $^ + +clean: +	rm -f $(XPI_NAME) + +.PHONY: all clean diff --git a/xul-ext/chrome.manifest b/xul-ext/chrome.manifest new file mode 100644 index 0000000..14cc449 --- /dev/null +++ b/xul-ext/chrome.manifest @@ -0,0 +1,3 @@ +content icevault chrome/content/ +locale icevault en-US chrome/locale/en-US/ +overlay chrome://browser/content/browser.xul chrome://icevault/content/icevault.xul diff --git a/xul-ext/chrome/content/icevault.js b/xul-ext/chrome/content/icevault.js new file mode 100644 index 0000000..2006e3a --- /dev/null +++ b/xul-ext/chrome/content/icevault.js @@ -0,0 +1,223 @@ +/* + * IceVault - An external password manager for firefox (XUL extension) + * Copyright © 2015 Guilhem Moulin <guilhem@fripost.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +// IceVault global namespace +var icevault = (function() { + +  const Cc = Components.classes, +        CC = Components.Constructor, +        Ci = Components.interfaces; + +  const UnixServerSocket = CC( "@mozilla.org/network/server-socket;1" +                             , "nsIServerSocket" +                             , "initWithFilename" ), +        InputPump = CC( "@mozilla.org/network/input-stream-pump;1" +                      , "nsIInputStreamPump" +                      , "init" ), +        BinaryInputStream = CC( "@mozilla.org/binaryinputstream;1" +                               , "nsIBinaryInputStream" +                               , "setInputStream" ), +        ConverterOutputStream = CC( "@mozilla.org/intl/converter-output-stream;1" +                                  , "nsIConverterOutputStream" +                                  , "init" ); + +  // XXX should be changeable from the preferences +  let sockName = Cc["@mozilla.org/file/directory_service;1"] +                .getService(Ci.nsIProperties) +                .get("ProfD", Ci.nsIFile); +  sockName.append("S.IceVault"); +  const sockPerms = parseInt("0770", 8); + +  const convertStringToUTF8 = Cc["@mozilla.org/intl/utf8converterservice;1"] +                                .getService(Ci.nsIUTF8ConverterService) +                                .convertStringToUTF8; + +  // Write the given UTF8 string to the ConverterOutputStream, then flush +  var send = function(stream, msg) { +    //console.log("Sending: " + msg) +    try { +      stream.writeString(msg + "\n"); +    } +    finally { +      stream.flush(); +    } +  }; + +  // Refresh the internal state: URI is set to the current tab's and the +  // form state is cleared +  var refreshDocument = function(state) { +    let tab = Application.activeWindow.activeTab; +    state.document = tab.document; +    state.forms = null; +    send(state.outStream, 'OK ' + JSON.stringify(tab.uri.scheme+'://'+tab.uri.hostPort)); +  }; + +  // Process the command +  var process = function(state, data) { +    data = convertStringToUTF8(data, "UTF-8", false, true); // convert the bytes to UTF8 +    //console.log("Received: " + data.replace(/\n$/,'')); +    let command = /^\S*/.exec(data)[0]; + +    switch (command) { +      case "REFRESH": +        // refresh current (active) tab +        refreshDocument(state); +        break; + +      case "GETFORMS": +        // refresh the form list +        let forms = [], getForms = []; +        for (let i = 0; i < state.document.forms.length; i++) { +          let form = state.document.forms[i], rect = form.getBoundingClientRect(); +          if (rect.width == 0 || rect.height == 0) +            continue; // invisible form +          let fields = [], getFields = []; +          for (let i = 0; i < form.elements.length; i++) { +            let field = form.elements[i]; +            if (["text","email","password"].indexOf(field.type) > -1) { +              // store only these kinds of fields +              let getField = { name: field.name, type: field.type, value: field.value }; +              if (field.maxLength > -1) getField.maxLength = field.maxLength; +              fields.push(field) +              getFields.push(getField) +            } +          } + +          if (fields.length) { +            // keep the element objects in the state, and build an assoc +            // list with the wanted field properties +            forms.push(fields); +            getForms.push({ method: form.method.toUpperCase(), action: form.action, fields: getFields }); +          } +        } + +        state.forms = forms; +        send(state.outStream, 'OK ' + JSON.stringify(getForms)); +        break; + +      case "FILL": +        // fill a form with the given values (unless it's null) +        try { +          if (state.forms == null) +            throw "Must issue a GETFORMS command first"; +          let formIdx = /^FILL (\d+) /.exec(data); +          if (formIdx == null) +            throw "Syntax error" +          formIdx = formIdx[1]; +          let values = JSON.parse(data.replace(/^FILL \d+ /, '')); +          if (formIdx < 0 || formIdx >= state.forms.length) +            throw (formIdx + ": Form index out of bounds"); +          let form = state.forms[formIdx]; +          if (form.length < values.length) +            throw (form.length + '<' + values.length + ": Too many values"); +          for (let i = 0; i < values.length; i++) +            if (values[i] != null) +              form[i].value = values[i] +          send(state.outStream, 'OK'); +        } +        catch (e if typeof e == "string" || e instanceof SyntaxError) { +          send(state.outStream, 'ERROR ' + (typeof e == "string" ? e : e.message)); +        } +        break; + +      case "QUIT": +        state.inStream.close(); +        send(state.outStream, 'BYE'); +        state.outStream.close(); +        break; + +      default: +        send(state.outStream, 'ERROR Invalid command: ' + command); +    } +  }; + +  var serverSocketListener = { +    onSocketAccepted: function(server, transport) { +      // New connection: asynchronously read data on the (binary) input +      // stream, and open the output stream as an UTF-8.  We don't use +      // ConverterInputStream as onDataAvailable's counts bytes not +      // chars. +      let inputStream  = transport.openInputStream(0, 0, 0), +          serverInput  = new InputPump(inputStream, -1, -1, 0, 0, false); +          outputStream = transport.openOutputStream(0, 0, 0); + +      let state = { inStream: BinaryInputStream(inputStream) +                  , outStream: ConverterOutputStream(outputStream, 'UTF-8', 0, 0x0000) +                  }; + +      console.log("Connection accepted"); +      serverInput.asyncRead(streamListener(state), null); // spawn the listener +      refreshDocument(state); +    }, + +    onStopListening: function(server, StatusCode) { +      // server is dead +      console.log('Server stopped listening'); +    }, +  }; + +  var streamListener = function(state) { +     return { +      onStartRequest: function(request, context) { +        this.data = ''; +      }, +      onDataAvailable: function(request, context, stream, offset, count) { +        this.data += state.inStream.readBytes(count); +        if (this.data.slice(-1) == "\n") { +          // full input, process the command then clear the cache +          process(state, this.data); +          this.data = ''; +        } +      }, +      onStopRequest: function(request, context, statusCode) { +        state.inStream.close(); +        state.outStream.close(); +      } +    } +  }; + + +  // External interface +  return { +    init: function() { +      let server; // share the server accross multiple browsers (windows) +      if (Application.storage.has("IceVault.serverSocket")) +        server = Application.storage.get("IceVault.serverSocket", null); +      else { +        console.log("Initializing IceVault"); +        try { +          if (sockName.exists()) +            sockName.remove(false); // remove stalled socket +          server = new UnixServerSocket(sockName, sockPerms, -1); +        } +        finally { +          Application.storage.set("IceVault.serverSocket", server); +        } +      } +      server.asyncListen(serverSocketListener); +    }, + +    clean: function() { +      if (!Application.windows.length && sockName.exists()) { +        // remove the socket if the last window was just closed +        console.log("Removing " + sockName.path); +        sockName.remove(false); +      } +    } +  } +})(); diff --git a/xul-ext/chrome/content/icevault.xul b/xul-ext/chrome/content/icevault.xul new file mode 100644 index 0000000..6aca893 --- /dev/null +++ b/xul-ext/chrome/content/icevault.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> + +<!-- +IceVault - An external password manager for firefox (XUL extension) +Copyright © 2015 Guilhem Moulin <guilhem@fripost.org> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +--> +<!DOCTYPE messagesDTD SYSTEM "chrome://icevault/locale/messages.dtd"> + +<overlay id="icevault" +  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +  <script type="application/x-javascript" src="icevault.js" /> + +  <script type="text/javascript"> +    window.addEventListener('load', icevault.init, false); +    window.addEventListener('unload', icevault.clean, false); +  </script> +</overlay> diff --git a/xul-ext/chrome/locale/en-US/messages b/xul-ext/chrome/locale/en-US/messages new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/xul-ext/chrome/locale/en-US/messages diff --git a/xul-ext/chrome/locale/en-US/messages.dtd b/xul-ext/chrome/locale/en-US/messages.dtd new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/xul-ext/chrome/locale/en-US/messages.dtd diff --git a/xul-ext/install.rdf b/xul-ext/install.rdf new file mode 100644 index 0000000..d804731 --- /dev/null +++ b/xul-ext/install.rdf @@ -0,0 +1,42 @@ +<?xml version="1.0"?> + +<!-- +IceVault - An external password manager for firefox (XUL extension) +Copyright © 2015 Guilhem Moulin <guilhem@fripost.org> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +--> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" +     xmlns:em="http://www.mozilla.org/2004/em-rdf#"> +   +  <Description about="urn:mozilla:install-manifest"> +    <em:id>icevault@guilhem.org</em:id> +    <em:name>IceVault</em:name> +    <em:description>A secure external password manager</em:description> +    <em:version>0.1</em:version> +    <em:creator>Guilhem Moulin</em:creator> +    <em:homepageURL>https://guilhem.org/icevault/</em:homepageURL> +    <em:type>2</em:type> +  +    <!-- Mozilla Firefox --> +    <em:targetApplication>    +      <Description> +        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> +        <em:minVersion>3.5</em:minVersion> +	      <em:maxVersion>26.*</em:maxVersion> +      </Description> +    </em:targetApplication> + </Description> +</RDF> | 
