aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2015-03-16 02:05:26 +0100
committerGuilhem Moulin <guilhem@fripost.org>2015-03-16 02:57:15 +0100
commit3750346a8e8736c726e6ad8fcb8ad82d81975fbc (patch)
tree9fdce67616fee40662009f5e2ed70c632f2a133b
parent64a74af9c1bc79cdc1aaeb6e160eedf80aaf97dd (diff)
XUL extension
l---------xul-ext/COPYING1
-rw-r--r--xul-ext/Makefile11
-rw-r--r--xul-ext/chrome.manifest3
-rw-r--r--xul-ext/chrome/content/icevault.js223
-rw-r--r--xul-ext/chrome/content/icevault.xul31
-rw-r--r--xul-ext/chrome/locale/en-US/messages0
-rw-r--r--xul-ext/chrome/locale/en-US/messages.dtd0
-rw-r--r--xul-ext/install.rdf42
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>