RetroZilla/extensions/irc/js/lib/connection-xpcom.js
2015-10-20 23:03:22 -04:00

710 lines
22 KiB
JavaScript

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* 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 JSIRC Library.
*
* The Initial Developer of the Original Code is
* New Dimensions Consulting, Inc.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Robert Ginda, rginda@ndcico.com, original author
* Peter Van der Beken, peter.vanderbeken@pandora.be, necko-only version
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const NS_ERROR_MODULE_NETWORK = 2152398848;
const NS_ERROR_UNKNOWN_HOST = NS_ERROR_MODULE_NETWORK + 30;
const NS_ERROR_CONNECTION_REFUSED = NS_ERROR_MODULE_NETWORK + 13;
const NS_ERROR_NET_TIMEOUT = NS_ERROR_MODULE_NETWORK + 14;
const NS_ERROR_NET_RESET = NS_ERROR_MODULE_NETWORK + 20;
const NS_NET_STATUS_RESOLVING_HOST = NS_ERROR_MODULE_NETWORK + 3;
const NS_NET_STATUS_CONNECTED_TO = NS_ERROR_MODULE_NETWORK + 4;
const NS_NET_STATUS_SENDING_TO = NS_ERROR_MODULE_NETWORK + 5;
const NS_NET_STATUS_RECEIVING_FROM = NS_ERROR_MODULE_NETWORK + 6;
const NS_NET_STATUS_CONNECTING_TO = NS_ERROR_MODULE_NETWORK + 7;
// Security Constants.
const STATE_IS_BROKEN = 1;
const STATE_IS_SECURE = 2;
const STATE_IS_INSECURE = 3;
const STATE_SECURE_LOW = 1;
const STATE_SECURE_HIGH = 2;
const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
const nsIBinaryInputStream = Components.interfaces.nsIBinaryInputStream;
const nsIBinaryOutputStream = Components.interfaces.nsIBinaryOutputStream;
function toSInputStream(stream, binary)
{
var sstream;
if (binary)
{
sstream = Components.classes["@mozilla.org/binaryinputstream;1"];
sstream = sstream.createInstance(nsIBinaryInputStream);
sstream.setInputStream(stream);
}
else
{
sstream = Components.classes["@mozilla.org/scriptableinputstream;1"];
sstream = sstream.createInstance(nsIScriptableInputStream);
sstream.init(stream);
}
return sstream;
}
function toSOutputStream(stream, binary)
{
var sstream;
if (binary)
{
sstream = Components.classes["@mozilla.org/binaryoutputstream;1"];
sstream = sstream.createInstance(Components.interfaces.nsIBinaryOutputStream);
sstream.setOutputStream(stream);
}
else
{
sstream = stream;
}
return sstream;
}
function CBSConnection (binary)
{
/* Since 2003-01-17 18:14, Mozilla has had this contract ID for the STS.
* Prior to that it didn't have one, so we also include the CID for the
* STS back then - DO NOT UPDATE THE ID if it changes in Mozilla.
*/
const sockClassByName =
Components.classes["@mozilla.org/network/socket-transport-service;1"];
const sockClassByID =
Components.classesByID["{c07e81e0-ef12-11d2-92b6-00105a1b0d64}"];
var sockServiceClass = (sockClassByName || sockClassByID);
if (!sockServiceClass)
throw ("Couldn't get socket service class.");
var sockService = sockServiceClass.getService();
if (!sockService)
throw ("Couldn't get socket service.");
this._sockService = sockService.QueryInterface
(Components.interfaces.nsISocketTransportService);
/* Note: as part of the mess from bug 315288 and bug 316178, ChatZilla now
* uses the *binary* stream interfaces for all network
* communications.
*
* However, these interfaces do not exist prior to 1999-11-05. To
* make matters worse, an incompatible change to the "readBytes"
* method of this interface was made on 2003-03-13; luckly, this
* change also added a "readByteArray" method, which we will check
* for below, to determin if we can use the binary streams.
*/
// We want to check for working binary streams only the first time.
if (CBSConnection.prototype.workingBinaryStreams == -1)
{
CBSConnection.prototype.workingBinaryStreams = false;
if (typeof nsIBinaryInputStream != "undefined")
{
var isCls = Components.classes["@mozilla.org/binaryinputstream;1"];
var inputStream = isCls.createInstance(nsIBinaryInputStream);
if ("readByteArray" in inputStream)
CBSConnection.prototype.workingBinaryStreams = true;
}
}
this.wrappedJSObject = this;
if (typeof binary != "undefined")
this.binaryMode = binary;
else
this.binaryMode = this.workingBinaryStreams;
if (!ASSERT(!this.binaryMode || this.workingBinaryStreams,
"Unable to use binary streams in this build."))
{
return null;
}
}
CBSConnection.prototype.workingBinaryStreams = -1;
CBSConnection.prototype.connect =
function bc_connect(host, port, config, observer)
{
this.host = host.toLowerCase();
this.port = port;
if (typeof config != "object")
config = {};
// Lets get a transportInfo for this
var pps = getService("@mozilla.org/network/protocol-proxy-service;1",
"nsIProtocolProxyService");
if (!pps)
throw ("Couldn't get protocol proxy service");
var ios = getService("@mozilla.org/network/io-service;1", "nsIIOService");
function getProxyFor(uri)
{
var uri = ios.newURI(uri, null, null);
// As of 2005-03-25, 'examineForProxy' was replaced by 'resolve'.
if ("resolve" in pps)
return pps.resolve(uri, 0);
if ("examineForProxy" in pps)
return pps.examineForProxy(uri);
return null;
};
var proxyInfo = null;
var usingHTTPCONNECT = false;
if ("proxy" in config)
{
/* Force Necko to supply the HTTP proxy info if desired. For none,
* force no proxy. Other values will get default treatment.
*/
if (config.proxy == "http")
proxyInfo = getProxyFor("http://" + host + ":" + port);
else if (config.proxy != "none")
proxyInfo = getProxyFor("irc://" + host + ":" + port);
/* Since the proxy info is opaque, we need to check that we got
* something for our HTTP proxy - we can't just check proxyInfo.type.
*/
usingHTTPCONNECT = ((config.proxy == "http") && proxyInfo);
}
else
{
proxyInfo = getProxyFor("irc://" + host + ":" + port);
}
if (jsenv.HAS_STREAM_PROVIDER)
{
if (("isSecure" in config) && config.isSecure)
{
this._transport = this._sockService.
createTransportOfType("ssl", host, port,
proxyInfo, 0, 0);
}
else
{
this._transport = this._sockService.
createTransport(host, port, proxyInfo, 0, 0);
}
if (!this._transport)
throw ("Error creating transport.");
if (jsenv.HAS_NSPR_EVENTQ)
{ /* we've got an event queue, so start up an async write */
this._streamProvider = new StreamProvider (observer);
this._write_req =
this._transport.asyncWrite (this._streamProvider, this,
0, -1, 0);
}
else
{
/* no nspr event queues in this environment, we can't use async
* calls, so set up the streams. */
this._outputStream = this._transport.openOutputStream(0, -1, 0);
if (!this._outputStream)
throw "Error getting output stream.";
this._sOutputStream = toSOutputStream(this._outputStream,
this.binaryMode);
this._inputStream = this._transport.openInputStream(0, -1, 0);
if (!this._inputStream)
throw "Error getting input stream.";
this._sInputStream = toSInputStream(this._inputStream,
this.binaryMode);
}
}
else
{
/* use new necko interfaces */
if (("isSecure" in config) && config.isSecure)
{
this._transport = this._sockService.
createTransport(["ssl"], 1, host, port,
proxyInfo);
}
else
{
this._transport = this._sockService.
createTransport(null, 0, host, port, proxyInfo);
}
if (!this._transport)
throw ("Error creating transport.");
/* if we don't have an event queue, then all i/o must be blocking */
var openFlags;
if (jsenv.HAS_NSPR_EVENTQ)
openFlags = 0;
else
openFlags = Components.interfaces.nsITransport.OPEN_BLOCKING;
/* no limit on the output stream buffer */
this._outputStream =
this._transport.openOutputStream(openFlags, 4096, -1);
if (!this._outputStream)
throw "Error getting output stream.";
this._sOutputStream = toSOutputStream(this._outputStream,
this.binaryMode);
this._inputStream = this._transport.openInputStream(openFlags, 0, 0);
if (!this._inputStream)
throw "Error getting input stream.";
this._sInputStream = toSInputStream(this._inputStream,
this.binaryMode);
}
this.connectDate = new Date();
this.isConnected = true;
// Bootstrap the connection if we're proxying via an HTTP proxy.
if (usingHTTPCONNECT)
{
this.sendData("CONNECT " + host + ":" + port + " HTTP/1.1\r\n\r\n");
}
return this.isConnected;
}
CBSConnection.prototype.listen =
function bc_listen(port, observer)
{
var serverSockClass =
Components.classes["@mozilla.org/network/server-socket;1"];
if (!serverSockClass)
throw ("Couldn't get server socket class.");
var serverSock = serverSockClass.createInstance();
if (!serverSock)
throw ("Couldn't get server socket.");
this._serverSock = serverSock.QueryInterface
(Components.interfaces.nsIServerSocket);
this._serverSock.init(port, false, -1);
this._serverSockListener = new SocketListener(this, observer);
this._serverSock.asyncListen(this._serverSockListener);
this.port = this._serverSock.port;
return true;
}
CBSConnection.prototype.accept =
function bc_accept(transport, observer)
{
this._transport = transport;
this.host = this._transport.host.toLowerCase();
this.port = this._transport.port;
if (jsenv.HAS_STREAM_PROVIDER)
{
if (jsenv.HAS_NSPR_EVENTQ)
{ /* we've got an event queue, so start up an async write */
this._streamProvider = new StreamProvider (observer);
this._write_req =
this._transport.asyncWrite (this._streamProvider, this,
0, -1, 0);
}
else
{
/* no nspr event queues in this environment, we can't use async
* calls, so set up the streams. */
this._outputStream = this._transport.openOutputStream(0, -1, 0);
if (!this._outputStream)
throw "Error getting output stream.";
this._sOutputStream = toSOutputStream(this._outputStream,
this.binaryMode);
//this._scriptableInputStream =
this._inputStream = this._transport.openInputStream(0, -1, 0);
if (!this._inputStream)
throw "Error getting input stream.";
this._sInputStream = toSInputStream(this._inputStream,
this.binaryMode);
}
}
else
{
/* if we don't have an event queue, then all i/o must be blocking */
var openFlags;
if (jsenv.HAS_NSPR_EVENTQ)
openFlags = 0;
else
openFlags = Components.interfaces.nsITransport.OPEN_BLOCKING;
/* no limit on the output stream buffer */
this._outputStream =
this._transport.openOutputStream(openFlags, 4096, -1);
if (!this._outputStream)
throw "Error getting output stream.";
this._sOutputStream = toSOutputStream(this._outputStream,
this.binaryMode);
this._inputStream = this._transport.openInputStream(openFlags, 0, 0);
if (!this._inputStream)
throw "Error getting input stream.";
this._sInputStream = toSInputStream(this._inputStream,
this.binaryMode);
}
this.connectDate = new Date();
this.isConnected = true;
// Clean up listening socket.
this.close();
return this.isConnected;
}
CBSConnection.prototype.close =
function bc_close()
{
if ("_serverSock" in this && this._serverSock)
this._serverSock.close();
}
CBSConnection.prototype.disconnect =
function bc_disconnect()
{
if ("_inputStream" in this && this._inputStream)
this._inputStream.close();
if ("_outputStream" in this && this._outputStream)
this._outputStream.close();
this.isConnected = false;
/*
this._streamProvider.close();
if (this._streamProvider.isBlocked)
this._write_req.resume();
*/
}
CBSConnection.prototype.sendData =
function bc_senddata(str)
{
if (!this.isConnected)
throw "Not Connected.";
if (jsenv.HAS_NSPR_EVENTQ && jsenv.HAS_STREAM_PROVIDER)
this.asyncWrite (str);
else
this.sendDataNow (str);
}
CBSConnection.prototype.readData =
function bc_readdata(timeout, count)
{
if (!this.isConnected)
throw "Not Connected.";
var rv;
try
{
if (this.binaryMode)
rv = this._sInputStream.readBytes(count);
else
rv = this._sInputStream.read(count);
}
catch (ex)
{
dd ("*** Caught " + ex + " while reading.");
this.disconnect();
throw (ex);
}
return rv;
}
CBSConnection.prototype.startAsyncRead =
function bc_saread (observer)
{
if (jsenv.HAS_STREAM_PROVIDER)
{
this._transport.asyncRead (new StreamListener (observer),
this, 0, -1, 0);
}
else
{
var cls = Components.classes["@mozilla.org/network/input-stream-pump;1"];
var pump = cls.createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(this._inputStream, -1, -1, 0, 0, false);
pump.asyncRead(new StreamListener(observer), this);
}
}
CBSConnection.prototype.asyncWrite =
function bc_awrite (str)
{
this._streamProvider.pendingData += str;
if (this._streamProvider.isBlocked)
{
this._write_req.resume();
this._streamProvider.isBlocked = false;
}
}
CBSConnection.prototype.hasPendingWrite =
function bc_haspwrite ()
{
if (jsenv.HAS_STREAM_PROVIDER)
return (this._streamProvider.pendingData != "");
else
return false; /* data already pushed to necko */
}
CBSConnection.prototype.sendDataNow =
function bc_senddatanow(str)
{
var rv = false;
try
{
if (this.binaryMode)
this._sOutputStream.writeBytes(str, str.length);
else
this._sOutputStream.write(str, str.length);
rv = true;
}
catch (ex)
{
dd ("*** Caught " + ex + " while sending.");
this.disconnect();
throw (ex);
}
return rv;
}
/* getSecurityState returns an array containing information about the security
* of the connection. The array always has at least one item, which contains a
* value from the STATE_IS_* enumeration at the top of this file. Iff this is
* STATE_IS_SECURE, the array has a second item indicating the level of
* security - a value from the STATE_SECURE_* enumeration.
*
* STATE_IS_BROKEN is returned if any errors occur, and STATE_IS_INSECURE is
* returned for disconnected sockets.
*/
CBSConnection.prototype.getSecurityState =
function bc_getsecuritystate()
{
if (!this.isConnected || !this._transport.securityInfo)
return [STATE_IS_INSECURE];
try
{
var sslSp = Components.interfaces.nsISSLStatusProvider;
var sslStatus = Components.interfaces.nsISSLStatus;
// Get the actual SSL Status
sslSp = this._transport.securityInfo.QueryInterface(sslSp);
sslStatus = sslSp.SSLStatus.QueryInterface(sslStatus);
// Store appropriate status
if (!("keyLength" in sslStatus) || !sslStatus.keyLength)
return [STATE_IS_BROKEN];
else if (sslStatus.keyLength >= 90)
return [STATE_IS_SECURE, STATE_SECURE_HIGH];
else
return [STATE_IS_SECURE, STATE_SECURE_LOW];
}
catch (ex)
{
// Something goes wrong -> broken security icon
dd("Exception getting certificate for connection: " + ex.message);
return [STATE_IS_BROKEN];
}
}
CBSConnection.prototype.getCertificate =
function bc_getcertificate()
{
if (!this.isConnected || !this._transport.securityInfo)
return null;
var sslSp = Components.interfaces.nsISSLStatusProvider;
var sslStatus = Components.interfaces.nsISSLStatus;
// Get the actual SSL Status
sslSp = this._transport.securityInfo.QueryInterface(sslSp);
sslStatus = sslSp.SSLStatus.QueryInterface(sslStatus);
// return the certificate
return sslStatus.serverCert;
}
function _notimpl ()
{
throw "Not Implemented.";
}
if (!jsenv.HAS_NSPR_EVENTQ)
{
CBSConnection.prototype.startAsyncRead = _notimpl;
CBSConnection.prototype.asyncWrite = _notimpl;
}
else if (jsenv.HAS_STREAM_PROVIDER)
{
CBSConnection.prototype.sendDataNow = _notimpl;
}
else
{
CBSConnection.prototype.asyncWrite = _notimpl;
}
delete _notimpl;
function StreamProvider(observer)
{
this._observer = observer;
}
StreamProvider.prototype.pendingData = "";
StreamProvider.prototype.isBlocked = true;
StreamProvider.prototype.close =
function sp_close ()
{
this.isClosed = true;
}
StreamProvider.prototype.onDataWritable =
function sp_datawrite (request, ctxt, ostream, offset, count)
{
//dd ("StreamProvider.prototype.onDataWritable");
if ("isClosed" in this && this.isClosed)
throw Components.results.NS_BASE_STREAM_CLOSED;
if (!this.pendingData)
{
this.isBlocked = true;
/* this is here to support pre-XPCDOM builds (0.9.0 era), which
* don't have this result code mapped. */
if (!Components.results.NS_BASE_STREAM_WOULD_BLOCK)
throw 2152136711;
throw Components.results.NS_BASE_STREAM_WOULD_BLOCK;
}
var len = ostream.write (this.pendingData, this.pendingData.length);
this.pendingData = this.pendingData.substr (len);
}
StreamProvider.prototype.onStartRequest =
function sp_startreq (request, ctxt)
{
//dd ("StreamProvider::onStartRequest: " + request + ", " + ctxt);
}
StreamProvider.prototype.onStopRequest =
function sp_stopreq (request, ctxt, status)
{
//dd ("StreamProvider::onStopRequest: " + request + ", " + ctxt + ", " +
// status);
if (this._observer)
this._observer.onStreamClose(status);
}
function StreamListener(observer)
{
this._observer = observer;
}
StreamListener.prototype.onStartRequest =
function sl_startreq (request, ctxt)
{
//dd ("StreamListener::onStartRequest: " + request + ", " + ctxt);
}
StreamListener.prototype.onStopRequest =
function sl_stopreq (request, ctxt, status)
{
//dd ("StreamListener::onStopRequest: " + request + ", " + ctxt + ", " +
//status);
if (this._observer)
this._observer.onStreamClose(status);
}
StreamListener.prototype.onDataAvailable =
function sl_dataavail (request, ctxt, inStr, sourceOffset, count)
{
ctxt = ctxt.wrappedJSObject;
if (!ctxt)
{
dd ("*** Can't get wrappedJSObject from ctxt in " +
"StreamListener.onDataAvailable ***");
return;
}
if (!("_sInputStream" in ctxt))
ctxt._sInputStream = toSInputStream(inStr, false);
if (this._observer)
this._observer.onStreamDataAvailable(request, inStr, sourceOffset,
count);
}
function SocketListener(connection, observer)
{
this._connection = connection;
this._observer = observer;
}
SocketListener.prototype.onSocketAccepted =
function sl_onSocketAccepted(socket, transport)
{
this._observer.onSocketAccepted(socket, transport);
}
SocketListener.prototype.onStopListening =
function sl_onStopListening(socket, status)
{
delete this._connection._serverSockListener;
delete this._connection._serverSock;
}