Bug 703508: Make nsNSSSocketInfo::GetErrorMessage() lazily format the error message, r=honzab
authorBrian Smith <bsmith@mozilla.com>
Thu, 01 Dec 2011 14:36:41 -0800
changeset 81893 7cd14fbf2789fd83ab093b032c81f22d3736e23a
parent 81892 0ef53633ccc715a5705788ae22c8a844ba0bd88d
child 81894 552810b7513bbe577daa857624ac4b7a2d449ca7
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs703508
milestone11.0a1
Bug 703508: Make nsNSSSocketInfo::GetErrorMessage() lazily format the error message, r=honzab
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
security/manager/ssl/src/nsSSLThread.cpp
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -192,28 +192,28 @@ bool nsSSLSocketThreadData::ensure_buffe
 
     mSSLDataBufferAllocatedSize = amount;
   }
   
   return true;
 }
 
 nsNSSSocketInfo::nsNSSSocketInfo()
-  : mFd(nsnull),
+  : mMutex("nsNSSSocketInfo::nsNSSSocketInfo"),
+    mFd(nsnull),
     mBlockingState(blocking_state_unknown),
     mSecurityState(nsIWebProgressListener::STATE_IS_INSECURE),
     mSubRequestsHighSecurity(0),
     mSubRequestsLowSecurity(0),
     mSubRequestsBrokenSecurity(0),
     mSubRequestsNoSecurity(0),
-    mDocShellDependentStuffKnown(false),
-    mExternalErrorReporting(false),
+    mErrorCode(0),
+    mErrorMessageType(PlainErrorMessage),
     mForSTARTTLS(false),
     mHandshakePending(true),
-    mCanceled(false),
     mHasCleartextPhase(false),
     mHandshakeInProgress(false),
     mAllowTLSIntoleranceTimeout(true),
     mRememberClientAuthCertificate(false),
     mHandshakeStartTime(0),
     mPort(0),
     mIsCertIssuerBlacklisted(false)
 {
@@ -282,24 +282,33 @@ nsNSSSocketInfo::SetPort(PRInt32 aPort)
 
 nsresult
 nsNSSSocketInfo::GetPort(PRInt32 *aPort)
 {
   *aPort = mPort;
   return NS_OK;
 }
 
-void nsNSSSocketInfo::SetCanceled(bool aCanceled)
+PRErrorCode
+nsNSSSocketInfo::GetErrorCode() const
 {
-  mCanceled = aCanceled;
+  MutexAutoLock lock(mMutex);
+
+  return mErrorCode;
 }
 
-bool nsNSSSocketInfo::GetCanceled()
+void
+nsNSSSocketInfo::SetCanceled(PRErrorCode errorCode,
+                             ErrorMessageType errorMessageType)
 {
-  return mCanceled;
+  MutexAutoLock lock(mMutex);
+
+  mErrorCode = errorCode;
+  mErrorMessageType = errorMessageType;
+  mErrorMessageCached.Truncate();
 }
 
 NS_IMETHODIMP nsNSSSocketInfo::GetRememberClientAuthCertificate(bool *aRememberClientAuthCertificate)
 {
   NS_ENSURE_ARG_POINTER(aRememberClientAuthCertificate);
   *aRememberClientAuthCertificate = mRememberClientAuthCertificate;
   return NS_OK;
 }
@@ -368,33 +377,16 @@ getSecureBrowserUI(nsIInterfaceRequestor
       
     nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(rootItem);
     if (docShell) {
       (void) docShell->GetSecurityUI(result);
     }
   }
 }
 
-// Are we running within a context that wants external SSL error reporting?
-// We'll look at the presence of a security UI object inside docshell.
-// If the docshell wants the lock icon, you'll get the ssl error pages, too.
-// This is helpful to distinguish from all other contexts, like mail windows,
-// or any other SSL connections running in the background.
-bool
-nsNSSSocketInfo::GetExternalErrorReporting()
-{
-  NS_ASSERTION(NS_IsMainThread(),
-               "nsNSSSocketInfo::GetExternalErrorReporting called off the "
-               "main thread.");
-
-  nsCOMPtr<nsISecureBrowserUI> secureUI;
-  getSecureBrowserUI(mCallbacks, getter_AddRefs(secureUI));
-  return secureUI != nsnull;
-}
-
 NS_IMETHODIMP
 nsNSSSocketInfo::GetSecurityState(PRUint32* state)
 {
   *state = mSecurityState;
   return NS_OK;
 }
 
 nsresult
@@ -469,29 +461,77 @@ nsNSSSocketInfo::GetShortSecurityDescrip
 
 nsresult
 nsNSSSocketInfo::SetShortSecurityDescription(const PRUnichar* aText) {
   mShortDesc.Assign(aText);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNSSSocketInfo::GetErrorMessage(PRUnichar** aText) {
-  if (mErrorMessage.IsEmpty())
-    *aText = nsnull;
-  else {
-    *aText = ToNewUnicode(mErrorMessage);
-    NS_ENSURE_TRUE(*aText, NS_ERROR_OUT_OF_MEMORY);
+nsNSSSocketInfo::GetErrorMessage(PRUnichar** aText)
+{
+  NS_ENSURE_ARG_POINTER(aText);
+  *aText = nsnull;
+
+  if (!NS_IsMainThread()) {
+    NS_ERROR("nsNSSSocketInfo::GetErrorMessage called off the main thread");
+    return NS_ERROR_NOT_SAME_THREAD;
   }
-  return NS_OK;
+
+  MutexAutoLock lock(mMutex);
+
+  nsresult rv = formatErrorMessage(lock);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aText = ToNewUnicode(mErrorMessageCached);
+  return *aText != nsnull ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
-void
-nsNSSSocketInfo::SetErrorMessage(const PRUnichar* aText) {
-  mErrorMessage.Assign(aText);
+static nsresult
+formatPlainErrorMessage(nsXPIDLCString const & host, PRInt32 port,
+                        PRErrorCode err, nsString &returnedMessage);
+
+static nsresult
+formatOverridableCertErrorMessage(nsISSLStatus & sslStatus,
+                                  PRErrorCode errorCodeToReport, 
+                                  const nsXPIDLCString & host, PRInt32 port,
+                                  nsString & returnedMessage);
+
+// XXX: uses nsNSSComponent string bundles off the main thread when called by
+//      nsNSSSocketInfo::Write(). When we remove the error message from the
+//      serialization of nsNSSSocketInfo (bug 697781) we can inline
+//      formatErrorMessage into GetErrorMessage().
+nsresult
+nsNSSSocketInfo::formatErrorMessage(MutexAutoLock const & proofOfLock)
+{
+  if (mErrorCode == 0 || !mErrorMessageCached.IsEmpty()) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+  NS_ConvertASCIItoUTF16 hostNameU(mHostName);
+  NS_ASSERTION(mErrorMessageType != OverridableCertErrorMessage || 
+                (mSSLStatus && mSSLStatus->mServerCert &&
+                 mSSLStatus->mHaveCertErrorBits),
+                "GetErrorMessage called for cert error without cert");
+  if (mErrorMessageType == OverridableCertErrorMessage && 
+      mSSLStatus && mSSLStatus->mServerCert) {
+    rv = formatOverridableCertErrorMessage(*mSSLStatus, mErrorCode,
+                                           mHostName, mPort,
+                                           mErrorMessageCached);
+  } else {
+    rv = formatPlainErrorMessage(mHostName, mPort, mErrorCode,
+                                 mErrorMessageCached);
+  }
+
+  if (NS_FAILED(rv)) {
+    mErrorMessageCached.Truncate();
+  }
+
+  return rv;
 }
 
 /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */
 NS_IMETHODIMP nsNSSSocketInfo::GetInterface(const nsIID & uuid, void * *result)
 {
   if (!NS_IsMainThread()) {
     NS_ERROR("nsNSSSocketInfo::GetInterface called off the main thread");
     return NS_ERROR_NOT_SAME_THREAD;
@@ -543,16 +583,18 @@ static NS_DEFINE_CID(kNSSCertificateCID,
 #define NSSSOCKETINFOMAGIC { 0xa9863a23, 0x26b8, 0x4a9c, \
   { 0x83, 0xf1, 0xe9, 0xda, 0xdb, 0x36, 0xb8, 0x30 } }
 static NS_DEFINE_CID(kNSSSocketInfoMagic, NSSSOCKETINFOMAGIC);
 
 NS_IMETHODIMP
 nsNSSSocketInfo::Write(nsIObjectOutputStream* stream) {
   stream->WriteID(kNSSSocketInfoMagic);
 
+  MutexAutoLock lock(mMutex);
+
   nsRefPtr<nsSSLStatus> status = mSSLStatus;
   nsCOMPtr<nsISerializable> certSerializable;
 
   // Write a redundant copy of the certificate for backward compatibility
   // with previous versions, which also unnecessarily wrote it.
   //
   // As we are reading the object our self, not using ReadObject, we have
   // to store it here 'manually' as well, mimicking our object stream
@@ -583,17 +625,21 @@ nsNSSSocketInfo::Write(nsIObjectOutputSt
   // to distinguish version number from mSecurityState
   // field stored in times before versioning has been introduced.
   // This mask value has been chosen as mSecurityState could
   // never be assigned such value.
   PRUint32 version = 3;
   stream->Write32(version | 0xFFFF0000);
   stream->Write32(mSecurityState);
   stream->WriteWStringZ(mShortDesc.get());
-  stream->WriteWStringZ(mErrorMessage.get());
+
+  // XXX: uses nsNSSComponent string bundles off the main thread
+  nsresult rv = formatErrorMessage(lock); 
+  NS_ENSURE_SUCCESS(rv, rv);
+  stream->WriteWStringZ(mErrorMessageCached.get());
 
   stream->WriteCompoundObject(NS_ISUPPORTS_CAST(nsISSLStatus*, status),
                               NS_GET_IID(nsISupports), true);
 
   stream->Write32((PRUint32)mSubRequestsHighSecurity);
   stream->Write32((PRUint32)mSubRequestsLowSecurity);
   stream->Write32((PRUint32)mSubRequestsBrokenSecurity);
   stream->Write32((PRUint32)mSubRequestsNoSecurity);
@@ -663,29 +709,32 @@ nsNSSSocketInfo::Read(nsIObjectInputStre
     // as we did before.
     stream->Read32(&version);
   }
   else {
     // There seems not to be the certificate present in the stream.
     version = UUID_0;
   }
 
+  MutexAutoLock lock(mMutex);
+
   // If the version field we have just read is not masked with 0xFFFF0000
   // then it is stored mSecurityState field and this is version 1 of
   // the binary data stream format.
   if ((version & 0xFFFF0000) == 0xFFFF0000) {
     version &= ~0xFFFF0000;
     stream->Read32(&mSecurityState);
   }
   else {
     mSecurityState = version;
     version = 1;
   }
   stream->ReadString(mShortDesc);
-  stream->ReadString(mErrorMessage);
+  stream->ReadString(mErrorMessageCached);
+  mErrorCode = 0;
 
   nsCOMPtr<nsISupports> obj;
   stream->ReadObject(true, getter_AddRefs(obj));
   
   mSSLStatus = reinterpret_cast<nsSSLStatus*>(obj.get());
 
   if (!mSSLStatus) {
     NS_WARNING("deserializing nsNSSSocketInfo without mSSLStatus");
@@ -907,48 +956,48 @@ void nsSSLIOLayerHelpers::Cleanup()
   }
 
   if (mHostsWithCertErrors) {
     delete mHostsWithCertErrors;
     mHostsWithCertErrors = nsnull;
   }
 }
 
+/* Formats an error message for non-certificate-related SSL errors
+ * and non-overridable certificate errors (both are of type
+ * PlainErrormMessage). Use formatOverridableCertErrorMessage
+ * for overridable cert errors.
+ */
 static nsresult
-getErrorMessage(PRInt32 err, 
-                const nsString &host,
-                PRInt32 port,
-                nsINSSComponent *component,
-                nsString &returnedMessage)
+formatPlainErrorMessage(const nsXPIDLCString &host, PRInt32 port,
+                        PRErrorCode err, nsString &returnedMessage)
 {
-  NS_ENSURE_ARG_POINTER(component);
-
   const PRUnichar *params[1];
   nsresult rv;
 
+  nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   if (host.Length())
   {
     nsString hostWithPort;
 
     // For now, hide port when it's 443 and we're reporting the error.
     // In the future a better mechanism should be used
     // to make a decision about showing the port number, possibly by requiring
     // the context object to implement a specific interface.
     // The motivation is that Mozilla browser would like to hide the port number
     // in error pages in the common case.
 
-    if (port == 443) {
-      params[0] = host.get();
-    }
-    else {
-      hostWithPort = host;
+    hostWithPort.AssignASCII(host);
+    if (port != 443) {
       hostWithPort.AppendLiteral(":");
       hostWithPort.AppendInt(port);
-      params[0] = hostWithPort.get();
     }
+    params[0] = hostWithPort.get();
 
     nsString formattedString;
     rv = component->PIPBundleFormatStringFromName("SSLConnectionErrorPrefix", 
                                                   params, 1, 
                                                   formattedString);
     if (NS_SUCCEEDED(rv))
     {
       returnedMessage.Append(formattedString);
@@ -1297,90 +1346,103 @@ AppendErrorTextCode(PRErrorCode errorCod
     else {
       returnedMessage.Append(NS_LITERAL_STRING(" ("));
       returnedMessage.Append(idU);
       returnedMessage.Append(NS_LITERAL_STRING(")"));
     }
   }
 }
 
-static void
-getInvalidCertErrorMessage(PRUint32 multipleCollectedErrors, 
-                           PRErrorCode errorCodeToReport, 
-                           PRErrorCode errTrust, 
-                           PRErrorCode errMismatch, 
-                           PRErrorCode errExpired,
-                           const nsString &host,
-                           const nsString &hostWithPort,
-                           PRInt32 port,
-                           nsIX509Cert* ix509,
-                           nsString &returnedMessage)
+/* Formats an error message for overridable certificate errors (of type
+ * OverridableCertErrorMessage). Use formatPlainErrorMessage to format
+ * non-overridable cert errors and non-cert-related errors.
+ */
+static nsresult
+formatOverridableCertErrorMessage(nsISSLStatus & sslStatus,
+                                  PRErrorCode errorCodeToReport, 
+                                  const nsXPIDLCString & host, PRInt32 port,
+                                  nsString & returnedMessage)
 {
   const PRUnichar *params[1];
   nsresult rv;
+  nsAutoString hostWithPort;
+  nsAutoString hostWithoutPort;
 
   // For now, hide port when it's 443 and we're reporting the error.
   // In the future a better mechanism should be used
   // to make a decision about showing the port number, possibly by requiring
   // the context object to implement a specific interface.
   // The motivation is that Mozilla browser would like to hide the port number
   // in error pages in the common case.
   
-  if (port == 443)
-    params[0] = host.get();
-  else
+  hostWithoutPort.AppendASCII(host);
+  if (port == 443) {
+    params[0] = hostWithoutPort.get();
+  } else {
+    hostWithPort.AppendASCII(host);
+    hostWithPort.Append(':');
+    hostWithPort.AppendInt(port);
     params[0] = hostWithPort.get();
+  }
 
   nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv);
-  if (NS_FAILED(rv))
-    return;
-
-  nsString formattedString;
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  returnedMessage.Truncate();
   rv = component->PIPBundleFormatStringFromName("certErrorIntro", params, 1,
-                                                formattedString);
-  if (NS_SUCCEEDED(rv))
-  {
-    returnedMessage.Append(formattedString);
-    returnedMessage.Append(NS_LITERAL_STRING("\n\n"));
-  }
-
-  if (multipleCollectedErrors & nsICertOverrideService::ERROR_UNTRUSTED)
-  {
-    AppendErrorTextUntrusted(errTrust, host, ix509, 
+                                                returnedMessage);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  returnedMessage.Append(NS_LITERAL_STRING("\n\n"));
+
+  nsRefPtr<nsIX509Cert> ix509;
+  rv = sslStatus.GetServerCert(getter_AddRefs(ix509));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool isUntrusted;
+  rv = sslStatus.GetIsUntrusted(&isUntrusted);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (isUntrusted) {
+    AppendErrorTextUntrusted(errorCodeToReport, hostWithoutPort, ix509, 
                              component, returnedMessage);
   }
 
-  if (multipleCollectedErrors & nsICertOverrideService::ERROR_MISMATCH)
-  {
-    AppendErrorTextMismatch(host, ix509, component, returnedMessage);
+  bool isDomainMismatch;
+  rv = sslStatus.GetIsDomainMismatch(&isDomainMismatch);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (isDomainMismatch) {
+    AppendErrorTextMismatch(hostWithoutPort, ix509, component, returnedMessage);
   }
 
-  if (multipleCollectedErrors & nsICertOverrideService::ERROR_TIME)
-  {
+  bool isNotValidAtThisTime;
+  rv = sslStatus.GetIsNotValidAtThisTime(&isNotValidAtThisTime);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (isNotValidAtThisTime) {
     AppendErrorTextTime(ix509, component, returnedMessage);
   }
 
   AppendErrorTextCode(errorCodeToReport, component, returnedMessage);
+
+  return NS_OK;
 }
 
 static void
-nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRInt32 err)
+nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRErrorCode err)
 {
   if (!NS_IsMainThread()) {
     NS_ERROR("nsHandleSSLError called off the main thread");
     return;
   }
 
   // SetCanceled is only called by the main thread or the SSL thread. Whenever
   // this function is called, the SSL thread is waiting on this thread (the
-  // main thread). So, no mutex is necessary for SetCanceled()/GetCanceled().
-  if (socketInfo->GetCanceled()) {
+  // main thread). So, no mutex is necessary for SetCanceled()/GetError*().
+  if (socketInfo->GetErrorCode()) {
     // If the socket has been flagged as canceled,
-    // the code who did was responsible for showing
-    // an error message (if desired).
+    // the code who did was responsible for setting the error code.
     return;
   }
 
   if (nsSSLThread::stoppedOrStopping()) {
     return;
   }
 
   nsresult rv;
@@ -1390,40 +1452,33 @@ nsHandleSSLError(nsNSSSocketInfo *socket
     return;
 
   nsXPIDLCString hostName;
   socketInfo->GetHostName(getter_Copies(hostName));
 
   PRInt32 port;
   socketInfo->GetPort(&port);
 
-  bool suppressMessage = false;
-
   // Try to get a nsISSLErrorListener implementation from the socket consumer.
   nsCOMPtr<nsIInterfaceRequestor> cb;
   socketInfo->GetNotificationCallbacks(getter_AddRefs(cb));
   if (cb) {
     nsCOMPtr<nsISSLErrorListener> sel = do_GetInterface(cb);
     if (sel) {
       nsIInterfaceRequestor *csi = static_cast<nsIInterfaceRequestor*>(socketInfo);
       nsCString hostWithPortString = hostName;
       hostWithPortString.AppendLiteral(":");
       hostWithPortString.AppendInt(port);
+    
+      bool suppressMessage = false; // obsolete, ignored
       rv = sel->NotifySSLError(csi, err, hostWithPortString, &suppressMessage);
-      if (NS_SUCCEEDED(rv) && suppressMessage)
-        return;
     }
   }
 
-  if (socketInfo->GetExternalErrorReporting()) {
-    NS_ConvertASCIItoUTF16 hostNameU(hostName);
-    nsString formattedString;
-    (void) getErrorMessage(err, hostNameU, port, nssComponent, formattedString);
-    socketInfo->SetErrorMessage(formattedString.get());
-  }
+  socketInfo->SetCanceled(err, nsNSSSocketInfo::PlainErrorMessage);
 }
 
 static PRStatus PR_CALLBACK
 nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr,
                     PRIntervalTime timeout)
 {
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] connecting SSL socket\n", (void*)fd));
   nsNSSShutDownPreventionLock locker;
@@ -3239,88 +3294,86 @@ class CertErrorRunnable : public SyncRun
   const bool mHasCertNameMismatch;
   nsXPIDLCString mHostname;
 
   SECStatus mRv; // out
   PRErrorCode mErrorCodeToReport; // in/out
 };
 
 static SECStatus
-cancel_and_failure(nsNSSSocketInfo* infoObject)
+cancel_and_failure(PRErrorCode errorCode, nsNSSSocketInfo* infoObject)
 {
-  infoObject->SetCanceled(true);
+  infoObject->SetCanceled(errorCode, nsNSSSocketInfo::PlainErrorMessage);
+  PR_SetError(errorCode, 0);
   return SECFailure;
 }
 
 static SECStatus
 nsNSSBadCertHandler(void *arg, PRFileDesc *sslSocket)
 {
   // cert was revoked, don't do anything else
-  // Calling cancel_and_failure is not necessary, and would be wrong,
-  // [for errors other than the ones explicitly handled below,] 
-  // because it suppresses error reporting.
   PRErrorCode defaultErrorCodeToReport = PR_GetError();
   if (defaultErrorCodeToReport == SEC_ERROR_REVOKED_CERTIFICATE)
     return SECFailure;
 
   if (defaultErrorCodeToReport == 0) {
     NS_ERROR("No error code set during certificate validation failure.");
     defaultErrorCodeToReport = SEC_ERROR_CERT_NOT_VALID;
   }
 
   nsNSSShutDownPreventionLock locker;
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo *)arg;
   if (!infoObject)
     return SECFailure;
 
   if (nsSSLThread::stoppedOrStopping())
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(PR_INVALID_STATE_ERROR, infoObject);
 
   CERTCertificate *peerCert = nsnull;
   CERTCertificateCleaner peerCertCleaner(peerCert);
   peerCert = SSL_PeerCertificate(sslSocket);
   if (!peerCert)
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
 
   nsRefPtr<nsNSSCertificate> nssCert;
   nssCert = nsNSSCertificate::Create(peerCert);
   if (!nssCert)
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
 
   SECStatus srv;
   nsresult nsrv;
 
   nsCOMPtr<nsINSSComponent> inss = do_GetService(kNSSComponentCID, &nsrv);
   if (!inss)
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
   nsRefPtr<nsCERTValInParamWrapper> survivingParams;
   nsrv = inss->GetDefaultCERTValInParam(survivingParams);
   if (NS_FAILED(nsrv))
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(PR_INVALID_STATE_ERROR, infoObject);
   
   char *hostname = SSL_RevealURL(sslSocket);
   if (!hostname)
-    return cancel_and_failure(infoObject);
+    return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
 
   charCleaner hostnameCleaner(hostname); 
 
   // Check the name field against the desired hostname.
   bool hasCertNameMismatch =
       hostname[0] && CERT_VerifyCertName(peerCert, hostname) != SECSuccess;
 
   {
     PRArenaPool *log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
     if (!log_arena)    
-      return cancel_and_failure(infoObject);
+      return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
 
     PRArenaPoolCleanerFalseParam log_arena_cleaner(log_arena);
 
     CERTVerifyLog *verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog);
     if (!verify_log)
-      return cancel_and_failure(infoObject);
+      return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject);
 
     CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log);
 
     verify_log->arena = log_arena;
 
     if (!nsNSSComponent::globalConstFlagUsePKIXVerification) {
       srv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), peerCert,
                                   true, certificateUsageSSLServer,
@@ -3439,17 +3492,16 @@ void CertErrorRunnable::RunOnTargetThrea
           }
           break;
         default:
           // we are not willing to continue on any other error
           nsHandleSSLError(mInfoObject, i_node->error);
           // this error is our stop condition, so let's make sure
           // this error code will be reported to the external world.
           mErrorCodeToReport = i_node->error;
-          mInfoObject->SetCanceled(true);
           return;
       }
     }
   }
 
   if (!collected_errors)
   {
     NS_NOTREACHED("why did NSS call our bad cert handler if all looks good? Let's cancel the connection");
@@ -3543,29 +3595,28 @@ void CertErrorRunnable::RunOnTargetThrea
             "transport layer\n", mFdForLogging, this));
   }
 
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
          ("[%p][%p] Certificate error was not overridden\n",
          mFdForLogging, this));
 
   // Ok, this is a full stop.
-  // First, deliver the technical details of the broken SSL status,
-  // giving the caller a chance to suppress the error messages.
-
-  bool suppressMessage = false;
+  // First, deliver the technical details of the broken SSL status.
 
   // Try to get a nsIBadCertListener2 implementation from the socket consumer.
   nsCOMPtr<nsIInterfaceRequestor> cb;
   mInfoObject->GetNotificationCallbacks(getter_AddRefs(cb));
   if (cb) {
     nsCOMPtr<nsIBadCertListener2> bcl = do_GetInterface(cb);
     if (bcl) {
       nsIInterfaceRequestor *csi = static_cast<nsIInterfaceRequestor*>(mInfoObject);
-      nsrv = bcl->NotifyCertProblem(csi, status, hostWithPortString, &suppressMessage);
+      bool suppressMessage = false; // obsolete, ignored
+      nsrv = bcl->NotifyCertProblem(csi, status, hostWithPortString,
+                                    &suppressMessage);
     }
   }
 
   nsCOMPtr<nsIRecentBadCertsService> recentBadCertsService = 
     do_GetService(NS_RECENTBADCERTS_CONTRACTID);
 
   if (recentBadCertsService) {
     recentBadCertsService->AddBadCert(hostWithPortStringUTF16, status);
@@ -3575,28 +3626,18 @@ void CertErrorRunnable::RunOnTargetThrea
   mErrorCodeToReport = 0;
   if (remaining_display_errors & nsICertOverrideService::ERROR_UNTRUSTED)
     mErrorCodeToReport = errorCodeTrust;
   else if (remaining_display_errors & nsICertOverrideService::ERROR_MISMATCH)
     mErrorCodeToReport = errorCodeMismatch;
   else if (remaining_display_errors & nsICertOverrideService::ERROR_TIME)
     mErrorCodeToReport = errorCodeExpired;
 
-  if (!suppressMessage && mInfoObject->GetExternalErrorReporting()) {
-    NS_ConvertASCIItoUTF16 hostU(hostString);
-    NS_ConvertASCIItoUTF16 hostWithPortU(hostWithPortString);
-    nsString formattedString;
-    getInvalidCertErrorMessage(remaining_display_errors, mErrorCodeToReport,
-                               errorCodeTrust, errorCodeMismatch,
-                               errorCodeExpired, hostU, hostWithPortU, port,
-                               mCert, formattedString);
-    mInfoObject->SetErrorMessage(formattedString.get());
-  }
-
-  mInfoObject->SetCanceled(true);
+  mInfoObject->SetCanceled(mErrorCodeToReport,
+                           nsNSSSocketInfo::OverridableCertErrorMessage);
 }
 
 static PRFileDesc*
 nsSSLIOLayerImportFD(PRFileDesc *fd,
                      nsNSSSocketInfo *infoObject,
                      const char *host,
                      bool anonymousLoad)
 {
--- a/security/manager/ssl/src/nsNSSIOLayer.h
+++ b/security/manager/ssl/src/nsNSSIOLayer.h
@@ -57,16 +57,17 @@
 #include "nsNSSShutDown.h"
 #include "nsIClientAuthDialogs.h"
 #include "nsAutoPtr.h"
 #include "nsNSSCertificate.h"
 #include "nsDataHashtable.h"
 
 class nsIChannel;
 class nsSSLThread;
+class ::mozilla::MutexAutoLock;
 
 /*
  * This class is used to store SSL socket I/O state information,
  * that is not being executed directly, but defered to 
  * the separate SSL thread.
  */
 class nsSSLSocketThreadData
 {
@@ -148,17 +149,16 @@ public:
   NS_DECL_NSISSLSTATUSPROVIDER
   NS_DECL_NSIASSOCIATEDCONTENTSECURITY
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
   NS_DECL_NSICLIENTAUTHUSERDECISION
 
   nsresult SetSecurityState(PRUint32 aState);
   nsresult SetShortSecurityDescription(const PRUnichar *aText);
-  void SetErrorMessage(const PRUnichar *aText);
 
   nsresult SetForSTARTTLS(bool aForSTARTTLS);
   nsresult GetForSTARTTLS(bool *aForSTARTTLS);
 
   nsresult GetFileDescPtr(PRFileDesc** aFilePtr);
   nsresult SetFileDescPtr(PRFileDesc* aFilePtr);
 
   nsresult GetHandshakePending(bool *aHandshakePending);
@@ -167,62 +167,70 @@ public:
   nsresult GetHostName(char **aHostName);
   nsresult SetHostName(const char *aHostName);
 
   nsresult GetPort(PRInt32 *aPort);
   nsresult SetPort(PRInt32 aPort);
 
   void GetPreviousCert(nsIX509Cert** _result);
 
-  void SetCanceled(bool aCanceled);
-  bool GetCanceled();
+  enum ErrorMessageType {
+    OverridableCertErrorMessage  = 1, // for *overridable* certificate errors
+    PlainErrorMessage = 2,            // all other errors
+  };
+  void SetCanceled(PRErrorCode errorCode, ErrorMessageType errorMessageType);
+  PRErrorCode GetErrorCode() const;
   
   void SetHasCleartextPhase(bool aHasCleartextPhase);
   bool GetHasCleartextPhase();
   
   void SetHandshakeInProgress(bool aIsIn);
   bool GetHandshakeInProgress() { return mHandshakeInProgress; }
   bool HandshakeTimeout();
 
   void SetAllowTLSIntoleranceTimeout(bool aAllow);
 
-  bool GetExternalErrorReporting();
-
   nsresult RememberCAChain(CERTCertList *aCertList);
 
   /* Set SSL Status values */
   nsresult SetSSLStatus(nsSSLStatus *aSSLStatus);
   nsSSLStatus* SSLStatus() { return mSSLStatus; }
   
   PRStatus CloseSocketAndDestroy();
   
   bool IsCertIssuerBlacklisted() const {
     return mIsCertIssuerBlacklisted;
   }
   void SetCertIssuerBlacklisted() {
     mIsCertIssuerBlacklisted = true;
   }
 protected:
+  mutable ::mozilla::Mutex mMutex;
+
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   PRFileDesc* mFd;
   enum { 
     blocking_state_unknown, is_nonblocking_socket, is_blocking_socket 
   } mBlockingState;
   PRUint32 mSecurityState;
   PRInt32 mSubRequestsHighSecurity;
   PRInt32 mSubRequestsLowSecurity;
   PRInt32 mSubRequestsBrokenSecurity;
   PRInt32 mSubRequestsNoSecurity;
   nsString mShortDesc;
-  nsString mErrorMessage;
+
+  PRErrorCode mErrorCode;
+  ErrorMessageType mErrorMessageType;
+  nsString mErrorMessageCached;
+  nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock);
+
   bool mDocShellDependentStuffKnown;
   bool mExternalErrorReporting; // DocShellDependent
   bool mForSTARTTLS;
   bool mHandshakePending;
-  bool mCanceled;
   bool mHasCleartextPhase;
   bool mHandshakeInProgress;
   bool mAllowTLSIntoleranceTimeout;
   bool mRememberClientAuthCertificate;
   PRIntervalTime mHandshakeStartTime;
   PRInt32 mPort;
   nsXPIDLCString mHostName;
   PRErrorCode mIsCertIssuerBlacklisted;
--- a/security/manager/ssl/src/nsSSLThread.cpp
+++ b/security/manager/ssl/src/nsSSLThread.cpp
@@ -640,17 +640,17 @@ PRInt32 nsSSLThread::requestRead(nsNSSSo
       break;
   }
 
   if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) {
     PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
     return -1;
   }
 
-  if (si->GetCanceled()) {
+  if (si->GetErrorCode()) {
     return PR_FAILURE;
   }
 
   // si is idle and good, and no other socket is currently busy,
   // so it's fine to continue with the request.
 
   if (!si->mThreadData->ensure_buffer_size(amount))
   {
@@ -861,17 +861,17 @@ PRInt32 nsSSLThread::requestWrite(nsNSSS
       break;
   }
 
   if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) {
     PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
     return -1;
   }
 
-  if (si->GetCanceled()) {
+  if (si->GetErrorCode()) {
     return PR_FAILURE;
   }
 
   // si is idle and good, and no other socket is currently busy,
   // so it's fine to continue with the request.
 
   // However, use special handling for the 
   //   mOneBytePendingFromEarlierWrite