Expose File Writing To A Webpage With A Firefox Extension
Solution 1:
Sorry about the super-late reply.
If I understand your question correctly, you have a P.O.S application that runs in Firefox talking to some sort of local webserver via HTTP. The client-side JavaScript of your application needs to be able to read & write files from the local filesystem of the browser's PC.
If that's correct, then you can do so as follows. You'll need to create a Firefox addon, the simpliest kind of which is called a "bootstrapped" (or "restartless") addon.
A restartless addon consists of two files:
- bootstrap.js (The JavaScript file containing your 'privileged' code)
- install.rdf (an XML file describing your addon to Firefrox)
To build the addon, simply place both files inside the top-level (no folders!) of a ZIP file with the file extension .xpi
. To install the addon, navigate to about:addons
then from the tools menu, click Install from file
, find your XPI, open it, then after a short delay choose Install
.
In install.rdf
put something like this:
<?xml version="1.0"?><RDFxmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Descriptionabout="urn:mozilla:install-manifest"><em:id>youraddonname@yourdomain</em:id><em:type>2</em:type><em:name>Name of your addon</em:name><em:version>1.0</em:version><em:bootstrap>true</em:bootstrap><em:description>Describe your addon.</em:description><em:creator>Your name</em:creator><!-- Firefox Desktop --><em:targetApplication><Description><em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id><em:minVersion>4.0.*</em:minVersion><em:maxVersion>29.0.*</em:maxVersion></Description></em:targetApplication></Description></RDF>
You need to implement two mandatory JavaScript functions in the bootstrap.js
:
startup()
- called when you install the addon, and when your browser starts up.shutdown()
- called when you uninstall the addon, and when your browser shuts down.
You should call the rest of your 'privileged' code in startup()
. For hygiene, you can (and probably should) also implement install()
and uninstall()
functions.
Start by implementing the following code in bootstrap.js
:
constCc = Components.classes;
constCi = Components.interfaces;
let consoleService = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
functionLOG(msg) {
consoleService.logStringMessage("EXTENSION: "+msg);
}
functionstartup() {
try {
LOG("starting up...");
let windows = wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
WindowListener.setupBrowserUI(chromeWindow);
}
wm.addListener(WindowListener);
LOG("done startup.");
} catch (e) {
LOG("error starting up: "+e);
}
}
functionshutdown() {
try {
LOG("shutting down...");
let windows = wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
let chromeWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
WindowListener.tearDownBrowserUI(chromeWindow);
}
wm.addListener(WindowListener);
LOG("done shutdown.");
} catch (e) {
LOG("error shutting down: "+e);
}
}
Basically, that calls WindowListener.setupBrowserUI()
for each current & future window of your web-browser. WindowListener
is defined as follows:
varWindowListener = {
setupBrowserUI: function(chromeWindow) {
chromeWindow.gBrowser.addEventListener('load', my_load_handler, true);
},
tearDownBrowserUI: function(chromeWindow) {
chromeWindow.gBrowser.removeEventListener('load', my_load_handler, true);
},
onOpenWindow: function(xulWindow) {
let chromeWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
chromeWindow.addEventListener("load", functionlistener() {
chromeWindow.removeEventListener("load", listener, false);
var domDocument = chromeWindow.document.documentElement;
var windowType = domDocument.getAttribute("windowtype");
if (windowType == "navigator:browser")
WindowListener.setupBrowserUI(chromeWindow);
}, false);
},
onCloseWindow: function(chromeWindow) { },
onWindowTitleChange: function(chromeWindow, newTitle) { }
};
That sets up an event listener for the OpenWindow
event, and in turn installs an event listener for load
events in the TabBrowser
of each ChromeWindow. The load
event handler is defined as:
var my_load_handler = function (evt) {
try {
var browserEnumerator = wm.getEnumerator("navigator:browser");
while (browserEnumerator.hasMoreElements()) {
var browserWin = browserEnumerator.getNext();
var tabbrowser = browserWin.gBrowser;
var numTabs = tabbrowser.browsers.length;
for (var index = 0; index < numTabs; index++) {
var currentBrowser = tabbrowser.getBrowserAtIndex(index);
var domWindow = currentBrowser.contentWindow.wrappedJSObject;
// identify your target page(s)...if (domWindow.location.href == 'http://yourserver/yourpage') {
// install the privileged methods (if not already there)if (!domWindow.hasOwnProperty('__my_priv_members__') {
install_my_privileged_methods(browserWin, domWindow);
}
}
}
}
} catch (e) {
LOG(e);
}
}
That targets the correct pages (by checking the window.location.href
and calls install_my_privileged_methods
on their window
object, which is defined as:
functioninstall_my_privileged_methods(chromeWindow, domWindow) {
install_privileged_method(chromeWindow, domWindow, 'WriteFile',
function(priv) {
returnfunction(File, Text, cb) {
priv.call([File, Text], function(rstatus, rdata, rerror){
if (cb) cb(rstatus, rerror);
});
};
},
function (chromeWindow, args, cb) {
var [File, Text] = args;
if (!File) returncb(0, null, "need a filename");
try {
const unicodeConverter =
Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";
Text = unicodeConverter.ConvertFromUnicode(Text);
const os = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
os.init(File, 0x02 | 0x08 | 0x20, 0700, 0);
os.write(Text, Text.length);
os.close();
cb(1, null, null);
} catch (e) {
cb(0, null, "error writing file: "+e);
}
}
);
install_privileged_method(chromeWindow, domWindow, 'ReadFile',
function(priv) {
returnfunction(File, cb) {
priv.call([File], function(rstatus, rdata, rerror){
if (cb) cb(rstatus, rdata, rerror);
});
};
},
function (chromeWindow, args, cb) {
var [File] = args;
if (!File) returncb(0, null, "need a filename");
try {
const is = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
const sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
is.init(File, 0x01, 0400, null);
sis.init(is);
varText = sis.read(sis.available());
is.close();
cb(1, Text, null);
} catch (e) {
cb(0, null, "error reading file: "+e);
}
}
);
}
I didn't test this code. It's a straigh-forward translation of what you wrote above... I'll assume that works!
That add two special methods, WriteFile
& ReadFile
, to the chosen window
objects. In your web application's (unprivileged) JavaScript code use them like this:
var buffer = '...'; // the text to be writtenwindow.WriteFile('C:\\path\\to\\file.txt', buffer, function(ok, errmsg) {
if (!ok) alert(errmsg);
});
window.ReadFile('C:\\path\\to\\file.txt', function(ok, buffer, errmsg) {
if (!ok) returnalert(errmsg);
// use buffer here!
});
Finally, install_privileged_method
is defined as:
var install_privileged_method = (function(){
var gensym = (function (){
var __sym = 0;
returnfunction () { return'__sym_'+(__sym++); }
})();
returnfunction (chromeWindow, target, slot, handler, methodFactory) {
try {
target.__pmcache__ = target.hasOwnProperty('__pmcache__')
? target.__pmcache__
: { ticket_no: 0, callbacks: {}, namespace: gensym() };
target[slot] = methodFactory({ call: function(fargs, fcb) {
try {
var ticket_no = target.__pmcache__.ticket_no++;
target.__pmcache__.callbacks[ticket_no] = fcb;
var cevent = target.document.createEvent("CustomEvent");
cevent.initCustomEvent(
target.__pmcache__.namespace+'.'+slot,
true, true, { fargs: fargs, ticket_no: ticket_no }
);
target.dispatchEvent(cevent);
} catch (ue) {
fcb(0, null, 'untrusted dispatcher error: '+ue);
}
}});
LOG("installed untrusted dispatcher for method '"+slot+"'.");
target.addEventListener(
target.__pmcache__.namespace+'.'+slot,
function(cevent){
var ticket_no = cevent.detail.ticket_no;
var fargs = cevent.detail.fargs;
var fcb = target.__pmcache__.callbacks[ticket_no];
try {
handler(chromeWindow, fargs, fcb);
} catch (pe) {
fcb(0, null, 'privileged handler error: '+pe);
}
},
false,
true
);
LOG("installed privileged handler for method '"+slot+"'.");
} catch (ie) {
LOG("ERROR installing handler/factory for privileged "+
"method '"+slot+"': "+ie);
}
};
})();
Post a Comment for "Expose File Writing To A Webpage With A Firefox Extension"