author | Patrick McManus <mcmanus@ducksong.com> |
Sat, 21 May 2011 21:27:52 -0400 | |
changeset 69862 | 1a2f85fcf598098d15645d6204994b9575a1377c |
parent 69861 | edfde66134a66c36412aafea42df8537ed3316d8 |
child 69863 | 9c8537aa965a8e7cc1e7c0ff4519627e6eb72c42 |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | smaug, biesi, bz |
bugs | 640003 |
milestone | 6.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/configure.in +++ b/configure.in @@ -4856,17 +4856,17 @@ MOZ_XUL=1 MOZ_ZIPWRITER=1 NS_PRINTING=1 MOZ_PDF_PRINTING= MOZ_DISABLE_DOMCRYPTO= NSS_DISABLE_DBM= NECKO_WIFI=1 NECKO_COOKIES=1 NECKO_DISK_CACHE=1 -NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource wyciwyg" +NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg" USE_ARM_KUSER= BUILD_CTYPES=1 XPC_IDISPATCH_SUPPORT= case "${target}" in *android*|*darwin*) ACCESSIBILITY=
--- a/content/base/public/nsIWebSocket.idl +++ b/content/base/public/nsIWebSocket.idl @@ -46,20 +46,21 @@ interface nsPIDOMWindow; /** * The nsIWebSocket interface enables Web applications to maintain * bidirectional communications with server-side processes as described in: * * http://dev.w3.org/html5/websockets/ * */ -[scriptable, uuid(4403cd57-07fc-477f-a062-d6ba7dd0781b)] +[scriptable, uuid(431aea4c-568a-470e-b876-c57a29ff0fc6)] interface nsIWebSocket : nsISupports { readonly attribute DOMString url; + readonly attribute DOMString protocol; //ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSING = 2; const unsigned short CLOSED = 3; readonly attribute unsigned short readyState;
--- a/content/base/src/nsWebSocket.cpp +++ b/content/base/src/nsWebSocket.cpp @@ -17,16 +17,17 @@ * * The Initial Developer of the Original Code is * Wellington Fernando de Macedo. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Wellington Fernando de Macedo <wfernandom2004@gmail.com> (original author) + * Patrick McManus <mcmanus@ducksong.com> * * Alternatively, the contents of this file may be used under the terms of * either of 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 @@ -45,74 +46,49 @@ #include "nsIXPConnect.h" #include "nsContentUtils.h" #include "nsEventDispatcher.h" #include "nsDOMError.h" #include "nsIScriptObjectPrincipal.h" #include "nsIDOMClassInfo.h" #include "nsDOMClassInfo.h" #include "jsapi.h" -#include "nsNetUtil.h" -#include "nsIStandardURL.h" #include "nsIURL.h" #include "nsIPrivateDOMEvent.h" -#include "nsISocketTransportService.h" -#include "nsIProtocolProxyCallback.h" -#include "nsISocketTransport.h" -#include "nsIAsyncInputStream.h" -#include "nsIAsyncOutputStream.h" -#include "nsICancelable.h" #include "nsIInterfaceRequestor.h" -#include "nsISSLSocketControl.h" -#include "nsISocketProviderService.h" -#include "nsIProtocolProxyService2.h" -#include "nsISocketProvider.h" -#include "nsDeque.h" -#include "nsICookieService.h" #include "nsICharsetConverterManager.h" #include "nsIUnicodeEncoder.h" #include "nsThreadUtils.h" #include "nsIDOMDocumentEvent.h" #include "nsIDOMMessageEvent.h" -#include "nsIStandardURL.h" #include "nsIPromptFactory.h" #include "nsIWindowWatcher.h" #include "nsIPrompt.h" #include "nsIStringBundle.h" #include "nsIConsoleService.h" -#include "nsITimer.h" -#include "nsIDNSListener.h" -#include "nsIDNSRecord.h" -#include "nsIDNSService.h" #include "nsLayoutStatics.h" -#include "nsIHttpAuthenticableChannel.h" -#include "nsIHttpChannelAuthProvider.h" -#include "mozilla/Mutex.h" #include "nsIDOMCloseEvent.h" #include "nsICryptoHash.h" #include "jsdbgapi.h" #include "nsIJSContextStack.h" #include "nsJSUtils.h" #include "nsIScriptError.h" +#include "nsNetUtil.h" +#include "nsIWebSocketProtocol.h" +#include "nsILoadGroup.h" +#include "nsIRequest.h" using namespace mozilla; -static nsIThread *gWebSocketThread = nsnull; - //////////////////////////////////////////////////////////////////////////////// // nsWebSocketEstablishedConnection //////////////////////////////////////////////////////////////////////////////// -#define DEFAULT_BUFFER_SIZE 2048 #define UTF_8_REPLACEMENT_CHAR static_cast<PRUnichar>(0xFFFD) -#define TIMEOUT_TRY_CONNECT_AGAIN 1000 -#define TIMEOUT_WAIT_FOR_SERVER_RESPONSE 20000 -#define TIMEOUT_WAIT_FOR_CLOSING 20000 - #define ENSURE_TRUE_AND_FAIL_IF_FAILED(x, ret) \ PR_BEGIN_MACRO \ if (NS_UNLIKELY(!(x))) { \ NS_WARNING("ENSURE_TRUE_AND_FAIL_IF_FAILED(" #x ") failed"); \ FailConnection(); \ return ret; \ } \ PR_END_MACRO @@ -122,615 +98,106 @@ static nsIThread *gWebSocketThread = nsn nsresult __rv = res; \ if (NS_FAILED(__rv)) { \ NS_ENSURE_SUCCESS_BODY(res, ret) \ FailConnection(); \ return ret; \ } \ PR_END_MACRO -#define CHECK_TRUE_AND_FAIL_IF_FAILED(x) \ - PR_BEGIN_MACRO \ - if (NS_UNLIKELY(!(x))) { \ - NS_WARNING("CHECK_TRUE_AND_FAIL_IF_FAILED(" #x ") failed"); \ - FailConnection(); \ - return; \ - } \ - PR_END_MACRO - -#define CHECK_SUCCESS_AND_FAIL_IF_FAILED(res) \ - PR_BEGIN_MACRO \ - nsresult __rv = res; \ - if (NS_FAILED(__rv)) { \ - NS_ENSURE_SUCCESS_BODY(res, ret) \ - FailConnection(); \ - return; \ - } \ - PR_END_MACRO - -#define CHECK_SUCCESS_AND_FAIL_IF_FAILED2(res) \ - PR_BEGIN_MACRO \ - nsresult __rv = res; \ - if (NS_FAILED(__rv)) { \ - NS_ENSURE_SUCCESS_BODY(res, ret) \ - thisObject->FailConnection(); \ - return; \ - } \ - PR_END_MACRO - -#define WARN_IF_FALSE_AND_RETURN(_expr, _msg) \ - if (!(_expr)) { \ - NS_WARNING(_msg); \ - return; \ - } - -#define DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(_method) \ - nsresult _method(); \ - void MainRunnable##_method(); - -#define IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(_method) \ - nsresult \ - nsWebSocketEstablishedConnection::_method() \ - { \ - if (!NS_IsMainThread()) { \ - nsCOMPtr<nsIRunnable> event = \ - NS_NewRunnableMethod(this, &nsWebSocketEstablishedConnection:: \ - MainRunnable##_method); \ - if (!event) { \ - return NS_ERROR_OUT_OF_MEMORY; \ - } \ - return NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); \ - } \ - MainRunnable##_method(); \ - return NS_OK; \ - } \ - \ - void \ - nsWebSocketEstablishedConnection::MainRunnable##_method() \ - { \ - if (!mOwner) { \ - return; \ - } - -#define IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END \ - } - -// protocol specific -#define IS_HIGH_BIT_OF_FRAME_TYPE_SET(_v) ((_v & 0x80) == 0x80) -#define IS_HIGH_BIT_OF_BYTE_SET(_v) ((_v & 0x80) == 0x80) -#define START_BYTE_OF_MESSAGE 0x00 -#define END_BYTE_OF_MESSAGE 0xff -#define START_BYTE_OF_CLOSE_FRAME 0xff -#define END_BYTE_OF_CLOSE_FRAME 0x00 - // nsIInterfaceRequestor will be the unambiguous class for this class class nsWebSocketEstablishedConnection: public nsIInterfaceRequestor, - public nsIDNSListener, - public nsIProtocolProxyCallback, - public nsIInputStreamCallback, - public nsIOutputStreamCallback, - public nsIChannel, - public nsIHttpAuthenticableChannel + public nsIWebSocketListener, + public nsIRequest { -friend class nsWSNetAddressComparator; -friend class nsWSAutoClose; - public: nsWebSocketEstablishedConnection(); virtual ~nsWebSocketEstablishedConnection(); NS_DECL_ISUPPORTS - NS_DECL_NSIDNSLISTENER - NS_DECL_NSIPROTOCOLPROXYCALLBACK - NS_DECL_NSIINPUTSTREAMCALLBACK - NS_DECL_NSIOUTPUTSTREAMCALLBACK NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWEBSOCKETLISTENER NS_DECL_NSIREQUEST - NS_DECL_NSICHANNEL - NS_DECL_NSIPROXIEDCHANNEL - - // nsIHttpAuthenticableChannel. - // We can't use NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates - // cancel() and others. - NS_IMETHOD GetRequestMethod(nsACString &method); - NS_IMETHOD GetIsSSL(PRBool *aIsSSL); - NS_IMETHOD GetProxyMethodIsConnect(PRBool *aProxyMethodIsConnect); - NS_IMETHOD GetServerResponseHeader(nsACString & aServerResponseHeader); - NS_IMETHOD GetProxyChallenges(nsACString & aChallenges); - NS_IMETHOD GetWWWChallenges(nsACString & aChallenges); - NS_IMETHOD SetProxyCredentials(const nsACString & aCredentials); - NS_IMETHOD SetWWWCredentials(const nsACString & aCredentials); - NS_IMETHOD OnAuthAvailable(); - NS_IMETHOD OnAuthCancelled(PRBool userCancel); nsresult Init(nsWebSocket *aOwner); nsresult Disconnect(); - // These are called always on the main thread (they dispatch themselves). - // ATTENTION, these method when called can release both the WebSocket object + // these method when called can release both the WebSocket object // (i.e. mOwner) and its connection (i.e. *this*). - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(Close) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(FailConnection) + nsresult Close(); + nsresult FailConnection(); + nsresult ConsoleError(); PRBool HasOutgoingMessages() - { return mOutgoingMessages.GetSize() != 0; } + { return mOutgoingBufferedAmount != 0; } PRBool ClosedCleanly() { return mClosedCleanly; } nsresult PostMessage(const nsString& aMessage); PRUint32 GetOutgoingBufferedAmount() { return mOutgoingBufferedAmount; } - // prevent more than one instance from connecting at a time per IP - static nsTArray<nsRefPtr<nsWebSocketEstablishedConnection> >* sWSsConnecting; - private: - enum WSFrameType { - eConnectFrame, - eUTF8MessageFrame, - eCloseFrame - }; - - struct nsWSFrame { - WSFrameType mType; - nsAutoPtr<nsCString> mData; - }; - - // We can only establish one connection at a time per IP address. - // TryConnect ensures this by checking sWSsConnecting. - // If there is a IP address entry there it tries again after - // TIMEOUT_TRY_CONNECT_AGAIN milliseconds. - static void TryConnect(nsITimer *aTimer, - void *aClosure); - - // We wait for the initial server response for - // TIMEOUT_WAIT_FOR_SERVER_RESPONSE milliseconds - static void TimerInitialServerResponseCallback(nsITimer *aTimer, - void *aClosure); - // We wait TIMEOUT_WAIT_FOR_CLOSING milliseconds to the connection be closed - // after setting the readyState to CLOSING. - static void TimerForceCloseCallback(nsITimer *aTimer, - void *aClosure); - - // Similar to the Close method, but this one neither sends the close frame - // nor expects the connection to be closed (mStatus == CONN_CLOSED) to - // disconnect. - void ForceClose(); - - nsresult DoConnect(); - nsresult HandleNewInputString(PRUint32 aStart); - nsresult AddAuthorizationHeaders(nsCString& aAuthHeaderStr, - PRBool aIsProxyAuth); - nsresult AddCookiesToRequest(nsCString& aAuthHeaderStr); - - // Returns the related number of the generated key. - PRUint32 GenerateSecKey(nsCString& aKey); - nsresult GenerateRequestKeys(nsCString& aKey1, - nsCString& aKey2, - nsCString& aKey3); - - PRBool UsingHttpProxy(); - nsresult Reset(); - void RemoveFromLoadGroup(); - nsresult ProcessHeaders(); - nsresult PostData(nsCString *aBuffer, - WSFrameType aWSFrameType); nsresult PrintErrorOnConsole(const char *aBundleURI, const PRUnichar *aError, const PRUnichar **aFormatStrings, PRUint32 aFormatStringsLen); - - // auth specific methods - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(ProcessAuthentication) - - // these are called always on the main thread (they dispatch themselves) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(AddWSConnecting) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(RemoveWSConnecting) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(HandleSetCookieHeader) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(DoInitialRequest) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(Connected) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(FrameError) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(DispatchNewMessage) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(Retry) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(ResolveNextProxyAndConnect) - DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(UpdateMustKeepAlive) - - // called to cause the underlying socket to start speaking SSL - nsresult ProxyStartSSL(); - - // shared by both threads (the main and the socket ones) - Mutex mLockDisconnect; - Mutex mLockOutgoingMessages; - Mutex mLockReceivedMessages; - nsCOMPtr<nsISocketTransport> mSocketTransport; - nsCOMPtr<nsIAsyncInputStream> mSocketInput; - nsCOMPtr<nsIAsyncOutputStream> mSocketOutput; - nsCOMPtr<nsIProxyInfo> mProxyInfo; - nsDeque mOutgoingMessages; // has nsWSFrame* which need to be sent - PRUint32 mBytesAlreadySentOfFirstOutString; - PRUint32 mOutgoingBufferedAmount; // not really necessary, but it is - // here for fast access. - nsDeque mReceivedMessages; // has nsCString* messages that need - // to be dispatched as message events - nsCString mExpectedMD5Challenge; - nsWebSocket* mOwner; // WEAK - - // used in mHeaders - enum { - kUpgradePos = 0, - kConnectionPos, - kSecWebSocketOriginPos, - kSecWebSocketLocationPos, - kSecWebSocketProtocolPos, - kSetCookiePos, - kProxyAuthenticatePos, - kServerPos, // for digest auth - kHeadersLen - }; - - // used only by the socket thread - nsCString mBuffer; - PRUint32 mBytesInBuffer; // it is needed because mBuffer.SetLength() does - // change also the buffer's Capacity. - nsCString mHeaders[kHeadersLen]; - PRUint32 mLengthToDiscard; - PRPackedBool mReadingProxyConnectResponse; - - // WebSockets should resolve proxy in this order: - // (1) socks, (2) https, (3) http - enum ProxyConfig { - eNotResolvingProxy, - eResolvingSOCKSProxy, - eResolvingHTTPSProxy, - eResolvingHTTPProxy, - eResolvingProxyFailed - }; - ProxyConfig mCurrentProxyConfig; - - nsresult mProxyFailureReason; - nsCOMPtr<nsICancelable> mProxyResolveCancelable; - nsCOMPtr<nsICancelable> mDNSRequest; - PRNetAddr mPRNetAddr; - - nsCOMPtr<nsITimer> mTryConnectTimer; - nsCOMPtr<nsITimer> mInitialServerResponseTimer; - nsCOMPtr<nsITimer> mCloseFrameServerResponseTimer; - - // for nsIRequest implementation - nsCString mRequestName; - nsresult mFailureStatus; + nsresult UpdateMustKeepAlive(); + + // Frames that have been sent to websockethandler but not placed on wire + PRUint32 mOutgoingBufferedAmount; - // auth specific data - nsCString mProxyCredentials; - nsCString mCredentials; - nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider; - PRPackedBool mAuthenticating; - - PRPackedBool mPostedCloseFrame; - PRPackedBool mSentCloseFrame; - PRPackedBool mClosedCleanly; + nsWebSocket* mOwner; // weak reference + nsCOMPtr<nsIWebSocketProtocol> mWebSocketProtocol; - /** - * A simple state machine used to manage the flow status of the input/output - * streams of the connection. - * - * CONN_NOT_CONNECTED (initial state) -> - * CONN_CONNECTING | - * CONN_CLOSED - * - * CONN_CONNECTING -> - * CONN_CONNECTING_TO_HTTP_PROXY | - * CONN_SENDING_INITIAL_REQUEST | - * CONN_CLOSED - * - * CONN_RETRYING_TO_AUTHENTICATE -> - * CONN_CONNECTING_TO_HTTP_PROXY | - * CONN_SENDING_INITIAL_REQUEST | - * CONN_CLOSED - * - * CONN_CONNECTING_TO_HTTP_PROXY -> - * CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME | - * CONN_CLOSED - * - * CONN_SENDING_INITIAL_REQUEST -> - * CONN_WAITING_RESPONSE_FOR_INITIAL_REQUEST | - * CONN_CLOSED - * - * CONN_WAITING_RESPONSE_FOR_INITIAL_REQUEST -> - * CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME | - * CONN_CLOSED - * - * CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME -> - * CONN_READING_RESPONSE_HEADER_NAME | - * CONN_WAITING_LF_CHAR_TO_CONNECTING | - * CONN_CLOSED - * - * CONN_READING_RESPONSE_HEADER_NAME -> - * CONN_READING_RESPONSE_HEADER_VALUE | - * CONN_CLOSED - * - * CONN_READING_RESPONSE_HEADER_VALUE -> - * CONN_WAITING_LF_CHAR_OF_RESPONSE_HEADER | - * CONN_CLOSED - * - * CONN_WAITING_LF_CHAR_OF_RESPONSE_HEADER -> - * CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME | - * CONN_CLOSED - * - * CONN_WAITING_LF_CHAR_TO_CONNECTING -> - * CONN_SENDING_INITIAL_REQUEST | - * CONN_READING_CHALLENGE_RESPONSE | - * CONN_RETRYING_TO_AUTHENTICATE | - * CONN_CLOSED - * - * CONN_READING_CHALLENGE_RESPONSE -> - * CONN_CONNECTED_AND_READY | - * CONN_CLOSED - * - * CONN_CONNECTED_AND_READY -> - * CONN_HIGH_BIT_OF_FRAME_TYPE_SET | - * CONN_HIGH_BIT_OF_FRAME_TYPE_NOT_SET | - * CONN_CLOSED - * - * CONN_HIGH_BIT_OF_FRAME_TYPE_SET -> - * CONN_READING_AND_DISCARDING_LENGTH_BYTES | - * CONN_SENDING_ACK_CLOSE_FRAME | - * CONN_CLOSED - * - * CONN_READING_AND_DISCARDING_LENGTH_BYTES -> - * CONN_CONNECTED_AND_READY | - * CONN_CLOSED - * - * CONN_HIGH_BIT_OF_FRAME_TYPE_NOT_SET -> - * CONN_CONNECTED_AND_READY | - * CONN_CLOSED - * - * CONN_SENDING_ACK_CLOSE_FRAME -> - * CONN_CLOSED - * - * CONN_CLOSED (final state) - * - */ + PRPackedBool mClosedCleanly; enum ConnectionStatus { CONN_NOT_CONNECTED, - CONN_CONNECTING, - // connecting to the http proxy - CONN_RETRYING_TO_AUTHENTICATE, - CONN_CONNECTING_TO_HTTP_PROXY, - // doing the websocket handshake - CONN_SENDING_INITIAL_REQUEST, - CONN_WAITING_RESPONSE_FOR_INITIAL_REQUEST, - CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME, - CONN_READING_RESPONSE_HEADER_NAME, - CONN_READING_RESPONSE_HEADER_VALUE, - CONN_WAITING_LF_CHAR_OF_RESPONSE_HEADER, - CONN_WAITING_LF_CHAR_TO_CONNECTING, - CONN_READING_CHALLENGE_RESPONSE, - // the websocket connection is established CONN_CONNECTED_AND_READY, - CONN_HIGH_BIT_OF_FRAME_TYPE_SET, - CONN_READING_AND_DISCARDING_LENGTH_BYTES, - CONN_HIGH_BIT_OF_FRAME_TYPE_NOT_SET, - // the websocket server requested to close the connection and because of - // this the close frame in acknowledgement is expected to be sent - CONN_SENDING_ACK_CLOSE_FRAME, - // the websocket connection is closed (or it is going to) CONN_CLOSED }; - // Shared by both threads (the main and the socket ones). - // The initial states (CONN_NOT_CONNECTED, CONN_CONNECTING, - // CONN_CONNECTING_TO_HTTP_PROXY and CONN_SENDING_INITIAL_REQUEST) are set by - // the main thread. The other ones are set by the socket thread. + ConnectionStatus mStatus; }; -nsTArray<nsRefPtr<nsWebSocketEstablishedConnection> >* - nsWebSocketEstablishedConnection::sWSsConnecting = nsnull; - -//------------------------------------------------------------------------------ -// Helper classes -//------------------------------------------------------------------------------ - -class nsWSNetAddressComparator -{ -public: - // when comparing, if the connection is under a proxy it'll use its hostname, - // otherwise it'll use its mPRNetAddr. - PRBool Equals(nsWebSocketEstablishedConnection* a, - nsWebSocketEstablishedConnection* b) const; - PRBool LessThan(nsWebSocketEstablishedConnection* a, - nsWebSocketEstablishedConnection* b) const; -}; - -class nsWSAutoClose -{ -public: - nsWSAutoClose(nsWebSocketEstablishedConnection* conn) : mConnection(conn) - {} - - ~nsWSAutoClose() { mConnection->Close(); } - -private: - nsWebSocketEstablishedConnection* mConnection; -}; - -PRBool -nsWSNetAddressComparator::Equals(nsWebSocketEstablishedConnection* a, - nsWebSocketEstablishedConnection* b) const -{ - NS_ASSERTION(a->mOwner && b->mOwner, "Unexpected disconnected connection"); - - if ((a->mProxyInfo && !b->mProxyInfo) || - (!a->mProxyInfo && b->mProxyInfo)) { - return PR_FALSE; - } - - if (a->mProxyInfo) { - return a->mOwner->mAsciiHost.Equals(b->mOwner->mAsciiHost); - } - - if (a->mPRNetAddr.raw.family != b->mPRNetAddr.raw.family) { - return PR_FALSE; - } - - if (a->mPRNetAddr.raw.family == PR_AF_INET) { - return a->mPRNetAddr.inet.ip == b->mPRNetAddr.inet.ip; - } - - NS_ASSERTION(a->mPRNetAddr.raw.family == PR_AF_INET6, - "Invalid net raw family"); - - return a->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] == - b->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] && - a->mPRNetAddr.ipv6.ip.pr_s6_addr64[1] == - b->mPRNetAddr.ipv6.ip.pr_s6_addr64[1]; -} - -PRBool -nsWSNetAddressComparator::LessThan(nsWebSocketEstablishedConnection* a, - nsWebSocketEstablishedConnection* b) const -{ - NS_ASSERTION(a->mOwner && b->mOwner, "Unexpected disconnected connection"); - - if (a->mProxyInfo && !b->mProxyInfo) { - return PR_FALSE; - } - - if (!a->mProxyInfo && b->mProxyInfo) { - return PR_TRUE; - } - - if (a->mProxyInfo) { - return (a->mOwner->mAsciiHost < b->mOwner->mAsciiHost); - } - - if (a->mPRNetAddr.raw.family == PR_AF_INET && - b->mPRNetAddr.raw.family == PR_AF_INET6) { - return PR_TRUE; - } - - if (a->mPRNetAddr.raw.family == PR_AF_INET6 && - b->mPRNetAddr.raw.family == PR_AF_INET) { - return PR_FALSE; - } - - if (a->mPRNetAddr.raw.family == PR_AF_INET && - b->mPRNetAddr.raw.family == PR_AF_INET) { - return a->mPRNetAddr.inet.ip < b->mPRNetAddr.inet.ip; - } - - NS_ASSERTION(a->mPRNetAddr.raw.family == PR_AF_INET6 && - b->mPRNetAddr.raw.family == PR_AF_INET6, - "Invalid net raw family"); - - return a->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] < - b->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] || - (a->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] == - b->mPRNetAddr.ipv6.ip.pr_s6_addr64[0] && - a->mPRNetAddr.ipv6.ip.pr_s6_addr64[1] < - b->mPRNetAddr.ipv6.ip.pr_s6_addr64[1]); -} - //----------------------------------------------------------------------------- // nsWebSocketEstablishedConnection::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_THREADSAFE_ISUPPORTS9(nsWebSocketEstablishedConnection, +NS_IMPL_THREADSAFE_ISUPPORTS3(nsWebSocketEstablishedConnection, nsIInterfaceRequestor, - nsIDNSListener, - nsIProtocolProxyCallback, - nsIInputStreamCallback, - nsIOutputStreamCallback, - nsIRequest, - nsIChannel, - nsIProxiedChannel, - nsIHttpAuthenticableChannel) + nsIWebSocketListener, + nsIRequest) //----------------------------------------------------------------------------- // nsWebSocketEstablishedConnection methods: //----------------------------------------------------------------------------- nsWebSocketEstablishedConnection::nsWebSocketEstablishedConnection() : - mLockDisconnect("WebSocket's disconnect lock"), - mLockOutgoingMessages("WebSocket's outgoing messages lock"), - mLockReceivedMessages("WebSocket's received messages lock"), - mBytesAlreadySentOfFirstOutString(0), mOutgoingBufferedAmount(0), mOwner(nsnull), - mBytesInBuffer(0), - mLengthToDiscard(0), - mReadingProxyConnectResponse(PR_FALSE), - mCurrentProxyConfig(eNotResolvingProxy), - mProxyFailureReason(NS_OK), - mFailureStatus(NS_OK), - mAuthenticating(PR_FALSE), - mPostedCloseFrame(PR_FALSE), - mSentCloseFrame(PR_FALSE), mClosedCleanly(PR_FALSE), mStatus(CONN_NOT_CONNECTED) { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsLayoutStatics::AddRef(); } nsWebSocketEstablishedConnection::~nsWebSocketEstablishedConnection() { - NS_ASSERTION(!mOwner, "Disconnect wasn't called!"); -} - -nsresult -nsWebSocketEstablishedConnection::PostData(nsCString *aBuffer, - WSFrameType aWSFrameType) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsAutoPtr<nsCString> data(aBuffer); - - if (mStatus == CONN_CLOSED) { - NS_ASSERTION(mOwner, "Posting data after disconnecting the websocket!"); - // the tcp connection has been closed, but the main thread hasn't received - // the event for disconnecting the object yet. - return NS_BASE_STREAM_CLOSED; - } - - MutexAutoLock lockOut(mLockOutgoingMessages); - - nsAutoPtr<nsWSFrame> frame(new nsWSFrame()); - NS_ENSURE_TRUE(frame.get(), NS_ERROR_OUT_OF_MEMORY); - frame->mType = aWSFrameType; - frame->mData = data.forget(); - - nsresult rv; - PRInt32 sizeBefore = mOutgoingMessages.GetSize(); - mOutgoingMessages.Push(frame.forget()); - NS_ENSURE_TRUE(mOutgoingMessages.GetSize() == sizeBefore + 1, - NS_ERROR_OUT_OF_MEMORY); - if (aWSFrameType == eUTF8MessageFrame) { - // without the START_BYTE_OF_MESSAGE and END_BYTE_OF_MESSAGE bytes - mOutgoingBufferedAmount += aBuffer->Length() - 2; - } else if (aWSFrameType == eCloseFrame) { - mPostedCloseFrame = PR_TRUE; - } - - if (sizeBefore == 0) { - mBytesAlreadySentOfFirstOutString = 0; - rv = mSocketOutput->AsyncWait(this, 0, 0, gWebSocketThread); - NS_ENSURE_SUCCESS(rv, rv); - } - - UpdateMustKeepAlive(); - - return NS_OK; + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(!mOwner, "Disconnect wasn't called!"); + NS_ABORT_IF_FALSE(!mWebSocketProtocol, "Disconnect wasn't called!"); } nsresult nsWebSocketEstablishedConnection::PostMessage(const nsString& aMessage) { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (!mOwner) { return NS_OK; } // only send messages when connected NS_ENSURE_STATE(mStatus >= CONN_CONNECTED_AND_READY); @@ -747,1208 +214,110 @@ nsWebSocketEstablishedConnection::PostMe rv = converter->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nsnull, UTF_8_REPLACEMENT_CHAR); ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); PRInt32 inLen = aMessage.Length(); PRInt32 maxLen; rv = converter->GetMaxLength(aMessage.BeginReading(), inLen, &maxLen); ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - maxLen += 2; // 2 bytes for START_BYTE_OF_MESSAGE and END_BYTE_OF_MESSAGE - nsAutoPtr<nsCString> buf(new nsCString()); - ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.get(), NS_ERROR_OUT_OF_MEMORY); - - buf->SetLength(maxLen); - ENSURE_TRUE_AND_FAIL_IF_FAILED(buf->Length() == static_cast<PRUint32>(maxLen), + nsCString buf; + buf.SetLength(maxLen); + ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.Length() == static_cast<PRUint32>(maxLen), NS_ERROR_OUT_OF_MEMORY); - char* start = buf->BeginWriting(); - *start = static_cast<char>(START_BYTE_OF_MESSAGE); - ++start; + char* start = buf.BeginWriting(); PRInt32 outLen = maxLen; rv = converter->Convert(aMessage.BeginReading(), &inLen, start, &outLen); if (NS_SUCCEEDED(rv)) { PRInt32 outLen2 = maxLen - outLen; rv = converter->Finish(start + outLen, &outLen2); outLen += outLen2; } if (NS_FAILED(rv) || rv == NS_ERROR_UENC_NOMAPPING) { // Yes, NS_ERROR_UENC_NOMAPPING is a success code return NS_ERROR_DOM_SYNTAX_ERR; } - char* end = buf->BeginWriting() + outLen + 1; - *end = static_cast<char>(END_BYTE_OF_MESSAGE); - - outLen += 2; - - buf->SetLength(outLen); - ENSURE_TRUE_AND_FAIL_IF_FAILED(buf->Length() == static_cast<PRUint32>(outLen), + buf.SetLength(outLen); + ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.Length() == static_cast<PRUint32>(outLen), NS_ERROR_UNEXPECTED); - rv = PostData(buf.forget(), eUTF8MessageFrame); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - return NS_OK; -} - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(DoInitialRequest) -{ - nsresult rv; - nsString strRequestTmp; - - nsAutoPtr<nsCString> buf(new nsCString()); - CHECK_TRUE_AND_FAIL_IF_FAILED(buf.get()); - - // GET resource HTTP/1.1 - buf->AppendLiteral("GET "); - buf->Append(mOwner->mResource); - buf->AppendLiteral(" HTTP/1.1\r\n"); - - nsCAutoString key_1, key_2, key_3; - rv = GenerateRequestKeys(key_1, key_2, key_3); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - - // the headers should be sent in a random order - - enum eRequestHeader { upgradeHeader = 0, connectionHeader, hostHeader, - originHeader, secWebSocketProtocolHeader, - authorizationHeaders, cookieHeaders, - secWebSocketKey1Header, secWebSocketKey2Header, - numberRequestHeaders }; - nsAutoTArray<PRUint32, numberRequestHeaders> headersToSend; - for (PRUint32 i = 0; i < numberRequestHeaders; ++i) { - headersToSend.AppendElement(i); - } - - while (!headersToSend.IsEmpty()) { - PRUint8 headerPosToSendNow = rand() % headersToSend.Length(); - eRequestHeader headerToSendNow = - static_cast<eRequestHeader>(headersToSend[headerPosToSendNow]); - - switch (headerToSendNow) - { - case upgradeHeader: - { - buf->AppendLiteral("Upgrade: WebSocket\r\n"); - } - break; - - case connectionHeader: - { - buf->AppendLiteral("Connection: Upgrade\r\n"); - } - break; - - case hostHeader: - { - buf->AppendLiteral("Host: "); - buf->Append(mOwner->mAsciiHost); - buf->AppendLiteral(":"); - buf->AppendInt(mOwner->mPort); - buf->AppendLiteral("\r\n"); - } - break; - - case originHeader: - { - buf->AppendLiteral("Origin: "); - buf->Append(mOwner->mOrigin); - buf->AppendLiteral("\r\n"); - } - break; - - case secWebSocketProtocolHeader: - { - if (!mOwner->mProtocol.IsEmpty()) { - buf->AppendLiteral("Sec-WebSocket-Protocol: "); - buf->Append(mOwner->mProtocol); - buf->AppendLiteral("\r\n"); - } - } - break; - - case authorizationHeaders: - { - rv = AddAuthorizationHeaders(*buf, PR_FALSE); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - } - break; - - case cookieHeaders: - { - rv = AddCookiesToRequest(*buf); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - } - break; - - case secWebSocketKey1Header: - { - buf->AppendLiteral("Sec-WebSocket-Key1: "); - buf->Append(key_1); - buf->AppendLiteral("\r\n"); - } - break; - - case secWebSocketKey2Header: - { - buf->AppendLiteral("Sec-WebSocket-Key2: "); - buf->Append(key_2); - buf->AppendLiteral("\r\n"); - } - break; - - case numberRequestHeaders: - break; - } - - headersToSend.RemoveElementAt(headerPosToSendNow); - } - - buf->AppendLiteral("\r\n"); - buf->Append(key_3); - - mStatus = CONN_SENDING_INITIAL_REQUEST; - - rv = PostData(buf.forget(), eConnectFrame); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -static -nsresult -GetHttpResponseCode(const nsCString& aLine, PRUint32 aLen, - PRUint32 *aStatusCode, PRUint32 *aLineLen) -{ - // make sure we have HTTP at the beginning - if (aLen < 4) { - return NS_ERROR_IN_PROGRESS; - } - if (!StringBeginsWith(aLine, NS_LITERAL_CSTRING("HTTP"))) { - return NS_ERROR_UNEXPECTED; - } - - // get the response code - PRUint32 responseCode = 0; - PRUint8 responseCodeReadingState = 0; // 0:not reading, 1:reading, - // 2:done reading - char last2Chrs[2] = {'\0', '\0'}; - PRUint32 i = 4; // just after the HTTP - for (; i < aLen; ++i) { - if (responseCodeReadingState == 0 && aLine[i] == ' ') { - responseCodeReadingState = 1; - } else if (responseCodeReadingState == 1) { - if (aLine[i] == ' ') { - responseCodeReadingState = 2; - } else if (aLine[i] >= '0' && aLine[i] <= '9') { - responseCode = 10 * responseCode + (aLine[i] - '0'); - if (responseCode > 999) { // the response code must be three digits long - return NS_ERROR_UNEXPECTED; - } - } else { - return NS_ERROR_UNEXPECTED; - } - } - - last2Chrs[0] = last2Chrs[1]; - last2Chrs[1] = aLine[i]; - if (last2Chrs[0] == '\r' && last2Chrs[1] == '\n') { // CR LF - *aStatusCode = responseCode; - *aLineLen = i + 1; - return NS_OK; - } - } - - return NS_ERROR_IN_PROGRESS; -} - -nsresult -nsWebSocketEstablishedConnection::HandleNewInputString(PRUint32 aStart) -{ - NS_ASSERTION(!NS_IsMainThread(), "Not running on socket thread"); - - if (mBytesInBuffer == 0 || aStart == mBytesInBuffer) { - return NS_OK; + if (mStatus == CONN_CLOSED) { + NS_ABORT_IF_FALSE(mOwner, "Posting data after disconnecting the websocket"); + // the tcp connection has been closed, but the main thread hasn't received + // the event for disconnecting the object yet. + rv = NS_BASE_STREAM_CLOSED; + } else { + mOutgoingBufferedAmount += buf.Length(); + mWebSocketProtocol->SendMsg(buf); + rv = NS_OK; } - NS_ENSURE_STATE(aStart < mBytesInBuffer); - - nsresult rv; - - switch (mStatus) - { - case CONN_CONNECTING_TO_HTTP_PROXY: - { - PRUint32 statusCode, lengthStr; - - rv = GetHttpResponseCode(mBuffer, mBytesInBuffer, &statusCode, - &lengthStr); - if (rv != NS_ERROR_IN_PROGRESS) { - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - - if (statusCode == 200) { - mReadingProxyConnectResponse = PR_TRUE; - mAuthenticating = PR_FALSE; - } else if (statusCode == 407) { - mReadingProxyConnectResponse = PR_TRUE; - mAuthenticating = PR_TRUE; - } else { - return NS_ERROR_UNEXPECTED; - } - - mStatus = CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME; - mBuffer.Cut(0, lengthStr); - mBytesInBuffer -= lengthStr; - - return HandleNewInputString(0); - } - } - break; - - case CONN_SENDING_ACK_CLOSE_FRAME: - case CONN_CLOSED: - { - mBytesInBuffer = 0; - } - break; - - case CONN_WAITING_RESPONSE_FOR_INITIAL_REQUEST: - { - PRUint32 statusCode, lengthStr; - - rv = GetHttpResponseCode(mBuffer, mBytesInBuffer, &statusCode, - &lengthStr); - if (rv != NS_ERROR_IN_PROGRESS) { - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - - if (statusCode != 101) { - return NS_ERROR_UNEXPECTED; - } - - mReadingProxyConnectResponse = PR_FALSE; - mAuthenticating = PR_FALSE; - - mStatus = CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME; - mBuffer.Cut(0, lengthStr); - mBytesInBuffer -= lengthStr; - - // we have just received the server response. We must cancel this timer - // or it will fail the connection. - mInitialServerResponseTimer->Cancel(); - - return HandleNewInputString(0); - } - } - break; - - case CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME: - { - if (mBuffer[aStart] == '\r') { - mStatus = CONN_WAITING_LF_CHAR_TO_CONNECTING; - return HandleNewInputString(aStart + 1); - } - - NS_ENSURE_STATE(mBuffer[aStart] != '\n'); - - mStatus = CONN_READING_RESPONSE_HEADER_NAME; - return HandleNewInputString(aStart); - } - break; - - case CONN_READING_RESPONSE_HEADER_NAME: - { - PRUint32 i; - for (i = aStart; i < mBytesInBuffer; ++i) { - NS_ENSURE_STATE(mBuffer[i] != '\r' && mBuffer[i] != '\n'); - - if (mBuffer[i] == ':') { - mStatus = CONN_READING_RESPONSE_HEADER_VALUE; - return HandleNewInputString(i + 1); - } - } - } - break; - - case CONN_READING_RESPONSE_HEADER_VALUE: - { - PRUint32 i; - for (i = aStart; i < mBytesInBuffer; ++i) { - if (mBuffer[i] == '\r') { - mStatus = CONN_WAITING_LF_CHAR_OF_RESPONSE_HEADER; - return HandleNewInputString(i + 1); - } - - NS_ENSURE_STATE(mBuffer[i] != '\n'); - } - } - break; - - case CONN_WAITING_LF_CHAR_OF_RESPONSE_HEADER: - { - NS_ENSURE_STATE(mBuffer[aStart] == '\n'); - - PRUint32 posColon = mBuffer.FindChar(':'); - PRUint32 posCR = mBuffer.FindChar('\r'); - - const nsCSubstring& headerName = Substring(mBuffer, 0, posColon); - - nsCString headerValue; - if (mBuffer[posColon + 1] == 0x20 && posColon + 2 != posCR) { - headerValue = Substring(mBuffer, posColon + 2, posCR - posColon - 2); - } else if (posColon + 1 != posCR) { - headerValue = Substring(mBuffer, posColon + 1, posCR - posColon - 1); - } else { - ; // No header value - } - - NS_ENSURE_STATE(!headerName.IsEmpty()); - - PRInt32 headerPos = -1; - if (mReadingProxyConnectResponse) { - if (headerName.LowerCaseEqualsLiteral("proxy-authenticate")) { - headerPos = kProxyAuthenticatePos; - } - } else { - if (headerName.LowerCaseEqualsLiteral("upgrade")) { - headerPos = kUpgradePos; - } else if (headerName.LowerCaseEqualsLiteral("connection")) { - headerPos = kConnectionPos; - } else if (headerName.LowerCaseEqualsLiteral("sec-websocket-origin")) { - headerPos = kSecWebSocketOriginPos; - } else if (headerName.LowerCaseEqualsLiteral("sec-websocket-location")) { - headerPos = kSecWebSocketLocationPos; - } else if (headerName.LowerCaseEqualsLiteral("sec-websocket-protocol")) { - headerPos = kSecWebSocketProtocolPos; - } else if (headerName.LowerCaseEqualsLiteral("set-cookie")) { - headerPos = kSetCookiePos; - } - } - if (headerPos == -1 && headerName.LowerCaseEqualsLiteral("server")) { - headerPos = kServerPos; - } - - if (headerPos != -1) { - NS_ENSURE_STATE(mHeaders[headerPos].IsEmpty()); - mHeaders[headerPos] = headerValue; - } - - mStatus = CONN_READING_FIRST_CHAR_OF_RESPONSE_HEADER_NAME; - mBuffer.Cut(0, aStart + 1); - mBytesInBuffer -= aStart + 1; - - return HandleNewInputString(0); - } - break; - - case CONN_WAITING_LF_CHAR_TO_CONNECTING: - { - NS_ENSURE_STATE(mBuffer[aStart] == '\n'); - - rv = ProcessHeaders(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - - if (mAuthenticating) { - Reset(); - mStatus = CONN_RETRYING_TO_AUTHENTICATE; - return ProcessAuthentication(); - } - - if (mReadingProxyConnectResponse) { - if (mOwner->mSecure) { - rv = ProxyStartSSL(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - } - - mBytesInBuffer = 0; - return DoInitialRequest(); - } - - mStatus = CONN_READING_CHALLENGE_RESPONSE; - - mBuffer.Cut(0, aStart + 1); - mBytesInBuffer -= aStart + 1; - return HandleNewInputString(0); - } - - case CONN_READING_CHALLENGE_RESPONSE: - { - NS_ENSURE_STATE(aStart == 0); - - if (mBytesInBuffer < 16) { - return NS_OK; - } - - const nsCSubstring& receivedMD5Challenge = Substring(mBuffer, 0, 16); - if (!mExpectedMD5Challenge.Equals(receivedMD5Challenge)) { - return NS_ERROR_UNEXPECTED; - } - - mStatus = CONN_CONNECTED_AND_READY; - rv = Connected(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - - mBuffer.Cut(0, aStart + 16); - mBytesInBuffer -= aStart + 16; - return HandleNewInputString(0); - } - - case CONN_CONNECTED_AND_READY: - { - NS_ENSURE_STATE(aStart == 0); - PRUint8 frameType = mBuffer[0]; - - if (IS_HIGH_BIT_OF_FRAME_TYPE_SET(frameType)) { - mStatus = CONN_HIGH_BIT_OF_FRAME_TYPE_SET; - mLengthToDiscard = 0; - } else { - mStatus = CONN_HIGH_BIT_OF_FRAME_TYPE_NOT_SET; - } - - return HandleNewInputString(1); - } - break; - - case CONN_HIGH_BIT_OF_FRAME_TYPE_SET: - { - PRUint32 i; - PRUint8 frameType = mBuffer[0]; - for (i = aStart; i < mBytesInBuffer; ++i) { - PRUint8 b, bv; - b = mBuffer[i]; - bv = (b & 0x7f); - - // prevent overflow - NS_ENSURE_STATE(mLengthToDiscard <= ((PR_UINT32_MAX - bv) / 128)); - - mLengthToDiscard = mLengthToDiscard * 128 + bv; - - if (!IS_HIGH_BIT_OF_BYTE_SET(b)) { - // check if it is the close frame - if (mLengthToDiscard == 0 && frameType == START_BYTE_OF_CLOSE_FRAME) { - mBytesInBuffer = 0; - if (mSentCloseFrame) { - mClosedCleanly = PR_TRUE; - mStatus = CONN_CLOSED; - } else { - mStatus = CONN_SENDING_ACK_CLOSE_FRAME; - } - return Close(); - } - FrameError(); - mStatus = CONN_READING_AND_DISCARDING_LENGTH_BYTES; - return HandleNewInputString(i + 1); - } - } - mBytesInBuffer = 0; - } - break; - - case CONN_READING_AND_DISCARDING_LENGTH_BYTES: - { - if (mBytesInBuffer - aStart >= mLengthToDiscard) { - mBuffer.Cut(0, aStart + mLengthToDiscard); - mBytesInBuffer -= aStart + mLengthToDiscard; - - mStatus = CONN_CONNECTED_AND_READY; - return HandleNewInputString(0); - } - - mLengthToDiscard -= mBytesInBuffer - aStart; - mBytesInBuffer = 0; - } - break; - - case CONN_HIGH_BIT_OF_FRAME_TYPE_NOT_SET: - { - PRUint32 i; - for (i = aStart; i < mBytesInBuffer; ++i) { - PRUint8 b; - b = mBuffer[i]; - if (b == END_BYTE_OF_MESSAGE) { - PRUint8 frameType = mBuffer[0]; - if (frameType == START_BYTE_OF_MESSAGE) { - // get the message, without the START_BYTE_OF_MESSAGE and - // END_BYTE_OF_MESSAGE bytes - nsAutoPtr<nsCString> dataMessage(new nsCString()); - NS_ENSURE_TRUE(dataMessage.get(), NS_ERROR_OUT_OF_MEMORY); - dataMessage->Assign(Substring(mBuffer, 1, i - 1)); - - // push the new message onto our stack - { - MutexAutoLock lockIn(mLockReceivedMessages); - - PRInt32 sizeBefore = mReceivedMessages.GetSize(); - mReceivedMessages.Push(dataMessage.forget()); - NS_ENSURE_TRUE(mReceivedMessages.GetSize() == sizeBefore + 1, - NS_ERROR_OUT_OF_MEMORY); - } - - // and dispatch it - rv = DispatchNewMessage(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); - } else { - FrameError(); - } - - mBuffer.Cut(0, i + 1); - mBytesInBuffer -= i + 1; - - mStatus = CONN_CONNECTED_AND_READY; - return HandleNewInputString(0); - } - } - } - break; - - default: - NS_ASSERTION(PR_FALSE, "Invalid state."); - } + UpdateMustKeepAlive(); + ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); return NS_OK; } nsresult -nsWebSocketEstablishedConnection::AddAuthorizationHeaders(nsCString& aStr, - PRBool aIsProxyAuth) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - mAuthProvider->AddAuthorizationHeaders(); - - if (UsingHttpProxy() && !mProxyCredentials.IsEmpty()) { - aStr.AppendLiteral("Proxy-Authorization: "); - aStr.Append(mProxyCredentials); - aStr.AppendLiteral("\r\n"); - } - - if (!aIsProxyAuth && !mCredentials.IsEmpty()) { - aStr.AppendLiteral("Authorization: "); - aStr.Append(mCredentials); - aStr.AppendLiteral("\r\n"); - } - return NS_OK; -} - -nsresult -nsWebSocketEstablishedConnection::AddCookiesToRequest(nsCString& aStr) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - // get the content of the cookie request header - nsCOMPtr<nsICookieService> cookieService = - do_GetService(NS_COOKIESERVICE_CONTRACTID); - nsCOMPtr<nsIDocument> doc = - nsContentUtils::GetDocumentFromScriptContext(mOwner->mScriptContext); - - if (!cookieService || !doc) { - return NS_OK; - } - - nsCOMPtr<nsIURI> documentURI = doc->GetDocumentURI(); - if (!documentURI) { - return NS_OK; - } - - nsXPIDLCString cookieValue; - cookieService->GetCookieStringFromHttp(documentURI, - documentURI, - nsnull, - getter_Copies(cookieValue)); - if (!cookieValue.IsEmpty()) { - aStr.AppendLiteral("Cookie: "); - aStr.Append(cookieValue); - aStr.AppendLiteral("\r\n"); - } - - return NS_OK; -} - -PRUint32 -nsWebSocketEstablishedConnection::GenerateSecKey(nsCString& aKey) -{ - PRUint32 i; - - PRUint32 spaces = rand() % 12 + 1; - PRUint32 max = PR_UINT32_MAX / spaces; - PRUint32 number = rand() % max; - PRUint32 product = number * spaces; - - nsCAutoString key; - - key.AppendInt(product); - - // Insert between one and twelve random characters from the ranges - // U+0021 to U+002F and U+003A to U+007E into the key at random - // positions. - PRUint32 numberOfCharsToInsert = rand() % 12 + 1; - for (i = 0; i < numberOfCharsToInsert; ++i) { - PRUint32 posToInsert = rand() % key.Length(); - char charToInsert = - rand() % 2 == 0 ? - static_cast<char>(0x21 + (rand() % (0x2F - 0x21 + 1))) : - static_cast<char>(0x3A + (rand() % (0x7E - 0x3A + 1))); - - key.Insert(charToInsert, posToInsert); - } - - // Insert /spaces/ U+0020 SPACE characters into the key at random - // positions other than the start or end of the string. - for (i = 0; i < spaces; ++i) { - PRUint32 posToInsert = rand() % (key.Length() - 1) + 1; - key.Insert(static_cast<char>(0x20), posToInsert); - } - - aKey = key; - return number; -} - -nsresult -nsWebSocketEstablishedConnection::GenerateRequestKeys(nsCString& aKey1, - nsCString& aKey2, - nsCString& aKey3) -{ - nsresult rv; - PRUint32 i; - - nsCAutoString key_1; - PRUint32 number_1; - - nsCAutoString key_2; - PRUint32 number_2; - - // generate the sec-keys headers values - number_1 = GenerateSecKey(key_1); - number_2 = GenerateSecKey(key_2); - - // key3 must be a string consisting of eight random bytes - nsCAutoString key_3; - for (i = 0; i < 8; ++i) { - // get a byte between 1 and 255. 0x00 was discarted to prevent possible - // issues in ws servers. - key_3 += static_cast<char>(rand() % 0xff + 1); - } - - // since we have the keys, we calculate the server md5 challenge response, - // which is the md5 string of the concatenation of /number_1/, expressed as - // a big-endian 32 bit integer, /number_2/, expressed as a big-endian - // 32 bit integer, and the eight bytes of /key_3/ - - nsCOMPtr<nsICryptoHash> md5CryptoHash = - do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = md5CryptoHash->Init(nsICryptoHash::MD5); - NS_ENSURE_SUCCESS(rv, rv); - - PRUint8 data[16]; - for (i = 1; i <= 4; ++i) { - data[i - 1] = static_cast<PRUint8>(number_1 >> (32 - i * 8)); - } - for (i = 1; i <= 4; ++i) { - data[i + 3] = static_cast<PRUint8>(number_2 >> (32 - i * 8)); - } - for (i = 0; i < 8; ++i) { - data[i + 8] = static_cast<PRUint8>(key_3[i]); - } - - rv = md5CryptoHash->Update(data, 16); - NS_ENSURE_SUCCESS(rv, rv); - - rv = md5CryptoHash->Finish(PR_FALSE, mExpectedMD5Challenge); - NS_ENSURE_SUCCESS(rv, rv); - - aKey1 = key_1; - aKey2 = key_2; - aKey3 = key_3; - - return NS_OK; -} - -PRBool -nsWebSocketEstablishedConnection::UsingHttpProxy() -{ - if (!mProxyInfo) { - return PR_FALSE; - } - - nsCAutoString proxyType; - mProxyInfo->GetType(proxyType); - return proxyType.EqualsLiteral("http"); -} - -// it is called by both main and socket threads under the mLockDisconnect -nsresult -nsWebSocketEstablishedConnection::Reset() -{ - RemoveWSConnecting(); - - mStatus = CONN_NOT_CONNECTED; - - if (mSocketTransport) { - mSocketTransport->Close(NS_OK); - mSocketTransport = nsnull; - } - mSocketInput = nsnull; - mSocketOutput = nsnull; - - while (mOutgoingMessages.GetSize() != 0) { - delete static_cast<nsWSFrame*>(mOutgoingMessages.PopFront()); - } - - while (mReceivedMessages.GetSize() != 0) { - delete static_cast<nsCString*>(mReceivedMessages.PopFront()); - } - - mBytesAlreadySentOfFirstOutString = 0; - mBytesInBuffer = 0; - - return NS_OK; -} - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(Connected) -{ - RemoveWSConnecting(); - - if (mAuthProvider) { - mAuthProvider->Disconnect(NS_ERROR_ABORT); - mAuthProvider = nsnull; - } - - mOwner->SetReadyState(nsIWebSocket::OPEN); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(FrameError) -{ - nsresult rv = - mOwner->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to dispatch the error event"); - } -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(DispatchNewMessage) -{ - nsresult rv; - - while (PR_TRUE) { - nsAutoPtr<nsCString> data; - - { - MutexAutoLock lockIn(mLockReceivedMessages); - - if (mReceivedMessages.GetSize() == 0) { - return; - } - - data = static_cast<nsCString*>(mReceivedMessages.PopFront()); - } - - rv = mOwner->CreateAndDispatchMessageEvent(data); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to dispatch the message event"); - } - } -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -nsresult -nsWebSocketEstablishedConnection::ProxyStartSSL() -{ - NS_ASSERTION(!NS_IsMainThread(), "Not running on socket thread"); - - nsCOMPtr<nsISupports> securityInfo; - nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - return ssl->ProxyStartSSL(); -} - -nsresult nsWebSocketEstablishedConnection::Init(nsWebSocket *aOwner) { - // test if it has been alredy initialized - NS_ASSERTION(!mOwner, "WebSocket's connection is already initialized"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(!mOwner, "WebSocket's connection is already initialized"); nsresult rv; - mOwner = aOwner; if (mOwner->mSecure) { - // HACK: make sure PSM gets initialized on the main thread. - nsCOMPtr<nsISocketProviderService> spserv = - do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID); - NS_ENSURE_STATE(spserv); - - nsCOMPtr<nsISocketProvider> provider; - rv = spserv->GetSocketProvider("ssl", getter_AddRefs(provider)); - NS_ENSURE_SUCCESS(rv, rv); + mWebSocketProtocol = + do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv); } - - mTryConnectTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + else { + mWebSocketProtocol = + do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = mWebSocketProtocol->SetNotificationCallbacks(this); NS_ENSURE_SUCCESS(rv, rv); - mInitialServerResponseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - - // add ourselves to the document's load group + // add ourselves to the document's load group and + // provide the http stack the loadgroup info too nsCOMPtr<nsILoadGroup> loadGroup; rv = GetLoadGroup(getter_AddRefs(loadGroup)); - NS_ENSURE_SUCCESS(rv, rv); if (loadGroup) { + rv = mWebSocketProtocol->SetLoadGroup(loadGroup); + NS_ENSURE_SUCCESS(rv, rv); rv = loadGroup->AddRequest(this, nsnull); NS_ENSURE_SUCCESS(rv, rv); } - mAuthProvider = - do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1", - &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mAuthProvider->Init(this); + if (!mOwner->mProtocol.IsEmpty()) + rv = mWebSocketProtocol->SetProtocol(mOwner->mProtocol); NS_ENSURE_SUCCESS(rv, rv); - CopyUTF16toUTF8(mOwner->mOriginalURL, mRequestName); - - if (!sWSsConnecting) { - sWSsConnecting = - new nsTArray<nsRefPtr<nsWebSocketEstablishedConnection> >(); - ENSURE_TRUE_AND_FAIL_IF_FAILED(sWSsConnecting, NS_ERROR_OUT_OF_MEMORY); - } - - if (!gWebSocketThread) { - rv = NS_NewThread(&gWebSocketThread); - NS_ENSURE_SUCCESS(rv, rv); - } - - rv = ResolveNextProxyAndConnect(); + nsCString utf8Origin; + CopyUTF16toUTF8(mOwner->mUTF16Origin, utf8Origin); + rv = mWebSocketProtocol->AsyncOpen(mOwner->mURI, + utf8Origin, this, nsnull); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult -nsWebSocketEstablishedConnection::DoConnect() -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsresult rv; - - rv = AddWSConnecting(); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - mStatus = CONN_CONNECTING; - - nsCOMPtr<nsISocketTransportService> sts = - do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - // configure the socket type based on the connection type requested. - const char* types[1]; - nsAdoptingCString value = - nsContentUtils::GetCharPref("network.http.default-socket-type"); - - if (mOwner->mSecure) { - types[0] = "ssl"; - } else { - if (value.IsEmpty()) { - types[0] = nsnull; - } else { - types[0] = value.get(); - } - } - - nsCOMPtr<nsISocketTransport> strans; - PRUint32 typeCount = (types[0] != nsnull ? 1 : 0); - - rv = sts->CreateTransport(types, typeCount, mOwner->mAsciiHost, mOwner->mPort, - mProxyInfo, getter_AddRefs(strans)); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - rv = strans->SetSecurityCallbacks(this); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - nsCOMPtr<nsIOutputStream> outStream; - rv = strans->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, - getter_AddRefs(outStream)); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - nsCOMPtr<nsIInputStream> inStream; - rv = strans->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, - getter_AddRefs(inStream)); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - mSocketTransport = strans; - mSocketInput = do_QueryInterface(inStream); - mSocketOutput = do_QueryInterface(outStream); - mProxyResolveCancelable = nsnull; - - if (!UsingHttpProxy()) { - rv = DoInitialRequest(); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - return NS_OK; - } - - nsAutoPtr<nsCString> buf(new nsCString()); - ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.get(), NS_ERROR_OUT_OF_MEMORY); - - nsString strRequestTmp; - - // CONNECT host:port HTTP/1.1 - buf->AppendLiteral("CONNECT "); - buf->Append(mOwner->mAsciiHost); - buf->AppendLiteral(":"); - buf->AppendInt(mOwner->mPort); - buf->AppendLiteral(" HTTP/1.1\r\n"); - - // Host - // all HTTP/1.1 requests must include a Host header (even though it - // may seem redundant in this case; see bug 82388). - buf->AppendLiteral("Host: "); - buf->Append(mOwner->mAsciiHost); - buf->AppendLiteral(":"); - buf->AppendInt(mOwner->mPort); - buf->AppendLiteral("\r\n"); - - // Proxy Authorization - rv = AddAuthorizationHeaders(*buf, PR_TRUE); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - buf->AppendLiteral("\r\n"); - - mStatus = CONN_CONNECTING_TO_HTTP_PROXY; - - rv = PostData(buf.forget(), eConnectFrame); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - return NS_OK; -} - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(AddWSConnecting) -{ -#ifdef DEBUG - PRUint32 index = - sWSsConnecting->BinaryIndexOf(this, nsWSNetAddressComparator()); - NS_ASSERTION(index == nsTArray<PRNetAddr>::NoIndex, - "The ws connection shouldn't be already added in the " - "serialization list."); - bool inserted = !! -#endif - sWSsConnecting->InsertElementSorted(this, nsWSNetAddressComparator()); - NS_ASSERTION(inserted, "Couldn't insert the ws connection into the " - "serialization list."); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(RemoveWSConnecting) -{ - if (mStatus == CONN_NOT_CONNECTED) { - return; - } - PRUint32 index = - sWSsConnecting->BinaryIndexOf(this, nsWSNetAddressComparator()); - if (index != nsTArray<PRNetAddr>::NoIndex) { - sWSsConnecting->RemoveElementAt(index); - } -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -// static -void -nsWebSocketEstablishedConnection::TryConnect(nsITimer* aTimer, - void* aClosure) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsresult rv; - nsRefPtr<nsWebSocketEstablishedConnection> thisObject = - static_cast<nsWebSocketEstablishedConnection*>(aClosure); - - if (!thisObject->mOwner) { // we have been disconnected - return; - } - - PRUint32 index = sWSsConnecting->BinaryIndexOf(thisObject, - nsWSNetAddressComparator()); - if (index != nsTArray<PRNetAddr>::NoIndex) { - // try again after TIMEOUT_TRY_CONNECT_AGAIN second - rv = thisObject->mTryConnectTimer-> - InitWithFuncCallback(TryConnect, thisObject, - TIMEOUT_TRY_CONNECT_AGAIN, nsITimer::TYPE_ONE_SHOT); - CHECK_SUCCESS_AND_FAIL_IF_FAILED2(rv); - } else { - rv = thisObject->DoConnect(); - CHECK_SUCCESS_AND_FAIL_IF_FAILED2(rv); - } -} - -// static -void -nsWebSocketEstablishedConnection:: - TimerInitialServerResponseCallback(nsITimer* aTimer, - void* aClosure) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsRefPtr<nsWebSocketEstablishedConnection> thisObject = - static_cast<nsWebSocketEstablishedConnection*>(aClosure); - - if (!thisObject->mOwner) { // we have been disconnected - return; - } - - thisObject->FailConnection(); -} - -// static -void -nsWebSocketEstablishedConnection::TimerForceCloseCallback(nsITimer* aTimer, - void* aClosure) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsRefPtr<nsWebSocketEstablishedConnection> thisObject = - static_cast<nsWebSocketEstablishedConnection*>(aClosure); - - if (!thisObject->mOwner) { // we have been disconnected - return; - } - - thisObject->ForceClose(); -} - -nsresult -nsWebSocketEstablishedConnection::ProcessHeaders() -{ - NS_ASSERTION(!NS_IsMainThread(), "Not running on socket thread"); - - nsresult rv; - - if (mAuthenticating) { - if (mHeaders[kProxyAuthenticatePos].IsEmpty()) - return NS_ERROR_UNEXPECTED; - return NS_OK; - } - - if (mReadingProxyConnectResponse) { - return NS_OK; - } - - // test the upgrade header - - if (!mHeaders[kUpgradePos].EqualsLiteral("WebSocket")) { - return NS_ERROR_UNEXPECTED; - } - - // test the connection header - - if (!mHeaders[kConnectionPos].LowerCaseEqualsLiteral("upgrade")) { - return NS_ERROR_UNEXPECTED; - } - - // test the sec-websocket-origin header - - nsCString responseOriginHeader = mHeaders[kSecWebSocketOriginPos]; - ToLowerCase(responseOriginHeader); - - if (!responseOriginHeader.Equals(mOwner->mOrigin)) { - return NS_ERROR_UNEXPECTED; - } - - // test the sec-websocket-location header - - nsCString validWebSocketLocation1, validWebSocketLocation2; - validWebSocketLocation1.Append(mOwner->mSecure ? "wss://" : "ws://"); - validWebSocketLocation1.Append(mOwner->mAsciiHost); - validWebSocketLocation1.Append(":"); - validWebSocketLocation1.AppendInt(mOwner->mPort); - validWebSocketLocation1.Append(mOwner->mResource); - - if ((mOwner->mSecure && mOwner->mPort != DEFAULT_WSS_SCHEME_PORT) || - (!mOwner->mSecure && mOwner->mPort != DEFAULT_WS_SCHEME_PORT)) { - validWebSocketLocation2 = validWebSocketLocation1; - } else { - validWebSocketLocation2.Append(mOwner->mSecure ? "wss://" : "ws://"); - validWebSocketLocation2.Append(mOwner->mAsciiHost); - validWebSocketLocation2.Append(mOwner->mResource); - } - - if (!mHeaders[kSecWebSocketLocationPos].Equals(validWebSocketLocation1) && - !mHeaders[kSecWebSocketLocationPos].Equals(validWebSocketLocation2)) { - return NS_ERROR_UNEXPECTED; - } - - // handle the sec-websocket-protocol header - if (!mOwner->mProtocol.IsEmpty() && - !mHeaders[kSecWebSocketProtocolPos]. - Equals(mOwner->mProtocol)) { - return NS_ERROR_UNEXPECTED; - } - - // handle the set-cookie header - - if (!mHeaders[kSetCookiePos].IsEmpty()) { - rv = HandleSetCookieHeader(); - NS_ENSURE_SUCCESS(rv, rv); - } - - return NS_OK; -} - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(HandleSetCookieHeader) -{ - nsresult rv; - - nsCOMPtr<nsICookieService> cookieService = - do_GetService(NS_COOKIESERVICE_CONTRACTID); - nsCOMPtr<nsIDocument> doc = - nsContentUtils::GetDocumentFromScriptContext(mOwner->mScriptContext); - - if (!cookieService || !doc) { - return; - } - - nsCOMPtr<nsIURI> documentURI = doc->GetDocumentURI(); - if (!documentURI) { - return; - } - - nsCOMPtr<nsIPromptFactory> wwatch = - do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - - nsCOMPtr<nsIPrompt> prompt; - nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow(); - rv = wwatch->GetPrompt(outerWindow, NS_GET_IID(nsIPrompt), - getter_AddRefs(prompt)); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - - rv = cookieService->SetCookieStringFromHttp(documentURI, - documentURI, - prompt, - mHeaders[kSetCookiePos].get(), - nsnull, - nsnull); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -nsresult nsWebSocketEstablishedConnection::PrintErrorOnConsole(const char *aBundleURI, const PRUnichar *aError, const PRUnichar **aFormatStrings, PRUint32 aFormatStringsLen) { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIStringBundle> strBundle; @@ -1985,864 +354,227 @@ nsWebSocketEstablishedConnection::PrintE // print the error message directly to the JS console nsCOMPtr<nsIScriptError> logError(do_QueryInterface(errorObject)); rv = console->LogMessage(logError); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(Close) +// when this is called the browser side wants no more part of it +nsresult +nsWebSocketEstablishedConnection::Close() { - nsresult rv; + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); // Disconnect() can release this object, so we keep a // reference until the end of the method nsRefPtr<nsWebSocketEstablishedConnection> kungfuDeathGrip = this; if (mOwner->mReadyState == nsIWebSocket::CONNECTING) { mOwner->SetReadyState(nsIWebSocket::CLOSING); mOwner->SetReadyState(nsIWebSocket::CLOSED); Disconnect(); - return; + return NS_OK; } mOwner->SetReadyState(nsIWebSocket::CLOSING); - if (!mCloseFrameServerResponseTimer) { - mCloseFrameServerResponseTimer = - do_CreateInstance("@mozilla.org/timer;1", &rv); - - if (NS_FAILED(rv)) { - NS_WARNING("Failed to create mCloseFrameServerResponseTimer."); - } else { - rv = mCloseFrameServerResponseTimer-> - InitWithFuncCallback(TimerForceCloseCallback, this, - TIMEOUT_WAIT_FOR_CLOSING, nsITimer::TYPE_ONE_SHOT); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to start the ForceClose timeout."); - } - } - } if (mStatus == CONN_CLOSED) { mOwner->SetReadyState(nsIWebSocket::CLOSED); Disconnect(); - } else if (!mPostedCloseFrame) { - nsAutoPtr<nsCString> closeFrame(new nsCString()); - if (!closeFrame.get()) { - return; - } - - closeFrame->SetLength(2); - if (closeFrame->Length() != 2) { - return; - } - - closeFrame->SetCharAt(START_BYTE_OF_CLOSE_FRAME, 0); - closeFrame->SetCharAt(END_BYTE_OF_CLOSE_FRAME, 1); + return NS_OK; + } - rv = PostData(closeFrame.forget(), eCloseFrame); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to post the close frame"); - return; - } - } else { - // Probably failed to send the close frame. Just disconnect. - Disconnect(); - } -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -void -nsWebSocketEstablishedConnection::ForceClose() -{ - // Disconnect() can release this object, so we keep a - // reference until the end of the method - nsRefPtr<nsWebSocketEstablishedConnection> kungfuDeathGrip = this; - - mOwner->SetReadyState(nsIWebSocket::CLOSING); - mOwner->SetReadyState(nsIWebSocket::CLOSED); - Disconnect(); + return mWebSocketProtocol->Close(); } -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(FailConnection) +nsresult +nsWebSocketEstablishedConnection::ConsoleError() { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; - nsWSAutoClose autoClose(this); - - if (mFailureStatus == NS_OK) { - mFailureStatus = NS_ERROR_UNEXPECTED; - } - + if (!mOwner) return NS_OK; + nsCAutoString targetSpec; rv = mOwner->mURI->GetSpec(targetSpec); - WARN_IF_FALSE_AND_RETURN(NS_SUCCEEDED(rv), "Failed to get targetSpec"); - - NS_ConvertUTF8toUTF16 specUTF16(targetSpec); - const PRUnichar *formatStrings[] = { specUTF16.get() }; - - if (mStatus < CONN_CONNECTED_AND_READY) { - if (mCurrentProxyConfig == eResolvingProxyFailed) { - PrintErrorOnConsole("chrome://browser/locale/appstrings.properties", - NS_LITERAL_STRING("proxyConnectFailure").get(), - nsnull, 0); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get targetSpec"); + } else { + NS_ConvertUTF8toUTF16 specUTF16(targetSpec); + const PRUnichar *formatStrings[] = { specUTF16.get() }; + + if (mStatus < CONN_CONNECTED_AND_READY) { + PrintErrorOnConsole("chrome://global/locale/appstrings.properties", + NS_LITERAL_STRING("connectionFailure").get(), + formatStrings, NS_ARRAY_LENGTH(formatStrings)); + } else { + PrintErrorOnConsole("chrome://global/locale/appstrings.properties", + NS_LITERAL_STRING("netInterrupt").get(), + formatStrings, NS_ARRAY_LENGTH(formatStrings)); } - PrintErrorOnConsole("chrome://browser/locale/appstrings.properties", - NS_LITERAL_STRING("connectionFailure").get(), - formatStrings, NS_ARRAY_LENGTH(formatStrings)); - } else { - PrintErrorOnConsole("chrome://browser/locale/appstrings.properties", - NS_LITERAL_STRING("netInterrupt").get(), - formatStrings, NS_ARRAY_LENGTH(formatStrings)); } + /// todo some sepcific errors - like for message too large + return rv; } -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END + + +nsresult +nsWebSocketEstablishedConnection::FailConnection() +{ + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + nsresult rv = ConsoleError(); + Close(); + return rv; +} nsresult nsWebSocketEstablishedConnection::Disconnect() { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - { - if (!mOwner) { - return NS_OK; - } - - MutexAutoLock lockDisconnect(mLockDisconnect); - - // If mOwner is deleted when calling mOwner->DontKeepAliveAnyMore() - // then this method can be called again, and we will get a deadlock. - nsRefPtr<nsWebSocket> kungfuDeathGrip = mOwner; - - mOwner->DontKeepAliveAnyMore(); - - RemoveWSConnecting(); - - mStatus = CONN_CLOSED; - mOwner = nsnull; + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); - if (mAuthProvider) { - mAuthProvider->Disconnect(NS_ERROR_ABORT); - mAuthProvider = nsnull; - } - - if (mTryConnectTimer) { - mTryConnectTimer->Cancel(); - mTryConnectTimer = nsnull; - } - - if (mInitialServerResponseTimer) { - mInitialServerResponseTimer->Cancel(); - mInitialServerResponseTimer = nsnull; - } - - if (mCloseFrameServerResponseTimer) { - mCloseFrameServerResponseTimer->Cancel(); - mCloseFrameServerResponseTimer = nsnull; - } + if (!mOwner) { + return NS_OK; + } + + nsCOMPtr<nsILoadGroup> loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) + loadGroup->RemoveRequest(this, nsnull, NS_OK); - if (mProxyResolveCancelable) { - mProxyResolveCancelable->Cancel(NS_ERROR_ABORT); - mProxyResolveCancelable = nsnull; - } - - if (mDNSRequest) { - mDNSRequest->Cancel(NS_ERROR_ABORT); - mDNSRequest = nsnull; - } + // If mOwner is deleted when calling mOwner->DontKeepAliveAnyMore() + // then this method can be called again, and we will get a deadlock. + nsRefPtr<nsWebSocket> kungfuDeathGrip = mOwner; + + mOwner->DontKeepAliveAnyMore(); + mStatus = CONN_CLOSED; + mOwner = nsnull; + mWebSocketProtocol = nsnull; - if (mSocketInput) { - mSocketInput->Close(); - mSocketInput = nsnull; - } - if (mSocketOutput) { - mSocketOutput->Close(); - mSocketOutput = nsnull; - } - if (mSocketTransport) { - mSocketTransport->Close(NS_OK); - mSocketTransport = nsnull; - } - mProxyInfo = nsnull; - - while (mOutgoingMessages.GetSize() != 0) { - delete static_cast<nsWSFrame*>(mOutgoingMessages.PopFront()); - } - - while (mReceivedMessages.GetSize() != 0) { - delete static_cast<nsCString*>(mReceivedMessages.PopFront()); - } - - // Remove ourselves from the document's load group. nsIRequest expects - // this be done asynchronously. - nsCOMPtr<nsIRunnable> event = - NS_NewRunnableMethod(this, &nsWebSocketEstablishedConnection:: - RemoveFromLoadGroup); - if (event) { - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - - nsLayoutStatics::Release(); - } - + nsLayoutStatics::Release(); return NS_OK; } -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(Retry) -{ - nsresult rv; - - for (PRUint32 i = 0; i < kHeadersLen; ++i) { - mHeaders[i].Truncate(); - } - - rv = OnProxyAvailable(nsnull, mOwner->mURI, mProxyInfo, NS_OK); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(ResolveNextProxyAndConnect) -{ - nsresult rv; - - if (mCurrentProxyConfig == eResolvingProxyFailed) { - return; - } - - nsCOMPtr<nsIProtocolProxyService2> proxyService = - do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); - if (NS_FAILED(rv)) { - NS_WARNING("Failed getting proxyService"); - mCurrentProxyConfig = eResolvingProxyFailed; - mFailureStatus = NS_ERROR_UNKNOWN_PROXY_HOST; - FailConnection(); - return; - } - - if (mProxyInfo) { - // If there was already a proxy info it means we tried to connect to its - // proxy, but we couldn't. We have to remove the connection from the - // serialization list, reset it, and try to get a failover proxy. - - { - MutexAutoLock lockDisconnect(mLockDisconnect); - rv = Reset(); - CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv); - } - - nsCOMPtr<nsIProxyInfo> pi; - rv = proxyService->GetFailoverForProxy(mProxyInfo, mOwner->mURI, - mProxyFailureReason, - getter_AddRefs(pi)); - if (NS_FAILED(rv)) { - mProxyInfo = nsnull; - ResolveNextProxyAndConnect(); - return; - } - - OnProxyAvailable(nsnull, mOwner->mURI, pi, NS_OK); - return; - } - - // if (!mProxyInfo) - - PRUint32 flags = nsIProtocolProxyService::RESOLVE_IGNORE_URI_SCHEME; - - if (mCurrentProxyConfig == eNotResolvingProxy) { - mCurrentProxyConfig = eResolvingSOCKSProxy; - flags |= nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY; - } else if (mCurrentProxyConfig == eResolvingSOCKSProxy) { - flags |= nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY; - mCurrentProxyConfig = eResolvingHTTPSProxy; - } else if (mCurrentProxyConfig == eResolvingHTTPSProxy) { - mCurrentProxyConfig = eResolvingHTTPProxy; - } else if (mCurrentProxyConfig == eResolvingHTTPProxy) { - mCurrentProxyConfig = eResolvingProxyFailed; - mFailureStatus = NS_ERROR_UNKNOWN_PROXY_HOST; - FailConnection(); - return; - } - - rv = proxyService->AsyncResolve(mOwner->mURI, - flags, this, - getter_AddRefs(mProxyResolveCancelable)); - if (NS_FAILED(rv)) { - ResolveNextProxyAndConnect(); - return; - } -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(UpdateMustKeepAlive) -{ - mOwner->UpdateMustKeepAlive(); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -void -nsWebSocketEstablishedConnection::RemoveFromLoadGroup() -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - nsCOMPtr<nsILoadGroup> loadGroup; - GetLoadGroup(getter_AddRefs(loadGroup)); - if (loadGroup) { - loadGroup->RemoveRequest(this, nsnull, NS_OK); - } -} - -//----------------------------------------------------------------------------- -// Authentication -//----------------------------------------------------------------------------- - -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(ProcessAuthentication) +nsresult +nsWebSocketEstablishedConnection::UpdateMustKeepAlive() { - nsresult rv = mAuthProvider->ProcessAuthentication(407, PR_FALSE); - - if (rv == NS_ERROR_IN_PROGRESS) { - return; - } - - if (NS_FAILED(rv)) { - NS_WARNING("ProcessAuthentication failed"); - FailConnection(); - return; - } - - Retry(); -} -IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -#define NOT_IMPLEMENTED_IF_FUNC_BEGIN(_func) \ - NS_IMETHODIMP \ - nsWebSocketEstablishedConnection::_func - -#define NOT_IMPLEMENTED_IF_FUNC_END(_func) \ - { \ - return NS_ERROR_NOT_IMPLEMENTED; \ - } - -#define NOT_IMPLEMENTED_IF_FUNC_0(_func) \ - NOT_IMPLEMENTED_IF_FUNC_BEGIN(_func) () \ - NOT_IMPLEMENTED_IF_FUNC_END(_func) - -#define NOT_IMPLEMENTED_IF_FUNC_1(_func, _arg) \ - NOT_IMPLEMENTED_IF_FUNC_BEGIN(_func) (_arg) \ - NOT_IMPLEMENTED_IF_FUNC_END(_func) - -#define NOT_IMPLEMENTED_IF_FUNC_2(_func, _arg1, arg2) \ - NOT_IMPLEMENTED_IF_FUNC_BEGIN(_func) (_arg1, arg2) \ - NOT_IMPLEMENTED_IF_FUNC_END(_func) - -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIRequest -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetName(nsACString &aName) -{ - aName = mRequestName; - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::IsPending(PRBool *aValue) -{ - *aValue = !!(mOwner); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetStatus(nsresult *aStatus) -{ - *aStatus = mFailureStatus; - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::Cancel(nsresult aStatus) -{ - if (!mOwner) { - return NS_OK; - } - - mFailureStatus = aStatus; - - return Close(); -} - -NOT_IMPLEMENTED_IF_FUNC_0(Suspend) -NOT_IMPLEMENTED_IF_FUNC_0(Resume) - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetLoadGroup(nsILoadGroup **aLoadGroup) -{ - *aLoadGroup = nsnull; - if (!mOwner) - return NS_OK; - - nsCOMPtr<nsIDocument> doc = - nsContentUtils::GetDocumentFromScriptContext(mOwner->mScriptContext); - - if (doc) { - *aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed - } - - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::SetLoadGroup(nsILoadGroup *aLoadGroup) -{ - return NS_ERROR_UNEXPECTED; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetLoadFlags(nsLoadFlags *aLoadFlags) -{ - *aLoadFlags = nsIRequest::LOAD_BACKGROUND; - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::SetLoadFlags(nsLoadFlags aLoadFlags) -{ - // we won't change the load flags at all. + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + mOwner->UpdateMustKeepAlive(); return NS_OK; } //----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIChannel -//----------------------------------------------------------------------------- - -NOT_IMPLEMENTED_IF_FUNC_1(GetOriginalURI, nsIURI **originalURI) -NOT_IMPLEMENTED_IF_FUNC_1(SetOriginalURI, nsIURI *originalURI) -NOT_IMPLEMENTED_IF_FUNC_1(GetOwner, nsISupports **owner) -NOT_IMPLEMENTED_IF_FUNC_1(SetOwner, nsISupports *owner) -NOT_IMPLEMENTED_IF_FUNC_1(SetNotificationCallbacks, - nsIInterfaceRequestor *callbacks) -NOT_IMPLEMENTED_IF_FUNC_1(GetSecurityInfo, nsISupports **securityInfo) -NOT_IMPLEMENTED_IF_FUNC_1(GetContentType, nsACString &value) -NOT_IMPLEMENTED_IF_FUNC_1(SetContentType, const nsACString &value) -NOT_IMPLEMENTED_IF_FUNC_1(GetContentCharset, nsACString &value) -NOT_IMPLEMENTED_IF_FUNC_1(SetContentCharset, const nsACString &value) -NOT_IMPLEMENTED_IF_FUNC_1(GetContentLength, PRInt32 *value) -NOT_IMPLEMENTED_IF_FUNC_1(SetContentLength, PRInt32 value) -NOT_IMPLEMENTED_IF_FUNC_1(Open, nsIInputStream **_retval) -NOT_IMPLEMENTED_IF_FUNC_2(AsyncOpen, nsIStreamListener *listener, - nsISupports *context) - -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIHttpAuthenticableChannel +// nsWebSocketEstablishedConnection::nsIWebSocketListener methods: //----------------------------------------------------------------------------- NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetProxyInfo(nsIProxyInfo **result) -{ - NS_IF_ADDREF(*result = mProxyInfo); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetIsSSL(PRBool *aIsSSL) -{ - *aIsSSL = mOwner->mSecure; - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetProxyMethodIsConnect(PRBool *aProxyMethodIsConnect) +nsWebSocketEstablishedConnection::OnMessageAvailable(nsISupports *aContext, + const nsACString & aMsg) { - *aProxyMethodIsConnect = UsingHttpProxy(); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetURI(nsIURI **aURI) -{ - NS_IF_ADDREF(*aURI = mOwner->mURI); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks) -{ - NS_ADDREF(*callbacks = this); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetRequestMethod(nsACString &method) -{ - if (mAuthenticating) { - method.AssignLiteral("CONNECT"); - } else { - method.AssignLiteral("GET"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + if (!mOwner) + return NS_ERROR_NOT_AVAILABLE; + + // Dispatch New Message + nsresult rv = mOwner->CreateAndDispatchMessageEvent(aMsg); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch the message event"); } return NS_OK; } NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetServerResponseHeader(nsACString &value) +nsWebSocketEstablishedConnection::OnBinaryMessageAvailable( + nsISupports *aContext, + const nsACString & aMsg) { - if (mHeaders[kServerPos].IsEmpty()) { - return NS_ERROR_NOT_AVAILABLE; - } - - value.Assign(mHeaders[kServerPos]); - return NS_OK; + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetProxyChallenges(nsACString &value) -{ - if (mHeaders[kProxyAuthenticatePos].IsEmpty()) { - return NS_ERROR_NOT_AVAILABLE; - } - value.Assign(mHeaders[kProxyAuthenticatePos]); - return NS_OK; -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::GetWWWChallenges(nsACString &value) +nsWebSocketEstablishedConnection::OnStart(nsISupports *aContext) { - return NS_ERROR_NOT_AVAILABLE; -} + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + if (!mOwner) + return NS_OK; -NS_IMETHODIMP -nsWebSocketEstablishedConnection::SetProxyCredentials(const nsACString &value) -{ - mProxyCredentials.Assign(value); - return NS_OK; -} + if (!mOwner->mProtocol.IsEmpty()) + mWebSocketProtocol->GetProtocol(mOwner->mProtocol); -NS_IMETHODIMP -nsWebSocketEstablishedConnection::SetWWWCredentials(const nsACString &value) -{ - mCredentials.Assign(value); + mStatus = CONN_CONNECTED_AND_READY; + mOwner->SetReadyState(nsIWebSocket::OPEN); return NS_OK; } NS_IMETHODIMP -nsWebSocketEstablishedConnection::OnAuthAvailable() +nsWebSocketEstablishedConnection::OnStop(nsISupports *aContext, + nsresult aStatusCode) { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - if (!mOwner) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + if (!mOwner) return NS_OK; - } - - return Retry(); -} - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::OnAuthCancelled(PRBool userCancel) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - if (!mOwner) { - return NS_OK; - } - if (!userCancel) { - return FailConnection(); - } - - return Close(); -} + mClosedCleanly = NS_SUCCEEDED(aStatusCode); -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIDNSListener -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::OnLookupComplete(nsICancelable *aRequest, - nsIDNSRecord *aRec, - nsresult aStatus) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - if (!mOwner) { - return NS_ERROR_ABORT; + if (aStatusCode == NS_BASE_STREAM_CLOSED && + mOwner->mReadyState >= nsIWebSocket::CLOSING) { + // don't generate an error event just because of an unclean close + aStatusCode = NS_OK; } - mDNSRequest = nsnull; - mFailureStatus = aStatus; - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(aStatus, aStatus); - - nsresult rv; - - rv = aRec->GetNextAddr(mOwner->mPort, &mPRNetAddr); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - TryConnect(nsnull, this); - - return NS_OK; -} - -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIProtocolProxyCallback -//----------------------------------------------------------------------------- - -NS_IMETHODIMP -nsWebSocketEstablishedConnection::OnProxyAvailable(nsICancelable *aRequest, - nsIURI *aUri, - nsIProxyInfo *aProxyInfo, - nsresult aStatus) -{ - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); - - nsresult rv; - - if (!mOwner) { - return NS_ERROR_ABORT; + if (NS_FAILED(aStatusCode)) { + ConsoleError(); + if (mOwner && mOwner->mReadyState != nsIWebSocket::CONNECTING) { + nsresult rv = + mOwner->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); + if (NS_FAILED(rv)) + NS_WARNING("Failed to dispatch the error event"); + } } - if (NS_FAILED(aStatus)) { - return ResolveNextProxyAndConnect(); + mStatus = CONN_CLOSED; + if (mOwner) { + mOwner->SetReadyState(nsIWebSocket::CLOSED); + Disconnect(); } - - mProxyInfo = aProxyInfo; - - if (mProxyInfo) { - TryConnect(nsnull, this); - } else { - // we need the server IP address because it must connect only one instance - // per IP address at a time. - - nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - nsCOMPtr<nsIThread> thread = do_GetMainThread(); - rv = dns->AsyncResolve(mOwner->mAsciiHost, - 0, this, thread, getter_AddRefs(mDNSRequest)); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } - return NS_OK; } -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIInputStreamCallback methods: -//----------------------------------------------------------------------------- - -nsresult -nsWebSocketEstablishedConnection::OnInputStreamReady(nsIAsyncInputStream *aStream) +NS_IMETHODIMP +nsWebSocketEstablishedConnection::OnAcknowledge(nsISupports *aContext, + PRUint32 aSize) { - NS_ASSERTION(!NS_IsMainThread(), "Not running on socket thread"); - - nsresult rv; - - { - MutexAutoLock lockDisconnect(mLockDisconnect); - - if (!mOwner) { - return NS_ERROR_ABORT; - } - - NS_ASSERTION(aStream == mSocketInput, "unexpected stream"); - - while (PR_TRUE) { - if (mBuffer.Length() - mBytesInBuffer < DEFAULT_BUFFER_SIZE) { - PRUint32 newLen = mBuffer.Length() + DEFAULT_BUFFER_SIZE; - mBuffer.SetLength(newLen); - ENSURE_TRUE_AND_FAIL_IF_FAILED(mBuffer.Length() == newLen, - NS_ERROR_OUT_OF_MEMORY); - } + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); - PRUint32 read; - rv = aStream->Read(mBuffer.BeginWriting() + mBytesInBuffer, - DEFAULT_BUFFER_SIZE, &read); - if (rv == NS_BASE_STREAM_WOULD_BLOCK) { - break; - } - mFailureStatus = rv; - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - - // check if the stream has been closed - if (read == 0) { - // If we are asking for credentials then the old connection has been - // closed. In this case we have to reset the WebSocket, not Close it. - if (mStatus != CONN_RETRYING_TO_AUTHENTICATE) { - mStatus = CONN_CLOSED; - mFailureStatus = NS_BASE_STREAM_CLOSED; - if (mStatus < CONN_CONNECTED_AND_READY) { - FailConnection(); - } else { - Close(); - } - } - return NS_BASE_STREAM_CLOSED; - } - - PRUint32 start = mBytesInBuffer; - mBytesInBuffer += read; - rv = HandleNewInputString(start); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } - - rv = mSocketInput->AsyncWait(this, 0, 0, gWebSocketThread); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } - + if (aSize > mOutgoingBufferedAmount) + return NS_ERROR_UNEXPECTED; + + mOutgoingBufferedAmount -= aSize; return NS_OK; } -//----------------------------------------------------------------------------- -// nsWebSocketEstablishedConnection::nsIOutputStreamCallback methods: -//----------------------------------------------------------------------------- - -nsresult -nsWebSocketEstablishedConnection::OnOutputStreamReady(nsIAsyncOutputStream *aStream) +NS_IMETHODIMP +nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext) { - NS_ASSERTION(!NS_IsMainThread(), "Not running on socket thread"); - - nsresult rv; - - { - MutexAutoLock lockDisconnect(mLockDisconnect); - - if (!mOwner) { - return NS_ERROR_ABORT; - } - - NS_ASSERTION(aStream == mSocketOutput, "unexpected stream"); - - { - MutexAutoLock lockOut(mLockOutgoingMessages); - - while (PR_TRUE) { - if (mOutgoingMessages.GetSize() == 0) { - break; - } - - // send what we can of the 1st string - - nsWSFrame *frameToSend = - static_cast<nsWSFrame*>(mOutgoingMessages.PeekFront()); - nsCString *strToSend = frameToSend->mData; - PRUint32 sizeToSend = - strToSend->Length() - mBytesAlreadySentOfFirstOutString; - PRBool currentStrHasStartFrameByte = - (mBytesAlreadySentOfFirstOutString == 0); - - if (sizeToSend != 0) { - PRUint32 written; - rv = aStream->Write(strToSend->get() + mBytesAlreadySentOfFirstOutString, - sizeToSend, &written); - if (rv == NS_BASE_STREAM_WOULD_BLOCK) { - break; - } - - // on proxy errors when connecting, try to failover - if ((mStatus == CONN_CONNECTING_TO_HTTP_PROXY || - (mStatus == CONN_SENDING_INITIAL_REQUEST && mProxyInfo)) && - (rv == NS_ERROR_PROXY_CONNECTION_REFUSED || - rv == NS_ERROR_UNKNOWN_PROXY_HOST || - rv == NS_ERROR_NET_TIMEOUT || - rv == NS_ERROR_NET_RESET)) { - mProxyFailureReason = rv; - return ResolveNextProxyAndConnect(); - } - - mFailureStatus = rv; - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); - if (written == 0) { - mStatus = CONN_CLOSED; - mFailureStatus = NS_BASE_STREAM_CLOSED; - if (mStatus < CONN_CONNECTED_AND_READY) { - FailConnection(); - } else { - Close(); - } - return NS_BASE_STREAM_CLOSED; - } - - if (frameToSend->mType == eUTF8MessageFrame) { - PRBool currentStrHasEndFrameByte = - (mBytesAlreadySentOfFirstOutString + written == - strToSend->Length()); - - // START_BYTE_OF_MESSAGE and END_BYTE_OF_MESSAGE bytes don't count - if (currentStrHasStartFrameByte) { - if (currentStrHasEndFrameByte) { - mOutgoingBufferedAmount -= written - 2; - } else { - mOutgoingBufferedAmount -= written - 1; - } - } else { - if (currentStrHasEndFrameByte) { - mOutgoingBufferedAmount -= written - 1; - } else { - mOutgoingBufferedAmount -= written; - } - } - } - - mBytesAlreadySentOfFirstOutString += written; - } - - sizeToSend = strToSend->Length() - mBytesAlreadySentOfFirstOutString; - if (sizeToSend != 0) { // if different, we try sending what remain after - break; - } - - // ok, send the next string - if (frameToSend->mType == eCloseFrame) { - mSentCloseFrame = PR_TRUE; - } - mOutgoingMessages.PopFront(); - delete frameToSend; - mBytesAlreadySentOfFirstOutString = 0; - UpdateMustKeepAlive(); - } - - if (mOutgoingMessages.GetSize() != 0) { - rv = mSocketOutput->AsyncWait(this, 0, 0, gWebSocketThread); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } else { - if (mStatus == CONN_SENDING_ACK_CLOSE_FRAME && mSentCloseFrame) { - mClosedCleanly = PR_TRUE; - mStatus = CONN_CLOSED; - return Close(); - } - - if (mStatus == CONN_SENDING_INITIAL_REQUEST || - mStatus == CONN_CONNECTING_TO_HTTP_PROXY) { - if (mStatus == CONN_SENDING_INITIAL_REQUEST) { - mStatus = CONN_WAITING_RESPONSE_FOR_INITIAL_REQUEST; - - rv = mInitialServerResponseTimer-> - InitWithFuncCallback(TimerInitialServerResponseCallback, this, - TIMEOUT_WAIT_FOR_SERVER_RESPONSE, - nsITimer::TYPE_ONE_SHOT); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } - rv = mSocketInput->AsyncWait(this, 0, 0, gWebSocketThread); - ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv); - } - } - } - } - + Close(); /* reciprocate! */ return NS_OK; } //----------------------------------------------------------------------------- // nsWebSocketEstablishedConnection::nsIInterfaceRequestor //----------------------------------------------------------------------------- NS_IMETHODIMP nsWebSocketEstablishedConnection::GetInterface(const nsIID &aIID, void **aResult) { - NS_ASSERTION(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { nsresult rv; nsCOMPtr<nsIDocument> doc = nsContentUtils::GetDocumentFromScriptContext(mOwner->mScriptContext); @@ -2868,20 +600,22 @@ nsWebSocketEstablishedConnection::GetInt nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE), mCheckMustKeepAlive(PR_TRUE), mTriggeredCloseEvent(PR_FALSE), mReadyState(nsIWebSocket::CONNECTING), mOutgoingBufferedAmount(0), mScriptLine(0), mWindowID(0) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); } nsWebSocket::~nsWebSocket() { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (mConnection) { mConnection->Disconnect(); mConnection = nsnull; } if (mListenerManager) { mListenerManager->Disconnect(); mListenerManager = nsnull; } @@ -2938,16 +672,17 @@ NS_IMPL_RELEASE_INHERITED(nsWebSocket, n */ NS_IMETHODIMP nsWebSocket::Initialize(nsISupports* aOwner, JSContext* aContext, JSObject* aObject, PRUint32 aArgc, jsval* aArgv) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsAutoString urlParam, protocolParam; if (!PrefEnabled()) { return NS_ERROR_DOM_SECURITY_ERR; } if (aArgc != 1 && aArgc != 2) { return NS_ERROR_DOM_SYNTAX_ERR; @@ -3006,23 +741,23 @@ nsWebSocket::Initialize(nsISupports* aOw //----------------------------------------------------------------------------- // nsWebSocket methods: //----------------------------------------------------------------------------- nsresult nsWebSocket::EstablishConnection() { - NS_ASSERTION(!mConnection, "mConnection should be null"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + NS_ABORT_IF_FALSE(!mConnection, "mConnection should be null"); nsresult rv; nsRefPtr<nsWebSocketEstablishedConnection> conn = new nsWebSocketEstablishedConnection(); - NS_ENSURE_TRUE(conn, NS_ERROR_OUT_OF_MEMORY); rv = conn->Init(this); NS_ENSURE_SUCCESS(rv, rv); mConnection = conn; return NS_OK; } @@ -3045,16 +780,17 @@ public: private: nsRefPtr<nsWebSocket> mWebSocket; PRBool mWasClean; }; nsresult nsWebSocket::CreateAndDispatchSimpleEvent(const nsString& aName) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; rv = CheckInnerWindowCorrectness(); if (NS_FAILED(rv)) { return NS_OK; } nsCOMPtr<nsIDOMEvent> event; @@ -3068,50 +804,52 @@ nsWebSocket::CreateAndDispatchSimpleEven nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event); rv = privateEvent->SetTrusted(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); return DispatchDOMEvent(nsnull, event, nsnull, nsnull); } nsresult -nsWebSocket::CreateAndDispatchMessageEvent(nsCString *aData) +nsWebSocket::CreateAndDispatchMessageEvent(const nsACString& aData) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; - + rv = CheckInnerWindowCorrectness(); if (NS_FAILED(rv)) { return NS_OK; } // create an event that uses the MessageEvent interface, // which does not bubble, is not cancelable, and has no default action nsCOMPtr<nsIDOMEvent> event; rv = NS_NewDOMMessageEvent(getter_AddRefs(event), nsnull, nsnull); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event); rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"), PR_FALSE, PR_FALSE, - NS_ConvertUTF8toUTF16(*aData), - NS_ConvertUTF8toUTF16(mOrigin), + NS_ConvertUTF8toUTF16(aData), + mUTF16Origin, EmptyString(), nsnull); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event); rv = privateEvent->SetTrusted(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); return DispatchDOMEvent(nsnull, event, nsnull, nsnull); } nsresult nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; mTriggeredCloseEvent = PR_TRUE; rv = CheckInnerWindowCorrectness(); if (NS_FAILED(rv)) { return NS_OK; } @@ -3134,73 +872,71 @@ nsWebSocket::CreateAndDispatchCloseEvent NS_ENSURE_SUCCESS(rv, rv); return DispatchDOMEvent(nsnull, event, nsnull, nsnull); } PRBool nsWebSocket::PrefEnabled() { - return nsContentUtils::GetBoolPref("network.websocket.enabled", PR_TRUE) && - nsContentUtils::GetBoolPref("network.websocket.override-security-block", - PR_FALSE); + return nsContentUtils::GetBoolPref("network.websocket.enabled", PR_TRUE); } void nsWebSocket::SetReadyState(PRUint16 aNewReadyState) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; if (mReadyState == aNewReadyState) { return; } - NS_ASSERTION((aNewReadyState == nsIWebSocket::OPEN) || - (aNewReadyState == nsIWebSocket::CLOSING) || - (aNewReadyState == nsIWebSocket::CLOSED), - "unexpected readyState"); + NS_ABORT_IF_FALSE((aNewReadyState == nsIWebSocket::OPEN) || + (aNewReadyState == nsIWebSocket::CLOSING) || + (aNewReadyState == nsIWebSocket::CLOSED), + "unexpected readyState"); if (aNewReadyState == nsIWebSocket::OPEN) { - NS_ASSERTION(mReadyState == nsIWebSocket::CONNECTING, - "unexpected readyState transition"); + NS_ABORT_IF_FALSE(mReadyState == nsIWebSocket::CONNECTING, + "unexpected readyState transition"); mReadyState = aNewReadyState; rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open")); if (NS_FAILED(rv)) { NS_WARNING("Failed to dispatch the open event"); } UpdateMustKeepAlive(); return; } if (aNewReadyState == nsIWebSocket::CLOSING) { - NS_ASSERTION((mReadyState == nsIWebSocket::CONNECTING) || - (mReadyState == nsIWebSocket::OPEN), - "unexpected readyState transition"); + NS_ABORT_IF_FALSE((mReadyState == nsIWebSocket::CONNECTING) || + (mReadyState == nsIWebSocket::OPEN), + "unexpected readyState transition"); mReadyState = aNewReadyState; return; } if (aNewReadyState == nsIWebSocket::CLOSED) { - NS_ASSERTION(mReadyState == nsIWebSocket::CLOSING, - "unexpected readyState transition"); mReadyState = aNewReadyState; - // The close event must be dispatched asynchronously. - nsCOMPtr<nsIRunnable> event = - new nsWSCloseEvent(this, mConnection->ClosedCleanly()); + if (mConnection) { + // The close event must be dispatched asynchronously. + nsCOMPtr<nsIRunnable> event = + new nsWSCloseEvent(this, mConnection->ClosedCleanly()); + mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount(); + mConnection = nsnull; // this is no longer necessary - mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount(); - mConnection = nsnull; // this is no longer necessary - - rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to dispatch the close event"); - mTriggeredCloseEvent = PR_TRUE; - UpdateMustKeepAlive(); + rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch the close event"); + mTriggeredCloseEvent = PR_TRUE; + UpdateMustKeepAlive(); + } } } } nsresult nsWebSocket::ParseURL(const nsString& aURL) { nsresult rv; @@ -3255,19 +991,19 @@ nsWebSocket::ParseURL(const nsString& aU mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port; } else if (scheme.LowerCaseEqualsLiteral("wss")) { mSecure = PR_TRUE; mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port; } else { return NS_ERROR_DOM_SYNTAX_ERR; } - mOrigin = origin; - ToLowerCase(mOrigin); - + ToLowerCase(origin); + CopyUTF8toUTF16(origin, mUTF16Origin); + mAsciiHost = host; ToLowerCase(mAsciiHost); mResource = filePath; if (!query.IsEmpty()) { mResource.AppendLiteral("?"); mResource.Append(query); } @@ -3276,19 +1012,17 @@ nsWebSocket::ParseURL(const nsString& aU for (i = 0; i < length; ++i) { if (mResource[i] < static_cast<PRUnichar>(0x0021) || mResource[i] > static_cast<PRUnichar>(0x007E)) { return NS_ERROR_DOM_SYNTAX_ERR; } } mOriginalURL = aURL; - mURI = parsedURL; - return NS_OK; } nsresult nsWebSocket::SetProtocol(const nsString& aProtocol) { if (aProtocol.IsEmpty()) { return NS_ERROR_DOM_SYNTAX_ERR; @@ -3312,16 +1046,17 @@ nsWebSocket::SetProtocol(const nsString& // 1. the object has registered event listeners that can be triggered // ("strong event listeners"); // 2. there are outgoing not sent messages. //----------------------------------------------------------------------------- void nsWebSocket::UpdateMustKeepAlive() { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (!mCheckMustKeepAlive) { return; } PRBool shouldKeepAlive = PR_FALSE; if (mListenerManager) { switch (mReadyState) @@ -3363,58 +1098,62 @@ nsWebSocket::UpdateMustKeepAlive() mKeepingAlive = PR_TRUE; static_cast<nsPIDOMEventTarget*>(this)->AddRef(); } } void nsWebSocket::DontKeepAliveAnyMore() { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (mKeepingAlive) { mKeepingAlive = PR_FALSE; static_cast<nsPIDOMEventTarget*>(this)->Release(); } mCheckMustKeepAlive = PR_FALSE; } NS_IMETHODIMP nsWebSocket::AddEventListener(const nsAString& aType, nsIDOMEventListener* aListener, PRBool aUseCapture) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv = nsDOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture); if (NS_SUCCEEDED(rv)) { UpdateMustKeepAlive(); } return rv; } NS_IMETHODIMP nsWebSocket::RemoveEventListener(const nsAString& aType, nsIDOMEventListener* aListener, PRBool aUseCapture) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv = nsDOMEventTargetHelper::RemoveEventListener(aType, aListener, aUseCapture); if (NS_SUCCEEDED(rv)) { UpdateMustKeepAlive(); } return rv; } NS_IMETHODIMP nsWebSocket::AddEventListener(const nsAString& aType, nsIDOMEventListener *aListener, PRBool aUseCapture, PRBool aWantsUntrusted, PRUint8 optional_argc) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv = nsDOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted, optional_argc); if (NS_SUCCEEDED(rv)) { UpdateMustKeepAlive(); } @@ -3428,16 +1167,23 @@ nsWebSocket::AddEventListener(const nsAS NS_IMETHODIMP nsWebSocket::GetUrl(nsAString& aURL) { aURL = mOriginalURL; return NS_OK; } NS_IMETHODIMP +nsWebSocket::GetProtocol(nsAString& aProtocol) +{ + CopyUTF8toUTF16(mProtocol, aProtocol); + return NS_OK; +} + +NS_IMETHODIMP nsWebSocket::GetReadyState(PRUint16 *aReadyState) { *aReadyState = mReadyState; return NS_OK; } NS_IMETHODIMP nsWebSocket::GetBufferedAmount(PRUint32 *aBufferedAmount) @@ -3467,16 +1213,17 @@ nsWebSocket::GetBufferedAmount(PRUint32 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(open, mOnOpenListener) NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(error, mOnErrorListener) NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(message, mOnMessageListener) NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(close, mOnCloseListener) NS_IMETHODIMP nsWebSocket::Send(const nsAString& aData, PRBool *aRet) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); *aRet = PR_FALSE; if (mReadyState == nsIWebSocket::CONNECTING) { return NS_ERROR_DOM_INVALID_STATE_ERR; } // We need to check if there isn't unpaired surrogates. PRUint32 i, length = aData.Length(); @@ -3503,16 +1250,17 @@ nsWebSocket::Send(const nsAString& aData *aRet = NS_SUCCEEDED(rv); return NS_OK; } NS_IMETHODIMP nsWebSocket::Close() { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); if (mReadyState == nsIWebSocket::CLOSING || mReadyState == nsIWebSocket::CLOSED) { return NS_OK; } if (mReadyState == nsIWebSocket::CONNECTING) { // FailConnection() can release the object, so we keep a reference // before calling it @@ -3533,16 +1281,17 @@ nsWebSocket::Close() */ NS_IMETHODIMP nsWebSocket::Init(nsIPrincipal* aPrincipal, nsIScriptContext* aScriptContext, nsPIDOMWindow* aOwnerWindow, const nsAString& aURL, const nsAString& aProtocol) { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); nsresult rv; NS_ENSURE_ARG(aPrincipal); if (!PrefEnabled()) { return NS_ERROR_DOM_SECURITY_ERR; } @@ -3588,119 +1337,98 @@ nsWebSocket::Init(nsIPrincipal* aPrincip // the constructor should throw a SYNTAX_ERROR only if it fails to parse the // url parameter, so we don't care about the EstablishConnection result. EstablishConnection(); return NS_OK; } -// static -void -nsWebSocket::ReleaseGlobals() +//----------------------------------------------------------------------------- +// nsWebSocketEstablishedConnection::nsIRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsWebSocketEstablishedConnection::GetName(nsACString &aName) { - if (nsWebSocketEstablishedConnection::sWSsConnecting) { - nsWebSocketEstablishedConnection::sWSsConnecting->Clear(); - delete nsWebSocketEstablishedConnection::sWSsConnecting; - nsWebSocketEstablishedConnection::sWSsConnecting = nsnull; - } - if (gWebSocketThread) { - gWebSocketThread->Shutdown(); - NS_RELEASE(gWebSocketThread); - } + if (!mOwner) + return NS_ERROR_UNEXPECTED; + + CopyUTF16toUTF8(mOwner->mOriginalURL, aName); + return NS_OK; } -//////////////////////////////////////////////////////////////////////////////// -// nsWSProtocolHandler -//////////////////////////////////////////////////////////////////////////////// - -NS_IMPL_ISUPPORTS2(nsWSProtocolHandler, - nsIProtocolHandler, nsIProxiedProtocolHandler) - NS_IMETHODIMP -nsWSProtocolHandler::GetScheme(nsACString& aScheme) +nsWebSocketEstablishedConnection::IsPending(PRBool *aValue) { - aScheme.AssignLiteral("ws"); + *aValue = !!(mOwner); return NS_OK; } NS_IMETHODIMP -nsWSProtocolHandler::GetDefaultPort(PRInt32 *aDefaultPort) +nsWebSocketEstablishedConnection::GetStatus(nsresult *aStatus) { - *aDefaultPort = DEFAULT_WS_SCHEME_PORT; + *aStatus = NS_OK; return NS_OK; } +// probably means window went away or stop button pressed NS_IMETHODIMP -nsWSProtocolHandler::GetProtocolFlags(PRUint32 *aProtocolFlags) +nsWebSocketEstablishedConnection::Cancel(nsresult aStatus) { - *aProtocolFlags = URI_STD | URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA | - ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_DANGEROUS_TO_LOAD; - return NS_OK; + NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); + + if (!mOwner) { + return NS_OK; + } + + ConsoleError(); + return Close(); } NS_IMETHODIMP -nsWSProtocolHandler::NewURI(const nsACString& aSpec, - const char *aCharset, - nsIURI *aBaseURI, - nsIURI **aURI) +nsWebSocketEstablishedConnection::Suspend() { - nsresult rv; - nsCOMPtr<nsIStandardURL> url(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv)); - NS_ENSURE_SUCCESS(rv, rv); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWebSocketEstablishedConnection::Resume() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} - PRInt32 defaultPort; - GetDefaultPort(&defaultPort); +NS_IMETHODIMP +nsWebSocketEstablishedConnection::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + *aLoadGroup = nsnull; + if (!mOwner) + return NS_OK; - rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, - defaultPort, aSpec, aCharset, aBaseURI); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDocument> doc = + nsContentUtils::GetDocumentFromScriptContext(mOwner->mScriptContext); - rv = CallQueryInterface(url, aURI); - NS_ENSURE_SUCCESS(rv, rv); + if (doc) { + *aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed + } return NS_OK; } NS_IMETHODIMP -nsWSProtocolHandler::NewChannel(nsIURI *aURI, - nsIChannel **aChannel) +nsWebSocketEstablishedConnection::SetLoadGroup(nsILoadGroup *aLoadGroup) { - return NS_ERROR_NOT_AVAILABLE; -} - -NS_IMETHODIMP -nsWSProtocolHandler::NewProxiedChannel(nsIURI *aURI, - nsIProxyInfo* aProxyInfo, - nsIChannel **aChannel) -{ - return NS_ERROR_NOT_AVAILABLE; + return NS_ERROR_UNEXPECTED; } NS_IMETHODIMP -nsWSProtocolHandler::AllowPort(PRInt32 aPort, - const char *aScheme, - PRBool *aAllowPort) +nsWebSocketEstablishedConnection::GetLoadFlags(nsLoadFlags *aLoadFlags) { - PRInt32 defaultPort; - GetDefaultPort(&defaultPort); - - *aAllowPort = (aPort == defaultPort); - return NS_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -// nsWSSProtocolHandler -//////////////////////////////////////////////////////////////////////////////// - -NS_IMETHODIMP -nsWSSProtocolHandler::GetScheme(nsACString& aScheme) -{ - aScheme.AssignLiteral("wss"); + *aLoadFlags = nsIRequest::LOAD_BACKGROUND; return NS_OK; } NS_IMETHODIMP -nsWSSProtocolHandler::GetDefaultPort(PRInt32 *aDefaultPort) +nsWebSocketEstablishedConnection::SetLoadFlags(nsLoadFlags aLoadFlags) { - *aDefaultPort = DEFAULT_WSS_SCHEME_PORT; + // we won't change the load flags at all. return NS_OK; }
--- a/content/base/src/nsWebSocket.h +++ b/content/base/src/nsWebSocket.h @@ -45,17 +45,16 @@ #include "nsCOMPtr.h" #include "nsString.h" #include "nsIJSNativeInitializer.h" #include "nsIPrincipal.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMEventListener.h" #include "nsDOMEventTargetWrapperCache.h" #include "nsAutoPtr.h" -#include "nsIProxiedProtocolHandler.h" #define DEFAULT_WS_SCHEME_PORT 80 #define DEFAULT_WSS_SCHEME_PORT 443 #define NS_WEBSOCKET_CID \ { /* 7ca25214-98dc-40a6-bc1f-41ddbe41f46c */ \ 0x7ca25214, 0x98dc, 0x40a6, \ {0xbc, 0x1f, 0x41, 0xdd, 0xbe, 0x41, 0xf4, 0x6c} } @@ -96,32 +95,30 @@ public: // nsIDOMNSEventTarget NS_IMETHOD AddEventListener(const nsAString& aType, nsIDOMEventListener *aListener, PRBool aUseCapture, PRBool aWantsUntrusted, PRUint8 optional_argc); - static void ReleaseGlobals(); - // Determine if preferences allow WebSocket static PRBool PrefEnabled(); const PRUint64 WindowID() const { return mWindowID; } const nsCString& GetScriptFile() const { return mScriptFile; } const PRUint32 GetScriptLine() const { return mScriptLine; } protected: nsresult ParseURL(const nsString& aURL); nsresult SetProtocol(const nsString& aProtocol); nsresult EstablishConnection(); nsresult CreateAndDispatchSimpleEvent(const nsString& aName); - nsresult CreateAndDispatchMessageEvent(nsCString *aData); + nsresult CreateAndDispatchMessageEvent(const nsACString& aData); nsresult CreateAndDispatchCloseEvent(PRBool aWasClean); // called from mConnection accordingly to the situation void SetReadyState(PRUint16 aNewReadyState); // if there are "strong event listeners" (see comment in nsWebSocket.cpp) or // outgoing not sent messages then this method keeps the object alive // when js doesn't have strong references to it. @@ -142,17 +139,18 @@ protected: PRPackedBool mKeepingAlive; PRPackedBool mCheckMustKeepAlive; PRPackedBool mTriggeredCloseEvent; nsCString mAsciiHost; // hostname PRUint32 mPort; nsCString mResource; // [filepath[?query]] - nsCString mOrigin; + nsString mUTF16Origin; + nsCOMPtr<nsIURI> mURI; nsCString mProtocol; PRUint16 mReadyState; nsCOMPtr<nsIPrincipal> mPrincipal; nsRefPtr<nsWebSocketEstablishedConnection> mConnection; @@ -170,49 +168,9 @@ protected: PRUint32 mScriptLine; PRUint64 mWindowID; private: nsWebSocket(const nsWebSocket& x); // prevent bad usage nsWebSocket& operator=(const nsWebSocket& x); }; -#define NS_WSPROTOCOLHANDLER_CONTRACTID \ - NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ws" - -#define NS_WSSPROTOCOLHANDLER_CONTRACTID \ - NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wss" - -#define NS_WSPROTOCOLHANDLER_CID \ -{ /* a4e6aa3b-b6db-4809-aa11-e292e074cbc4 */ \ - 0xa4e6aa3b, \ - 0xb6db, \ - 0x4809, \ - {0xaa, 0x11, 0xe2, 0x92, 0xe0, 0x74, 0xcb, 0xc4} \ -} - -#define NS_WSSPROTOCOLHANDLER_CID \ -{ /* c6531804-b5c8-4a53-80bf-e339b82d3161 */ \ - 0xc6531804, \ - 0xb5c8, \ - 0x4a53, \ - {0x80, 0xbf, 0xe3, 0x39, 0xb8, 0x2d, 0x31, 0x61} \ -} - -class nsWSProtocolHandler: public nsIProxiedProtocolHandler -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIPROTOCOLHANDLER - NS_DECL_NSIPROXIEDPROTOCOLHANDLER - - nsWSProtocolHandler() {}; -}; - -class nsWSSProtocolHandler: public nsWSProtocolHandler -{ -public: - NS_IMETHOD GetScheme(nsACString & aScheme); - NS_IMETHOD GetDefaultPort(PRInt32 *aDefaultPort); - nsWSSProtocolHandler() {}; -}; - #endif
--- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -304,18 +304,16 @@ static void Shutdown(); // Factory Constructor NS_GENERIC_FACTORY_CONSTRUCTOR(txMozillaXSLTProcessor) NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsXPathEvaluator, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(txNodeSetAdaptor, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMSerializer) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsXMLHttpRequest, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebSocket) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsWSProtocolHandler) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsWSSProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDOMFileReader, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormData) NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileDataProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMParser) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDOMStorageManager, nsDOMStorageManager::GetInstance) NS_GENERIC_FACTORY_CONSTRUCTOR(nsChannelPolicy) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(IndexedDatabaseManager, @@ -837,18 +835,16 @@ NS_DEFINE_NAMED_CID(TRANSFORMIIX_XSLT_PR NS_DEFINE_NAMED_CID(TRANSFORMIIX_XPATH_EVALUATOR_CID); NS_DEFINE_NAMED_CID(TRANSFORMIIX_NODESET_CID); NS_DEFINE_NAMED_CID(NS_XMLSERIALIZER_CID); NS_DEFINE_NAMED_CID(NS_FILEREADER_CID); NS_DEFINE_NAMED_CID(NS_FORMDATA_CID); NS_DEFINE_NAMED_CID(NS_FILEDATAPROTOCOLHANDLER_CID); NS_DEFINE_NAMED_CID(NS_XMLHTTPREQUEST_CID); NS_DEFINE_NAMED_CID(NS_WEBSOCKET_CID); -NS_DEFINE_NAMED_CID(NS_WSPROTOCOLHANDLER_CID); -NS_DEFINE_NAMED_CID(NS_WSSPROTOCOLHANDLER_CID); NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID); NS_DEFINE_NAMED_CID(NS_DOMSTORAGE_CID); NS_DEFINE_NAMED_CID(NS_DOMSTORAGE2_CID); NS_DEFINE_NAMED_CID(NS_DOMSTORAGEMANAGER_CID); NS_DEFINE_NAMED_CID(NS_DOMJSON_CID); NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID); NS_DEFINE_NAMED_CID(INDEXEDDB_MANAGER_CID ); #ifdef ENABLE_EDITOR_API_LOG @@ -986,18 +982,16 @@ static const mozilla::Module::CIDEntry k { &kTRANSFORMIIX_XPATH_EVALUATOR_CID, false, NULL, nsXPathEvaluatorConstructor }, { &kTRANSFORMIIX_NODESET_CID, false, NULL, txNodeSetAdaptorConstructor }, { &kNS_XMLSERIALIZER_CID, false, NULL, nsDOMSerializerConstructor }, { &kNS_FILEREADER_CID, false, NULL, nsDOMFileReaderConstructor }, { &kNS_FORMDATA_CID, false, NULL, nsFormDataConstructor }, { &kNS_FILEDATAPROTOCOLHANDLER_CID, false, NULL, nsFileDataProtocolHandlerConstructor }, { &kNS_XMLHTTPREQUEST_CID, false, NULL, nsXMLHttpRequestConstructor }, { &kNS_WEBSOCKET_CID, false, NULL, nsWebSocketConstructor }, - { &kNS_WSPROTOCOLHANDLER_CID, false, NULL, nsWSProtocolHandlerConstructor }, - { &kNS_WSSPROTOCOLHANDLER_CID, false, NULL, nsWSSProtocolHandlerConstructor }, { &kNS_DOMPARSER_CID, false, NULL, nsDOMParserConstructor }, { &kNS_DOMSTORAGE_CID, false, NULL, NS_NewDOMStorage }, { &kNS_DOMSTORAGE2_CID, false, NULL, NS_NewDOMStorage2 }, { &kNS_DOMSTORAGEMANAGER_CID, false, NULL, nsDOMStorageManagerConstructor }, { &kNS_DOMJSON_CID, false, NULL, NS_NewJSON }, { &kNS_TEXTEDITOR_CID, false, NULL, nsPlaintextEditorConstructor }, { &kINDEXEDDB_MANAGER_CID, false, NULL, IndexedDatabaseManagerConstructor }, #ifdef ENABLE_EDITOR_API_LOG @@ -1130,18 +1124,16 @@ static const mozilla::Module::ContractID { NS_XPATH_EVALUATOR_CONTRACTID, &kTRANSFORMIIX_XPATH_EVALUATOR_CID }, { TRANSFORMIIX_NODESET_CONTRACTID, &kTRANSFORMIIX_NODESET_CID }, { NS_XMLSERIALIZER_CONTRACTID, &kNS_XMLSERIALIZER_CID }, { NS_FILEREADER_CONTRACTID, &kNS_FILEREADER_CID }, { NS_FORMDATA_CONTRACTID, &kNS_FORMDATA_CID }, { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX FILEDATA_SCHEME, &kNS_FILEDATAPROTOCOLHANDLER_CID }, { NS_XMLHTTPREQUEST_CONTRACTID, &kNS_XMLHTTPREQUEST_CID }, { NS_WEBSOCKET_CONTRACTID, &kNS_WEBSOCKET_CID }, - { NS_WSPROTOCOLHANDLER_CONTRACTID, &kNS_WSPROTOCOLHANDLER_CID }, - { NS_WSSPROTOCOLHANDLER_CONTRACTID, &kNS_WSSPROTOCOLHANDLER_CID }, { NS_DOMPARSER_CONTRACTID, &kNS_DOMPARSER_CID }, { "@mozilla.org/dom/storage;1", &kNS_DOMSTORAGE_CID }, { "@mozilla.org/dom/storage;2", &kNS_DOMSTORAGE2_CID }, { "@mozilla.org/dom/storagemanager;1", &kNS_DOMSTORAGEMANAGER_CID }, { "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID }, { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID }, { INDEXEDDB_MANAGER_CONTRACTID, &kINDEXEDDB_MANAGER_CID }, #ifdef ENABLE_EDITOR_API_LOG
--- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -75,17 +75,16 @@ #include "txMozillaXSLTProcessor.h" #include "nsDOMStorage.h" #include "nsCellMap.h" #include "nsTextFrameTextRunCache.h" #include "nsCCUncollectableMarker.h" #include "nsTextFragment.h" #include "nsCSSRuleProcessor.h" #include "nsCrossSiteListenerProxy.h" -#include "nsWebSocket.h" #include "nsDOMThreadService.h" #include "nsHTMLDNSPrefetch.h" #include "nsHtml5Module.h" #include "nsCrossSiteListenerProxy.h" #include "nsFocusManager.h" #include "nsFrameList.h" #include "nsListControlFrame.h" #include "nsHTMLInputElement.h" @@ -364,18 +363,16 @@ nsLayoutStatics::Shutdown() nsDOMThreadService::Shutdown(); #ifdef MOZ_SYDNEYAUDIO nsAudioStream::ShutdownLibrary(); #endif nsCORSListenerProxy::Shutdown(); - nsWebSocket::ReleaseGlobals(); - nsIPresShell::ReleaseStatics(); nsHtml5Module::ReleaseStatics(); nsRegion::ShutdownStatic(); NS_ShutdownChainItemPool();
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -777,16 +777,43 @@ pref("network.http.connection-retry-time // per Section 4.7 "Low-Latency Data Service Class". pref("network.ftp.data.qos", 0); pref("network.ftp.control.qos", 0); // </http> // <ws>: WebSocket pref("network.websocket.enabled", true); + +// mobile might want to set this much smaller +pref("network.websocket.max-message-size", 16000000); + +// Should we automatically follow http 3xx redirects during handshake +pref("network.websocket.auto-follow-http-redirects", false); + +// the number of seconds to wait for websocket connection to be opened +pref("network.websocket.timeout.open", 20); + +// the number of seconds to wait for a clean close after sending the client +// close message +pref("network.websocket.timeout.close", 20); + +// the number of seconds of idle read activity to sustain before sending a +// ping probe. 0 to disable. +pref("network.websocket.timeout.ping.request", 0); + +// the deadline, expressed in seconds, for some read activity to occur after +// generating a ping. If no activity happens then an error and unclean close +// event is sent to the javascript websockets application +pref("network.websocket.timeout.ping.response", 10); + +// Defines whether or not to try and negotiate the stream-deflate compression +// extension with the websocket server +pref("network.websocket.extensions.stream-deflate", true); + // </ws> // If false, remote JAR files that are served with a content type other than // application/java-archive or application/x-jar will not be opened // by the jar channel. pref("network.jar.open-unsafe-types", false); // This preference controls whether or not internationalized domain names (IDN)
--- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -730,16 +730,36 @@ { /* {0xe7509b46-2eB2-410a-9d7c-c3ce73284d01} */ \ 0xe7509b46, \ 0x2eb2, \ 0x410a, \ {0x9d, 0x7c, 0xc3, 0xce, 0x73, 0x28, 0x4d, 0x01} \ } /****************************************************************************** + * netwerk/protocol/websocket/ classes + */ + +#define NS_WEBSOCKETPROTOCOLHANDLER_CID \ +{ /* {dc01db59-a513-4c90-824b-085cce06c0aa} */ \ + 0xdc01db59, \ + 0xa513, \ + 0x4c90, \ + {0x82, 0x4b, 0x08, 0x5c, 0xce, 0x06, 0xc0, 0xaa} \ +} + +#define NS_WEBSOCKETSSLPROTOCOLHANDLER_CID \ +{ /* {dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb} */ \ + 0xdc01dbbb, \ + 0xa5bb, \ + 0x4cbb, \ + {0x82, 0xbb, 0x08, 0x5c, 0xce, 0x06, 0xc0, 0xbb} \ +} + +/****************************************************************************** * netwerk/protocol/about/ classes */ #define NS_ABOUTPROTOCOLHANDLER_CLASSNAME \ "About Protocol Handler" #define NS_ABOUTPROTOCOLHANDLER_CID \ { /* 9e3b6c90-2f75-11d3-8cd0-0060b0fc14a3 */ \ 0x9e3b6c90, \
--- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -276,16 +276,26 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsViewSou #include "nsDataHandler.h" #endif #ifdef NECKO_PROTOCOL_wyciwyg #include "nsWyciwygProtocolHandler.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsWyciwygProtocolHandler) #endif +#ifdef NECKO_PROTOCOL_websocket +#include "nsWebSocketHandler.h" +namespace mozilla { +namespace net { +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebSocketHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebSocketSSLHandler) +} // namespace mozilla::net +} // namespace mozilla +#endif + /////////////////////////////////////////////////////////////////////////////// #include "nsURIChecker.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsURIChecker) /////////////////////////////////////////////////////////////////////////////// #include "nsURLParsers.h" @@ -619,16 +629,19 @@ static void nsNetShutdown() #endif // Release necko strings delete gNetStrings; gNetStrings = nsnull; // Release DNS service reference. nsDNSPrefetch::Shutdown(); + + // Release the Websocket Admission Manager + mozilla::net::nsWebSocketHandler::Shutdown(); } NS_DEFINE_NAMED_CID(NS_IOSERVICE_CID); NS_DEFINE_NAMED_CID(NS_STREAMTRANSPORTSERVICE_CID); NS_DEFINE_NAMED_CID(NS_SOCKETTRANSPORTSERVICE_CID); NS_DEFINE_NAMED_CID(NS_SERVERSOCKET_CID); NS_DEFINE_NAMED_CID(NS_SOCKETPROVIDERSERVICE_CID); NS_DEFINE_NAMED_CID(NS_DNSSERVICE_CID); @@ -735,16 +748,20 @@ NS_DEFINE_NAMED_CID(NS_DATAPROTOCOLHANDL NS_DEFINE_NAMED_CID(NS_DEVICEPROTOCOLHANDLER_CID); #endif #ifdef NECKO_PROTOCOL_viewsource NS_DEFINE_NAMED_CID(NS_VIEWSOURCEHANDLER_CID); #endif #ifdef NECKO_PROTOCOL_wyciwyg NS_DEFINE_NAMED_CID(NS_WYCIWYGPROTOCOLHANDLER_CID); #endif +#ifdef NECKO_PROTOCOL_websocket +NS_DEFINE_NAMED_CID(NS_WEBSOCKETPROTOCOLHANDLER_CID); +NS_DEFINE_NAMED_CID(NS_WEBSOCKETSSLPROTOCOLHANDLER_CID); +#endif #if defined(XP_WIN) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_WIDGET_COCOA) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_ENABLE_LIBCONIC) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_ENABLE_QTNETWORK) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); @@ -864,16 +881,22 @@ static const mozilla::Module::CIDEntry k { &kNS_DEVICEPROTOCOLHANDLER_CID, false, NULL, nsDeviceProtocolHandlerConstructor}, #endif #ifdef NECKO_PROTOCOL_viewsource { &kNS_VIEWSOURCEHANDLER_CID, false, NULL, nsViewSourceHandlerConstructor }, #endif #ifdef NECKO_PROTOCOL_wyciwyg { &kNS_WYCIWYGPROTOCOLHANDLER_CID, false, NULL, nsWyciwygProtocolHandlerConstructor }, #endif +#ifdef NECKO_PROTOCOL_websocket + { &kNS_WEBSOCKETPROTOCOLHANDLER_CID, false, NULL, + mozilla::net::nsWebSocketHandlerConstructor }, + { &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID, false, NULL, + mozilla::net::nsWebSocketSSLHandlerConstructor }, +#endif #if defined(XP_WIN) { &kNS_NETWORK_LINK_SERVICE_CID, false, NULL, nsNotifyAddrListenerConstructor }, #elif defined(MOZ_WIDGET_COCOA) { &kNS_NETWORK_LINK_SERVICE_CID, false, NULL, nsNetworkLinkServiceConstructor }, #elif defined(MOZ_ENABLE_LIBCONIC) { &kNS_NETWORK_LINK_SERVICE_CID, false, NULL, nsMaemoNetworkLinkServiceConstructor }, #elif defined(MOZ_ENABLE_QTNETWORK) { &kNS_NETWORK_LINK_SERVICE_CID, false, NULL, nsQtNetworkLinkServiceConstructor }, @@ -1000,16 +1023,20 @@ static const mozilla::Module::ContractID { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-device", &kNS_DEVICEPROTOCOLHANDLER_CID }, #endif #ifdef NECKO_PROTOCOL_viewsource { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "view-source", &kNS_VIEWSOURCEHANDLER_CID }, #endif #ifdef NECKO_PROTOCOL_wyciwyg { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wyciwyg", &kNS_WYCIWYGPROTOCOLHANDLER_CID }, #endif +#ifdef NECKO_PROTOCOL_websocket + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ws", &kNS_WEBSOCKETPROTOCOLHANDLER_CID }, + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wss", &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID }, +#endif #if defined(XP_WIN) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_WIDGET_COCOA) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_ENABLE_LIBCONIC) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_ENABLE_QTNETWORK) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
--- a/netwerk/necko-config.h.in +++ b/netwerk/necko-config.h.in @@ -46,11 +46,12 @@ #undef NECKO_PROTOCOL_about #undef NECKO_PROTOCOL_data #undef NECKO_PROTOCOL_device #undef NECKO_PROTOCOL_file #undef NECKO_PROTOCOL_ftp #undef NECKO_PROTOCOL_http #undef NECKO_PROTOCOL_res #undef NECKO_PROTOCOL_viewsource +#undef NECKO_PROTOCOL_websocket #undef NECKO_PROTOCOL_wyciwyg #endif
--- a/netwerk/protocol/Makefile.in +++ b/netwerk/protocol/Makefile.in @@ -46,14 +46,15 @@ PARALLEL_DIRS = \ about \ data \ device \ file \ ftp \ http \ res \ viewsource \ + websocket \ wyciwyg \ $(NULL) include $(topsrcdir)/config/rules.mk DEFINES += -DIMPL_NS_NET
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/websocket/Makefile.in @@ -0,0 +1,69 @@ +# ***** 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 +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Patrick McManus <mcmanus@ducksong.com> +# +# Alternatively, the contents of this file may be used under the terms of +# either of 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 ***** */ + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = necko +LIBRARY_NAME = nkwebsocket_s +LIBXUL_LIBRARY = 1 +XPIDL_MODULE = necko_websocket +GRE_MODULE = 1 +FORCE_STATIC_LIB = 1 + +XPIDLSRCS = \ + nsIWebSocketProtocol.idl \ + $(NULL) + +CPPSRCS = \ + nsWebSocketHandler.cpp + $(NULL) + +LOCAL_INCLUDES = \ + -I$(srcdir)/../../base/src \ + -I$(topsrcdir)/xpcom/ds \ + $(NULL) + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/ipc/chromium/chromium-config.mk +include $(topsrcdir)/config/rules.mk + +DEFINES += -DIMPL_NS_NET
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/websocket/nsIWebSocketProtocol.idl @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; 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 Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +interface nsIURI; +interface nsIInterfaceRequestor; +interface nsIRunnable; +interface nsILoadGroup; + +#include "nsISupports.idl" + +/** + * nsIWebSocketListener + */ +[scriptable, uuid(b0c27050-31e9-42e5-bc59-499d54b52f99)] +interface nsIWebSocketListener : nsISupports +{ + /** + * Called to signify the establishment of the message stream. + * Any listener that receives onStart will also receive OnStop. + * + * @param aContext user defined context + */ + void onStart(in nsISupports aContext); + + /** + * Called to signify the completion of the message stream. + * OnStop is the final notification the listener will receive and it + * completes the WebSocket connection. This event can be received in error + * cases even if nsIWebSocketProtocol::Close() has not been called. + * + * @param aContext user defined context + * @param aStatusCode reason for stopping (NS_OK if completed successfully) + */ + void onStop(in nsISupports aContext, + in nsresult aStatusCode); + + /** + * Called to deliver text message. + * + * @param aContext user defined context + * @param aMsg the message data + */ + void onMessageAvailable(in nsISupports aContext, + in AUTF8String aMsg); + + /** + * Called to deliver binary message. + * + * @param aContext user defined context + * @param aMsg the message data + */ + void onBinaryMessageAvailable(in nsISupports aContext, + in ACString aMsg); + + /** + * Called to acknowledge message sent via sendMsg() or sendBinaryMsg. + * + * @param aContext user defined context + * @param aSize number of bytes placed in OS send buffer + */ + void onAcknowledge(in nsISupports aContext, in PRUint32 aSize); + + /** + * Called to inform receipt of WebSocket Close message from server. + * In the case of errors onStop() can be called without ever + * receiving server close. + * + * No additional messages through onMessageAvailable(), + * onBinaryMessageAvailable() or onAcknowledge() will be delievered + * to the listener after onServerClose(), though outgoing messages can still + * be sent through the nsIWebSocketProtocol connection. + */ + void onServerClose(in nsISupports aContext); + +}; + +[scriptable, uuid(dc01db59-a513-4c90-824b-085cce06c0aa)] +interface nsIWebSocketProtocol : nsISupports +{ + /** + * The original URI used to construct the protocol connection. This is used + * in the case of a redirect or URI "resolution" (e.g. resolving a + * resource: URI to a file: URI) so that the original pre-redirect + * URI can still be obtained. This is never null. + */ + readonly attribute nsIURI originalURI; + + /** + * The readonly URI corresponding to the protocol connection after any + * redirections are completed. + */ + readonly attribute nsIURI URI; + + /** + * The notification callbacks for authorization, etc.. + */ + attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * Transport-level security information (if any) + */ + readonly attribute nsISupports securityInfo; + + /** + * The load group of the websockets code. + */ + attribute nsILoadGroup loadGroup; + + /** + * Sec-Websocket-Protocol value + */ + attribute ACString protocol; + + /** + * Asynchronously open the websocket connection. Received messages are fed + * to the socket listener as they arrive. The socket listener's methods + * are called on the thread that calls asyncOpen and are not called until + * after asyncOpen returns. If asyncOpen returns successfully, the + * protocol implementation promises to call at least onStart and onStop of + * the listener. + * + * NOTE: Implementations should throw NS_ERROR_ALREADY_OPENED if the + * websocket connection is reopened. + * + * @param aURI the uri of the websocket protocol - may be redirected + * @param aOrigin the uri of the originating resource + * @param aListener the nsIWebSocketListener implementation + * @param aContext an opaque parameter forwarded to aListener's methods + */ + void asyncOpen(in nsIURI aURI, + in ACString aOrigin, + in nsIWebSocketListener aListener, + in nsISupports aContext); + + /* + * Close the websocket connection for writing - no more calls to sendMsg + * or sendBinaryMsg should be made after calling this. The listener object + * may receive more messages if a server close has not yet been received. + */ + void close(); + + /** + * Use to send text message down the connection to WebSocket peer. + * + * @param aMsg the utf8 string to send + */ + void sendMsg(in AUTF8String aMsg); + + /** + * Use to send binary message down the connection to WebSocket peer. + * + * @param aMsg the data to send + */ + void sendBinaryMsg(in ACString aMsg); +};
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/websocket/nsWebSocketHandler.cpp @@ -0,0 +1,2549 @@ +/* -*- Mode: C++; tab-width: 4; 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 Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Wellington Fernando de Macedo <wfernandom2004@gmail.com> (original author) + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 "nsWebSocketHandler.h" + +#include "nsISocketTransportService.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsICryptoHash.h" +#include "nsIRunnable.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsICancelable.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsIStreamConverterService.h" +#include "nsIIOService2.h" +#include "nsIProtocolProxyService.h" + +#include "nsAutoPtr.h" +#include "nsStandardURL.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsNetError.h" +#include "nsStringStream.h" +#include "nsAlgorithm.h" +#include "nsProxyRelease.h" + +#include "plbase64.h" +#include "prmem.h" +#include "prnetdb.h" +#include "prbit.h" +#include "prlog.h" +#include "zlib.h" + +extern PRThread *gSocketThread; + +namespace mozilla { +namespace net { + +NS_IMPL_THREADSAFE_ISUPPORTS11(nsWebSocketHandler, + nsIWebSocketProtocol, + nsIHttpUpgradeListener, + nsIRequestObserver, + nsIStreamListener, + nsIProtocolHandler, + nsIInputStreamCallback, + nsIOutputStreamCallback, + nsITimerCallback, + nsIDNSListener, + nsIInterfaceRequestor, + nsIChannelEventSink) + +#if defined(PR_LOGGING) +static PRLogModuleInfo *webSocketLog = nsnull; +#endif +#define LOG(args) PR_LOG(webSocketLog, PR_LOG_DEBUG, args) + +// Use this fake ptr so the Fin message stays in sequence in the +// main transmit queue +#define kFinMessage (reinterpret_cast<nsCString *>(0x01)) + +// An implementation of draft-ietf-hybi-thewebsocketprotocol-07 +#define SEC_WEBSOCKET_VERSION "7" + +/* + * About using rand() without a srand() or initstate() + * + * rand() is commonly called throughout the codebase with the assumption + * that it has been seeded securely. That does indeed happen in + * the initialization of the nsUUIDGenerator sevice - getting secure + * seed information from PR_GetRandomNoise() and giving that to + * initstate(). We won't repeat that here "just in case" because + * it is easy to drain some systems of their true random sources. + */ + + +/* + * About SSL unsigned certificates + * + * wss will not work to a host using an unsigned certificate unless there + * is already an exception (i.e. it cannot popup a dialog asking for + * a security exception). This is similar to how an inlined img will + * fail without a dialog if fails for the same reason. This should not + * be a problem in practice as it is expected the websocket javascript + * is served from the same host as the websocket server (or of course, + * a valid cert could just be provided). + * + */ + +// some helper classes + +class CallOnMessageAvailable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + CallOnMessageAvailable(nsIWebSocketListener *aListener, + nsISupports *aContext, + nsCString &aData, + PRInt32 aLen) + : mListener(aListener), + mContext(aContext), + mData(aData), + mLen(aLen) {} + + NS_SCRIPTABLE NS_IMETHOD Run() + { + if (mLen < 0) + mListener->OnMessageAvailable(mContext, mData); + else + mListener->OnBinaryMessageAvailable(mContext, mData); + return NS_OK; + } + +private: + ~CallOnMessageAvailable() {} + + nsCOMPtr<nsIWebSocketListener> mListener; + nsCOMPtr<nsISupports> mContext; + nsCString mData; + PRInt32 mLen; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnMessageAvailable, nsIRunnable) + +class CallOnStop : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + CallOnStop(nsIWebSocketListener *aListener, + nsISupports *aContext, + nsresult aData) + : mListener(aListener), + mContext(aContext), + mData(aData) {} + + NS_SCRIPTABLE NS_IMETHOD Run() + { + mListener->OnStop(mContext, mData); + return NS_OK; + } + +private: + ~CallOnStop() {} + + nsCOMPtr<nsIWebSocketListener> mListener; + nsCOMPtr<nsISupports> mContext; + nsresult mData; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnStop, nsIRunnable) + +class CallOnServerClose : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + CallOnServerClose(nsIWebSocketListener *aListener, + nsISupports *aContext) + : mListener(aListener), + mContext(aContext) {} + + NS_SCRIPTABLE NS_IMETHOD Run() + { + mListener->OnServerClose(mContext); + return NS_OK; + } + +private: + ~CallOnServerClose() {} + + nsCOMPtr<nsIWebSocketListener> mListener; + nsCOMPtr<nsISupports> mContext; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable) + +class CallAcknowledge : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + CallAcknowledge(nsIWebSocketListener *aListener, + nsISupports *aContext, + PRUint32 aSize) + : mListener(aListener), + mContext(aContext), + mSize(aSize) {} + + NS_SCRIPTABLE NS_IMETHOD Run() + { + LOG(("WebSocketHandler::CallAcknowledge Size %u\n", mSize)); + mListener->OnAcknowledge(mContext, mSize); + return NS_OK; + } + +private: + ~CallAcknowledge() {} + + nsCOMPtr<nsIWebSocketListener> mListener; + nsCOMPtr<nsISupports> mContext; + PRUint32 mSize; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(CallAcknowledge, nsIRunnable) + +class nsPostMessage : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsPostMessage(nsWebSocketHandler *handler, + nsCString *aData, + PRInt32 aDataLen) + : mHandler(handler), + mData(aData), + mDataLen(aDataLen) {} + + NS_SCRIPTABLE NS_IMETHOD Run() + { + if (mData) + mHandler->SendMsgInternal(mData, mDataLen); + return NS_OK; + } + +private: + ~nsPostMessage() {} + + nsRefPtr<nsWebSocketHandler> mHandler; + nsCString *mData; + PRInt32 mDataLen; +}; +NS_IMPL_THREADSAFE_ISUPPORTS1(nsPostMessage, nsIRunnable) + + +// Section 5.1 requires that a client rate limit its connects to a single +// TCP session in the CONNECTING state (i.e. anything before the 101 upgrade +// complete response comes back and an open javascript event is created) + +class nsWSAdmissionManager +{ +public: + nsWSAdmissionManager() {} + + class nsOpenConn + { + public: + nsOpenConn(nsCString &addr, nsWebSocketHandler *handler) + : mAddress(addr), mHandler(handler) + {} + ~nsOpenConn() {} + + nsCString mAddress; + nsRefPtr<nsWebSocketHandler> mHandler; + }; + + ~nsWSAdmissionManager() + { + for (PRUint32 i = 0; i < mData.Length(); i++) + delete mData[i]; + } + + PRBool ConditionallyConnect(nsCString &aStr, nsWebSocketHandler *ws) + { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + // if aStr is not in mData then we return true, else false. + // in either case aStr is then added to mData - meaning + // there will be duplicates when this function has been + // called with the same parameter multiple times. + + // we could hash this, but the dataset is expected to be + // small + + PRBool found = (IndexOf(aStr) >= 0); + nsOpenConn *newdata = new nsOpenConn(aStr, ws); + mData.AppendElement(newdata); + + if (!found) + ws->BeginOpen(); + return !found; + } + + PRBool Complete(nsCString &aStr) + { + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + PRInt32 index = IndexOf(aStr); + NS_ABORT_IF_FALSE(index >= 0, "completed connection not in open list"); + + nsOpenConn *olddata = mData[index]; + mData.RemoveElementAt(index); + delete olddata; + + // are there more of the same address pending dispatch? + index = IndexOf(aStr); + if (index >= 0) { + (mData[index])->mHandler->BeginOpen(); + return PR_TRUE; + } + return PR_FALSE; + } + +private: + nsTArray<nsOpenConn *> mData; + + PRInt32 IndexOf(nsCString &aStr) + { + for (PRUint32 i = 0; i < mData.Length(); i++) + if (aStr == (mData[i])->mAddress) + return i; + return -1; + } +}; + +// similar to nsDeflateConverter except for the mandatory FLUSH calls +// required by websocket and the absence of the deflate termination +// block which is appropriate because it would create data bytes after +// sending the websockets CLOSE message. + +class nsWSCompression +{ +public: + nsWSCompression(nsIStreamListener *aListener, + nsISupports *aContext) + : mActive(PR_FALSE), + mContext(aContext), + mListener(aListener) + { + mZlib.zalloc = allocator; + mZlib.zfree = destructor; + mZlib.opaque = Z_NULL; + + // Initialize the compressor - these are all the normal zlib + // defaults except window size is set to -15 instead of +15. + // This is the zlib way of specifying raw RFC 1951 output instead + // of the zlib rfc 1950 format which has a 2 byte header and + // adler checksum as a trailer + + nsresult rv; + mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && aContext && aListener && + deflateInit2(&mZlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -15, 8, Z_DEFAULT_STRATEGY) == Z_OK) { + mActive = PR_TRUE; + } + } + + ~nsWSCompression() + { + if (mActive) + deflateEnd(&mZlib); + } + + PRBool Active() + { + return mActive; + } + + nsresult Deflate(PRUint8 *buf1, PRUint32 buf1Len, + PRUint8 *buf2, PRUint32 buf2Len) + { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + NS_ABORT_IF_FALSE(mActive, "not active"); + + mZlib.avail_out = kBufferLen; + mZlib.next_out = mBuffer; + mZlib.avail_in = buf1Len; + mZlib.next_in = buf1; + + nsresult rv; + + while (mZlib.avail_in > 0) { + deflate(&mZlib, (buf2Len > 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH); + rv = PushData(); + if (NS_FAILED(rv)) + return rv; + mZlib.avail_out = kBufferLen; + mZlib.next_out = mBuffer; + } + + mZlib.avail_in = buf2Len; + mZlib.next_in = buf2; + + while (mZlib.avail_in > 0) { + deflate(&mZlib, Z_SYNC_FLUSH); + rv = PushData(); + if (NS_FAILED(rv)) + return rv; + mZlib.avail_out = kBufferLen; + mZlib.next_out = mBuffer; + } + + return NS_OK; + } + +private: + + // use zlib data types + static void *allocator(void *opaque, uInt items, uInt size) + { + return moz_xmalloc(items * size); + } + + static void destructor(void *opaque, void *addr) + { + moz_free(addr); + } + + nsresult PushData() + { + PRUint32 bytesToWrite = kBufferLen - mZlib.avail_out; + if (bytesToWrite > 0) { + mStream->ShareData(reinterpret_cast<char *>(mBuffer), bytesToWrite); + nsresult rv; + rv = mListener->OnDataAvailable(nsnull, mContext, + mStream, 0, bytesToWrite); + if (NS_FAILED(rv)) + return rv; + } + return NS_OK; + } + + PRBool mActive; + z_stream mZlib; + nsCOMPtr<nsIStringInputStream> mStream; + + nsISupports *mContext; /* weak ref */ + nsIStreamListener *mListener; /* weak ref */ + + const static PRInt32 kBufferLen = 4096; + PRUint8 mBuffer[kBufferLen]; +}; + +static nsWSAdmissionManager *sWebSocketAdmissions = nsnull; + +// nsWebSocketHandler + +nsWebSocketHandler::nsWebSocketHandler() : + mEncrypted(PR_FALSE), + mCloseTimeout(20000), + mOpenTimeout(20000), + mPingTimeout(0), + mPingResponseTimeout(10000), + mRecvdHttpOnStartRequest(0), + mRecvdHttpUpgradeTransport(0), + mRequestedClose(0), + mClientClosed(0), + mServerClosed(0), + mStopped(0), + mCalledOnStop(0), + mPingOutstanding(0), + mAllowCompression(1), + mAutoFollowRedirects(0), + mReleaseOnTransmit(0), + mMaxMessageSize(16000000), + mStopOnClose(NS_OK), + mFragmentOpcode(0), + mFragmentAccumulator(0), + mBuffered(0), + mBufferSize(16384), + mCurrentOut(nsnull), + mCurrentOutSent(0), + mCompressor(nsnull), + mDynamicOutputSize(0), + mDynamicOutput(nsnull) +{ + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); +#if defined(PR_LOGGING) + if (!webSocketLog) + webSocketLog = PR_NewLogModule("nsWebSocket"); +#endif + + LOG(("WebSocketHandler::nsWebSocketHandler() %p\n", this)); + + if (!sWebSocketAdmissions) + sWebSocketAdmissions = new nsWSAdmissionManager(); + + mFramePtr = mBuffer = static_cast<PRUint8 *>(moz_xmalloc(mBufferSize)); +} + +nsWebSocketHandler::~nsWebSocketHandler() +{ + LOG(("WebSocketHandler::~nsWebSocketHandler() %p\n", this)); + + // this stop is a nop if the normal connect/close is followed + mStopped = 1; + StopSession(NS_ERROR_UNEXPECTED); + + moz_free(mBuffer); + moz_free(mDynamicOutput); + delete mCompressor; + delete mCurrentOut; + + while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront())) + delete mCurrentOut; + while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront())) + delete mCurrentOut; + while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront())) + delete mCurrentOut; + + nsCOMPtr<nsIThread> mainThread; + nsIURI *forgettable; + NS_GetMainThread(getter_AddRefs(mainThread)); + + if (mURI) { + mURI.forget(&forgettable); + NS_ProxyRelease(mainThread, forgettable, PR_FALSE); + } + + if (mOriginalURI) { + mOriginalURI.forget(&forgettable); + NS_ProxyRelease(mainThread, forgettable, PR_FALSE); + } + + if (mListener) { + nsIWebSocketListener *forgettableListener; + mListener.forget(&forgettableListener); + NS_ProxyRelease(mainThread, forgettableListener, PR_FALSE); + } + + if (mContext) { + nsISupports *forgettableContext; + mContext.forget(&forgettableContext); + NS_ProxyRelease(mainThread, forgettableContext, PR_FALSE); + } +} + +void +nsWebSocketHandler::Shutdown() +{ + delete sWebSocketAdmissions; + sWebSocketAdmissions = nsnull; +} + +nsresult +nsWebSocketHandler::BeginOpen() +{ + LOG(("WebSocketHandler::BeginOpen() %p\n", this)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + nsresult rv; + + if (mRedirectCallback) { + LOG(("WebSocketHandler::BeginOpen Resuming Redirect\n")); + rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK); + mRedirectCallback = nsnull; + return rv; + } + + nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv); + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler::BeginOpen cannot async open\n")); + AbortSession(NS_ERROR_CONNECTION_REFUSED); + return rv; + } + + rv = localChannel->AsyncOpen(this, mHttpChannel); + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler::BeginOpen cannot async open\n")); + AbortSession(NS_ERROR_CONNECTION_REFUSED); + return rv; + } + + mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_SUCCEEDED(rv)) + mOpenTimer->InitWithCallback(this, mOpenTimeout, + nsITimer::TYPE_ONE_SHOT); + + return rv; +} + +PRBool +nsWebSocketHandler::IsPersistentFramePtr() +{ + return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize); +} + +// extends the internal buffer by count and returns the total +// amount of data available for read +PRUint32 +nsWebSocketHandler::UpdateReadBuffer(PRUint8 *buffer, PRUint32 count) +{ + LOG(("WebSocketHandler::UpdateReadBuffer() %p [%p %u]\n", + this, buffer, count)); + + if (!mBuffered) + mFramePtr = mBuffer; + + NS_ABORT_IF_FALSE(IsPersistentFramePtr(), + "update read buffer bad mFramePtr"); + + if (mBuffered + count <= mBufferSize) { + // append to existing buffer + LOG(("WebSocketHandler:: update read buffer absorbed %u\n", count)); + } + else if (mBuffered + count - (mFramePtr - mBuffer) <= mBufferSize) { + // make room in existing buffer by shifting unused data to start + mBuffered -= (mFramePtr - mBuffer); + LOG(("WebSocketHandler:: update read buffer shifted %u\n", + mBuffered)); + ::memmove(mBuffer, mFramePtr, mBuffered); + mFramePtr = mBuffer; + } + else { + // existing buffer is not sufficient, extend it + mBufferSize += count + 8192; + LOG(("WebSocketHandler:: update read buffer extended to %u\n", + mBufferSize)); + PRUint8 *old = mBuffer; + mBuffer = (PRUint8 *)moz_xrealloc(mBuffer, mBufferSize); + mFramePtr = mBuffer + (mFramePtr - old); + } + + ::memcpy(mBuffer + mBuffered, buffer, count); + mBuffered += count; + + return mBuffered - (mFramePtr - mBuffer); +} + +nsresult +nsWebSocketHandler::ProcessInput(PRUint8 *buffer, PRUint32 count) +{ + LOG(("WebSocketHandler::ProcessInput %p [%d %d]\n", + this, count, mBuffered)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + + // reset the ping timer + if (mPingTimer) { + // The purpose of ping/pong is to actively probe the peer so that an + // unreachable peer is not mistaken for a period of idleness. This + // implementation accepts any application level read activity as a + // sign of life, it does not necessarily have to be a pong. + + mPingOutstanding = 0; + mPingTimer->SetDelay(mPingTimeout); + } + + PRUint32 avail; + + if (!mBuffered) { + // most of the time we can process right off the stack buffer + // without having to accumulate anything + + mFramePtr = buffer; + avail = count; + } + else { + avail = UpdateReadBuffer(buffer, count); + } + + PRUint8 *payload; + PRUint32 totalAvail = avail; + + while (avail >= 2) { + + PRInt64 payloadLength = mFramePtr[1] & 0x7F; + PRUint8 finBit = mFramePtr[0] & kFinalFragBit; + PRUint8 rsvBits = mFramePtr[0] & 0x70; + PRUint8 maskBit = mFramePtr[1] & kMaskBit; + PRUint8 opcode = mFramePtr[0] & 0x0F; + + PRUint32 framingLength = 2; + if (maskBit) + framingLength += 4; + + if (payloadLength < 126) { + if (avail < framingLength) + break; + } + else if (payloadLength == 126) { + // 16 bit length field + framingLength += 2; + if (avail < framingLength) + break; + + payloadLength = mFramePtr[2] << 8 | mFramePtr[3]; + } + else { + // 64 bit length + framingLength += 8; + if (avail < framingLength) + break; + // copy this in case it is unaligned + PRUint64 tempLen; + memcpy(&tempLen, mFramePtr + 2, 8); + payloadLength = PR_ntohll(tempLen); + } + + payload = mFramePtr + framingLength; + avail -= framingLength; + + LOG(("WebSocketHandler:: ProcessInput payload %lld avail %lu\n", + payloadLength, avail)); + + // we don't deal in > 31 bit websocket lengths.. and probably + // something considerably shorter (16MB by default) + if (payloadLength + mFragmentAccumulator > mMaxMessageSize) { + AbortSession(NS_ERROR_FILE_TOO_BIG); + return NS_ERROR_FILE_TOO_BIG; + } + + if (avail < payloadLength) + break; + + LOG(("WebSocketHandler::ProcessInput Frame accumulated - opcode %d\n", + opcode)); + + if (maskBit) { + // This is unexpected - the server does not generally send masked + // frames to the client, but it is allowed + LOG(("WebSocketHandler:: Client RECEIVING masked frame.")); + + PRUint32 mask; + memcpy(&mask, payload - 4, 4); + mask = PR_ntohl(mask); + ApplyMask(mask, payload, payloadLength); + } + + // control codes are required to have the fin bit set + if (!finBit && (opcode & kControlFrameMask)) { + LOG(("WebSocketHandler:: fragmented control frame code %d\n", + opcode)); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (rsvBits) { + LOG(("WebSocketHandler:: unexpected reserved bits %x\n", rsvBits)); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (!finBit || opcode == kContinuation) { + // This is part of a fragment response + + // only the first frame has a non zero op code + // make sure we don't see a first frame while some old + // fragments are open + if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) { + LOG(("WebSocketHeandler:: nested fragments\n")); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG(("WebSocketHandler:: Accumulating Fragment %lld\n", + payloadLength)); + + if (opcode == kContinuation) { + // for frag > 1 move the data body back on top of the headers + // so we have contiguous stream of data + NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload, + "payload offset from frameptr wrong"); + ::memmove (mFramePtr, payload, avail); + payload = mFramePtr; + if (mBuffered) + mBuffered -= framingLength; + } + else { + mFragmentOpcode = opcode; + } + + if (finBit) { + LOG(("WebSocketHandler:: Finalizing Fragment\n")); + payload -= mFragmentAccumulator; + payloadLength += mFragmentAccumulator; + avail += mFragmentAccumulator; + mFragmentAccumulator = 0; + opcode = mFragmentOpcode; + } else { + opcode = kContinuation; + mFragmentAccumulator += payloadLength; + } + } + else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) { + // this frame is not part of a fragment sequence but we + // have an open fragment.. it must be a control code or else + // we have a problem + LOG(("WebSocketHeandler:: illegal fragment sequence\n")); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (mServerClosed) { + LOG(("WebSocketHandler:: ignoring read frame code %d after close\n", + opcode)); + // nop + } + else if (mStopped) { + LOG(("WebSocketHandler:: " + "ignoring read frame code %d after completion\n", + opcode)); + } + else if (opcode == kText) { + LOG(("WebSocketHandler:: text frame received\n")); + if (mListener) { + nsCString utf8Data((const char *)payload, payloadLength); + nsCOMPtr<nsIRunnable> event = + new CallOnMessageAvailable(mListener, mContext, + utf8Data, -1); + NS_DispatchToMainThread(event); + } + } + else if (opcode & kControlFrameMask) { + // control frames + if (payloadLength > 125) { + LOG(("WebSocketHandler:: bad control frame code %d length %d\n", + opcode, payloadLength)); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (opcode == kClose) { + LOG(("WebSocketHandler:: close received\n")); + mServerClosed = 1; + + if (payloadLength >= 2) { + PRUint16 code; + memcpy(&code, payload, 2); + code = PR_ntohs(code); + LOG(("WebSocketHandler:: close recvd code %u\n", code)); + PRUint16 msglen = payloadLength - 2; + if (msglen > 0) { + nsCString utf8Data((const char *)payload + 2, msglen); + LOG(("WebSocketHandler:: close msg %s\n", + utf8Data.get())); + } + } + + if (mCloseTimer) { + mCloseTimer->Cancel(); + mCloseTimer = nsnull; + } + nsCOMPtr<nsIRunnable> event = + new CallOnServerClose(mListener, mContext); + NS_DispatchToMainThread(event); + + if (mClientClosed) + ReleaseSession(); + } + else if (opcode == kPing) { + LOG(("WebSocketHandler:: ping received\n")); + GeneratePong(payload, payloadLength); + } + else { + // opcode kPong + // The mere act of receiving the packet is all we need to + // do for the pong to trigger the activity timers + LOG(("WebSocketHandler:: pong received\n")); + } + + if (mFragmentAccumulator) { + // we need to remove the control frame from the stream + // so we have a contiguous data buffer of reassembled fragments + LOG(("WebSocketHandler:: Removing Control From Read buffer\n")); + NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload, + "payload offset from frameptr wrong"); + ::memmove (mFramePtr, payload + payloadLength, + avail - payloadLength); + payload = mFramePtr; + avail -= payloadLength; + payloadLength = 0; + if (mBuffered) + mBuffered -= framingLength + payloadLength; + } + } + else if (opcode == kBinary) { + LOG(("WebSocketHandler:: binary frame received\n")); + if (mListener) { + nsCString utf8Data((const char *)payload, payloadLength); + nsCOMPtr<nsIRunnable> event = + new CallOnMessageAvailable(mListener, mContext, + utf8Data, payloadLength); + NS_DispatchToMainThread(event); + } + } + else if (opcode != kContinuation) { + /* unknown opcode */ + LOG(("WebSocketHandler:: unknown op code %d\n", opcode)); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + mFramePtr = payload + payloadLength; + avail -= payloadLength; + totalAvail = avail; + } + + // Adjust the stateful buffer. If we were operating off the stack and + // now have a partial message then transition to the buffer, or if + // we were working off the buffer but no longer have any active state + // then transition to the stack + if (!IsPersistentFramePtr()) { + mBuffered = 0; + + if (mFragmentAccumulator) { + LOG(("WebSocketHandler:: Setup Buffer due to fragment")); + + UpdateReadBuffer(mFramePtr - mFragmentAccumulator, + totalAvail + mFragmentAccumulator); + + // UpdateReadBuffer will reset the frameptr to the beginning + // of new saved state, so we need to skip past processed framgents + mFramePtr += mFragmentAccumulator; + } + else if (totalAvail) { + LOG(("WebSocketHandler:: Setup Buffer due to partial frame")); + UpdateReadBuffer(mFramePtr, totalAvail); + } + } + else if (!mFragmentAccumulator && !totalAvail) { + // If we were working off a saved buffer state and there is no + // partial frame or fragment in process, then revert to stack + // behavior + LOG(("WebSocketHandler:: Internal buffering not needed anymore")); + mBuffered = 0; + } + return NS_OK; +} + +void +nsWebSocketHandler::ApplyMask(PRUint32 mask, PRUint8 *data, PRUint64 len) +{ + // Optimally we want to apply the mask 32 bits at a time, + // but the buffer might not be alligned. So we first deal with + // 0 to 3 bytes of preamble individually + + while (len && ((unsigned long) data & 3)) { + *data ^= mask >> 24; + mask = PR_ROTATE_LEFT32(mask, 8); + data++; + len--; + } + + // perform mask on full words of data + + PRUint32 *iData = (PRUint32 *) data; + PRUint32 *end = iData + (len / 4); + mask = PR_htonl(mask); + for (; iData < end; iData++) + *iData ^= mask; + mask = PR_ntohl(mask); + data = (PRUint8 *)iData; + len = len % 4; + + // There maybe up to 3 trailing bytes that need to be dealt with + // individually + + while (len) { + *data ^= mask >> 24; + mask = PR_ROTATE_LEFT32(mask, 8); + data++; + len--; + } +} + +void +nsWebSocketHandler::GeneratePing() +{ + LOG(("WebSocketHandler::GeneratePing() %p\n", this)); + + nsCString *buf = new nsCString(); + buf->Assign("PING"); + mOutgoingPingMessages.Push(new OutboundMessage(buf)); + OnOutputStreamReady(mSocketOut); +} + +void +nsWebSocketHandler::GeneratePong(PRUint8 *payload, PRUint32 len) +{ + LOG(("WebSocketHandler::GeneratePong() %p [%p %u]\n", this, payload, len)); + + nsCString *buf = new nsCString(); + buf->SetLength(len); + if (buf->Length() < len) { + LOG(("WebSocketHandler::GeneratePong Allocation Failure\n")); + delete buf; + return; + } + + memcpy(buf->BeginWriting(), payload, len); + mOutgoingPongMessages.Push(new OutboundMessage(buf)); + OnOutputStreamReady(mSocketOut); +} + +void +nsWebSocketHandler::SendMsgInternal(nsCString *aMsg, + PRInt32 aDataLen) +{ + LOG(("WebSocketHandler::SendMsgInternal %p [%p len=%d]\n", + this, aMsg, aDataLen)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + if (aMsg == kFinMessage) + mOutgoingMessages.Push(new OutboundMessage()); + else if (aDataLen < 0) + mOutgoingMessages.Push(new OutboundMessage(aMsg)); + else + mOutgoingMessages.Push(new OutboundMessage(aMsg, aDataLen)); + OnOutputStreamReady(mSocketOut); +} + +void +nsWebSocketHandler::PrimeNewOutgoingMessage() +{ + LOG(("WebSocketHandler::PrimeNewOutgoingMessage() %p\n", this)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress"); + + PRBool isPong = PR_FALSE; + PRBool isPing = PR_FALSE; + + mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront(); + if (mCurrentOut) { + isPong = PR_TRUE; + } else { + mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront(); + if (mCurrentOut) + isPing = PR_TRUE; + else + mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront(); + } + + if (!mCurrentOut) + return; + mCurrentOutSent = 0; + mHdrOut = mOutHeader; + + PRUint8 *payload = nsnull; + if (mCurrentOut->IsControl() && !isPing && !isPong) { + // This is a demand to create a close message + if (mClientClosed) { + PrimeNewOutgoingMessage(); + return; + } + + LOG(("WebSocketHandler:: PrimeNewOutgoingMessage() " + "found close request\n")); + mClientClosed = 1; + mOutHeader[0] = kFinalFragBit | kClose; + mOutHeader[1] = 0x02; // payload len = 2 + mOutHeader[1] |= kMaskBit; + + // payload is offset 6 including 4 for the mask + payload = mOutHeader + 6; + + // The close reason code sits in the first 2 bytes of payload + if (NS_SUCCEEDED(mStopOnClose)) + *((PRUint16 *)payload) = PR_htons(kCloseNormal); + else if (mStopOnClose == NS_ERROR_FILE_TOO_BIG) + *((PRUint16 *)payload) = PR_htons(kCloseTooLarge); + else + *((PRUint16 *)payload) = PR_htons(kCloseProtocolError); + + mHdrOutToSend = 8; + if (mServerClosed) { + /* bidi close complete */ + mReleaseOnTransmit = 1; + } + else if (NS_FAILED(mStopOnClose)) { + /* result of abort session - give up */ + StopSession(mStopOnClose); + } + else { + /* wait for reciprocal close from server */ + nsresult rv; + mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_SUCCEEDED(rv)) { + mCloseTimer->InitWithCallback(this, mCloseTimeout, + nsITimer::TYPE_ONE_SHOT); + } + else { + StopSession(rv); + } + } + } + else { + if (isPong) { + LOG(("WebSocketHandler:: PrimeNewOutgoingMessage() " + "found pong request\n")); + mOutHeader[0] = kFinalFragBit | kPong; + } + else if (isPing) { + LOG(("WebSocketHandler:: PrimeNewOutgoingMessage() " + "found ping request\n")); + mOutHeader[0] = kFinalFragBit | kPing; + } + else if (mCurrentOut->BinaryLen() < 0) { + LOG(("WebSocketHandler:: PrimeNewOutgoing Message() " + "found queued text message len %d\n", + mCurrentOut->Length())); + mOutHeader[0] = kFinalFragBit | kText; + } + else + { + LOG(("WebSocketHandler:: PrimeNewOutgoing Message() " + "found queued binary message len %d\n", + mCurrentOut->Length())); + mOutHeader[0] = kFinalFragBit | kBinary; + } + + if (mCurrentOut->Length() < 126) { + mOutHeader[1] = mCurrentOut->Length() | kMaskBit; + mHdrOutToSend = 6; + } + else if (mCurrentOut->Length() < 0xffff) { + mOutHeader[1] = 126 | kMaskBit; + ((PRUint16 *)mOutHeader)[1] = + PR_htons(mCurrentOut->Length()); + mHdrOutToSend = 8; + } + else { + mOutHeader[1] = 127 | kMaskBit; + PRUint64 tempLen = mCurrentOut->Length(); + tempLen = PR_htonll(tempLen); + memcpy(mOutHeader + 2, &tempLen, 8); + mHdrOutToSend = 14; + } + payload = mOutHeader + mHdrOutToSend; + } + + NS_ABORT_IF_FALSE(payload, "payload offset not found"); + + // Perfom the sending mask. never use a zero mask + PRUint32 mask; + while (!(mask = (PRUint32) rand())); + *(((PRUint32 *)payload) - 1) = PR_htonl(mask); + + LOG(("WebSocketHandler:: PrimeNewOutgoingMessage() " + "using mask %08x\n", mask)); + + // We don't mask the framing, but occasionally we stick a little payload + // data in the buffer used for the framing. Close frames are the + // current example. This data needs to be + // masked, but it is never more than a handful of bytes and might rotate + // the mask, so we can just do it locally. For real data frames we + // ship the bulk of the payload off to ApplyMask() + + while (payload < (mOutHeader + mHdrOutToSend)) { + *payload ^= mask >> 24; + mask = PR_ROTATE_LEFT32(mask, 8); + payload++; + } + + // Mask the real message payloads + + ApplyMask(mask, + mCurrentOut->BeginWriting(), + mCurrentOut->Length()); + + // for small frames, copy it all together for a contiguous write + if (mCurrentOut->Length() <= kCopyBreak) { + memcpy(mOutHeader + mHdrOutToSend, + mCurrentOut->BeginWriting(), + mCurrentOut->Length()); + mHdrOutToSend += mCurrentOut->Length(); + mCurrentOutSent = mCurrentOut->Length(); + } + + if (mCompressor) { + // assume a 1/3 reduction in size for sizing the buffer + // the buffer is used multiple times if necessary + PRUint32 currentHeaderSize = mHdrOutToSend; + mHdrOutToSend = 0; + + EnsureHdrOut(32 + + (currentHeaderSize + + mCurrentOut->Length() - mCurrentOutSent) / 2 * 3); + mCompressor-> + Deflate(mOutHeader, currentHeaderSize, + mCurrentOut->BeginReading() + mCurrentOutSent, + mCurrentOut->Length() - mCurrentOutSent); + + // all of the compressed data now resides in {mHdrOut, mHdrOutToSend} + // so do not send the body again + mCurrentOutSent = mCurrentOut->Length(); + } + + // now the transmitting begins - mHdrOutToSend bytes from mOutHeader + // and mCurrentOut->Length() bytes from mCurrentOut. The latter may + // be coaleseced into the former for small messages or as the result + // of the compression process, +} + +void +nsWebSocketHandler::EnsureHdrOut(PRUint32 size) +{ + LOG(("WebSocketHandler::EnsureHdrOut() %p [%d]\n", this, size)); + + if (mDynamicOutputSize < size) { + mDynamicOutputSize = size; + mDynamicOutput = + (PRUint8 *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize); + } + + mHdrOut = mDynamicOutput; +} + +void +nsWebSocketHandler::StopSession(nsresult reason) +{ + LOG(("WebSocketHandler::StopSession() %p [%x]\n", this, reason)); + + // normally this should be called on socket thread, but it is ok to call it + // from OnStartRequest before the socket thread machine has gotten underway + + NS_ABORT_IF_FALSE(mStopped, "stopsession() has not transitioned " + "through abort or close"); + + if (mCloseTimer) { + mCloseTimer->Cancel(); + mCloseTimer = nsnull; + } + + if (mOpenTimer) { + mOpenTimer->Cancel(); + mOpenTimer = nsnull; + } + + if (mPingTimer) { + mPingTimer->Cancel(); + mPingTimer = nsnull; + } + + if (mSocketIn) { + // drain, within reason, this socket. if we leave any data + // unconsumed (including the tcp fin) a RST will be generated + // The right thing to do here is shutdown(SHUT_WR) and then wait + // a little while to see if any data comes in.. but there is no + // reason to delay things for that when the websocket handshake + // is supposed to guarantee a quiet connection except for that fin. + + char buffer[512]; + PRUint32 count = 0; + PRUint32 total = 0; + nsresult rv; + do { + total += count; + rv = mSocketIn->Read(buffer, 512, &count); + } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000); + + mSocketIn->AsyncWait(nsnull, 0, 0, nsnull); + mSocketIn = nsnull; + } + + if (mSocketOut) { + mSocketOut->AsyncWait(nsnull, 0, 0, nsnull); + mSocketOut = nsnull; + } + + if (mTransport) { + mTransport->SetSecurityCallbacks(nsnull); + mTransport->SetEventSink(nsnull, nsnull); + mTransport->Close(NS_BASE_STREAM_CLOSED); + mTransport = nsnull; + } + + if (mDNSRequest) { + mDNSRequest->Cancel(NS_ERROR_UNEXPECTED); + mDNSRequest = nsnull; + } + + mInflateReader = nsnull; + mInflateStream = nsnull; + + delete mCompressor; + mCompressor = nsnull; + + if (!mCalledOnStop) { + mCalledOnStop = 1; + nsCOMPtr<nsIRunnable> event = + new CallOnStop(mListener, mContext, reason); + NS_DispatchToMainThread(event); + } + + return; +} + +void +nsWebSocketHandler::AbortSession(nsresult reason) +{ + LOG(("WebSocketHandler::AbortSession() %p [reason %x] stopped = %d\n", + this, reason, mStopped)); + + // normally this should be called on socket thread, but it is ok to call it + // from OnStartRequest before the socket thread machine has gotten underway + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread || + !(mRecvdHttpOnStartRequest && + mRecvdHttpUpgradeTransport), "wrong thread"); + + if (mStopped) + return; + mStopped = 1; + + if (mTransport && reason != NS_BASE_STREAM_CLOSED && + !mRequestedClose && !mClientClosed && !mServerClosed) { + mRequestedClose = 1; + nsCOMPtr<nsIRunnable> event = + new nsPostMessage(this, kFinMessage, -1); + mSocketThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); + mStopOnClose = reason; + } + else { + StopSession(reason); + } +} + +// ReleaseSession is called on orderly shutdown +void +nsWebSocketHandler::ReleaseSession() +{ + LOG(("WebSocketHandler::ReleaseSession() %p stopped = %d\n", + this, mStopped)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + + if (mStopped) + return; + mStopped = 1; + StopSession(NS_OK); +} + +nsresult +nsWebSocketHandler::HandleExtensions() +{ + LOG(("WebSocketHandler::HandleExtensions() %p\n", this)); + + nsresult rv; + nsCAutoString extensions; + + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + rv = mHttpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions); + if (NS_SUCCEEDED(rv)) { + if (!extensions.IsEmpty()) { + if (!extensions.Equals(NS_LITERAL_CSTRING("deflate-stream"))) { + LOG(("WebSocketHandler::OnStartRequest " + "HTTP Sec-WebSocket-Exensions negotiated " + "unknown value %s\n", + extensions.get())); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (!mAllowCompression) { + LOG(("WebSocketHandler::HandleExtensions " + "Recvd Compression Extension that wasn't offered\n")); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + nsCOMPtr<nsIStreamConverterService> serv = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler:: Cannot find compression service\n")); + AbortSession(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + + rv = serv->AsyncConvertData("deflate", + "uncompressed", + this, + nsnull, + getter_AddRefs(mInflateReader)); + + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler:: Cannot find inflate listener\n")); + AbortSession(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + + mInflateStream = + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler:: Cannot find inflate stream\n")); + AbortSession(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + + mCompressor = new nsWSCompression(this, mSocketOut); + if (!mCompressor->Active()) { + LOG(("WebSocketHandler:: Cannot init deflate object\n")); + delete mCompressor; + mCompressor = nsnull; + AbortSession(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + } + } + + return NS_OK; +} + +nsresult +nsWebSocketHandler::SetupRequest() +{ + LOG(("WebSocketHandler::SetupRequest() %p\n", this)); + + nsresult rv; + + if (mLoadGroup) { + rv = mHttpChannel->SetLoadGroup(mLoadGroup); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND | + nsIRequest::INHIBIT_CACHING | + nsIRequest::LOAD_BYPASS_CACHE); + NS_ENSURE_SUCCESS(rv, rv); + + // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket + // in lower case, so we will go with that. It is technically case + // insensitive. + rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this); + NS_ENSURE_SUCCESS(rv, rv); + + mHttpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Version"), + NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), PR_FALSE); + + if (!mOrigin.IsEmpty()) + mHttpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Origin"), + mOrigin, PR_FALSE); + + if (!mProtocol.IsEmpty()) + mHttpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), + mProtocol, PR_TRUE); + + if (mAllowCompression) + mHttpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), + NS_LITERAL_CSTRING("deflate-stream"), PR_FALSE); + + PRUint32 secKey[4]; + nsCAutoString secKeyString; + secKey[0] = (PRUint32) rand(); + secKey[1] = (PRUint32) rand(); + secKey[2] = (PRUint32) rand(); + secKey[3] = (PRUint32) rand(); + char* b64 = PL_Base64Encode((const char *)secKey, 16, nsnull); + if (!b64) return NS_ERROR_OUT_OF_MEMORY; + secKeyString.Assign(b64); + PR_Free(b64); + mHttpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Key"), secKeyString, PR_FALSE); + LOG(("WebSocketHandler::AsyncOpen() client key %s\n", secKeyString.get())); + + // prepare the value we expect to see in + // the sec-websocket-accept response header + secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + nsCOMPtr<nsICryptoHash> hasher = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = hasher->Init(nsICryptoHash::SHA1); + NS_ENSURE_SUCCESS(rv, rv); + rv = hasher->Update((const PRUint8 *) secKeyString.BeginWriting(), + secKeyString.Length()); + NS_ENSURE_SUCCESS(rv, rv); + rv = hasher->Finish(PR_TRUE, mHashedSecret); + NS_ENSURE_SUCCESS(rv, rv); + LOG(("WebSocketHandler::AsyncOpen() expected server key %s\n", + mHashedSecret.get())); + + return NS_OK; +} + +nsresult +nsWebSocketHandler::ApplyForAdmission() +{ + LOG(("WebSocketHandler::ApplyForAdmission() %p\n", this)); + + // Websockets has a policy of 1 session at a time being allowed in the + // CONNECTING state per server IP address (not hostname) + + nsresult rv; + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString hostName; + rv = mURI->GetHost(hostName); + NS_ENSURE_SUCCESS(rv, rv); + mAddress = hostName; + + // expect the callback in ::OnLookupComplete + LOG(("WebSocketHandler::AsyncOpen() checking for concurrent open\n")); + nsCOMPtr<nsIThread> mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + dns->AsyncResolve(hostName, + 0, + this, + mainThread, + getter_AddRefs(mDNSRequest)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// nsIDNSListener + +NS_IMETHODIMP +nsWebSocketHandler::OnLookupComplete(nsICancelable *aRequest, + nsIDNSRecord *aRecord, + nsresult aStatus) +{ + LOG(("WebSocketHandler::OnLookupComplete() %p [%p %p %x]\n", + this, aRequest, aRecord, aStatus)); + + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + NS_ABORT_IF_FALSE(aRequest == mDNSRequest, "wrong dns request"); + + mDNSRequest = nsnull; + + // These failures are not fatal - we just use the hostname as the key + if (NS_FAILED(aStatus)) { + LOG(("WebSocketHandler::OnLookupComplete No DNS Response\n")); + } + else { + nsresult rv = aRecord->GetNextAddrAsString(mAddress); + if (NS_FAILED(rv)) + LOG(("WebSocketHandler::OnLookupComplete Failed GetNextAddr\n")); + } + + if (sWebSocketAdmissions->ConditionallyConnect(mAddress, this)) { + LOG(("WebSocketHandler::OnLookupComplete Proceeding with Open\n")); + } + else { + LOG(("WebSocketHandler::OnLookupComplete Deferring Open\n")); + } + + return NS_OK; +} + +// nsIInterfaceRequestor + +NS_IMETHODIMP +nsWebSocketHandler::GetInterface(const nsIID & iid, void **result NS_OUTPARAM) +{ + LOG(("WebSocketHandler::GetInterface() %p\n", this)); + + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) + return QueryInterface(iid, result); + + if (mCallbacks) + return mCallbacks->GetInterface(iid, result); + + return NS_ERROR_FAILURE; +} + +// nsIChannelEventSink + +NS_IMETHODIMP +nsWebSocketHandler::AsyncOnChannelRedirect( + nsIChannel *oldChannel, + nsIChannel *newChannel, + PRUint32 flags, + nsIAsyncVerifyRedirectCallback *callback) +{ + LOG(("WebSocketHandler::AsyncOnChannelRedirect() %p\n", this)); + nsresult rv; + + nsCOMPtr<nsIURI> newuri; + rv = newChannel->GetURI(getter_AddRefs(newuri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mAutoFollowRedirects) { + nsCAutoString spec; + if (NS_SUCCEEDED(newuri->GetSpec(spec))) + LOG(("nsWebSocketHandler Redirect to %s denied by configuration\n", + spec.get())); + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return NS_OK; + } + + PRBool isHttps = PR_FALSE; + rv = newuri->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, rv); + + if (mEncrypted && !isHttps) { + nsCAutoString spec; + if (NS_SUCCEEDED(newuri->GetSpec(spec))) + LOG(("nsWebSocketHandler Redirect to %s violates encryption rule\n", + spec.get())); + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return NS_OK; + } + + nsCOMPtr<nsIHttpChannel> newHttpChannel = + do_QueryInterface(newChannel, &rv); + + if (NS_FAILED(rv)) { + LOG(("nsWebSocketHandler Redirect could not QI to HTTP\n")); + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return NS_OK; + } + + nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel = + do_QueryInterface(newChannel, &rv); + + if (NS_FAILED(rv)) { + LOG(("nsWebSocketHandler Redirect could not QI to HTTP Upgrade\n")); + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return NS_OK; + } + + // The redirect is OK + + newChannel->SetNotificationCallbacks(this); + mURI = newuri; + mHttpChannel = newHttpChannel; + mChannel = newUpgradeChannel; + SetupRequest(); + + // We cannot just tell the callback OK right now due to the 1 connect at + // a time policy. First we need to complete the old location and then + // start the lookup chain for the new location - once that is complete + // and we have been admitted, OnRedirectVerifyCallback(NS_OK) will be called + // out of BeginOpen() + + sWebSocketAdmissions->Complete(mAddress); + mAddress.Truncate(); + mRedirectCallback = callback; + + rv = ApplyForAdmission(); + if (NS_FAILED(rv)) { + LOG(("nsWebSocketHandler Redirect failed due to DNS failure\n")); + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + mRedirectCallback = nsnull; + } + + return NS_OK; +} + +// nsITimerCallback + +NS_IMETHODIMP +nsWebSocketHandler::Notify(nsITimer *timer) +{ + LOG(("WebSocketHandler::Notify() %p [%p]\n", this, timer)); + + if (timer == mCloseTimer) { + NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + + mCloseTimer = nsnull; + if (mStopped || mServerClosed) /* no longer relevant */ + return NS_OK; + + LOG(("nsWebSocketHandler:: Expecting Server Close - Timed Out\n")); + AbortSession(NS_ERROR_NET_TIMEOUT); + } + else if (timer == mOpenTimer) { + NS_ABORT_IF_FALSE(!mRecvdHttpOnStartRequest, + "Open Timer after open complete"); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + mOpenTimer = nsnull; + LOG(("nsWebSocketHandler:: Connection Timed Out\n")); + if (mStopped || mServerClosed) /* no longer relevant */ + return NS_OK; + + AbortSession(NS_ERROR_NET_TIMEOUT); + } + else if (timer == mPingTimer) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + + if (mClientClosed || mServerClosed || mRequestedClose) { + // no point in worrying about ping now + mPingTimer = nsnull; + return NS_OK; + } + + if (!mPingOutstanding) { + LOG(("nsWebSockethandler:: Generating Ping\n")); + mPingOutstanding = 1; + GeneratePing(); + mPingTimer->InitWithCallback(this, mPingResponseTimeout, + nsITimer::TYPE_ONE_SHOT); + } + else { + LOG(("nsWebSockethandler:: Timed out Ping\n")); + mPingTimer = nsnull; + AbortSession(NS_ERROR_NET_TIMEOUT); + } + } + else { + NS_ABORT_IF_FALSE(0, "Unknown Timer"); + } + + return NS_OK; +} + +// nsIWebSocketProtocol + +NS_IMETHODIMP +nsWebSocketHandler::GetOriginalURI(nsIURI **aOriginalURI) +{ + LOG(("WebSocketHandler::GetOriginalURI() %p\n", this)); + + if (!mOriginalURI) + return NS_ERROR_NOT_INITIALIZED; + NS_ADDREF(*aOriginalURI = mOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetURI(nsIURI **aURI) +{ + LOG(("WebSocketHandler::GetURI() %p\n", this)); + + if (!mOriginalURI) + return NS_ERROR_NOT_INITIALIZED; + if (mURI) + NS_ADDREF(*aURI = mURI); + else + NS_ADDREF(*aURI = mOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler:: +GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks) +{ + LOG(("WebSocketHandler::GetNotificationCallbacks() %p\n", this)); + NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler:: +SetNotificationCallbacks(nsIInterfaceRequestor *aNotificationCallbacks) +{ + LOG(("WebSocketHandler::SetNotificationCallbacks() %p\n", this)); + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetSecurityInfo(nsISupports **aSecurityInfo) +{ + LOG(("WebSocketHandler::GetSecurityInfo() %p\n", this)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + if (mTransport) { + if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo))) + *aSecurityInfo = nsnull; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + LOG(("WebSocketHandler::GetLoadGroup() %p\n", this)); + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::SetLoadGroup(nsILoadGroup *aLoadGroup) +{ + LOG(("WebSocketHandler::SetLoadGroup() %p\n", this)); + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetProtocol(nsACString &aProtocol) +{ + LOG(("WebSocketHandler::GetProtocol() %p\n", this)); + aProtocol = mProtocol; + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::SetProtocol(const nsACString &aProtocol) +{ + LOG(("WebSocketHandler::SetProtocol() %p\n", this)); + mProtocol = aProtocol; /* the sub protocol */ + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::AsyncOpen(nsIURI *aURI, + const nsACString &aOrigin, + nsIWebSocketListener *aListener, + nsISupports *aContext) +{ + LOG(("WebSocketHandler::AsyncOpen() %p\n", this)); + + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + if (!aURI || !aListener) { + LOG(("WebSocketHandler::AsyncOpen() Uri or Listener null")); + return NS_ERROR_UNEXPECTED; + } + + if (mListener) + return NS_ERROR_ALREADY_OPENED; + + nsresult rv; + + mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to continue without socket transport service"); + return rv; + } + + nsCOMPtr<nsIPrefBranch> prefService; + prefService = do_GetService(NS_PREFSERVICE_CONTRACTID); + + if (prefService) { + PRInt32 intpref; + PRBool boolpref; + rv = prefService-> + GetIntPref("network.websocket.max-message-size", &intpref); + if (NS_SUCCEEDED(rv)) { + mMaxMessageSize = NS_CLAMP(intpref, 1024, 1 << 30); + } + rv = prefService->GetIntPref + ("network.websocket.timeout.close", &intpref); + if (NS_SUCCEEDED(rv)) { + mCloseTimeout = NS_CLAMP(intpref, 1, 1800) * 1000; + } + rv = prefService->GetIntPref + ("network.websocket.timeout.open", &intpref); + if (NS_SUCCEEDED(rv)) { + mOpenTimeout = NS_CLAMP(intpref, 1, 1800) * 1000; + } + rv = prefService->GetIntPref + ("network.websocket.timeout.ping.request", &intpref); + if (NS_SUCCEEDED(rv)) { + mPingTimeout = NS_CLAMP(intpref, 0, 86400) * 1000; + } + rv = prefService->GetIntPref + ("network.websocket.timeout.ping.response", &intpref); + if (NS_SUCCEEDED(rv)) { + mPingResponseTimeout = NS_CLAMP(intpref, 1, 3600) * 1000; + } + rv = prefService->GetBoolPref + ("network.websocket.extensions.stream-deflate", &boolpref); + if (NS_SUCCEEDED(rv)) { + mAllowCompression = boolpref ? 1 : 0; + } + rv = prefService->GetBoolPref + ("network.websocket.auto-follow-http-redirects", &boolpref); + if (NS_SUCCEEDED(rv)) { + mAutoFollowRedirects = boolpref ? 1 : 0; + } + } + + if (mPingTimeout) { + mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to create ping timer. Carrying on."); + } + else { + LOG(("nsWebSocketHandler will generate ping after %d ms " + "of receive silence\n", mPingTimeout)); + mPingTimer->SetTarget(mSocketThread); + mPingTimer->InitWithCallback(this, mPingTimeout, + nsITimer::TYPE_ONE_SHOT); + } + } + + mOriginalURI = aURI; + mURI = mOriginalURI; + mListener = aListener; + mContext = aContext; + mOrigin = aOrigin; + + nsCOMPtr<nsIURI> localURI; + nsCOMPtr<nsIChannel> localChannel; + + mURI->Clone(getter_AddRefs(localURI)); + if (mEncrypted) + rv = localURI->SetScheme(NS_LITERAL_CSTRING("https")); + else + rv = localURI->SetScheme(NS_LITERAL_CSTRING("http")); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIIOService> ioService; + ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to continue without io service"); + return rv; + } + + nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to continue without ioservice2 interface"); + return rv; + } + + rv = io2->NewChannelFromURIWithProxyFlags( + localURI, + mURI, + nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY | + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, + getter_AddRefs(localChannel)); + NS_ENSURE_SUCCESS(rv, rv); + + // pass most GetInterface() requests through to our instantiator, but handle + // nsIChannelEventSink in this object in order to deal with redirects + localChannel->SetNotificationCallbacks(this); + + mChannel = do_QueryInterface(localChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mHttpChannel = do_QueryInterface(localChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetupRequest(); + if (NS_FAILED(rv)) + return rv; + + return ApplyForAdmission(); +} + +NS_IMETHODIMP +nsWebSocketHandler::Close() +{ + LOG(("WebSocketHandler::Close() %p\n", this)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + if (mRequestedClose) { + LOG(("WebSocketHandler:: Double close error\n")); + return NS_ERROR_UNEXPECTED; + } + + mRequestedClose = 1; + + nsCOMPtr<nsIRunnable> event = + new nsPostMessage(this, kFinMessage, -1); + return mSocketThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::SendMsg(const nsACString &aMsg) +{ + LOG(("WebSocketHandler::SendMsg() %p\n", this)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + if (mRequestedClose) { + LOG(("WebSocketHandler:: SendMsg when closed error\n")); + return NS_ERROR_UNEXPECTED; + } + + if (mStopped) { + LOG(("WebSocketHandler:: SendMsg when stopped error\n")); + return NS_ERROR_NOT_CONNECTED; + } + + nsCOMPtr<nsIRunnable> event = + new nsPostMessage(this, new nsCString(aMsg), -1); + return mSocketThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +nsWebSocketHandler::SendBinaryMsg(const nsACString &aMsg) +{ + LOG(("WebSocketHandler::SendBinaryMsg() %p len=%d\n", this, aMsg.Length())); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + if (mRequestedClose) { + LOG(("WebSocketHandler:: SendBinaryMsg when closed error\n")); + return NS_ERROR_UNEXPECTED; + } + + if (mStopped) { + LOG(("WebSocketHandler:: SendBinaryMsg when stopped error\n")); + return NS_ERROR_NOT_CONNECTED; + } + + nsCOMPtr<nsIRunnable> event = + new nsPostMessage(this, new nsCString(aMsg), aMsg.Length()); + return mSocketThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +nsWebSocketHandler::OnTransportAvailable(nsISocketTransport *aTransport, + nsIAsyncInputStream *aSocketIn, + nsIAsyncOutputStream *aSocketOut) +{ + LOG(("WebSocketHandler::OnTransportAvailable " + "%p [%p %p %p] rcvdonstart=%d\n", + this, aTransport, aSocketIn, aSocketOut, mRecvdHttpOnStartRequest)); + + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated"); + + mTransport = aTransport; + mSocketIn = aSocketIn; + mSocketOut = aSocketOut; + + nsresult rv; + rv = mTransport->SetEventSink(nsnull, nsnull); + if (NS_FAILED(rv)) return rv; + rv = mTransport->SetSecurityCallbacks(mCallbacks); + if (NS_FAILED(rv)) return rv; + + mRecvdHttpUpgradeTransport = 1; + if (mRecvdHttpOnStartRequest) + return mSocketIn->AsyncWait(this, 0, 0, mSocketThread); + return NS_OK; +} + +// nsIRequestObserver (from nsIStreamListener) + +NS_IMETHODIMP +nsWebSocketHandler::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + LOG(("WebSocketHandler::OnStartRequest() %p [%p %p] recvdhttpupgrade=%d\n", + this, aRequest, aContext, mRecvdHttpUpgradeTransport)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + NS_ABORT_IF_FALSE(!mRecvdHttpOnStartRequest, "OTA duplicated"); + + // Generating the onStart event will take us out of the + // CONNECTING state which means we can now open another, + // perhaps parallel, connection to the same host if one + // is pending + + if (sWebSocketAdmissions->Complete(mAddress)) + LOG(("nsWebSocketHandler::OnStartRequest Starting Pending Open\n")); + else + LOG(("nsWebSocketHandler::OnStartRequest No More Pending Opens\n")); + + if (mOpenTimer) { + mOpenTimer->Cancel(); + mOpenTimer = nsnull; + } + + if (mStopped) { + LOG(("WebSocketHandler::OnStartRequest Handler Already Done\n")); + AbortSession(NS_ERROR_CONNECTION_REFUSED); + return NS_ERROR_CONNECTION_REFUSED; + } + + nsresult rv; + PRUint32 status; + char *val, *token; + + rv = mHttpChannel->GetResponseStatus(&status); + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler::OnStartRequest No HTTP Response\n")); + AbortSession(NS_ERROR_CONNECTION_REFUSED); + return NS_ERROR_CONNECTION_REFUSED; + } + + LOG(("WebSocketHandler::OnStartRequest HTTP status %d\n", status)); + if (status != 101) { + AbortSession(NS_ERROR_CONNECTION_REFUSED); + return NS_ERROR_CONNECTION_REFUSED; + } + + nsCAutoString respUpgrade; + rv = mHttpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Upgrade"), respUpgrade); + + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_ILLEGAL_VALUE; + if (!respUpgrade.IsEmpty()) { + val = respUpgrade.BeginWriting(); + while ((token = nsCRT::strtok(val, ", \t", &val))) { + if (PL_strcasecmp(token, "Websocket") == 0) { + rv = NS_OK; + break; + } + } + } + } + + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler::OnStartRequest " + "HTTP response header Upgrade: websocket not found\n")); + AbortSession(rv); + return rv; + } + + nsCAutoString respConnection; + rv = mHttpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Connection"), respConnection); + + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_ILLEGAL_VALUE; + if (!respConnection.IsEmpty()) { + val = respConnection.BeginWriting(); + while ((token = nsCRT::strtok(val, ", \t", &val))) { + if (PL_strcasecmp(token, "Upgrade") == 0) { + rv = NS_OK; + break; + } + } + } + } + + if (NS_FAILED(rv)) { + LOG(("WebSocketHandler::OnStartRequest " + "HTTP response header Connection: Upgrade not found\n")); + AbortSession(rv); + return rv; + } + + nsCAutoString respAccept; + rv = mHttpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), respAccept); + + if (NS_FAILED(rv) || + respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) { + LOG(("WebSocketHandler::OnStartRequest " + "HTTP response header Sec-WebSocket-Accept check failed\n")); + LOG(("WebSocketHandler::OnStartRequest " + "Expected %s recevied %s\n", + mHashedSecret.get(), respAccept.get())); + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; + } + + // If we sent a sub protocol header, verify the response matches + // If it does not, set mProtocol to "" so the protocol attribute + // of the WebSocket JS object reflects that + if (!mProtocol.IsEmpty()) { + nsCAutoString respProtocol; + rv = mHttpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), respProtocol); + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_ILLEGAL_VALUE; + val = mProtocol.BeginWriting(); + while ((token = nsCRT::strtok(val, ", \t", &val))) { + if (PL_strcasecmp(token, respProtocol.get()) == 0) { + rv = NS_OK; + break; + } + } + + if (NS_SUCCEEDED(rv)) { + LOG(("WebsocketHandler::OnStartRequest " + "subprotocol %s confirmed", respProtocol.get())); + mProtocol = respProtocol; + } + else { + LOG(("WebsocketHandler::OnStartRequest " + "subprotocol [%s] not found - %s returned", + mProtocol.get(), respProtocol.get())); + mProtocol.Truncate(); + } + } + else { + LOG(("WebsocketHandler::OnStartRequest " + "subprotocol [%s] not found - none returned", + mProtocol.get())); + mProtocol.Truncate(); + } + } + + rv = HandleExtensions(); + if (NS_FAILED(rv)) + return rv; + + LOG(("WebSocketHandler::OnStartRequest Notifying Listener %p\n", + mListener.get())); + + if (mListener) + mListener->OnStart(mContext); + + mRecvdHttpOnStartRequest = 1; + if (mRecvdHttpUpgradeTransport) + return mSocketIn->AsyncWait(this, 0, 0, mSocketThread); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + LOG(("WebSocketHandler::OnStopRequest() %p [%p %p %x]\n", + this, aRequest, aContext, aStatusCode)); + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + + // this is the stop of the HTTP upgrade transaction, the + // upgraded streams live on + + mChannel = nsnull; + mHttpChannel = nsnull; + mLoadGroup = nsnull; + mCallbacks = nsnull; + + return NS_OK; +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +nsWebSocketHandler::OnInputStreamReady(nsIAsyncInputStream *aStream) +{ + LOG(("WebSocketHandler::OnInputStreamReady() %p\n", this)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + + if (mStopped) + return NS_ERROR_UNEXPECTED; + + nsRefPtr<nsIStreamListener> deleteProtector1(mInflateReader); + nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream); + + // this is after the http upgrade - so we are speaking websockets + char buffer[2048]; + PRUint32 count; + nsresult rv; + + do { + rv = mSocketIn->Read((char *)buffer, 2048, &count); + LOG(("WebSocketHandler::OnInputStreamReady read %u rv %x\n", + count, rv)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mSocketIn->AsyncWait(this, 0, 0, mSocketThread); + return NS_OK; + } + + if (NS_FAILED(rv)) { + AbortSession(rv); + return rv; + } + + if (count == 0) { + AbortSession(NS_BASE_STREAM_CLOSED); + return NS_OK; + } + + if (mInflateReader) { + mInflateStream->ShareData(buffer, count); + rv = mInflateReader->OnDataAvailable(nsnull, mSocketIn, + mInflateStream, 0, count); + } + else { + rv = ProcessInput((PRUint8 *)buffer, count); + } + + if (NS_FAILED(rv)) { + AbortSession(rv); + return rv; + } + + } while (NS_SUCCEEDED(rv) && mSocketIn); + + return NS_OK; +} + + +// nsIOutputStreamCallback + +NS_IMETHODIMP +nsWebSocketHandler::OnOutputStreamReady(nsIAsyncOutputStream *aStream) +{ + LOG(("WebSocketHandler::OnOutputStreamReady() %p\n", this)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, + "not socket thread"); + nsresult rv; + + if (!mCurrentOut) + PrimeNewOutgoingMessage(); + + while (mCurrentOut && mSocketOut) { + const char *sndBuf; + PRUint32 toSend; + PRUint32 amtSent; + + if (mHdrOut) { + sndBuf = (const char *)mHdrOut; + toSend = mHdrOutToSend; + LOG(("WebSocketHandler::OnOutputStreamReady " + "Try to send %u of hdr/copybreak\n", + toSend)); + } + else { + sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent; + toSend = mCurrentOut->Length() - mCurrentOutSent; + if (toSend > 0) { + LOG(("WebSocketHandler::OnOutputStreamReady " + "Try to send %u of data\n", + toSend)); + } + } + + if (toSend == 0) { + amtSent = 0; + } + else { + rv = mSocketOut->Write(sndBuf, toSend, &amtSent); + LOG(("WebSocketHandler::OnOutputStreamReady write %u rv %x\n", + amtSent, rv)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mSocketOut->AsyncWait(this, 0, 0, nsnull); + return NS_OK; + } + + if (NS_FAILED(rv)) { + AbortSession(rv); + return NS_OK; + } + } + + if (mHdrOut) { + if (amtSent == toSend) { + mHdrOut = nsnull; + mHdrOutToSend = 0; + } + else { + mHdrOut += amtSent; + mHdrOutToSend -= amtSent; + } + } + else { + if (amtSent == toSend) { + if (!mStopped) { + nsCOMPtr<nsIRunnable> event = + new CallAcknowledge(mListener, mContext, + mCurrentOut->Length()); + NS_DispatchToMainThread(event); + } + delete mCurrentOut; + mCurrentOut = nsnull; + mCurrentOutSent = 0; + PrimeNewOutgoingMessage(); + } + else { + mCurrentOutSent += amtSent; + } + } + } + + if (mReleaseOnTransmit) + ReleaseSession(); + return NS_OK; +} + +// nsIStreamListener + +NS_IMETHODIMP +nsWebSocketHandler::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + PRUint32 aOffset, + PRUint32 aCount) +{ + LOG(("WebSocketHandler::OnDataAvailable() %p [%p %p %p %u %u]\n", + this, aRequest, aContext, aInputStream, aOffset, aCount)); + + if (aContext == mSocketIn) { + // This is the deflate decoder + + LOG(("WebSocketHandler::OnDataAvailable Deflate Data %u\n", + aCount)); + + PRUint8 buffer[2048]; + PRUint32 maxRead; + PRUint32 count; + nsresult rv; + + while (aCount > 0) { + if (mStopped) + return NS_BASE_STREAM_CLOSED; + + maxRead = NS_MIN(2048U, aCount); + rv = aInputStream->Read((char *)buffer, maxRead, &count); + LOG(("WebSocketHandler::OnDataAvailable " + "InflateRead read %u rv %x\n", + count, rv)); + if (NS_FAILED(rv) || count == 0) { + AbortSession(rv); + break; + } + + aCount -= count; + rv = ProcessInput(buffer, count); + } + return NS_OK; + } + + if (aContext == mSocketOut) { + // This is the deflate encoder + + PRUint32 maxRead; + PRUint32 count; + nsresult rv; + + while (aCount > 0) { + if (mStopped) + return NS_BASE_STREAM_CLOSED; + + maxRead = NS_MIN(2048U, aCount); + EnsureHdrOut(mHdrOutToSend + aCount); + rv = aInputStream->Read((char *)mHdrOut + mHdrOutToSend, + maxRead, &count); + LOG(("WebSocketHandler::OnDataAvailable " + "DeflateWrite read %u rv %x\n", count, rv)); + if (NS_FAILED(rv) || count == 0) { + AbortSession(rv); + break; + } + + mHdrOutToSend += count; + aCount -= count; + } + return NS_OK; + } + + + // Otherwise, this is the HTTP OnDataAvailable Method, which means + // this is http data in response to the upgrade request and + // there should be no http response body if the upgrade succeeded + + // This generally should be caught by a non 101 response code in + // OnStartRequest().. so we can ignore the data here + + LOG(("WebSocketHandler::OnDataAvailable HTTP data unexpected len>=%u\n", + aCount)); + + return NS_OK; +} + +// nsIProtocolHandler + +NS_IMETHODIMP +nsWebSocketHandler::GetScheme(nsACString &aScheme) +{ + LOG(("WebSocketHandler::GetScheme() %p\n", this)); + + if (mEncrypted) + aScheme.AssignLiteral("wss"); + else + aScheme.AssignLiteral("ws"); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetDefaultPort(PRInt32 *aDefaultPort) +{ + LOG(("WebSocketHandler::GetDefaultPort() %p\n", this)); + + if (mEncrypted) + *aDefaultPort = kDefaultWSSPort; + else + *aDefaultPort = kDefaultWSPort; + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::GetProtocolFlags(PRUint32 *aProtocolFlags) +{ + LOG(("WebSocketHandler::GetProtocolFlags() %p\n", this)); + + *aProtocolFlags = URI_NORELATIVE | URI_NON_PERSISTABLE | ALLOWS_PROXY | + ALLOWS_PROXY_HTTP | URI_DOES_NOT_RETURN_DATA | URI_DANGEROUS_TO_LOAD; + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::NewURI(const nsACString & aSpec, const char *aOriginCharset, + nsIURI *aBaseURI, nsIURI **_retval NS_OUTPARAM) +{ + LOG(("WebSocketHandler::NewURI() %p\n", this)); + + PRInt32 port; + nsresult rv = GetDefaultPort(&port); + if (NS_FAILED(rv)) + return rv; + + nsRefPtr<nsStandardURL> url = new nsStandardURL(); + rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, port, aSpec, + aOriginCharset, aBaseURI); + if (NS_FAILED(rv)) + return rv; + NS_ADDREF(*_retval = url); + return NS_OK; +} + +NS_IMETHODIMP +nsWebSocketHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval NS_OUTPARAM) +{ + LOG(("WebSocketHandler::NewChannel() %p\n", this)); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWebSocketHandler::AllowPort(PRInt32 port, const char *scheme, + PRBool *_retval NS_OUTPARAM) +{ + LOG(("WebSocketHandler::AllowPort() %p\n", this)); + + // do not override any blacklisted ports + *_retval = PR_FALSE; + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/websocket/nsWebSocketHandler.h @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 4; 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 Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Wellington Fernando de Macedo <wfernandom2004@gmail.com> (original author) + * Patrick McManus <mcmanus@ducksong.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 "nsIWebSocketProtocol.h" +#include "nsIURI.h" +#include "nsISupports.h" +#include "nsIInterfaceRequestor.h" +#include "nsIEventTarget.h" +#include "nsIStreamListener.h" +#include "nsIProtocolHandler.h" +#include "nsISocketTransport.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsILoadGroup.h" +#include "nsITimer.h" +#include "nsIDNSListener.h" +#include "nsIHttpChannel.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIStringStream.h" +#include "nsIHttpChannelInternal.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsDeque.h" + +namespace mozilla { namespace net { + +class nsPostMessage; +class nsWSAdmissionManager; +class nsWSCompression; +class WSCallOnInputStreamReady; + +class nsWebSocketHandler : public nsIWebSocketProtocol, + public nsIHttpUpgradeListener, + public nsIStreamListener, + public nsIProtocolHandler, + public nsIInputStreamCallback, + public nsIOutputStreamCallback, + public nsITimerCallback, + public nsIDNSListener, + public nsIInterfaceRequestor, + public nsIChannelEventSink +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBSOCKETPROTOCOL + NS_DECL_NSIHTTPUPGRADELISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPROTOCOLHANDLER + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIOUTPUTSTREAMCALLBACK + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIDNSLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + nsWebSocketHandler(); + static void Shutdown(); + + enum { + // Non Control Frames + kContinuation = 0x0, + kText = 0x1, + kBinary = 0x2, + + // Control Frames + kClose = 0x8, + kPing = 0x9, + kPong = 0xA + }; + + const static PRUint32 kControlFrameMask = 0x8; + const static PRInt32 kDefaultWSPort = 80; + const static PRInt32 kDefaultWSSPort = 443; + const static PRUint8 kMaskBit = 0x80; + const static PRUint8 kFinalFragBit = 0x80; + + // section 7.4.1 defines these + const static PRUint16 kCloseNormal = 1000; + const static PRUint16 kCloseGoingAway = 1001; + const static PRUint16 kCloseProtocolError = 1002; + const static PRUint16 kCloseUnsupported = 1003; + const static PRUint16 kCloseTooLarge = 1004; + +protected: + virtual ~nsWebSocketHandler(); + PRBool mEncrypted; + +private: + friend class nsPostMessage; + friend class nsWSAdmissionManager; + friend class WSCallOnInputStreamReady; + + void SendMsgInternal(nsCString *aMsg, PRInt32 datalen); + void PrimeNewOutgoingMessage(); + void GeneratePong(PRUint8 *payload, PRUint32 len); + void GeneratePing(); + + nsresult BeginOpen(); + nsresult HandleExtensions(); + nsresult SetupRequest(); + nsresult ApplyForAdmission(); + + void StopSession(nsresult reason); + void AbortSession(nsresult reason); + void ReleaseSession(); + + void EnsureHdrOut(PRUint32 size); + void ApplyMask(PRUint32 mask, PRUint8 *data, PRUint64 len); + + PRBool IsPersistentFramePtr(); + nsresult ProcessInput(PRUint8 *buffer, PRUint32 count); + PRUint32 UpdateReadBuffer(PRUint8 *buffer, PRUint32 count); + + class OutboundMessage + { + public: + OutboundMessage (nsCString *str) + : mMsg(str), mIsControl(PR_FALSE), mBinaryLen(-1) {} + + OutboundMessage (nsCString *str, PRInt32 dataLen) + : mMsg(str), mIsControl(PR_FALSE), mBinaryLen(dataLen) {} + + OutboundMessage () + : mMsg(nsnull), mIsControl(PR_TRUE), mBinaryLen(-1) {} + + ~OutboundMessage() { delete mMsg; } + + PRBool IsControl() { return mIsControl; } + const nsCString *Msg() { return mMsg; } + PRInt32 BinaryLen() { return mBinaryLen; } + PRInt32 Length() + { + if (mBinaryLen >= 0) + return mBinaryLen; + return mMsg ? mMsg->Length() : 0; + } + PRUint8 *BeginWriting() + { return (PRUint8 *)(mMsg ? mMsg->BeginWriting() : nsnull); } + PRUint8 *BeginReading() + { return (PRUint8 *)(mMsg ? mMsg->BeginReading() : nsnull); } + + private: + nsCString *mMsg; + PRBool mIsControl; + PRInt32 mBinaryLen; + }; + + nsCOMPtr<nsIURI> mOriginalURI; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIWebSocketListener> mListener; + nsCOMPtr<nsISupports> mContext; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIEventTarget> mSocketThread; + nsCOMPtr<nsIHttpChannelInternal> mChannel; + nsCOMPtr<nsIHttpChannel> mHttpChannel; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsICancelable> mDNSRequest; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; + + nsCString mProtocol; + nsCString mOrigin; + nsCString mHashedSecret; + nsCString mAddress; + + nsCOMPtr<nsISocketTransport> mTransport; + nsCOMPtr<nsIAsyncInputStream> mSocketIn; + nsCOMPtr<nsIAsyncOutputStream> mSocketOut; + + nsCOMPtr<nsITimer> mCloseTimer; + PRUint32 mCloseTimeout; /* milliseconds */ + + nsCOMPtr<nsITimer> mOpenTimer; + PRUint32 mOpenTimeout; /* milliseconds */ + + nsCOMPtr<nsITimer> mPingTimer; + PRUint32 mPingTimeout; /* milliseconds */ + PRUint32 mPingResponseTimeout; /* milliseconds */ + + PRUint32 mRecvdHttpOnStartRequest : 1; + PRUint32 mRecvdHttpUpgradeTransport : 1; + PRUint32 mRequestedClose : 1; + PRUint32 mClientClosed : 1; + PRUint32 mServerClosed : 1; + PRUint32 mStopped : 1; + PRUint32 mCalledOnStop : 1; + PRUint32 mPingOutstanding : 1; + PRUint32 mAllowCompression : 1; + PRUint32 mAutoFollowRedirects : 1; + PRUint32 mReleaseOnTransmit : 1; + + PRInt32 mMaxMessageSize; + nsresult mStopOnClose; + + // These are for the read buffers + PRUint8 *mFramePtr; + PRUint8 *mBuffer; + PRUint8 mFragmentOpcode; + PRUint32 mFragmentAccumulator; + PRUint32 mBuffered; + PRUint32 mBufferSize; + nsCOMPtr<nsIStreamListener> mInflateReader; + nsCOMPtr<nsIStringInputStream> mInflateStream; + + // These are for the send buffers + const static PRInt32 kCopyBreak = 1000; + + OutboundMessage *mCurrentOut; + PRUint32 mCurrentOutSent; + nsDeque mOutgoingMessages; + nsDeque mOutgoingPingMessages; + nsDeque mOutgoingPongMessages; + PRUint32 mHdrOutToSend; + PRUint8 *mHdrOut; + PRUint8 mOutHeader[kCopyBreak + 16]; + nsWSCompression *mCompressor; + PRUint32 mDynamicOutputSize; + PRUint8 *mDynamicOutput; +}; + +class nsWebSocketSSLHandler : public nsWebSocketHandler +{ +public: + nsWebSocketSSLHandler() {nsWebSocketHandler::mEncrypted = PR_TRUE;} +protected: + virtual ~nsWebSocketSSLHandler() {} +}; + +}} // namespace mozilla::net
--- a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp +++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp @@ -45,20 +45,20 @@ #include "nsCOMPtr.h" #include "nsReadableUtils.h" #include "nsNetError.h" #include "nsStreamUtils.h" #include "nsStringStream.h" #include "nsComponentManagerUtils.h" // nsISupports implementation -NS_IMPL_ISUPPORTS3(nsHTTPCompressConv, - nsIStreamConverter, - nsIStreamListener, - nsIRequestObserver) +NS_IMPL_THREADSAFE_ISUPPORTS3(nsHTTPCompressConv, + nsIStreamConverter, + nsIStreamListener, + nsIRequestObserver) // nsFTPDirListingConv methods nsHTTPCompressConv::nsHTTPCompressConv() : mListener(nsnull) , mMode(HTTP_COMPRESS_IDENTITY) , mOutBuffer(NULL) , mInpBuffer(NULL) , mOutBufferLen(0)