/* This code is issued under a disjunctive tri-license giving you the choice of one of the three following sets of free software/open source licensing terms: * Mozilla Public License, version 1.1 * GNU General Public License, version 2.0 * GNU Lesser General Public License, version 2.1 For users under the Mozilla Public License: The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is the OverbiteFF Gopher addon. The Initial Developer of the Original Code is Cameron Kaiser. Portions created by Cameron Kaiser are Copyright (C) 2008-2016 Cameron Kaiser. All Rights Reserved. Copyright (C) 2008-2016 Contributors to the Overbite Project. For users under the GNU Public License: OverbiteFF Gopher/CSO Firefox addon Copyright (C) 2008-2016 Cameron Kaiser Copyright (C) 2008-2016 Contributors to the Overbite Project 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; version 2. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. [ or http://www.gnu.org/licenses/gpl-2.0.html ] For users under the GNU Lesser General Public License: OverbiteFF Gopher/CSO Firefox addon Copyright (C) 2008-2016 Cameron Kaiser Copyright (C) 2008-2016 Contributors to the Overbite Project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2.1. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. [ or http://www.gnu.org/licenses/lgpl-2.1.html ] */ const OBFFVERS = 3.1; const OBFFBUILD = 1695; const OBFFBUILDPREF = "network.gopher.buildmark"; const OBFFDOTLESSPREF = "network.gopher.dotless"; const OBFFFIXITYPEPREF = "network.gopher.fixitype"; const OBFFSCHEME = "gopher"; const OBFFCHROMEURL = "chrome://libgopher"; const OBFFRABOUTURL = (OBFFCHROMEURL + "/content/startpage.html"); const OBFFPROT_HANDLER_CONTRACTID = "@mozilla.org/network/protocol;1?name="+OBFFSCHEME; const OBFFPROT_HANDLER_CID = Components.ID("{977ffc4c-a635-433d-8477-ea575bfb7b19}"); const Ci = Components.interfaces; const Cc = Components.classes; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const nsILoadInfo = Ci.nsILoadInfo; const nsIFactory = Ci.nsIFactory; const nsISupports = Ci.nsISupports; const nsIProtocolHandler = Ci.nsIProtocolHandler; const nsIProxiedProtocolHandler = Ci.nsIProxiedProtocolHandler; const nsIRequest = Ci.nsIRequest; const nsIChannel = Ci.nsIChannel; const nsIProxiedChannel = Ci.nsIProxiedChannel; const nsIProgressEventSink = Ci.nsIProgressEventSink; const nsISocketTransport = Ci.nsISocketTransport; const nsIStreamConverter = Ci.nsIStreamConverter; const nsIStreamListener = Ci.nsIStreamListener; const nsIObserver = Ci.nsIObserver; const nsIRequestObserver = Ci.nsIRequestObserver; const nsITimerCallback = Ci.nsITimerCallback; /* port control */ // 80 is okay because of some hybrid servers that can speak both on one port var alwayslet = [ 13, 43, 70, 71, 72, 79, 80, 105, 1070, 2347, 3000, 3070, 3099, 4323, 7055, 7070, 7071, 7072, 7077, 7080, 7777, 27070 ]; //var badports = [ 20,21,22,23,25,53,69,111,115,137,138,139,443,513,514,548 ]; /* cache */ var capscache = new Object(); var nullcaps = { PathKeepPreDelimiter : false, PathParentDouble : false, PathDelimiter : "", CapsVersion : 1 }; /* global functions for logging to the error console */ function OverbiteLogAlways(msg, error) { var consoleService = Cc["@mozilla.org/consoleservice;1"] .getService(Ci.nsIConsoleService); msg = "libgopher: "+msg; if (error) { consoleService.logStringError(msg); } else { consoleService.logStringMessage(msg); } } function OverbiteLog(msg, error) { // return Cr.NS_OK; // comment out for logging // OverbiteLogAlways(msg, error); } /* crap on a stick. you mean I have to implement my own NS_QueryNotificationCallbacks? so what do I have XPConnect for anyway?! rot in hell. */ function OverbiteQNC(one, two, three) { var progsink = null; if (three) return three; if (one.notificationCallbacks) { progsink = one.notificationCallbacks .getInterface(nsIProgressEventSink); if (!progsink && two) { // try that instead var cbs = two.notificationCallbacks; if (cbs) progsink = cbs.getInterface( nsIProgressEventSink); } } return(progsink); } /* global functions for processing caps */ function OverbiteCapsKey(host, port) { var nport = (port < 1) ? 70 : port; return host.toLowerCase()+":"+nport; } function OverbiteCapsPro(caps) { if (caps.substr(0, 4) != "CAPS") return nullcaps; var capsarr = caps.substr(4).split(/[\r\n]{1,2}/); var ncapsarr = nullcaps; var i; try { for (i=0; i opaque, jump to root var i = sel.lastIndexOf(d); if (i == -1) return ""; // no delimeter in sel => jump to root // putative var s = sel.substr(0, i); // handle the situation where the delimiter was on the end with nothing // after it if ((s + d) == sel) { // grrr i = s.lastIndexOf(d); if (i == -1) return ""; // no delimiter in sel => jump to root s = s.substr(0, i); } return s; } function OverbiteCapsUniversalBackURL(host, port, itype, sel) { // based on the caps, return either a .caps pseudo URL, a real URL or // nothing. used for the direct links. the capupurl is only valid iff: // // 1. we have a cached caps *and* it supports path delimeters // or // 2. we have no cached caps, in which case we hope for the best // // I thought about having this suppress the capupurl if it equals the // root, but this is inconsistent, especially with new hosts. var capupurl = ''; var caps = capscache[OverbiteCapsKey(host, port)]; // the itype could be in one of two places: either itself, or the // first character of the sel. if itype == '' then get it from there. if (itype == '') { itype = sel.substr(0, 1); sel = sel.substr(1); } // by definition backing up only can *ever* go up to a menu. itype = '1'; if (!caps) { // no cache; construct pseudo URL capupurl = OverbiteURLFromSel( host + '.caps', port, itype, sel); } else { var kk = caps["PathDelimiter"]; // if we have a path delimeter, use it if (kk && kk.length) capupurl = OverbiteURLFromSel( host, port, itype, OverbiteCapsBackPathFromSel(caps, sel)); } return capupurl; } // utility function, used by lots of things function OverbiteURLFromSel(host, port, itype, sel) { var nport = (!port || port < 1) ? 70 : port; var suburl = OBFFSCHEME + '://' + host + ((nport != 70)?(':'+nport): '') + '/' + encodeURI(itype + sel); ; return suburl; } /* * HACK! * the OverbitePrompter "debounces" back to back alerts and acts * effectively as the front end mutex. Needed since our channels * can get multiplexed even in single-process Firefox and our * channel object does a certain amount of front-end work currently. * * TODO: For e10s, remove all use of the prompter and use content * controls (a form for searches, HTML error messages, etc.) */ var lastPromptKey = null; var lastCachedResponse = null; // These need to be globals so that responses cache properly. var chequestub = { value : false }; var query = { value : '' }; function OverbitePrompter(newkey, response) { if (lastPromptKey == newkey) { OverbiteLog("prompter key "+lastPromptKey+" returning cached "+lastCachedResponse); lastPromptKey = null; return lastCachedResponse; } query = { value : '' }; lastPromptKey = newkey; lastCachedResponse = response(); OverbiteLog("caching prompt "+newkey+" responded "+lastCachedResponse); return lastCachedResponse; } /* * the OverbiteCaps object is a pseudo-object that controls browser navigation * and stores caps data in the cache. it is mostly the same as Dotless and * implements the same interfaces. * * future expansion: have the caps display a proper info page. */ function OverbiteCaps() { } OverbiteCaps.prototype = { // my stuff _contentType: null, _listener : null, _context : null, _buf : '', _sstream : null, _browser_callback : null, _host : '', _port : -1, _itemtype : '', _selector : '', // feed the beast (i.e., the listener on the other end) _feedBeast : function(request, listener, context, what) { // create a new instance each time (instead of reusing one) // just to make sure the previous instance has time to finish var stringstream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); stringstream.setData(what, what.length); listener.onDataAvailable(request, context, stringstream, 0, what.length); }, // nsISupports QueryInterface : XPCOMUtils.generateQI([nsISupports, nsIStreamListener, nsIStreamConverter, nsIRequestObserver]), // nsIRequestObserver onStartRequest : function(request, context) { // init the channel with data and content type // this is almost always text/plain but we are flexible this._context = context; this._buf = ''; var chan = request.QueryInterface(nsIChannel); if (chan) chan.contentType = "text/html"; this._listener.onStartRequest(request, context); }, /* when the request terminates, we then parse the data (using the global functions above) and manipulate the browser. the other end gets a StopRequest but we don't pass it any data. */ onStopRequest : function(request, context, status) { // stop the stream and destroy it if (this._sstream) this._sstream.close(); this._sstream = null; // process it here before we tear down everything var w = OverbiteCapsPro(this._buf); var k = OverbiteCapsKey(this._host, this._port); capscache[k] = w; OverbiteLog("cached caps entry created for "+k); // if there is a callback, send NO data. manipulate the URL // and we are done! if (this._browser_callback) { // compute the new URL ... var s = OverbiteCapsBackPathFromSel(w, this._selector); s = OverbiteURLFromSel(this._host, this._port, this._itemtype, s); OverbiteLog("backing up to "+s); // briefly stop the load group before we retarget ... this._listener.onStopRequest(request, this._context, status); this._listener = null; this._context = null; // ... and force the browser to the new URL. // we need to biff the current history entry so that // the caps redirect is seamless. this._browser_callback.webNavigation.loadURI(s, 128, // LOAD_FLAGS_REPLACE_HISTORY null, null, null); return; } // we are being asked to display the caps, so squirt it out // to the _listener, and conclude. var x = ""; for (var i in w) { if (w.hasOwnProperty(i)) { var v = w[i]; if (v == true) v = "true"; if (v == false) v = "false"; x += i + " ==> " +w[i] + "
\n"; } } this._feedBeast(request, this._listener, this._context, x); this._listener.onStopRequest(request, this._context, status); this._listener = null; this._context = null; }, // nsIStreamListener onDataAvailable : function(request, context, stream, offset, count) { var nbuf; if (!this._sstream) { // create (and cache) our scriptable input stream // note: this is NOT BINARY SAFE (but Gopher is // mostly 7-bit) this._sstream = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); this._sstream.init(stream); } nbuf = this._sstream.read(count); this._buf += nbuf; }, // nsIStreamConverter convert : function(from, to, listener, context) { // synchronous conversion will not be supported w/o good reason throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, asyncConvertData : function(from, to, listener, context) { // if this were a real translator, we'd uncomment this // if (from != ... || to != ...) // throw Cr.NS_ERROR_NOT_IMPLEMENTED; this._contentType = to; this._listener = listener; this._context = context; } } /* * the dotless object handles things like svg and xml that barf on the * terminal period that certain servers emit, and is also nice for * text/plain. it is similar to Caps and implements the same interfaces. */ function OverbiteDotless() { } OverbiteDotless.prototype = { // my stuff _contentType: null, _listener : null, _context : null, _buf : '', _sstream : null, // useful internal functions // feed the beast (i.e., the listener on the other end) _feedBeast : function(request, listener, context, what) { // create a new instance each time (instead of reusing one) // just to make sure the previous instance has time to finish var stringstream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); stringstream.setData(what, what.length); listener.onDataAvailable(request, context, stringstream, 0, what.length); }, // charAtEnd allows easier snarfing of trailing characters _charAtEnd : function(index) { return this._buf.charAt(this._buf.length - index); }, _lopOff : function(index) { OverbiteLog("dotless removed "+index+" characters"); this._buf = this._buf.substr(0, this._buf.length - index); OverbiteLog("now "+this._buf.length+" long"); }, // nsISupports QueryInterface : XPCOMUtils.generateQI([nsISupports, nsIStreamListener, nsIStreamConverter, nsIRequestObserver]), // nsIRequestObserver onStartRequest : function(request, context) { // init the channel with data and content type var chan = request.QueryInterface(nsIChannel); if (chan) chan.contentType = this._contentType; // from asyncConvertData this._context = context; this._buf = ''; this._listener.onStartRequest(request, context); }, /* this is a very simple construct with a rolling buffer. essentially we start by priming the pump with a small one character output, then delaying and buffering data until the last pulse of data and onStopRequest gets called, which if there is a final dot, should be in the leftover buffer. */ onStopRequest : function(request, context, status) { // trim the end of _buf if (this._buf.indexOf(".")) { // don't bother if there's no dot // check for very specific sequences var l = this._buf.length; if (l == 1) this._buf = ""; // must be a bare dot else if (this._buf == ".\r\n" || this._buf == ".\n") this._buf = ""; else if (this._buf == "\n." || this._buf == "\r\n.") this._buf = ""; else if (this._charAtEnd(1) == "." && this._charAtEnd(2) == "\n"){ this._lopOff(1); } else if (this._charAtEnd(1) == "\n") { if (l > 3 && this._charAtEnd(2) == "\r" && this._charAtEnd(3) == "." && this._charAtEnd(4) == "\n") { this._lopOff(3); } else if (l > 2 && this._charAtEnd(2) == "." && this._charAtEnd(3) == "\n") { this._lopOff(2); } } // otherwise must be some other construction } this._feedBeast(request, this._listener, this._context, this._buf) if (this._buf.length); this._buf = ''; this._listener.onStopRequest(request, this._context, status); this._listener = null; this._context = null; if (this._sstream) this._sstream.close(); this._sstream = null; }, // nsIStreamListener onDataAvailable : function(request, context, stream, offset, count) { var nbuf; if (!this._sstream) { // create (and cache) our scriptable input stream // note: this is NOT BINARY SAFE (but Gopher is // mostly 7-bit) this._sstream = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); this._sstream.init(stream); } nbuf = this._sstream.read(count); if (this._buf.length) { this._feedBeast(request, this._listener, this._context, this._buf); this._buf = nbuf; } else { if (count == 1) { // degenerate case this._feedBeast(request, this._listener, this._context, nbuf); // come back for more } else { this._buf = nbuf.substr(1); this._feedBeast(request, this._listener, this._context, nbuf.substr(0,1)); } } }, // nsIStreamConverter convert : function(from, to, listener, context) { // synchronous conversion will not be supported w/o good reason throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, asyncConvertData : function(from, to, listener, context) { // if this were a real translator, we'd uncomment this // if (from != ... || to != ...) // throw Cr.NS_ERROR_NOT_IMPLEMENTED; this._contentType = to; this._listener = listener; this._context = context; } } /* * the converter object for turning a gopher directory into HTML. we no * longer use HTTP_INDEX because frankly it can't handle all that gopher * offers. */ function OverbiteDirconv() { } OverbiteDirconv.prototype = { // my stuff _listener : null, _context : null, _buf : '', _pbuf : '', _sstream : null, _divlevel : 0, /* l10n/i18n support and string bundles */ // for strings that are already Unicode (like our localization) _unicodeEntityFix : function(what) { // needed to ampersand-encode ALT text and other stuff return "&#" + what.charCodeAt(0) + ";"; }, _unicodeStringFix : function(what) { var q; q = what; q = q.replace(/([\u0080-\uFFFF])/g, this._unicodeEntityFix); return q; }, // we load both itypes and obff because we use both of them now. _bundle : Cc["@mozilla.org/intl/stringbundle;1"] .getService(Ci.nsIStringBundleService) .createBundle(OBFFCHROMEURL + "/locale/itypes.properties"), _getL10NString : function(msg, args) { var q; OverbiteLog("string query: "+msg); if (args) return this._unicodeStringFix( this._bundle.formatStringFromName(msg, args, args.length)); else return this._unicodeStringFix( this._bundle.GetStringFromName(msg)); }, _obundle : Cc["@mozilla.org/intl/stringbundle;1"] .getService(Ci.nsIStringBundleService) .createBundle(OBFFCHROMEURL + "/locale/obff.properties"), _ogetL10NString : function(msg, args) { if (args) return this._obundle.formatStringFromName(msg, args, args.length); else return this._obundle.GetStringFromName(msg); }, _itypes : [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'g', 'I', 's', 'h', ';', 'p', 'd', 'T' ] , // useful internal functions // feed the beast (i.e., the listener on the other end) _feedBeast : function(request, listener, context, what) { // create a new instance each time (instead of reusing one) // just to make sure the previous instance has time to finish var stringstream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); stringstream.setData(what, what.length); listener.onDataAvailable(request, context, stringstream, 0, what.length); }, // mungers and twisters to convert RFC-1436 cdata into valid SGML _dsSpaceFix : function(what) { var wout = what; wout = wout.replace(/ /g, "  "); wout = wout.replace(/ \ /g, "  "); wout = wout.replace(/\  /g, "  "); wout = wout.replace(/^ /, " "); return wout; }, _entityFix : function(what) { var wout = what; wout = wout.replace(/\&/g, "&"); wout = wout.replace(/>/g, ">"); wout = wout.replace(/' ; rootbutt = '
' + capupurl + '' + '
' + "\n"; } whoami = whoami.asciiSpec; } if (whoami && whoami.length) { twhoami = ": "+whoami; if (whoami.indexOf("?") > -1) whoami = ((whoami.split("?"))[0]) + "?..."; else if (whoami.indexOf("%09") > -1) whoami = ((whoami.split("%09"))[0]) + "?..."; } if (!whoami) whoami = ''; var ibuf = '' + "\n" + "\n"+ "\n"+ '' + "\n" + '' + "\n" + "Gopher document" + twhoami + "\n"+ ''+ '\n"+ "\n"+ '
'+ '
'+ '
'+ '
' + whoami + '
' + "\n" + rootbutt + '
'+ '
' + "\n" + '' + "\n"; // init the channel with data and content type var chan = request.QueryInterface(nsIChannel); if (chan) chan.contentType = 'text/html'; this._context = context; this._listener.onStartRequest(request, context); this._feedBeast(request, this._listener, context, ibuf); }, onStopRequest : function(request, context, status) { this._buf += "
\n"+ "
\n"+ "
\n"+ "\n"+ "\n"; this._feedBeast(request, this._listener, this._context, this._buf); this._buf = ''; this._listener.onStopRequest(request, this._context, status); this._listener = null; this._context = null; if (this._sstream) this._sstream.close(); this._sstream = null; }, // nsIStreamListener onDataAvailable : function(request, context, stream, offset, count) { var i; var obuf = ''; if (!this._sstream) { // create (and cache) our scriptable input stream // note: this is NOT BINARY SAFE (but Gopher is // mostly 7-bit) this._sstream = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); this._sstream.init(stream); } this._pbuf += this._sstream.read(count); while((i = this._pbuf.indexOf("\n")) > -1) { // pull the next tab-delimited string off the buffer var w = this._pbuf.substr(0, i); if (i < this._pbuf.length) this._pbuf = this._pbuf.substr(i+1); else this._pbuf = ''; w = w.replace("\r", ""); w = w.replace("\n", ""); var itype = w.substr(0,1); var attribs = w.substr(1).split("\t"); var ds = this._dsSpaceFix(this._entityFix( attribs.shift())); if (!ds.length) ds = " "; var sel = attribs.shift(); if (!sel) sel = ''; var host = attribs.shift(); if (host && host.length) host = encodeURI(host); var port = parseInt(attribs.shift()); if (isNaN(port)) port = 0; // falls through to bogosity filter var icalt = (this._itypes.indexOf(itype) > -1) ? this._getL10NString(itype) : this._getL10NString('unknown'); var iconbase = "gopher:///internal-"; if (itype == "'" || itype == '"') { // these are just going to cause all kinds of // problems, so they are simply suppressed obuf += '\n"; } else if (itype == 'i' || itype == '3') { var lclass = (itype == '3') ? 'erroritem' : 'infoitem'; if (itype == '3') { obuf += '' + ''; //+ 'border = "0">'; } else obuf += ""; obuf += '' + '' + ds + "\n"; } else if (host && host.length && port > 1) { var suburl; var icon = "icn.png"; var lclass = "fileitem"; var spannable = 0; if (itype == '8' || itype == 'T') { // don't let them inject // arbitrary HTML with " sel = sel.replace(/["']/g, ""); suburl = "telnet://" + host + ":" + port + "/" + sel; var icon = "icn" + itype + ".png"; var lclass = "telnetitem"; icalt = this._getL10NString('telnet'); } else if (itype == 'h' && (sel.substr(0,4) == "URL:" || sel.substr(0,5) == "/URL:")) { var subn = (sel.substr(0,1) == "/") ? 5 : 4; suburl = encodeURI(sel.substr(subn)); if (suburl.match(/^javascript:/) || suburl.match(/^data:/)) { suburl = ""; ds += ' (' + this._getL10NString('unsafeurl') + ')'; } icon = "icnhURL.png"; lclass = "urlitem"; icalt = this._getL10NString('exturl'); } else if (port >= 80 && itype == 'h' && sel.match(/^[A-Z]+(%20| )/)) { sel = sel.replace(/^[A-Z]+(%20| )/,''); suburl = "http://"+host+":"+port+ (sel.substr(0,1) == "/" ? "" : "/") + encodeURI(unescape(sel)); icon = "icnhURL.png"; lclass = "urlitem"; icalt = this._getL10NString('exturl'); } else { suburl = OverbiteURLFromSel(host, port, itype, sel); if (itype == "g" || itype == "h" || itype == "p" || itype == "I" || itype == "7" || itype == "0") spannable = 1; this._divlevel += spannable; var dl = this._divlevel; // attempt to escape weird itypes var eitype = escape(itype); if (eitype.length > 1) // it was eitype = eitype.substr(1) .toLowerCase(); icon = (this._itypes.indexOf(itype)>-1) ? ("icn"+eitype+".png") : "icn.png"; lclass = (itype == '7' || itype == '2') ? 'searchitem': (itype == '1') ? 'diritem' : 'fileitem'; } obuf += ''; if (spannable) obuf += ''; obuf += '' + '' + '' + '' + '' + ds + "\n"; if (spannable) obuf += ''+ '
'+"\n"; } else { obuf += // no point in localizing this, I think "\n"; } } this._feedBeast(request, this._listener, this._context, obuf); }, // nsIStreamConverter convert : function(from, to, listener, context) { // synchronous conversion will not be supported w/o good reason throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, asyncConvertData : function(from, to, listener, context) { // if this were a real translator, we'd uncomment this // if (from != "text/x-overbite-gopher-dir" || to != "text/html") // throw Cr.NS_ERROR_NOT_IMPLEMENTED; this._listener = listener; this._context = context; } }; /* * the channel object that actually does the protocol negotiation. */ function OverbiteChannel(input_uri, proxyinfo, loadinfo) { this.init(input_uri, proxyinfo, loadinfo); } OverbiteChannel.prototype = { // my stuff _itemtype : null, _selector : null, _host : null, _port : null, _transport : null, _proxyinfo : null, _pump : null, _listener : null, _context : null, _progsink : null, _browser_callback : null, _is_caps : false, // for bug 769764 _proxyresolveflags : null, _proxy_uri : null, csoargs : '', queryargs : '', transreq : null, // actual transmitted request (see sendRequest) // we only need itypes for the notification helper. _bundle : Cc["@mozilla.org/intl/stringbundle;1"] .getService(Ci.nsIStringBundleService) .createBundle(OBFFCHROMEURL + "/locale/itypes.properties"), _getL10NString : function(msg, args) { var q; OverbiteLog("string query: "+msg); if (args) return this._bundle.formatStringFromName(msg, args, args.length); else return this._bundle.GetStringFromName(msg); }, // for strings that are already Unicode (like our localization) _unicodeEntityFix : function(what) { // needed to ampersand-encode ALT text and other stuff return "&#" + what.charCodeAt(0) + ";"; }, _unicodeStringFix : function(what) { var q; q = what; q = q.replace(/([\u0080-\uFFFF])/g, this._unicodeEntityFix); return q; }, // nsISupports // we can simplify this later; see nsIProxiedProtocolHandler QueryInterface : XPCOMUtils.generateQI(((Ci.nsIProtocolProxyCallback) ? [nsISupports, nsIChannel, nsIRequest, Ci.nsIProtocolProxyCallback] : [nsISupports, nsIChannel, nsIRequest])), // nsIRequest loadFlags : 0, name : null, _pending : false, isPending : function() { return this._pending; }, _status : Cr.NS_OK, _loadGroup : null, get status() { if (this._pump) return (this._pump.status); else return this._status; }, set status(status) { this._status = status; }, cancel : function(status) { if (this._pump) return (this._pump.cancel(status)); else return Cr.NS_OK; }, suspend : function() { if (this._pump) return (this._pump.suspend()); else return Cr.NS_OK; }, resume : function() { if (this._pump) return (this._pump.resume()); else return Cr.NS_OK; }, // nsIChannel loadInfo : null, loadAttributes : null, contentCharset : null, contentLength : -1, contentType : null, _notificationCallbacks : null , originalURI : null, owner : null, URI : null, init : function(input_uri, proxyinfo, loadinfo) { // constructor this.URI = input_uri; this.originalURI = input_uri; this.name = input_uri; if (proxyinfo) this._proxyinfo = proxyinfo; // process item type and set contentType var sel = decodeURI(input_uri.path); if (sel == null || !sel.length || sel == '/') { this._itemtype = '1'; this._selector = ''; } else if (sel.length == 1) { this._itemtype = sel; this._selector = ''; } else { this._itemtype = sel.substr(1,1); this._selector = sel.substr(2); } if (!input_uri.host || !input_uri.host.length) throw Cr.NS_ERROR_MALFORMED_URI; else this._host = input_uri.host; if (!input_uri.port || input_uri.port < 1) this._port = 70; else this._port = input_uri.port; // force our itemtype. realistically, the old Gopher let people // slide a lot with content sniffing, but that's not going to // happen anymore. this is written to be as rigid as possible // for a particular itemtype (but see type I, sigh). var c; switch(this._itemtype) { case '1' : case '7' : c = 'application/x-overbite-gopher-dir'; // this lets us override 1.9's gopher support break; case 'c' : c = 'text/css'; break; case 'x' : c = 'application/xml'; // I miss text/xml break; case '0' : case '2' : c = 'text/plain'; break; case 'g' : c = 'image/gif' ; break; case 'I' : // oh, man, this is gross -- designed to // support both the common use of I for // JPEG images, and the official spec that // I is a 'general image type' (we only // support the ones Mozilla will display) // // taken from netwerk/mime/public/nsMimeTypes.h if (this._selector.match(/\.jpe?g$/i)) c = 'image/jpeg' ; else if (this._selector.match(/\.gif$/i)) c = 'image/gif' ; // grrRRR! use 'g'!! else if (this._selector.match(/\.xbm$/i)) c = 'image/x-xbitmap' ; else if (this._selector.match(/\.png$/i)) c = 'image/png' ; else if (this._selector.match(/\.svg$/i)) c = 'image/svg+xml' ; else if (this._selector.match(/\.bmp$/i)) c = 'image/bmp' ; else if (this._selector.match(/\.icon?$/i)) c = 'image/x-icon' ; else if (this._selector.match(/\.tiff?$/i)) c = 'image/tiff' ; else c = 'image/jpeg' ; // this broke too many things //c = 'application/octet-stream'; break; case 'h' : c = 'text/html'; break; case 'p' : c = 'image/png'; break; case 'd' : c = 'application/pdf'; break; case '8' : case 'T' : throw Cr.NS_ERROR_NOT_IMPLEMENTED; break; default : c = 'application/octet-stream'; break; } this.contentType = c; this.loadInfo = loadinfo; OverbiteLog(("channel initialized: "+ this._host + " " + this._port + " " + this._itemtype + " " + this._selector + " " + "")); return Cr.NS_OK; }, /* open is not being implemented */ open : function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }, asyncOpen2 : function (listener) { return asyncOpen(listener, null); }, asyncOpen : function (listener, context) { this._listener = listener; this._context = context; // we can simplify this later; see nsIProxiedProtocolHandler if (Ci.nsIProtocolProxyCallback) { // if we can do asynchronous proxy resolution, ask // the proxy service what to do var nsPPS = Cc["@mozilla.org/network/protocol-proxy-service;1"] .getService(Ci.nsIProtocolProxyService); OverbiteLog(("async proxy resolution in progress")); nsPPS.asyncResolve(this.URI, this._proxyresolveflags, this); OverbiteLog(("waiting for proxy")); } else { // otherwise assume already synchronously resolved OverbiteLog(("no proxy callback needed")); this.onProxyAvailable(null, null, this._proxyinfo, Cr.NS_OK); } }, onProxyAvailable : function(request, uri, proxyinfo, status) { if (proxyinfo) OverbiteLog(("proxyinfo: "+proxyinfo)); this._proxyinfo = proxyinfo; OverbiteLog(("proxyinfo on; trying to initialize transport")); var transportService = Cc["@mozilla.org/network/socket-transport-service;1"] .getService(Ci.nsISocketTransportService); this._transport = transportService .createTransport(null, 0, this._host, this._port, this._proxyinfo); if (!(this.loadFlags & nsIRequest.LOAD_BACKGROUND)) { // hook up our event sink to the current UI thread var cq = Cc["@mozilla.org/thread-manager;1"] .getService().currentThread; OverbiteLog("yes, we have sink "+cq); this._transport.setEventSink(this, cq); } // open and initialize the data pump to read from the socket var sinput = this._transport.openInputStream(0,0,0); this.sendRequest(); this._pump = Cc["@mozilla.org/network/input-stream-pump;1"]. createInstance(Ci.nsIInputStreamPump); this._pump.init(sinput, -1, -1, 0, 0, true); this._pump.asyncRead(this, null); if (this._loadGroup) { this._loadGroup.addRequest(this, null); OverbiteLog("load group added"); } this._pending = true; // if this is caps, start it and abort now if (this._is_caps) { OverbiteLog("caps request in progress"); var caps = new OverbiteCaps(); caps._host = this._host; caps._port = this._port; caps._itemtype = this._itemtype; caps._selector = this._selector; caps.asyncConvertData( 'generic/caps', this.contentType, this._listener, this._context); this._listener = caps; this._context = null; caps._browser_callback = this._browser_callback; OverbiteLog(("now with caps: "+caps)); return Cr.NS_OK; } // push on another content listener (in this case us) for // those itemtypes requiring translation to something else var transitives = [ '1', '7' ]; // item types for translation if (transitives.indexOf(this._itemtype) > -1) { OverbiteLog(("this type requires translation")); var dirconv = new OverbiteDirconv(); dirconv.asyncConvertData( 'application/x-overbite-gopher-dir', 'text/html', this._listener, this._context); this._listener = dirconv; this._context = null; OverbiteLog(("now with dirconv: "+dirconv)); } // dotless conversion var prefs = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefBranch); var do_dotless = true; // first load hidden preference for dotless if (prefs.getPrefType(OBFFDOTLESSPREF) == prefs.PREF_BOOL) { do_dotless = prefs.getBoolPref(OBFFDOTLESSPREF); OverbiteLog(("dotless has been set to "+do_dotless)); // otherwise default is true } else { OverbiteLog(("no "+OBFFDOTLESSPREF+" not bool")); } if (do_dotless && ( this.contentType.match(/^text\//) || this.contentType == 'application/xml' || this.contentType == 'image/svg+xml' || 0)) { OverbiteLog("this type needs to be dotless"); var dotless = new OverbiteDotless(); dotless.asyncConvertData( 'generic/dotless-type', this.contentType, this._listener, this._context); this._listener = dotless; this._context = null; OverbiteLog(("now with dotless: "+dotless)); } OverbiteLog(("transport service for "+ this._host + " initialized")); return Cr.NS_OK; }, get loadGroup() { return this._loadGroup; }, set loadGroup(loadGroup) { this._loadGroup = loadGroup; this._progsink = null; }, get notificationCallbacks() { return this._notificationCallbacks; }, set notificationCallbacks(nc) { this._notificationCallbacks = nc; this._progsink = null; }, get securityInfo() { if (this._transport) return this._transport.securityInfo; //throw Cr.NS_ERROR_NOT_AVAILABLE; return null; }, // set securityInfo? bwahahaha set securityInfo(foo) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; return null; }, onStartRequest : function(request, context) { if (this._listener) this._listener.onStartRequest(this, this._context); OverbiteLog(("onStartRequest"+this._listener)); }, onStopRequest : function(request, context, status) { OverbiteLog(("onStopRequest: "+status)); if(Components.isSuccessCode(status)) this.status = status; if (this._listener) { this._listener.onStopRequest(this, this._context, this.status); OverbiteLog("listener stopped"); this._listener = null; this._context = null; } if (this._loadGroup) { this._loadGroup.removeRequest(this, context, // null, this.status); OverbiteLog("load group stopped"); } this._pump = null; this._transport.close(this.status); this._transport = null; this.notificationCallbacks = null; // use our own getter/setter this._progsink = null; this._pending = false; OverbiteLog("end of request"); // Attach a notification box for navigation. // Attach only iff: // - the tab URL matches ours. (We could be in an iframe.) // - there is no notification box there already. // - this is not a gopher menu. We do this in the HTML instead. // Backing up to a menu doesn't restore this box, so we should // only do this to a terminal document. var transitives = [ '1', '7' ]; // item types that are menus if (transitives.indexOf(this._itemtype) == -1) { try { // no meaning if this fails. OverbiteLog("attempting to load nav bar "+this.originalURI.spec); // XXX try selected tab first for speed? // Iterate over all windows and tabs to find us. // If there are multiple instances, add the box; it // won't hurt (as long as we didn't before). var wm = Cc["@mozilla.org/appshell/window-mediator;1"] .getService(Ci.nsIWindowMediator); var be = wm.getEnumerator("navigator:browser"); while(be.hasMoreElements()) { var win = be.getNext(); var tabb = win.gBrowser; // check each tab var tabs = tabb.browsers.length; for (var i=0; i 1 && (input_uri.path.charAt(input_uri.path.length-1) == "/" || input_uri.path.match(/^\/[^1]$/))) { var prefs = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefBranch); // first load hidden preference for cluefulness if (prefs.getPrefType(OBFFFIXITYPEPREF) == prefs.PREF_BOOL) { rv = prefs.getBoolPref(OBFFFIXITYPEPREF); // this is our default answer } else { rv = OverbitePrompter("swapitype", function() { return prompter.confirm(null, self._getL10NString('swapitype.title'), self._getL10NString('swapitype')) }); } if (rv) { var newuri = "gopher://" + input_uri.host + ((input_uri.port == 70 || input_uri.port == -1) ? "" : (":"+input_uri.port)) + "/1" + input_uri.path.substr(2); this._getBrowser().webNavigation.loadURI( newuri, 0, null, null, null); // this is a bit cheap, but we don't want // Fx freaking that we didn't give it a channel throw Cr.NS_ERROR_ABORT; return null; // above will do redirect } else { // INSERT COINS AND CONTINUE } } // handle itemtype 7 at this stage and turn into a channel // for itemtype 1 instead, except if this particular URL has // arguments (but wouldn't you want to do that as itype 1?). if (input_uri.path.substr(0,2) == "/7" && input_uri.path.indexOf("?") == -1 && input_uri.path.indexOf("%09") == -1) { // we will accept "blank" responses -- could be valid // use the global prompter objects var rv = OverbitePrompter("search", function() { return prompter.prompt(null, self._getL10NString('search.title'), self._getL10NString('search'), query, null, chequestub) }); if (!rv) { throw Cr.NS_ERROR_ABORT; return null; } // stuff query into channel query args rather than // kludging it into a URL var ob = new OverbiteChannel(input_uri, proxyinfo, loadinfo); ob._proxyresolveflags = proxyresolveflags; ob._proxy_uri = proxy_uri; ob.queryargs = query.value; return ob; } // similarly handle itemtype 2 // if it's /2....fjhgkrjgh then pass that on to the CSO server // (it's a fully qualified query that requires no parsing) if (input_uri.path == "/2" || input_uri.path == "/2/") { // unlike itype 7 we MUST enter a query for this var rv = OverbitePrompter("cso", function() { return prompter.prompt(null, self._getL10NString('cso.title'), self._getL10NString('cso'), query, null, chequestub) }); if (rv && !query.value.length) // blank query OverbitePrompter("csobogus", function() { prompter.alert(null, self._getL10NString('csobogus.title'), self._getL10NString('csobogus')) }); if (!rv || !query.value.length) { throw Cr.NS_ERROR_ABORT; return null; } // stuff query into channel csoargs var ob = new OverbiteChannel(input_uri, proxyinfo, loadinfo); ob._proxyresolveflags = proxyresolveflags; ob._proxy_uri = proxy_uri; ob.csoargs = query.value; return ob; } // make chrome channel either to images or CSS if // input_uri is "gopher:///internal-" and no / and // extension is .png, .css or .js if (!input_uri.host.length && // PARANOIA STRIKES DEEP IN THE HEARTLAND!!!1 !input_uri.path.substr(1).match(/\//) && input_uri.path.match(/^\/internal-[^/ ]+\.(css|png|js)$/)) { OverbiteLog("handling internal chrome: " +input_uri.asciiSpec); var IURL; var mpath = input_uri.path.substr(1) .replace(/^internal-/, ""); // try profile directory var dirService = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties); var uprof = dirService.get("ProfD", Ci.nsIFile); var basedir = "gopherchrome"; //uprof.appendRelativePath(basedir); uprof.append(basedir); if (uprof.exists() && uprof.isDirectory()) { // use the user's gopherchrome directory for // CSS, icons and content level javascript OverbiteLog("trying user directory"); var fileProServ = Cc["@mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService) .getProtocolHandler("file") .QueryInterface(Ci .nsIFileProtocolHandler); uprof.append(mpath); if (uprof.exists() && uprof.isFile()) IURL = fileProServ.getURLSpecFromFile( uprof); } if (!IURL) { OverbiteLog("trying internal chrome dir"); IURL = OBFFCHROMEURL+ "/content/chrome/" +mpath; } OverbiteLog("resulting URL: "+IURL); return ioService.newChannel(IURL, null, null); } // Reject empty-host Gopher URLs. The legacy about-page fallback has // intentionally been removed from the built-in browser integration. if (!input_uri.host.length) { throw Cr.NS_ERROR_MALFORMED_URI; } // otherwise // immediately reject "pseudo" item types we don't handle // do this here because it traps internal URLs var wontshow = ['/i', '/3', '/8', '/T']; if (input_uri.path && input_uri.path.length > 1 && wontshow.indexOf(input_uri.path.substr(0,2)) > -1) { OverbitePrompter("baditype", function() { prompter.alert(null, self._getL10NString('baditype.title'), self._getL10NString('baditype')) }); throw Cr.NS_ERROR_ABORT; return null; } // silently reject port numbers we will never allow //if (badports.indexOf(input_uri.port) > -1) { if (input_uri.port && input_uri.port >= 0 && alwayslet.indexOf(input_uri.port)== -1) { OverbiteLog("illegal port: "+input_uri.port); throw Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED; return null; } // else it's a legit gopher request // make our channel and gopher it ob = new OverbiteChannel(input_uri, proxyinfo, loadinfo); ob._proxyresolveflags = proxyresolveflags; ob._proxy_uri = proxy_uri; return ob; } }; /* Startup code */ var components = [ OverbiteProtocol ]; function NSGetModule(compMgr, fileSpec) { return XPCOMUtils.generateModule(components); } /* for Gecko 2 (deprecated) */ if (XPCOMUtils.generateNSGetFactory) var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); OverbiteLog("startup with version "+OBFFVERS+" build "+OBFFBUILD); // Built-in browser integration must not create or select a tab during // application startup.