/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim:set ts=4 sw=4 sts=4 et cin: */ /* ***** 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 Mozilla. * * The Initial Developer of the Original Code is * Netscape Communications. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Darin Fisher (original author) * * 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 ***** */ #include "nsHttpConnection.h" #include "nsHttpTransaction.h" #include "nsHttpRequestHead.h" #include "nsHttpResponseHead.h" #include "nsHttpHandler.h" #include "nsISocketTransportService.h" #include "nsISocketTransport.h" #include "nsIServiceManager.h" #include "nsISSLSocketControl.h" #include "nsIStringStream.h" #include "netCore.h" #include "nsNetCID.h" #include "nsAutoLock.h" #include "prmem.h" #include "plevent.h" #ifdef DEBUG // defined by the socket transport service while active extern PRThread *gSocketThread; #endif static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); //----------------------------------------------------------------------------- // nsHttpConnection //----------------------------------------------------------------------------- nsHttpConnection::nsHttpConnection() : mTransaction(nsnull) , mConnInfo(nsnull) , mLock(nsnull) , mLastReadTime(0) , mIdleTimeout(0) , mKeepAlive(PR_TRUE) // assume to keep-alive by default , mKeepAliveMask(PR_TRUE) , mSupportsPipelining(PR_FALSE) // assume low-grade server , mIsReused(PR_FALSE) , mCompletedSSLConnect(PR_FALSE) { LOG(("Creating nsHttpConnection @%x\n", this)); // grab a reference to the handler to ensure that it doesn't go away. nsHttpHandler *handler = gHttpHandler; NS_ADDREF(handler); } nsHttpConnection::~nsHttpConnection() { LOG(("Destroying nsHttpConnection @%x\n", this)); NS_IF_RELEASE(mConnInfo); NS_IF_RELEASE(mTransaction); if (mLock) { PR_DestroyLock(mLock); mLock = nsnull; } // release our reference to the handler nsHttpHandler *handler = gHttpHandler; NS_RELEASE(handler); } nsresult nsHttpConnection::Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime) { LOG(("nsHttpConnection::Init [this=%x]\n", this)); NS_ENSURE_ARG_POINTER(info); NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED); mLock = PR_NewLock(); if (!mLock) return NS_ERROR_OUT_OF_MEMORY; mConnInfo = info; NS_ADDREF(mConnInfo); mMaxHangTime = maxHangTime; mLastReadTime = NowInSeconds(); return NS_OK; } // called on the socket thread nsresult nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) { nsresult rv; LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n", this, trans, caps)); NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); // take ownership of the transaction mTransaction = trans; NS_ADDREF(mTransaction); // set mKeepAlive according to what will be requested mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE); // if we don't have a socket transport then create a new one if (!mSocketTransport) { rv = CreateTransport(); if (NS_FAILED(rv)) goto loser; } // need to handle SSL proxy CONNECT if this is the first time. if (mConnInfo->UsingSSL() && mConnInfo->UsingHttpProxy() && !mCompletedSSLConnect) { rv = SetupSSLProxyConnect(); if (NS_FAILED(rv)) goto loser; } // wait for the output stream to be readable rv = mSocketOut->AsyncWait(this, 0, 0, nsnull); if (NS_SUCCEEDED(rv)) return rv; loser: NS_RELEASE(mTransaction); return rv; } void nsHttpConnection::Close(nsresult reason) { LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (NS_FAILED(reason)) { if (mSocketTransport) { mSocketTransport->SetSecurityCallbacks(nsnull); mSocketTransport->SetEventSink(nsnull, nsnull); mSocketTransport->Close(reason); } mKeepAlive = PR_FALSE; } } // called on the socket thread nsresult nsHttpConnection::ProxyStartSSL() { LOG(("nsHttpConnection::ProxyStartSSL [this=%x]\n", this)); #ifdef DEBUG NS_PRECONDITION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); #endif nsCOMPtr securityInfo; nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); if (NS_FAILED(rv)) return rv; nsCOMPtr ssl = do_QueryInterface(securityInfo, &rv); if (NS_FAILED(rv)) return rv; return ssl->ProxyStartSSL(); } PRBool nsHttpConnection::CanReuse() { return IsKeepAlive() && (NowInSeconds() - mLastReadTime < mIdleTimeout) && IsAlive(); } PRBool nsHttpConnection::IsAlive() { if (!mSocketTransport) return PR_FALSE; PRBool alive; nsresult rv = mSocketTransport->IsAlive(&alive); if (NS_FAILED(rv)) alive = PR_FALSE; //#define TEST_RESTART_LOGIC #ifdef TEST_RESTART_LOGIC if (!alive) { LOG(("pretending socket is still alive to test restart logic\n")); alive = PR_TRUE; } #endif return alive; } PRBool nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) { // XXX there should be a strict mode available that disables this // blacklisting. // assuming connection is HTTP/1.1 with keep-alive enabled if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) { // XXX check for bad proxy servers... return PR_TRUE; } // XXX what about checking for a Via header? (transparent proxies) // check for bad origin servers const char *val = responseHead->PeekHeader(nsHttp::Server); if (!val) return PR_FALSE; // no header, no love // the list of servers known to do bad things with pipelined requests static const char *bad_servers[] = { "Microsoft-IIS/4.", "Microsoft-IIS/5.", "Netscape-Enterprise/3.", nsnull }; for (const char **server = bad_servers; *server; ++server) { if (PL_strcasestr(val, *server) != nsnull) { LOG(("looks like this server does not support pipelining")); return PR_FALSE; } } // ok, let's allow pipelining to this server return PR_TRUE; } //---------------------------------------------------------------------------- // nsHttpConnection::nsAHttpConnection compatible methods //---------------------------------------------------------------------------- nsresult nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, nsHttpRequestHead *requestHead, nsHttpResponseHead *responseHead, PRBool *reset) { LOG(("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p response-head=%p]\n", this, trans, responseHead)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ENSURE_ARG_POINTER(trans); NS_ASSERTION(responseHead, "No response head?"); // If the server issued an explicit timeout, then we need to close down the // socket transport. We pass an error code of NS_ERROR_NET_RESET to // trigger the transactions 'restart' mechanism. We tell it to reset its // response headers so that it will be ready to receive the new response. if (responseHead->Status() == 408) { Close(NS_ERROR_NET_RESET); *reset = PR_TRUE; return NS_OK; } // we won't change our keep-alive policy unless the server has explicitly // told us to do so. // inspect the connection headers for keep-alive info provided the // transaction completed successfully. const char *val = responseHead->PeekHeader(nsHttp::Connection); if (!val) val = responseHead->PeekHeader(nsHttp::Proxy_Connection); // reset to default (the server may have changed since we last checked) mSupportsPipelining = PR_FALSE; if ((responseHead->Version() < NS_HTTP_VERSION_1_1) || (requestHead->Version() < NS_HTTP_VERSION_1_1)) { // HTTP/1.0 connections are by default NOT persistent if (val && !PL_strcasecmp(val, "keep-alive")) mKeepAlive = PR_TRUE; else mKeepAlive = PR_FALSE; } else { // HTTP/1.1 connections are by default persistent if (val && !PL_strcasecmp(val, "close")) mKeepAlive = PR_FALSE; else { mKeepAlive = PR_TRUE; mSupportsPipelining = SupportsPipelining(responseHead); } } mKeepAliveMask = mKeepAlive; // if this connection is persistent, then the server may send a "Keep-Alive" // header specifying the maximum number of times the connection can be // reused as well as the maximum amount of time the connection can be idle // before the server will close it. we ignore the max reuse count, because // a "keep-alive" connection is by definition capable of being reused, and // we only care about being able to reuse it once. if a timeout is not // specified then we use our advertized timeout value. if (mKeepAlive) { val = responseHead->PeekHeader(nsHttp::Keep_Alive); const char *cp = PL_strcasestr(val, "timeout="); if (cp) mIdleTimeout = (PRUint32) atoi(cp + 8); else mIdleTimeout = gHttpHandler->IdleTimeout(); LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout)); } // if we're doing an SSL proxy connect, then we need to check whether or not // the connect was successful. if so, then we have to reset the transaction // and step-up the socket connection to SSL. finally, we have to wake up the // socket write request. if (mSSLProxyConnectStream) { mSSLProxyConnectStream = 0; if (responseHead->Status() == 200) { LOG(("SSL proxy CONNECT succeeded!\n")); *reset = PR_TRUE; nsresult rv = ProxyStartSSL(); if (NS_FAILED(rv)) // XXX need to handle this for real LOG(("ProxyStartSSL failed [rv=%x]\n", rv)); mCompletedSSLConnect = PR_TRUE; rv = mSocketOut->AsyncWait(this, 0, 0, nsnull); // XXX what if this fails -- need to handle this error NS_ASSERTION(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed"); } else { LOG(("SSL proxy CONNECT failed!\n")); // NOTE: this cast is valid since this connection cannot be // processing a transaction pipeline until after the first HTTP/1.1 // response. nsHttpTransaction *trans = NS_STATIC_CAST(nsHttpTransaction *, mTransaction); trans->SetSSLConnectFailed(); } } return NS_OK; } void nsHttpConnection::GetSecurityInfo(nsISupports **secinfo) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketTransport) { if (NS_FAILED(mSocketTransport->GetSecurityInfo(secinfo))) *secinfo = nsnull; } } nsresult nsHttpConnection::ResumeSend() { LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketOut) return mSocketOut->AsyncWait(this, 0, 0, nsnull); NS_NOTREACHED("no socket output stream"); return NS_ERROR_UNEXPECTED; } nsresult nsHttpConnection::ResumeRecv() { LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketIn) return mSocketIn->AsyncWait(this, 0, 0, nsnull); NS_NOTREACHED("no socket input stream"); return NS_ERROR_UNEXPECTED; } //----------------------------------------------------------------------------- // nsHttpConnection //----------------------------------------------------------------------------- nsresult nsHttpConnection::CreateTransport() { nsresult rv; NS_PRECONDITION(!mSocketTransport, "unexpected"); nsCOMPtr sts = do_GetService(kSocketTransportServiceCID, &rv); if (NS_FAILED(rv)) return rv; // configure the socket type based on the connection type requested. const char* types[1]; if (mConnInfo->UsingSSL()) types[0] = "ssl"; else types[0] = gHttpHandler->DefaultSocketType(); nsCOMPtr strans; PRUint32 typeCount = (types[0] != nsnull); rv = sts->CreateTransport(types, typeCount, nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), mConnInfo->ProxyInfo(), getter_AddRefs(strans)); if (NS_FAILED(rv)) return rv; // NOTE: these create cyclical references, which we break inside // nsHttpConnection::Close rv = strans->SetEventSink(this, nsnull); if (NS_FAILED(rv)) return rv; rv = strans->SetSecurityCallbacks(this); if (NS_FAILED(rv)) return rv; // next open the socket streams nsCOMPtr sout; rv = strans->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(sout)); if (NS_FAILED(rv)) return rv; nsCOMPtr sin; rv = strans->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(sin)); if (NS_FAILED(rv)) return rv; mSocketTransport = strans; mSocketIn = do_QueryInterface(sin); mSocketOut = do_QueryInterface(sout); return NS_OK; } void nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { LOG(("nsHttpConnection::CloseTransaction[this=%x trans=%x reason=%x]\n", this, trans, reason)); NS_ASSERTION(trans == mTransaction, "wrong transaction"); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // mask this error code because its not a real error. if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK; mTransaction->Close(reason); NS_RELEASE(mTransaction); mTransaction = 0; if (NS_FAILED(reason)) Close(reason); // flag the connection as reused here for convenience sake. certainly // it might be going away instead ;-) mIsReused = PR_TRUE; } NS_METHOD nsHttpConnection::ReadFromStream(nsIInputStream *input, void *closure, const char *buf, PRUint32 offset, PRUint32 count, PRUint32 *countRead) { // thunk for nsIInputStream instance nsHttpConnection *conn = (nsHttpConnection *) closure; return conn->OnReadSegment(buf, count, countRead); } nsresult nsHttpConnection::OnReadSegment(const char *buf, PRUint32 count, PRUint32 *countRead) { if (count == 0) { // some ReadSegments implementations will erroneously call the writer // to consume 0 bytes worth of data. we must protect against this case // or else we'd end up closing the socket prematurely. NS_ERROR("bad ReadSegments implementation"); return NS_ERROR_FAILURE; // stop iterating } nsresult rv = mSocketOut->Write(buf, count, countRead); if (NS_FAILED(rv)) mSocketOutCondition = rv; else if (*countRead == 0) mSocketOutCondition = NS_BASE_STREAM_CLOSED; else mSocketOutCondition = NS_OK; // reset condition return mSocketOutCondition; } nsresult nsHttpConnection::OnSocketWritable() { LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this)); nsresult rv; PRUint32 n; PRBool again = PR_TRUE; do { // if we're doing an SSL proxy connect, then we need to bypass calling // into the transaction. // // NOTE: this code path can't be shared since the transaction doesn't // implement nsIInputStream. doing so is not worth the added cost of // extra indirections during normal reading. // if (mSSLProxyConnectStream) { LOG((" writing CONNECT request stream\n")); rv = mSSLProxyConnectStream->ReadSegments(ReadFromStream, this, NS_HTTP_SEGMENT_SIZE, &n); } else { LOG((" writing transaction request stream\n")); rv = mTransaction->ReadSegments(this, NS_HTTP_SEGMENT_SIZE, &n); } LOG((" ReadSegments returned [rv=%x read=%u sock-cond=%x]\n", rv, n, mSocketOutCondition)); // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. if (rv == NS_BASE_STREAM_CLOSED) { rv = NS_OK; n = 0; } if (NS_FAILED(rv)) { // if the transaction didn't want to write any more data, then // wait for the transaction to call ResumeSend. if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; again = PR_FALSE; } else if (NS_FAILED(mSocketOutCondition)) { if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) rv = mSocketOut->AsyncWait(this, 0, 0, nsnull); // continue writing else rv = mSocketOutCondition; again = PR_FALSE; } else if (n == 0) { // // at this point we've written out the entire transaction, and now we // must wait for the server's response. we manufacture a status message // here to reflect the fact that we are waiting. this message will be // trumped (overwritten) if the server responds quickly. // mTransaction->OnTransportStatus(nsISocketTransport::STATUS_WAITING_FOR, LL_ZERO); rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading again = PR_FALSE; } // write more to the socket until error or end-of-request... } while (again); return rv; } nsresult nsHttpConnection::OnWriteSegment(char *buf, PRUint32 count, PRUint32 *countWritten) { if (count == 0) { // some WriteSegments implementations will erroneously call the reader // to provide 0 bytes worth of data. we must protect against this case // or else we'd end up closing the socket prematurely. NS_ERROR("bad WriteSegments implementation"); return NS_ERROR_FAILURE; // stop iterating } nsresult rv = mSocketIn->Read(buf, count, countWritten); if (NS_FAILED(rv)) mSocketInCondition = rv; else if (*countWritten == 0) mSocketInCondition = NS_BASE_STREAM_CLOSED; else mSocketInCondition = NS_OK; // reset condition return mSocketInCondition; } nsresult nsHttpConnection::OnSocketReadable() { LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this)); PRUint32 now = NowInSeconds(); if (mKeepAliveMask && (now - mLastReadTime >= PRUint32(mMaxHangTime))) { LOG(("max hang time exceeded!\n")); // give the handler a chance to create a new persistent connection to // this host if we've been busy for too long. mKeepAliveMask = PR_FALSE; gHttpHandler->ProcessPendingQ(mConnInfo); } mLastReadTime = now; nsresult rv; PRUint32 n; PRBool again = PR_TRUE; do { rv = mTransaction->WriteSegments(this, NS_HTTP_SEGMENT_SIZE, &n); if (NS_FAILED(rv)) { // if the transaction didn't want to take any more data, then // wait for the transaction to call ResumeRecv. if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; again = PR_FALSE; } else if (NS_FAILED(mSocketInCondition)) { // continue waiting for the socket if necessary... if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); else rv = mSocketInCondition; again = PR_FALSE; } // read more from the socket until error... } while (again); return rv; } nsresult nsHttpConnection::SetupSSLProxyConnect() { const char *val; LOG(("nsHttpConnection::SetupSSLProxyConnect [this=%x]\n", this)); NS_ENSURE_TRUE(!mSSLProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED); nsCAutoString buf; buf.Assign(mConnInfo->Host()); buf.Append(':'); buf.AppendInt(mConnInfo->Port()); // CONNECT host:port HTTP/1.1 nsHttpRequestHead request; request.SetMethod(nsHttp::Connect); request.SetVersion(gHttpHandler->HttpVersion()); request.SetRequestURI(buf); request.SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent()); // send this header for backwards compatibility. request.SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive")); // NOTE: this cast is valid since this connection cannot be processing a // transaction pipeline until after the first HTTP/1.1 response. nsHttpTransaction *trans = NS_STATIC_CAST(nsHttpTransaction *, mTransaction); val = trans->RequestHead()->PeekHeader(nsHttp::Host); if (val) { // all HTTP/1.1 requests must include a Host header (even though it // may seem redundant in this case; see bug 82388). request.SetHeader(nsHttp::Host, nsDependentCString(val)); } val = trans->RequestHead()->PeekHeader(nsHttp::Proxy_Authorization); if (val) { // we don't know for sure if this authorization is intended for the // SSL proxy, so we add it just in case. request.SetHeader(nsHttp::Proxy_Authorization, nsDependentCString(val)); } buf.Truncate(); request.Flatten(buf, PR_FALSE); buf.AppendLiteral("\r\n"); return NS_NewCStringInputStream(getter_AddRefs(mSSLProxyConnectStream), buf); } //----------------------------------------------------------------------------- // nsHttpConnection::nsISupports //----------------------------------------------------------------------------- NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpConnection, nsIInputStreamCallback, nsIOutputStreamCallback, nsITransportEventSink, nsIInterfaceRequestor) //----------------------------------------------------------------------------- // nsHttpConnection::nsIInputStreamCallback //----------------------------------------------------------------------------- // called on the socket transport thread NS_IMETHODIMP nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in) { NS_ASSERTION(in == mSocketIn, "unexpected stream"); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // if the transaction was dropped... if (!mTransaction) { LOG((" no transaction; ignoring event\n")); return NS_OK; } nsresult rv = OnSocketReadable(); if (NS_FAILED(rv)) CloseTransaction(mTransaction, rv); return NS_OK; } //----------------------------------------------------------------------------- // nsHttpConnection::nsIOutputStreamCallback //----------------------------------------------------------------------------- NS_IMETHODIMP nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out) { NS_ASSERTION(out == mSocketOut, "unexpected stream"); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // if the transaction was dropped... if (!mTransaction) { LOG((" no transaction; ignoring event\n")); return NS_OK; } nsresult rv = OnSocketWritable(); if (NS_FAILED(rv)) CloseTransaction(mTransaction, rv); return NS_OK; } //----------------------------------------------------------------------------- // nsHttpConnection::nsITransportEventSink //----------------------------------------------------------------------------- NS_IMETHODIMP nsHttpConnection::OnTransportStatus(nsITransport *trans, nsresult status, PRUint64 progress, PRUint64 progressMax) { if (mTransaction) mTransaction->OnTransportStatus(status, progress); return NS_OK; } //----------------------------------------------------------------------------- // nsHttpConnection::nsIInterfaceRequestor //----------------------------------------------------------------------------- // not called on the socket transport thread NS_IMETHODIMP nsHttpConnection::GetInterface(const nsIID &iid, void **result) { // NOTE: This function is only called on the UI thread via sync proxy from // the socket transport thread. If that weren't the case, then we'd // have to worry about the possibility of mTransaction going away // part-way through this function call. See CloseTransaction. NS_ASSERTION(PR_GetCurrentThread() != gSocketThread, "wrong thread"); if (mTransaction) { nsCOMPtr callbacks; mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); if (callbacks) return callbacks->GetInterface(iid, result); } return NS_ERROR_NO_INTERFACE; }