Security Conscious Javascript
Normally, javascript does not have access to local files. Rightfully so because almost every web server should be untrusted and allowing anybody to read your files is a large security risk. Mission Control Desktop, however is a javascript application with access to XPCOM, Mozilla’s component object model. This article is an example of how to use it to read local files.
Solaris Printing Configuration
I use MCD to configure thousands of users’ browser and mail clients. In Solaris, printer preferences are set according to the Mozilla Postscript subsystem in a .printers
file in the home directory. Firefox and Thunderbird do not support this file interface so I use autoconfig to read the file and set printer preferences accordingly.
I wrote an object prototype to read and parse $HOME/.printers
which the autoconfig uses to set the print.printer_list
preference. The prototype searches the file for a string indicating the list of printers to use:
# $HOME/.printers # Set the default printer to riemann _default riemann # Display a chooser for three printers, ignoring all others. _all newton,poincare,riemann |
The line beginning with _all
is quite important in my environment with 1,700 printers configured through LDAP. If Thunderbird or Firefox were to try to load the entire printer list, it would become overwhelmed and crash.
extract_allPrinters utility
First, a look at the utility function I use to find the right line.
if( typeof(FileInterface) == "undefined" ) // Barf if the object prototype is undefined throw("autoconfig constructed improperly: need classes/file_interface.js before extract_all_printers.js"); function extract_allPrinters(path) { if( typeof(path) == "undefined" ) path = env_home + "/.printers"; // Regular expression to search .printers for _all re = /^\s*_all\s+(.+)\s*/i; lines = new FileInterface(path).grep(re); if( typeof(lines) != "undefined" ) { // Take the first match allPrinters = re.exec( lines[0] )[1].replace(/,\s*/g, " "); } return allPrinters; } /* *snip* */ // Tell FF & TB which printers to offer a print dialogue for defaultPref("print.printer_list", extract_allPrinters() ); |
The first lines check that the FileInterface
object prototype is defined. This is necessary because my autoconfig script broken into component scripts in the filesystem. Besides making it easier to maintain, I can share functionality between Firefox and Thunderbird while excluding application-specific parts from the wrong autoconfig.
This function uses a prototype called FileInterface
(explained later) to grep .printers
for lines beginning with _all. If grep
returns a match, the function takes the first one. print.printer_list
takes a space-separated list of printers while _all
is comma-separated, so the final thing to do is replace
the commas with spaces.
Later on in the script I call defaultPref
to set the printer list.
The FileInterface object prototype
nsILocalFile
is the XPCOM interface used to access files on the client-side filesystem. This object prototype provides only enough functionality to search files, but remains extensible should I need to create or modify.
// All you need is a (string) path function FileInterface(path) { if( typeof(path) == "undefined" ) throw("Need a path for FileInterface()"); this.path = path; } /* Emulate the functionality of unix grep Takes a RegExp as it's only argument Returns an array of lines matching the RegExp E.G.: passwd = new FileInterface("/etc/passwd"); lines = passwd.grep(/brundage/); // lines[0] = brundage:x:1002:1002::/home/brundage:/bin/zsh */ FileInterface.prototype.grep = function(re) { matches = undefined; if( re ) { line = {}; matches = []; this.initIStream(); do { hasMore = this.iStream.readLine(line); if( re.test(line.value) ) matches.push(line.value); } while(hasMore); this.iStream.close(); } return matches; } /* Initializes a nsILineInputStream for higher-level functions Takes three arguments, assigning read-only defaults to undefined arguments. For possible values see: https://developer.mozilla.org/en/NsIFileInputStream */ FileInterface.prototype.initIStream = function(ioFlags,perm,behaviorFlags) { if( ! ioFlags ) ioFlags = -1; // Default mode (PR_READONLY) if( ! perm ) perm = -1; // Default mode (0) if( ! behaviorFlags ) behaviorFlags = 0; // Initializing the stream requires an nsILocalFile. Make one out of the path attribute. this.initLocalFile(); // Get the nsIFileInputStream instance from the global Components variable this.iStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream); if( ! this.iStream ) // Bad Things throw("network/file-input-stream component does not exist"); // Point the stream at the iLocalFile this.iStream.init(this.iLocalFile, ioFlags, perm, behaviorFlags); // Transform iStream into a nsILineInputStream this.iStream.QueryInterface(Components.interfaces.nsILineInputStream); } /* Initialize an nsILocalFile instance with the path attribute of this object Required for streams */ FileInterface.prototype.initLocalFile = function() { this.iLocalFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); if( ! this.iLocalFile ) // Bad Things throw("file/local component does not exist"); this.iLocalFile.initWithPath(this.path); } |
There you have it
I have only encountered this single situation that requires access to the local filesystem. Can you think of others?
One thought on “Reading Local Files With Javascript”