mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 01:48:05 +00:00
Bug 861196 - mozTCPSocket needs to translate nsISSLStatus error codes to something that can be exposed to content. r=bsmith, r=jst, a=blocking-b2g=tef+
sufficiently happy try build: https://tbpl.mozilla.org/?tree=Try&rev=36edcbc1e65a
This commit is contained in:
parent
24efd6fafd
commit
6af8bd397d
@ -183,11 +183,10 @@ interface nsIDOMTCPSocket : nsISupports
|
||||
* Needed to account for multiple possible types that can be provided to
|
||||
* the socket callbacks as arguments.
|
||||
*/
|
||||
[scriptable, uuid(fff9ed4a-5e94-4fc0-84e4-7f4d35d0a26c)]
|
||||
[scriptable, uuid(322193a3-da17-4ca5-ad26-3539c519ea4b)]
|
||||
interface nsITCPSocketInternal : nsISupports {
|
||||
// Trigger the callback for |type| and provide an Error() object with the given data
|
||||
void callListenerError(in DOMString type, in DOMString message, in DOMString filename,
|
||||
in uint32_t lineNumber, in uint32_t columnNumber);
|
||||
// Trigger the callback for |type| and provide a DOMError() object with the given data
|
||||
void callListenerError(in DOMString type, in DOMString name);
|
||||
|
||||
// Trigger the callback for |type| and provide a string argument
|
||||
void callListenerData(in DOMString type, in DOMString data);
|
||||
|
@ -11,11 +11,8 @@ include "mozilla/net/NeckoMessageUtils.h";
|
||||
|
||||
using mozilla::void_t;
|
||||
|
||||
struct JSError {
|
||||
nsString message;
|
||||
nsString filename;
|
||||
uint32_t lineNumber;
|
||||
uint32_t columnNumber;
|
||||
struct TCPError {
|
||||
nsString name;
|
||||
};
|
||||
|
||||
union SendableData {
|
||||
@ -26,7 +23,7 @@ union SendableData {
|
||||
union CallbackData {
|
||||
void_t;
|
||||
SendableData;
|
||||
JSError;
|
||||
TCPError;
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -35,6 +35,19 @@ const kCLOSED = 'closed';
|
||||
|
||||
const BUFFER_SIZE = 65536;
|
||||
|
||||
// XXX we have no TCPError implementation right now because it's really hard to
|
||||
// do on b2g18. On mozilla-central we want a proper TCPError that ideally
|
||||
// sub-classes DOMError. Bug 867872 has been filed to implement this and
|
||||
// contains a documented TCPError.webidl that maps all the error codes we use in
|
||||
// this file to slightly more readable explanations.
|
||||
function createTCPError(aErrorName, aErrorType) {
|
||||
let error = Cc["@mozilla.org/dom-error;1"]
|
||||
.createInstance(Ci.nsIDOMDOMError);
|
||||
error.wrappedJSObject.init(aErrorName);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Debug logging function
|
||||
*/
|
||||
@ -224,12 +237,10 @@ TCPSocket.prototype = {
|
||||
self._asyncCopierActive = false;
|
||||
self._multiplexStream.removeStream(0);
|
||||
|
||||
if (status) {
|
||||
self._readyState = kCLOSED;
|
||||
let err = new Error("Connection closed while writing: " + status);
|
||||
err.status = status;
|
||||
self.callListener("error", err);
|
||||
self.callListener("close");
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
// Note that we can/will get an error here as well as in the
|
||||
// onStopRequest for inbound data.
|
||||
self._maybeReportErrorAndCloseIfOpen(status);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -258,9 +269,10 @@ TCPSocket.prototype = {
|
||||
},
|
||||
|
||||
/* nsITCPSocketInternal methods */
|
||||
callListenerError: function ts_callListenerError(type, message, filename,
|
||||
lineNumber, columnNumber) {
|
||||
this.callListener(type, new Error(message, filename, lineNumber, columnNumber));
|
||||
callListenerError: function ts_callListenerError(type, name) {
|
||||
// XXX we're not really using TCPError at this time, so there's only a name
|
||||
// attribute to pass.
|
||||
this.callListener(type, createTCPError(name));
|
||||
},
|
||||
|
||||
callListenerData: function ts_callListenerString(type, data) {
|
||||
@ -381,7 +393,6 @@ TCPSocket.prototype = {
|
||||
|
||||
let transport = that._transport = this._createTransport(host, port, that._ssl);
|
||||
transport.setEventSink(that, Services.tm.currentThread);
|
||||
transport.securityCallbacks = new SecurityCallbacks(that);
|
||||
|
||||
that._socketInputStream = transport.openInputStream(0, 0, 0);
|
||||
that._socketOutputStream = transport.openOutputStream(
|
||||
@ -501,6 +512,135 @@ TCPSocket.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_maybeReportErrorAndCloseIfOpen: function(status) {
|
||||
// If we're closed, we've already reported the error or just don't need to
|
||||
// report the error.
|
||||
if (this._readyState === kCLOSED)
|
||||
return;
|
||||
this._readyState = kCLOSED;
|
||||
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
// Convert the status code to an appropriate error message. Raw constants
|
||||
// are used inline in all cases for consistency. Some error codes are
|
||||
// available in Components.results, some aren't. Network error codes are
|
||||
// effectively stable, NSS error codes are officially not, but we have no
|
||||
// symbolic way to dynamically resolve them anyways (other than an ability
|
||||
// to determine the error class.)
|
||||
let errName, errType;
|
||||
// security module? (and this is an error)
|
||||
if ((status & 0xff0000) === 0x5a0000) {
|
||||
const nsINSSErrorsService = Ci.nsINSSErrorsService;
|
||||
let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
|
||||
.getService(nsINSSErrorsService);
|
||||
let errorClass;
|
||||
// getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
|
||||
// somehow not in the set of covered errors.
|
||||
try {
|
||||
errorClass = nssErrorsService.getErrorClass(status);
|
||||
}
|
||||
catch (ex) {
|
||||
errorClass = 'SecurityProtocol';
|
||||
}
|
||||
switch (errorClass) {
|
||||
case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL:
|
||||
errType = 'SecurityProtocol';
|
||||
break;
|
||||
case nsINSSErrorsService.ERROR_CLASS_BAD_CERT:
|
||||
errType = 'SecurityCertificate';
|
||||
break;
|
||||
// no default is required; the platform impl automatically defaults to
|
||||
// ERROR_CLASS_SSL_PROTOCOL.
|
||||
}
|
||||
|
||||
// NSS_SEC errors (happen below the base value because of negative vals)
|
||||
if ((status & 0xffff) <
|
||||
Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
|
||||
// The bases are actually negative, so in our positive numeric space, we
|
||||
// need to subtract the base off our value.
|
||||
let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) -
|
||||
(status & 0xffff);
|
||||
switch (nssErr) {
|
||||
case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
|
||||
errName = 'SecurityExpiredCertificateError';
|
||||
break;
|
||||
case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
|
||||
errName = 'SecurityRevokedCertificateError';
|
||||
break;
|
||||
// per bsmith, we will be unable to tell these errors apart very soon,
|
||||
// so it makes sense to just folder them all together already.
|
||||
case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
|
||||
case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
|
||||
case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
|
||||
case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
|
||||
errName = 'SecurityUntrustedCertificateIssuerError';
|
||||
break;
|
||||
case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
|
||||
errName = 'SecurityInadequateKeyUsageError';
|
||||
break;
|
||||
case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
|
||||
errName = 'SecurityCertificateSignatureAlgorithmDisabledError';
|
||||
break;
|
||||
default:
|
||||
errName = 'SecurityError';
|
||||
break;
|
||||
}
|
||||
}
|
||||
// NSS_SSL errors
|
||||
else {
|
||||
let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) -
|
||||
(status & 0xffff);
|
||||
switch (sslErr) {
|
||||
case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
|
||||
errName = 'SecurityNoCertificateError';
|
||||
break;
|
||||
case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
|
||||
errName = 'SecurityBadCertificateError';
|
||||
break;
|
||||
case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
|
||||
errName = 'SecurityUnsupportedCertificateTypeError';
|
||||
break;
|
||||
case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
|
||||
errName = 'SecurityUnsupportedTLSVersionError';
|
||||
break;
|
||||
case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
|
||||
errName = 'SecurityCertificateDomainMismatchError';
|
||||
break;
|
||||
default:
|
||||
errName = 'SecurityError';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// must be network
|
||||
else {
|
||||
errType = 'Network';
|
||||
switch (status) {
|
||||
// connect to host:port failed
|
||||
case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
|
||||
errName = 'ConnectionRefusedError';
|
||||
break;
|
||||
// network timeout error
|
||||
case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
|
||||
errName = 'NetworkTimeoutError';
|
||||
break;
|
||||
// hostname lookup failed
|
||||
case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
|
||||
errName = 'DomainNotFoundError';
|
||||
break;
|
||||
case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
|
||||
errName = 'NetworkInterruptError';
|
||||
break;
|
||||
default:
|
||||
errName = 'NetworkError';
|
||||
break;
|
||||
}
|
||||
}
|
||||
let err = createTCPError(errName, errType);
|
||||
this.callListener("error", err);
|
||||
}
|
||||
this.callListener("close");
|
||||
},
|
||||
|
||||
// nsITransportEventSink (Triggered by transport.setEventSink)
|
||||
onTransportStatus: function ts_onTransportStatus(
|
||||
transport, status, progress, max) {
|
||||
@ -526,7 +666,8 @@ TCPSocket.prototype = {
|
||||
try {
|
||||
input.available();
|
||||
} catch (e) {
|
||||
this.callListener("error", new Error("Connection refused"));
|
||||
// NS_ERROR_CONNECTION_REFUSED
|
||||
this._maybeReportErrorAndCloseIfOpen(0x804B000C);
|
||||
}
|
||||
},
|
||||
|
||||
@ -540,7 +681,9 @@ TCPSocket.prototype = {
|
||||
|
||||
this._inputStreamPump = null;
|
||||
|
||||
if (buffered_output && !status) {
|
||||
let statusIsError = !Components.isSuccessCode(status);
|
||||
|
||||
if (buffered_output && !statusIsError) {
|
||||
// If we have some buffered output still, and status is not an
|
||||
// error, the other side has done a half-close, but we don't
|
||||
// want to be in the close state until we are done sending
|
||||
@ -549,15 +692,8 @@ TCPSocket.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
this._readyState = kCLOSED;
|
||||
|
||||
if (status) {
|
||||
let err = new Error("Connection closed: " + status);
|
||||
err.status = status;
|
||||
this.callListener("error", err);
|
||||
}
|
||||
|
||||
this.callListener("close");
|
||||
// We call this even if there is no error.
|
||||
this._maybeReportErrorAndCloseIfOpen(status);
|
||||
},
|
||||
|
||||
// nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
|
||||
@ -592,29 +728,4 @@ TCPSocket.prototype = {
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
function SecurityCallbacks(socket) {
|
||||
this._socket = socket;
|
||||
}
|
||||
|
||||
SecurityCallbacks.prototype = {
|
||||
notifyCertProblem: function sc_notifyCertProblem(socketInfo, status,
|
||||
targetSite) {
|
||||
this._socket.callListener("error", status);
|
||||
this._socket.close();
|
||||
return true;
|
||||
},
|
||||
|
||||
getInterface: function sc_getInterface(iid) {
|
||||
return this.QueryInterface(iid);
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIBadCertListener2,
|
||||
Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports
|
||||
])
|
||||
};
|
||||
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);
|
||||
|
@ -127,10 +127,9 @@ TCPSocketChild::RecvCallback(const nsString& aType,
|
||||
if (aData.type() == CallbackData::Tvoid_t) {
|
||||
rv = mSocket->CallListenerVoid(aType);
|
||||
|
||||
} else if (aData.type() == CallbackData::TJSError) {
|
||||
const JSError& err(aData.get_JSError());
|
||||
rv = mSocket->CallListenerError(aType, err.message(), err.filename(),
|
||||
err.lineNumber(), err.columnNumber());
|
||||
} else if (aData.type() == CallbackData::TTCPError) {
|
||||
const TCPError& err(aData.get_TCPError());
|
||||
rv = mSocket->CallListenerError(aType, err.name());
|
||||
|
||||
} else if (aData.type() == CallbackData::TSendableData) {
|
||||
const SendableData& data = aData.get_SendableData();
|
||||
|
@ -28,9 +28,8 @@ FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
|
||||
{
|
||||
mozilla::unused <<
|
||||
aActor->SendCallback(NS_LITERAL_STRING("onerror"),
|
||||
JSError(NS_LITERAL_STRING("Internal error"),
|
||||
NS_LITERAL_STRING(__FILE__), aLineNo, 0),
|
||||
NS_LITERAL_STRING("connecting"), 0);
|
||||
TCPError(NS_LITERAL_STRING("InvalidStateError")),
|
||||
NS_LITERAL_STRING("connecting"), 0);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_2(TCPSocketParent, mSocket, mIntermediary)
|
||||
@ -142,7 +141,7 @@ TCPSocketParent::SendCallback(const nsAString& aType, const JS::Value& aDataVal,
|
||||
FireInteralError(this, __LINE__);
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
data = str;
|
||||
data = SendableData(str);
|
||||
|
||||
} else if (aDataVal.isUndefined() || aDataVal.isNull()) {
|
||||
data = mozilla::void_t();
|
||||
@ -166,40 +165,18 @@ TCPSocketParent::SendCallback(const nsAString& aType, const JS::Value& aDataVal,
|
||||
data = SendableData(arr);
|
||||
|
||||
} else {
|
||||
nsDependentJSString message, filename;
|
||||
uint32_t lineNumber = 0;
|
||||
uint32_t columnNumber = 0;
|
||||
nsDependentJSString name;
|
||||
|
||||
JS::Value val;
|
||||
if (!JS_GetProperty(aCx, obj, "message", &val)) {
|
||||
NS_ERROR("No message property on supposed error object");
|
||||
if (!JS_GetProperty(aCx, obj, "name", &val)) {
|
||||
NS_ERROR("No name property on supposed error object");
|
||||
} else if (JSVAL_IS_STRING(val)) {
|
||||
if (!message.init(aCx, JSVAL_TO_STRING(val))) {
|
||||
if (!name.init(aCx, JSVAL_TO_STRING(val))) {
|
||||
NS_WARNING("couldn't initialize string");
|
||||
}
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(aCx, obj, "fileName", &val)) {
|
||||
NS_ERROR("No fileName property on supposed error object");
|
||||
} else if (JSVAL_IS_STRING(val)) {
|
||||
if (!filename.init(aCx, JSVAL_TO_STRING(val))) {
|
||||
NS_WARNING("couldn't initialize string");
|
||||
}
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(aCx, obj, "lineNumber", &val)) {
|
||||
NS_ERROR("No lineNumber property on supposed error object");
|
||||
} else if (JSVAL_IS_INT(val)) {
|
||||
lineNumber = JSVAL_TO_INT(val);
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(aCx, obj, "columnNumber", &val)) {
|
||||
NS_ERROR("No columnNumber property on supposed error object");
|
||||
} else if (JSVAL_IS_INT(val)) {
|
||||
columnNumber = JSVAL_TO_INT(val);
|
||||
}
|
||||
|
||||
data = JSError(message, filename, lineNumber, columnNumber);
|
||||
data = TCPError(name);
|
||||
}
|
||||
} else {
|
||||
NS_ERROR("Unexpected JS value encountered");
|
||||
|
@ -300,11 +300,11 @@ function sendData() {
|
||||
function sendBig() {
|
||||
var yays = makeJointSuccess(['serverdata', 'clientdrain']),
|
||||
amount = 0;
|
||||
|
||||
|
||||
server.ondata = function (data) {
|
||||
amount += data.length;
|
||||
if (amount === BIG_TYPED_ARRAY.length) {
|
||||
yays.serverdata();
|
||||
yays.serverdata();
|
||||
}
|
||||
};
|
||||
sock.ondrain = function(evt) {
|
||||
@ -379,21 +379,25 @@ function bufferedClose() {
|
||||
* Connect to a port we know is not listening so an error is assured,
|
||||
* and make sure that onerror and onclose are fired on the client side.
|
||||
*/
|
||||
|
||||
|
||||
function badConnect() {
|
||||
// There's probably nothing listening on tcp port 2.
|
||||
sock = TCPSocket.open('127.0.0.1', 2);
|
||||
|
||||
sock.onopen = makeFailureCase('open');
|
||||
sock.ondata = makeFailureCase('data');
|
||||
sock.onclose = makeFailureCase('close');
|
||||
|
||||
let success = makeSuccessCase('error');
|
||||
sock.onerror = function(data) {
|
||||
do_check_neq(data.data.message, '');
|
||||
do_check_neq(data.data.fileName, '');
|
||||
do_check_neq(data.data.lineNumber, 0);
|
||||
success();
|
||||
let gotError = false;
|
||||
sock.onerror = function(event) {
|
||||
do_check_eq(event.data.name, 'ConnectionRefusedError');
|
||||
gotError = true;
|
||||
};
|
||||
sock.onclose = function() {
|
||||
if (!gotError)
|
||||
do_throw('got close without error!');
|
||||
else
|
||||
success();
|
||||
};
|
||||
}
|
||||
|
||||
@ -505,7 +509,7 @@ add_test(cleanup);
|
||||
function run_test() {
|
||||
if (!gInChild)
|
||||
Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
|
||||
|
||||
|
||||
server = new TestServer();
|
||||
|
||||
run_next_test();
|
||||
|
Loading…
Reference in New Issue
Block a user