Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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_OFFLINE = NS_ERROR_MODULE_NETWORK + 16;
const NS_ERROR_NET_RESET = NS_ERROR_MODULE_NETWORK + 20;
const NS_ERROR_UNKNOWN_PROXY_HOST = NS_ERROR_MODULE_NETWORK + 42;
const NS_ERROR_NET_INTERRUPT = NS_ERROR_MODULE_NETWORK + 71;
const NS_ERROR_PROXY_CONNECTION_REFUSED = NS_ERROR_MODULE_NETWORK + 72;
// Offline error constants:
const NS_ERROR_BINDING_ABORTED = NS_ERROR_MODULE_NETWORK + 2;
const NS_ERROR_ABORT = 0x80004004;
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 error class constants:
const ERROR_CLASS_SSL_PROTOCOL = 1;
const ERROR_CLASS_BAD_CERT = 2;
// Security Constants.
const STATE_IS_BROKEN = 1;
const STATE_IS_SECURE = 2;
const STATE_IS_INSECURE = 3;
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;
}
/* This object implements nsIBadCertListener2
* The idea is to suppress the default UI's alert box
* and allow the exception to propagate normally
*/
function BadCertHandler()
{
}
BadCertHandler.prototype.getInterface =
function badcert_getinterface(aIID)
{
return this.QueryInterface(aIID);
}
BadCertHandler.prototype.QueryInterface =
function badcert_queryinterface(aIID)
{
if (aIID.equals(Components.interfaces.nsIBadCertListener2) ||
aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
aIID.equals(Components.interfaces.nsISupports))
{
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/* Returning true in the following two callbacks
* means suppress default the error UI (modal alert).
*/
BadCertHandler.prototype.notifyCertProblem =
function badcert_notifyCertProblem(socketInfo, sslStatus, targetHost)
{
return true;
}
/**
* Wraps up various mechanics of sockets for easy consumption by other code.
*
* @param binary Provide |true| or |false| here to override the automatic
* selection of binary or text streams. This should only ever be
* specified as |true| or omitted, otherwise you will be shooting
* yourself in the foot on some versions - let the code handle
* the choice unless you know you need binary.
*/
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 determine 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;
}
}
/*
* As part of the changes in Gecko 1.9, invalid SSL certificates now
* produce a horrible error message. We must look up the toolkit version
* to see if we need to catch these errors cleanly - see bug 454966.
*/
if (!("strictSSL" in CBSConnection.prototype))
{
CBSConnection.prototype.strictSSL = false;
var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
if (app && ("platformVersion" in app) &&
compareVersions("1.9", app.platformVersion) >= 0)
{
CBSConnection.prototype.strictSSL = 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."))
{
throw ("Unable to use binary streams in this build.");
}
}
CBSConnection.prototype.workingBinaryStreams = -1;
CBSConnection.prototype.connect =
function bc_connect(host, port, config, observer)
{
this.host = host.toLowerCase();
this.port = port;
/* The APIs below want host:port. Later on, we also reformat the host to
* strip IPv6 literal brackets.
*/
var hostPort = host + ":" + port;
if (!config)
config = {};
if (!("proxyInfo" in config))
{
// Lets get a transportInfo for this
var pps = getService("@mozilla.org/network/protocol-proxy-service;1",
"nsIProtocolProxyService");
/* Force Necko to supply the HTTP proxy info if desired. For none,
* force no proxy. Other values will get default treatment.
*/
var uri = "irc://" + hostPort;
if ("proxy" in config)
{
if (config.proxy == "http")
uri = "http://" + hostPort;
else if (config.proxy == "none")
uri = "";
}
var self = this;
function continueWithProxy(proxyInfo)
{
config.proxyInfo = proxyInfo;
try
{
self.connect(host, port, config, observer);
}
catch (ex)
{
if ("onSocketConnection" in observer)
observer.onSocketConnection(host, port, config, ex);
return;
}
if ("onSocketConnection" in observer)
observer.onSocketConnection(host, port, config);
}
if (uri)
{
uri = Services.io.newURI(uri);
if ("asyncResolve" in pps)
{
pps.asyncResolve(uri, 0, {
onProxyAvailable: function(request, uri, proxyInfo, status) {
continueWithProxy(proxyInfo);
}
});
}
else if ("resolve" in pps)
{
continueWithProxy(pps.resolve(uri, 0));
}
else if ("examineForProxy" in pps)
{
continueWithProxy(pps.examineForProxy(uri));
}
else
{
throw "Unable to find method to resolve proxies";
}
}
else
{
continueWithProxy(null);
}
return true;
}
// Strip the IPv6 literal brackets; all the APIs below don't want them.
if (host[0] == '[' && host[host.length - 1] == ']')
host = host.substr(1, host.length - 2);
/* 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.
*/
var proxyInfo = config.proxyInfo || null;
var usingHTTPCONNECT = ("proxy" in config) && (config.proxy == "http")
&& proxyInfo;
if (proxyInfo && ("type" in proxyInfo) && (proxyInfo.type == "unknown"))
throw JSIRC_ERR_PAC_LOADING;
/* use new necko interfaces */
if (("isSecure" in config) && config.isSecure)
{
this._transport = this._sockService.
createTransport(["ssl"], 1, host, port,
proxyInfo);
if (this.strictSSL)
this._transport.securityCallbacks = new BadCertHandler();
}
else
{
this._transport = this._sockService.
createTransport(["starttls"], 1, host, port, proxyInfo);
}
if (!this._transport)
throw ("Error creating transport.");
var openFlags = 0;
/* 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 " + hostPort + " HTTP/1.1\r\n\r\n");
return true;
}
CBSConnection.prototype.startTLS =
function bc_starttls()
{
if (!this.isConnected || !this._transport.securityInfo)
return;
var secInfo = this._transport.securityInfo;
var sockControl = secInfo.QueryInterface(Ci.nsITLSSocketControl);
sockControl.StartTLS();
}
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;
var openFlags = 0;
/* 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.";
this.sendDataNow(str);
}
CBSConnection.prototype.readData =
function bc_readdata(timeout, count)
{
if (!this.isConnected)
throw "Not Connected.";
var rv;
if (!("_sInputStream" in this)) {
this._sInputStream = toSInputStream(this._inputStream);
dump("OMG, setting up _sInputStream!\n");
}
try
{
// XPCshell h4x
if (typeof count == "undefined")
count = this._sInputStream.available();
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)
{
var cls = Components.classes["@mozilla.org/network/input-stream-pump;1"];
var pump = cls.createInstance(Components.interfaces.nsIInputStreamPump);
// Account for Bug 1402888 which removed the startOffset and readLimit
// parameters from init.
if (pump.init.length > 5)
{
pump.init(this._inputStream, -1, -1, 0, 0, false);
} else
{
pump.init(this._inputStream, 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 ()
{
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;
}
/**
* Gets information about the security of the connection.
*
* |STATE_IS_BROKEN| is returned if any errors occur and |STATE_IS_INSECURE| is
* returned for disconnected sockets.
*
* @returns A value from the |STATE_IS_*| enumeration at the top of this file.
*/
CBSConnection.prototype.getSecurityState =
function bc_getsecuritystate()
{
if (!this.isConnected || !this._transport.securityInfo)
return STATE_IS_INSECURE;
try
{
// Get the actual SSL Status
let sslSp = this._transport.securityInfo
.QueryInterface(Ci.nsISSLStatusProvider);
if (!sslSp.SSLStatus)
return STATE_IS_BROKEN;
let sslStatus = sslSp.SSLStatus.QueryInterface(Ci.nsISSLStatus);
// Store appropriate status
if (!("keyLength" in sslStatus) || !sslStatus.keyLength)
return STATE_IS_BROKEN;
return STATE_IS_SECURE;
}
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;
// Get the actual SSL Status
let sslSp = this._transport.securityInfo
.QueryInterface(Ci.nsISSLStatusProvider);
if (!sslSp.SSLStatus)
return null;
let sslStatus = sslSp.SSLStatus.QueryInterface(Ci.nsISSLStatus);
// return the certificate
return sslStatus.serverCert;
}
CBSConnection.prototype.asyncWrite =
function bc_asyncwrite()
{
throw "Not Implemented.";
}
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;
}