2011-07-07 12:39:24 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
2011-07-04 21:18:34 -07:00
|
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
2011-05-21 18:27:52 -07:00
|
|
|
/* ***** 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 ***** */
|
|
|
|
|
2011-05-04 06:36:23 -07:00
|
|
|
#include "WebSocketLog.h"
|
2011-07-04 21:18:33 -07:00
|
|
|
#include "WebSocketChannel.h"
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
#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 "zlib.h"
|
|
|
|
|
|
|
|
extern PRThread *gSocketThread;
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace net {
|
|
|
|
|
2011-07-04 21:18:33 -07:00
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS11(WebSocketChannel,
|
2011-07-04 21:18:32 -07:00
|
|
|
nsIWebSocketChannel,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsIHttpUpgradeListener,
|
|
|
|
nsIRequestObserver,
|
|
|
|
nsIStreamListener,
|
|
|
|
nsIProtocolHandler,
|
|
|
|
nsIInputStreamCallback,
|
|
|
|
nsIOutputStreamCallback,
|
|
|
|
nsITimerCallback,
|
|
|
|
nsIDNSListener,
|
|
|
|
nsIInterfaceRequestor,
|
|
|
|
nsIChannelEventSink)
|
|
|
|
|
|
|
|
// Use this fake ptr so the Fin message stays in sequence in the
|
|
|
|
// main transmit queue
|
|
|
|
#define kFinMessage (reinterpret_cast<nsCString *>(0x01))
|
|
|
|
|
2011-06-21 07:50:02 -07:00
|
|
|
// An implementation of draft-ietf-hybi-thewebsocketprotocol-08
|
|
|
|
#define SEC_WEBSOCKET_VERSION "8"
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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:
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
~CallOnMessageAvailable() {}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIWebSocketListener> mListener;
|
|
|
|
nsCOMPtr<nsISupports> mContext;
|
|
|
|
nsCString mData;
|
|
|
|
PRInt32 mLen;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnMessageAvailable, nsIRunnable)
|
|
|
|
|
|
|
|
class CallOnStop : public nsIRunnable
|
|
|
|
{
|
|
|
|
public:
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
|
|
|
|
CallOnStop(nsIWebSocketListener *aListener,
|
|
|
|
nsISupports *aContext,
|
|
|
|
nsresult aData)
|
2011-05-21 18:27:52 -07:00
|
|
|
: mListener(aListener),
|
|
|
|
mContext(aContext),
|
|
|
|
mData(aData) {}
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
NS_SCRIPTABLE NS_IMETHOD Run()
|
|
|
|
{
|
|
|
|
mListener->OnStop(mContext, mData);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
~CallOnStop() {}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIWebSocketListener> mListener;
|
|
|
|
nsCOMPtr<nsISupports> mContext;
|
|
|
|
nsresult mData;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnStop, nsIRunnable)
|
|
|
|
|
|
|
|
class CallOnServerClose : public nsIRunnable
|
|
|
|
{
|
|
|
|
public:
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
|
|
|
|
CallOnServerClose(nsIWebSocketListener *aListener,
|
2011-08-03 12:15:25 -07:00
|
|
|
nsISupports *aContext,
|
|
|
|
PRUint16 aCode,
|
|
|
|
nsCString &aReason)
|
2011-05-21 18:27:52 -07:00
|
|
|
: mListener(aListener),
|
2011-08-03 12:15:25 -07:00
|
|
|
mContext(aContext),
|
|
|
|
mCode(aCode),
|
|
|
|
mReason(aReason) {}
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
NS_SCRIPTABLE NS_IMETHOD Run()
|
|
|
|
{
|
2011-08-03 12:15:25 -07:00
|
|
|
mListener->OnServerClose(mContext, mCode, mReason);
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
~CallOnServerClose() {}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIWebSocketListener> mListener;
|
|
|
|
nsCOMPtr<nsISupports> mContext;
|
2011-08-03 12:15:25 -07:00
|
|
|
PRUint16 mCode;
|
|
|
|
nsCString mReason;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable)
|
|
|
|
|
|
|
|
class CallAcknowledge : public nsIRunnable
|
|
|
|
{
|
|
|
|
public:
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
|
|
|
|
CallAcknowledge(nsIWebSocketListener *aListener,
|
|
|
|
nsISupports *aContext,
|
|
|
|
PRUint32 aSize)
|
2011-05-21 18:27:52 -07:00
|
|
|
: mListener(aListener),
|
|
|
|
mContext(aContext),
|
|
|
|
mSize(aSize) {}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_SCRIPTABLE NS_IMETHOD Run()
|
|
|
|
{
|
|
|
|
LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
|
|
|
|
mListener->OnAcknowledge(mContext, mSize);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
~CallAcknowledge() {}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIWebSocketListener> mListener;
|
|
|
|
nsCOMPtr<nsISupports> mContext;
|
|
|
|
PRUint32 mSize;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(CallAcknowledge, nsIRunnable)
|
|
|
|
|
|
|
|
class nsPostMessage : public nsIRunnable
|
|
|
|
{
|
|
|
|
public:
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
|
|
|
|
nsPostMessage(WebSocketChannel *channel,
|
|
|
|
nsCString *aData,
|
|
|
|
PRInt32 aDataLen)
|
|
|
|
: mChannel(channel),
|
|
|
|
mData(aData),
|
|
|
|
mDataLen(aDataLen) {}
|
|
|
|
|
|
|
|
NS_SCRIPTABLE NS_IMETHOD Run()
|
|
|
|
{
|
|
|
|
if (mData)
|
|
|
|
mChannel->SendMsgInternal(mData, mDataLen);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
~nsPostMessage() {}
|
|
|
|
|
|
|
|
nsRefPtr<WebSocketChannel> mChannel;
|
|
|
|
nsCString *mData;
|
|
|
|
PRInt32 mDataLen;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
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:
|
2011-07-04 21:18:34 -07:00
|
|
|
nsWSAdmissionManager() : mConnectedCount(0)
|
|
|
|
{
|
|
|
|
MOZ_COUNT_CTOR(nsWSAdmissionManager);
|
|
|
|
}
|
|
|
|
|
|
|
|
class nsOpenConn
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
nsOpenConn(nsCString &addr, WebSocketChannel *channel)
|
|
|
|
: mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
|
|
|
|
~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
|
|
|
|
|
|
|
|
nsCString mAddress;
|
2011-08-07 06:43:53 -07:00
|
|
|
WebSocketChannel *mChannel;
|
2011-07-04 21:18:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
~nsWSAdmissionManager()
|
|
|
|
{
|
|
|
|
MOZ_COUNT_DTOR(nsWSAdmissionManager);
|
|
|
|
for (PRUint32 i = 0; i < mData.Length(); i++)
|
|
|
|
delete mData[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool ConditionallyConnect(nsCString &aStr, WebSocketChannel *ws)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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.
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// we could hash this, but the dataset is expected to be
|
|
|
|
// small
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
PRBool found = (IndexOf(aStr) >= 0);
|
|
|
|
nsOpenConn *newdata = new nsOpenConn(aStr, ws);
|
|
|
|
mData.AppendElement(newdata);
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
NS_ABORT_IF_FALSE (!ws->mOpenRunning && !ws->mOpenBlocked,
|
|
|
|
"opening state");
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
ws->mOpenRunning = 1;
|
2011-07-04 21:18:34 -07:00
|
|
|
ws->BeginOpen();
|
2011-08-07 06:44:19 -07:00
|
|
|
} else {
|
2011-08-07 06:43:53 -07:00
|
|
|
ws->mOpenBlocked = 1;
|
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return !found;
|
|
|
|
}
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
PRBool Complete(WebSocketChannel *aChannel)
|
2011-07-04 21:18:34 -07:00
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-08-07 06:43:53 -07:00
|
|
|
NS_ABORT_IF_FALSE(!aChannel->mOpenBlocked,
|
|
|
|
"blocked, but complete nsOpenConn");
|
|
|
|
|
|
|
|
// It is possible this has already been canceled
|
|
|
|
if (!aChannel->mOpenRunning)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
PRInt32 index = IndexOf(aChannel);
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_ABORT_IF_FALSE(index >= 0, "completed connection not in open list");
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
aChannel->mOpenRunning = 0;
|
2011-07-04 21:18:34 -07:00
|
|
|
nsOpenConn *olddata = mData[index];
|
|
|
|
mData.RemoveElementAt(index);
|
|
|
|
delete olddata;
|
|
|
|
|
|
|
|
// are there more of the same address pending dispatch?
|
2011-08-07 06:44:19 -07:00
|
|
|
return ConnectNext(aChannel->mAddress);
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
PRBool Cancel(WebSocketChannel *aChannel)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
|
|
|
PRInt32 index = IndexOf(aChannel);
|
|
|
|
NS_ABORT_IF_FALSE(index >= 0, "Cancelled connection not in open list");
|
|
|
|
NS_ABORT_IF_FALSE(aChannel->mOpenRunning ^ aChannel->mOpenBlocked,
|
|
|
|
"cancel without running xor blocked");
|
|
|
|
|
|
|
|
bool wasRunning = aChannel->mOpenRunning;
|
|
|
|
aChannel->mOpenRunning = 0;
|
|
|
|
aChannel->mOpenBlocked = 0;
|
|
|
|
nsOpenConn *olddata = mData[index];
|
|
|
|
mData.RemoveElementAt(index);
|
|
|
|
delete olddata;
|
|
|
|
|
|
|
|
// if we are running we can run another one
|
2011-08-07 06:44:19 -07:00
|
|
|
if (wasRunning)
|
|
|
|
return ConnectNext(aChannel->mAddress);
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool ConnectNext(nsCString &hostName)
|
|
|
|
{
|
|
|
|
PRInt32 index = IndexOf(hostName);
|
|
|
|
if (index >= 0) {
|
|
|
|
WebSocketChannel *chan = mData[index]->mChannel;
|
|
|
|
|
|
|
|
NS_ABORT_IF_FALSE(chan->mOpenBlocked,
|
2011-08-07 06:43:53 -07:00
|
|
|
"transaction not blocked but in queue");
|
2011-08-07 06:44:19 -07:00
|
|
|
NS_ABORT_IF_FALSE(!chan->mOpenRunning, "transaction already running");
|
2011-08-07 06:43:53 -07:00
|
|
|
|
2011-08-07 06:44:19 -07:00
|
|
|
chan->mOpenBlocked = 0;
|
|
|
|
chan->mOpenRunning = 1;
|
|
|
|
chan->BeginOpen();
|
|
|
|
return PR_TRUE;
|
2011-08-07 06:43:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
void IncrementConnectedCount()
|
|
|
|
{
|
|
|
|
PR_ATOMIC_INCREMENT(&mConnectedCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DecrementConnectedCount()
|
|
|
|
{
|
|
|
|
PR_ATOMIC_DECREMENT(&mConnectedCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
PRInt32 ConnectedCount()
|
|
|
|
{
|
|
|
|
return mConnectedCount;
|
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
private:
|
2011-07-04 21:18:34 -07:00
|
|
|
nsTArray<nsOpenConn *> mData;
|
|
|
|
|
|
|
|
PRInt32 IndexOf(nsCString &aStr)
|
|
|
|
{
|
|
|
|
for (PRUint32 i = 0; i < mData.Length(); i++)
|
|
|
|
if (aStr == (mData[i])->mAddress)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
PRInt32 IndexOf(WebSocketChannel *aChannel)
|
|
|
|
{
|
|
|
|
for (PRUint32 i = 0; i < mData.Length(); i++)
|
|
|
|
if (aChannel == (mData[i])->mChannel)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// ConnectedCount might be decremented from the main or the socket
|
|
|
|
// thread, so manage it with atomic counters
|
|
|
|
PRInt32 mConnectedCount;
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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)
|
2011-07-04 21:18:34 -07:00
|
|
|
: mActive(PR_FALSE),
|
|
|
|
mContext(aContext),
|
|
|
|
mListener(aListener)
|
|
|
|
{
|
|
|
|
MOZ_COUNT_CTOR(nsWSCompression);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mZlib.zalloc = allocator;
|
|
|
|
mZlib.zfree = destructor;
|
|
|
|
mZlib.opaque = Z_NULL;
|
2011-06-07 13:12:19 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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()
|
|
|
|
{
|
|
|
|
MOZ_COUNT_DTOR(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;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
private:
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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 =
|
|
|
|
mListener->OnDataAvailable(nsnull, mContext, mStream, 0, bytesToWrite);
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
PRBool mActive;
|
|
|
|
z_stream mZlib;
|
|
|
|
nsCOMPtr<nsIStringInputStream> mStream;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsISupports *mContext; /* weak ref */
|
|
|
|
nsIStreamListener *mListener; /* weak ref */
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
const static PRInt32 kBufferLen = 4096;
|
|
|
|
PRUint8 mBuffer[kBufferLen];
|
2011-05-21 18:27:52 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
static nsWSAdmissionManager *sWebSocketAdmissions = nsnull;
|
|
|
|
|
2011-07-04 21:18:33 -07:00
|
|
|
// WebSocketChannel
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::WebSocketChannel() :
|
2011-07-04 21:18:34 -07:00
|
|
|
mCloseTimeout(20000),
|
|
|
|
mOpenTimeout(20000),
|
|
|
|
mPingTimeout(0),
|
|
|
|
mPingResponseTimeout(10000),
|
|
|
|
mMaxConcurrentConnections(200),
|
|
|
|
mRecvdHttpOnStartRequest(0),
|
|
|
|
mRecvdHttpUpgradeTransport(0),
|
|
|
|
mRequestedClose(0),
|
|
|
|
mClientClosed(0),
|
|
|
|
mServerClosed(0),
|
|
|
|
mStopped(0),
|
|
|
|
mCalledOnStop(0),
|
|
|
|
mPingOutstanding(0),
|
|
|
|
mAllowCompression(1),
|
|
|
|
mAutoFollowRedirects(0),
|
|
|
|
mReleaseOnTransmit(0),
|
|
|
|
mTCPClosed(0),
|
2011-08-07 06:43:53 -07:00
|
|
|
mOpenBlocked(0),
|
|
|
|
mOpenRunning(0),
|
2011-08-07 06:44:19 -07:00
|
|
|
mChannelWasOpened(0),
|
2011-07-04 21:18:34 -07:00
|
|
|
mMaxMessageSize(16000000),
|
|
|
|
mStopOnClose(NS_OK),
|
2011-08-03 12:15:25 -07:00
|
|
|
mServerCloseCode(CLOSE_ABNORMAL),
|
|
|
|
mScriptCloseCode(0),
|
2011-07-04 21:18:34 -07:00
|
|
|
mFragmentOpcode(0),
|
|
|
|
mFragmentAccumulator(0),
|
|
|
|
mBuffered(0),
|
|
|
|
mBufferSize(16384),
|
|
|
|
mCurrentOut(nsnull),
|
|
|
|
mCurrentOutSent(0),
|
|
|
|
mCompressor(nsnull),
|
|
|
|
mDynamicOutputSize(0),
|
|
|
|
mDynamicOutput(nsnull)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!sWebSocketAdmissions)
|
|
|
|
sWebSocketAdmissions = new nsWSAdmissionManager();
|
|
|
|
|
|
|
|
mFramePtr = mBuffer = static_cast<PRUint8 *>(moz_xmalloc(mBufferSize));
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::~WebSocketChannel()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// this stop is a nop if the normal connect/close is followed
|
|
|
|
mStopped = 1;
|
|
|
|
StopSession(NS_ERROR_UNEXPECTED);
|
2011-08-07 06:43:53 -07:00
|
|
|
NS_ABORT_IF_FALSE(!mOpenRunning && !mOpenBlocked, "op");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
moz_free(mBuffer);
|
|
|
|
moz_free(mDynamicOutput);
|
|
|
|
delete mCompressor;
|
|
|
|
delete mCurrentOut;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
|
|
|
|
delete mCurrentOut;
|
|
|
|
while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
|
|
|
|
delete mCurrentOut;
|
|
|
|
while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
|
|
|
|
delete mCurrentOut;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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);
|
|
|
|
}
|
2011-07-07 12:36:37 -07:00
|
|
|
|
|
|
|
if (mLoadGroup) {
|
|
|
|
nsILoadGroup *forgettableGroup;
|
|
|
|
mLoadGroup.forget(&forgettableGroup);
|
|
|
|
NS_ProxyRelease(mainThread, forgettableGroup, PR_FALSE);
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::Shutdown()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
delete sWebSocketAdmissions;
|
|
|
|
sWebSocketAdmissions = nsnull;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::BeginOpen()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::BeginOpen() %p\n", this));
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mRedirectCallback) {
|
|
|
|
LOG(("WebSocketChannel::BeginOpen: Resuming Redirect\n"));
|
|
|
|
rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
|
|
|
|
mRedirectCallback = nsnull;
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
|
|
|
|
AbortSession(NS_ERROR_CONNECTION_REFUSED);
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = localChannel->AsyncOpen(this, mHttpChannel);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
|
|
|
|
AbortSession(NS_ERROR_CONNECTION_REFUSED);
|
|
|
|
return rv;
|
|
|
|
}
|
2011-08-07 06:44:19 -07:00
|
|
|
mChannelWasOpened = 1;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
|
|
|
if (NS_SUCCEEDED(rv))
|
|
|
|
mOpenTimer->InitWithCallback(this, mOpenTimeout, nsITimer::TYPE_ONE_SHOT);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::IsPersistentFramePtr()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// Extends the internal buffer by count and returns the total
|
2011-05-21 18:27:52 -07:00
|
|
|
// amount of data available for read
|
2011-07-22 13:06:57 -07:00
|
|
|
//
|
|
|
|
// Accumulated fragment size is passed in instead of using the member
|
|
|
|
// variable beacuse when transitioning from the stack to the persistent
|
|
|
|
// read buffer we want to explicitly include them in the buffer instead
|
|
|
|
// of as already existing data.
|
2011-05-21 18:27:52 -07:00
|
|
|
PRUint32
|
2011-07-22 13:06:57 -07:00
|
|
|
WebSocketChannel::UpdateReadBuffer(PRUint8 *buffer, PRUint32 count,
|
|
|
|
PRUint32 accumulatedFragments)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
|
2011-05-21 18:27:52 -07:00
|
|
|
this, buffer, count));
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!mBuffered)
|
|
|
|
mFramePtr = mBuffer;
|
|
|
|
|
|
|
|
NS_ABORT_IF_FALSE(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
|
2011-07-22 13:06:57 -07:00
|
|
|
NS_ABORT_IF_FALSE(mFramePtr - accumulatedFragments >= mBuffer,
|
|
|
|
"reserved FramePtr bad");
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
if (mBuffered + count <= mBufferSize) {
|
|
|
|
// append to existing buffer
|
|
|
|
LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
|
2011-07-22 13:06:57 -07:00
|
|
|
} else if (mBuffered + count -
|
|
|
|
(mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
|
2011-07-04 21:18:34 -07:00
|
|
|
// make room in existing buffer by shifting unused data to start
|
2011-07-22 13:06:57 -07:00
|
|
|
mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
|
2011-07-22 13:06:57 -07:00
|
|
|
::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
|
|
|
|
mFramePtr = mBuffer + accumulatedFragments;
|
2011-07-04 21:18:34 -07:00
|
|
|
} else {
|
|
|
|
// existing buffer is not sufficient, extend it
|
|
|
|
mBufferSize += count + 8192;
|
|
|
|
LOG(("WebSocketChannel: 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);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::ProcessInput(PRUint8 *buffer, PRUint32 count)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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 {
|
2011-07-22 13:06:57 -07:00
|
|
|
avail = UpdateReadBuffer(buffer, count, mFragmentAccumulator);
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (mFramePtr[2] & 0x80) {
|
|
|
|
// Section 4.2 says that the most significant bit MUST be
|
|
|
|
// 0. (i.e. this is really a 63 bit value)
|
|
|
|
LOG(("WebSocketChannel:: high bit of 64 bit length set"));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// copy this in case it is unaligned
|
|
|
|
PRUint64 tempLen;
|
|
|
|
memcpy(&tempLen, mFramePtr + 2, 8);
|
|
|
|
payloadLength = PR_ntohll(tempLen);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
payload = mFramePtr + framingLength;
|
|
|
|
avail -= framingLength;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
|
|
|
|
payloadLength, avail));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (avail < payloadLength)
|
|
|
|
break;
|
2011-06-21 07:49:04 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
|
|
|
|
opcode));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (maskBit) {
|
|
|
|
// This is unexpected - the server does not generally send masked
|
|
|
|
// frames to the client, but it is allowed
|
|
|
|
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
PRUint32 mask;
|
|
|
|
memcpy(&mask, payload - 4, 4);
|
|
|
|
mask = PR_ntohl(mask);
|
|
|
|
ApplyMask(mask, payload, payloadLength);
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// Control codes are required to have the fin bit set
|
|
|
|
if (!finBit && (opcode & kControlFrameMask)) {
|
|
|
|
LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (rsvBits) {
|
|
|
|
LOG(("WebSocketChannel:: unexpected reserved bits %x\n", rsvBits));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!finBit || opcode == kContinuation) {
|
|
|
|
// This is part of a fragment response
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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(("WebSocketChannel:: 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(("WebSocketChannel:: 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(("WebSocketChannel:: illegal fragment sequence\n"));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mServerClosed) {
|
|
|
|
LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
|
2011-05-21 18:27:52 -07:00
|
|
|
opcode));
|
2011-07-04 21:18:34 -07:00
|
|
|
// nop
|
|
|
|
} else if (mStopped) {
|
|
|
|
LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
|
|
|
|
opcode));
|
|
|
|
} else if (opcode == kText) {
|
|
|
|
LOG(("WebSocketChannel:: text frame received\n"));
|
|
|
|
if (mListener) {
|
|
|
|
nsCString utf8Data((const char *)payload, payloadLength);
|
|
|
|
|
|
|
|
// Section 8.1 says to replace received non utf-8 sequences
|
|
|
|
// (which are non-conformant to send) with u+fffd,
|
|
|
|
// but secteam feels that silently rewriting messages is
|
|
|
|
// inappropriate - so we will fail the connection instead.
|
|
|
|
if (!IsUTF8(utf8Data)) {
|
|
|
|
LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_DispatchToMainThread(new CallOnMessageAvailable(mListener, mContext,
|
|
|
|
utf8Data, -1));
|
|
|
|
}
|
|
|
|
} else if (opcode & kControlFrameMask) {
|
|
|
|
// control frames
|
|
|
|
if (payloadLength > 125) {
|
|
|
|
LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
|
|
|
|
opcode, payloadLength));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opcode == kClose) {
|
|
|
|
LOG(("WebSocketChannel:: close received\n"));
|
|
|
|
mServerClosed = 1;
|
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
mServerCloseCode = CLOSE_NO_STATUS;
|
2011-07-04 21:18:34 -07:00
|
|
|
if (payloadLength >= 2) {
|
2011-08-03 12:15:25 -07:00
|
|
|
memcpy(&mServerCloseCode, payload, 2);
|
|
|
|
mServerCloseCode = PR_ntohs(mServerCloseCode);
|
|
|
|
LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
|
2011-07-04 21:18:34 -07:00
|
|
|
PRUint16 msglen = payloadLength - 2;
|
|
|
|
if (msglen > 0) {
|
2011-08-03 12:15:25 -07:00
|
|
|
mServerCloseReason.SetLength(msglen);
|
|
|
|
memcpy(mServerCloseReason.BeginWriting(),
|
|
|
|
(const char *)payload + 2, msglen);
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
// section 8.1 says to replace received non utf-8 sequences
|
|
|
|
// (which are non-conformant to send) with u+fffd,
|
|
|
|
// but secteam feels that silently rewriting messages is
|
|
|
|
// inappropriate - so we will fail the connection instead.
|
2011-08-03 12:15:25 -07:00
|
|
|
if (!IsUTF8(mServerCloseReason)) {
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
LOG(("WebSocketChannel:: close msg %s\n",
|
|
|
|
mServerCloseReason.get()));
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mCloseTimer) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
}
|
|
|
|
if (mListener)
|
2011-08-03 12:15:25 -07:00
|
|
|
NS_DispatchToMainThread(
|
|
|
|
new CallOnServerClose(mListener, mContext,
|
|
|
|
mServerCloseCode, mServerCloseReason));
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
if (mClientClosed)
|
|
|
|
ReleaseSession();
|
|
|
|
} else if (opcode == kPing) {
|
|
|
|
LOG(("WebSocketChannel:: ping received\n"));
|
|
|
|
GeneratePong(payload, payloadLength);
|
2011-08-04 22:45:55 -07:00
|
|
|
} else if (opcode == kPong) {
|
2011-07-04 21:18:34 -07:00
|
|
|
// opcode kPong: the mere act of receiving the packet is all we need
|
|
|
|
// to do for the pong to trigger the activity timers
|
|
|
|
LOG(("WebSocketChannel:: pong received\n"));
|
2011-08-04 22:45:55 -07:00
|
|
|
} else {
|
|
|
|
/* unknown control frame opcode */
|
|
|
|
LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mFragmentAccumulator) {
|
|
|
|
// Remove the control frame from the stream so we have a contiguous
|
|
|
|
// data buffer of reassembled fragments
|
|
|
|
LOG(("WebSocketChannel:: 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;
|
2011-05-21 18:27:52 -07:00
|
|
|
avail -= payloadLength;
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mBuffered)
|
|
|
|
mBuffered -= framingLength + payloadLength;
|
2011-08-04 22:43:52 -07:00
|
|
|
payloadLength = 0;
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
} else if (opcode == kBinary) {
|
|
|
|
LOG(("WebSocketChannel:: binary frame received\n"));
|
|
|
|
if (mListener) {
|
|
|
|
nsCString binaryData((const char *)payload, payloadLength);
|
|
|
|
NS_DispatchToMainThread(new CallOnMessageAvailable(mListener, mContext,
|
|
|
|
binaryData,
|
|
|
|
payloadLength));
|
|
|
|
}
|
|
|
|
} else if (opcode != kContinuation) {
|
|
|
|
/* unknown opcode */
|
|
|
|
LOG(("WebSocketChannel:: 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(("WebSocketChannel:: Setup Buffer due to fragment"));
|
|
|
|
|
|
|
|
UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
|
2011-07-22 13:06:57 -07:00
|
|
|
totalAvail + mFragmentAccumulator, 0);
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
// 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(("WebSocketChannel:: Setup Buffer due to partial frame"));
|
2011-07-22 13:06:57 -07:00
|
|
|
UpdateReadBuffer(mFramePtr, totalAvail, 0);
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
} 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(("WebSocketChannel:: Internal buffering not needed anymore"));
|
|
|
|
mBuffered = 0;
|
|
|
|
}
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::ApplyMask(PRUint32 mask, PRUint8 *data, PRUint64 len)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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 && (reinterpret_cast<PRUptrdiff>(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--;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::GeneratePing()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::GeneratePing() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCString *buf = new nsCString();
|
|
|
|
buf->Assign("PING");
|
|
|
|
mOutgoingPingMessages.Push(new OutboundMessage(buf));
|
|
|
|
OnOutputStreamReady(mSocketOut);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::GeneratePong(PRUint8 *payload, PRUint32 len)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::GeneratePong() %p [%p %u]\n", this, payload, len));
|
|
|
|
|
|
|
|
nsCString *buf = new nsCString();
|
|
|
|
buf->SetLength(len);
|
|
|
|
if (buf->Length() < len) {
|
|
|
|
LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
|
|
|
|
delete buf;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(buf->BeginWriting(), payload, len);
|
|
|
|
mOutgoingPongMessages.Push(new OutboundMessage(buf));
|
|
|
|
OnOutputStreamReady(mSocketOut);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::SendMsgInternal(nsCString *aMsg,
|
2011-05-21 18:27:52 -07:00
|
|
|
PRInt32 aDataLen)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-06-21 07:49:38 -07:00
|
|
|
PRUint16
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::ResultToCloseCode(nsresult resultCode)
|
2011-06-21 07:49:38 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
if (NS_SUCCEEDED(resultCode))
|
2011-08-03 12:15:25 -07:00
|
|
|
return CLOSE_NORMAL;
|
2011-07-04 21:18:34 -07:00
|
|
|
if (resultCode == NS_ERROR_FILE_TOO_BIG)
|
2011-08-03 12:15:25 -07:00
|
|
|
return CLOSE_TOO_LARGE;
|
2011-07-04 21:18:34 -07:00
|
|
|
if (resultCode == NS_BASE_STREAM_CLOSED ||
|
|
|
|
resultCode == NS_ERROR_NET_TIMEOUT ||
|
|
|
|
resultCode == NS_ERROR_CONNECTION_REFUSED) {
|
2011-08-03 12:15:25 -07:00
|
|
|
return CLOSE_ABNORMAL;
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
return CLOSE_PROTOCOL_ERROR;
|
2011-06-21 07:49:38 -07:00
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::PrimeNewOutgoingMessage()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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();
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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(("WebSocketChannel:: PrimeNewOutgoingMessage() found close request\n"));
|
|
|
|
mClientClosed = 1;
|
|
|
|
mOutHeader[0] = kFinalFragBit | kClose;
|
2011-08-03 12:15:25 -07:00
|
|
|
mOutHeader[1] = 0x02; // payload len = 2, maybe more for reason
|
2011-07-04 21:18:34 -07:00
|
|
|
mOutHeader[1] |= kMaskBit;
|
|
|
|
|
|
|
|
// payload is offset 6 including 4 for the mask
|
|
|
|
payload = mOutHeader + 6;
|
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
// length is 8 plus any reason information
|
|
|
|
mHdrOutToSend = 8;
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// The close reason code sits in the first 2 bytes of payload
|
2011-08-03 12:15:25 -07:00
|
|
|
// If the channel user provided a code and reason during Close()
|
|
|
|
// and there isn't an internal error, use that.
|
|
|
|
if (NS_SUCCEEDED(mStopOnClose) && mScriptCloseCode) {
|
|
|
|
*((PRUint16 *)payload) = PR_htons(mScriptCloseCode);
|
|
|
|
if (!mScriptCloseReason.IsEmpty()) {
|
|
|
|
NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
|
|
|
|
"Close Reason Too Long");
|
|
|
|
mOutHeader[1] += mScriptCloseReason.Length();
|
|
|
|
mHdrOutToSend += mScriptCloseReason.Length();
|
|
|
|
memcpy (payload + 2,
|
|
|
|
mScriptCloseReason.BeginReading(),
|
|
|
|
mScriptCloseReason.Length());
|
|
|
|
}
|
2011-08-07 06:44:19 -07:00
|
|
|
} else {
|
2011-08-03 12:15:25 -07:00
|
|
|
*((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
|
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
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(("WebSocketChannel::PrimeNewOutgoingMessage() found pong request\n"));
|
|
|
|
mOutHeader[0] = kFinalFragBit | kPong;
|
|
|
|
} else if (isPing) {
|
|
|
|
LOG(("WebSocketChannel::PrimeNewOutgoingMessage() found ping request\n"));
|
|
|
|
mOutHeader[0] = kFinalFragBit | kPing;
|
|
|
|
} else if (mCurrentOut->BinaryLen() < 0) {
|
|
|
|
LOG(("WebSocketChannel::PrimeNewOutgoingMessage() "
|
|
|
|
"found queued text message len %d\n", mCurrentOut->Length()));
|
|
|
|
mOutHeader[0] = kFinalFragBit | kText;
|
|
|
|
} else {
|
|
|
|
LOG(("WebSocketChannel::PrimeNewOutgoingMessage() "
|
|
|
|
"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;
|
|
|
|
do {
|
|
|
|
PRUint8 *buffer;
|
|
|
|
nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
|
|
|
|
"GenerateRandomBytes failure %x\n", rv));
|
|
|
|
StopSession(rv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mask = * reinterpret_cast<PRUint32 *>(buffer);
|
|
|
|
NS_Free(buffer);
|
|
|
|
} while (!mask);
|
|
|
|
*(((PRUint32 *)payload) - 1) = PR_htonl(mask);
|
|
|
|
|
|
|
|
LOG(("WebSocketChannel::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();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::EnsureHdrOut(PRUint32 size)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mDynamicOutputSize < size) {
|
|
|
|
mDynamicOutputSize = size;
|
|
|
|
mDynamicOutput =
|
|
|
|
(PRUint8 *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
mHdrOut = mDynamicOutput;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-06-21 07:49:50 -07:00
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::CleanupConnection()
|
2011-06-21 07:49:50 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::CleanupConnection() %p", this));
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mLingeringCloseTimer) {
|
|
|
|
mLingeringCloseTimer->Cancel();
|
|
|
|
mLingeringCloseTimer = nsnull;
|
|
|
|
}
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mSocketIn) {
|
|
|
|
if (sWebSocketAdmissions)
|
|
|
|
sWebSocketAdmissions->DecrementConnectedCount();
|
|
|
|
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;
|
|
|
|
}
|
2011-06-21 07:49:50 -07:00
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::StopSession(nsresult reason)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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");
|
|
|
|
|
2011-08-07 06:44:19 -07:00
|
|
|
if (!mChannelWasOpened) {
|
2011-08-07 06:43:53 -07:00
|
|
|
// The HTTP channel information will never be used in this case
|
|
|
|
mChannel = nsnull;
|
|
|
|
mHttpChannel = nsnull;
|
|
|
|
mLoadGroup = nsnull;
|
|
|
|
mCallbacks = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mOpenRunning || mOpenBlocked)
|
|
|
|
sWebSocketAdmissions->Cancel(this);
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mCloseTimer) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mOpenTimer) {
|
|
|
|
mOpenTimer->Cancel();
|
|
|
|
mOpenTimer = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPingTimer) {
|
|
|
|
mPingTimer->Cancel();
|
|
|
|
mPingTimer = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mSocketIn && !mTCPClosed) {
|
|
|
|
// 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);
|
|
|
|
if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
|
|
|
|
(NS_FAILED(rv) || count == 0))
|
|
|
|
mTCPClosed = PR_TRUE;
|
|
|
|
} while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mTCPClosed && mTransport && sWebSocketAdmissions &&
|
|
|
|
sWebSocketAdmissions->ConnectedCount() < kLingeringCloseThreshold) {
|
|
|
|
|
|
|
|
// 7.1.1 says that the client SHOULD wait for the server to close the TCP
|
|
|
|
// connection. This is so we can reuse port numbers before 2 MSL expires,
|
|
|
|
// which is not really as much of a concern for us as the amount of state
|
|
|
|
// that might be accrued by keeping this channel object around waiting for
|
|
|
|
// the server. We handle the SHOULD by waiting a short time in the common
|
|
|
|
// case, but not waiting in the case of high concurrency.
|
|
|
|
//
|
|
|
|
// Normally this will be taken care of in AbortSession() after mTCPClosed
|
|
|
|
// is set when the server close arrives without waiting for the timeout to
|
|
|
|
// expire.
|
|
|
|
|
|
|
|
LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
|
|
|
mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
|
|
|
if (NS_SUCCEEDED(rv))
|
|
|
|
mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
|
|
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
else
|
|
|
|
CleanupConnection();
|
|
|
|
} else {
|
|
|
|
CleanupConnection();
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mDNSRequest) {
|
|
|
|
mDNSRequest->Cancel(NS_ERROR_UNEXPECTED);
|
|
|
|
mDNSRequest = nsnull;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mInflateReader = nsnull;
|
|
|
|
mInflateStream = nsnull;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
delete mCompressor;
|
|
|
|
mCompressor = nsnull;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!mCalledOnStop) {
|
|
|
|
mCalledOnStop = 1;
|
|
|
|
if (mListener)
|
|
|
|
NS_DispatchToMainThread(new CallOnStop(mListener, mContext, reason));
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::AbortSession(nsresult reason)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
|
|
|
|
this, reason, mStopped));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// normally this should be called on socket thread, but it is ok to call it
|
|
|
|
// from the main thread before StartWebsocketData() has completed
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// When we are failing we need to close the TCP connection immediately
|
|
|
|
// as per 7.1.1
|
|
|
|
mTCPClosed = PR_TRUE;
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mLingeringCloseTimer) {
|
|
|
|
NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop");
|
|
|
|
LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
|
|
|
|
CleanupConnection();
|
|
|
|
return;
|
|
|
|
}
|
2011-06-21 07:49:50 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mStopped)
|
|
|
|
return;
|
|
|
|
mStopped = 1;
|
|
|
|
|
|
|
|
if (mTransport && reason != NS_BASE_STREAM_CLOSED &&
|
|
|
|
!mRequestedClose && !mClientClosed && !mServerClosed) {
|
|
|
|
mRequestedClose = 1;
|
|
|
|
mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1),
|
|
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
|
|
|
mStopOnClose = reason;
|
|
|
|
} else {
|
|
|
|
StopSession(reason);
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseSession is called on orderly shutdown
|
|
|
|
void
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::ReleaseSession()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::HandleExtensions()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
|
|
|
nsCAutoString extensions;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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(("WebSocketChannel::OnStartRequest: "
|
|
|
|
"HTTP Sec-WebSocket-Exensions negotiated unknown value %s\n",
|
|
|
|
extensions.get()));
|
|
|
|
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!mAllowCompression) {
|
|
|
|
LOG(("WebSocketChannel::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(("WebSocketChannel:: 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(("WebSocketChannel:: 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(("WebSocketChannel:: Cannot find inflate stream\n"));
|
|
|
|
AbortSession(NS_ERROR_UNEXPECTED);
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
mCompressor = new nsWSCompression(this, mSocketOut);
|
|
|
|
if (!mCompressor->Active()) {
|
|
|
|
LOG(("WebSocketChannel:: Cannot init deflate object\n"));
|
|
|
|
delete mCompressor;
|
|
|
|
mCompressor = nsnull;
|
|
|
|
AbortSession(NS_ERROR_UNEXPECTED);
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
2011-08-03 20:46:13 -07:00
|
|
|
mNegotiatedExtensions = extensions;
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::SetupRequest()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::SetupRequest() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mLoadGroup) {
|
|
|
|
rv = mHttpChannel->SetLoadGroup(mLoadGroup);
|
2011-05-21 18:27:52 -07:00
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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 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);
|
|
|
|
|
|
|
|
PRUint8 *secKey;
|
|
|
|
nsCAutoString secKeyString;
|
|
|
|
|
|
|
|
rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
char* b64 = PL_Base64Encode((const char *)secKey, 16, nsnull);
|
|
|
|
NS_Free(secKey);
|
|
|
|
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);
|
2011-08-02 16:55:17 -07:00
|
|
|
LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
|
2011-07-04 21:18:34 -07:00
|
|
|
|
|
|
|
// 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);
|
2011-08-02 16:55:17 -07:00
|
|
|
LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
|
2011-07-04 21:18:34 -07:00
|
|
|
mHashedSecret.get()));
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::ApplyForAdmission()
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// Websockets has a policy of 1 session at a time being allowed in the
|
|
|
|
// CONNECTING state per server IP address (not hostname)
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCString hostName;
|
|
|
|
rv = mURI->GetHost(hostName);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mAddress = hostName;
|
|
|
|
|
|
|
|
// expect the callback in ::OnLookupComplete
|
2011-08-02 16:55:17 -07:00
|
|
|
LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-06-25 11:28:27 -07:00
|
|
|
// Called after both OnStartRequest and OnTransportAvailable have
|
|
|
|
// executed. This essentially ends the handshake and starts the websockets
|
|
|
|
// protocol state machine.
|
|
|
|
nsresult
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::StartWebsocketData()
|
2011-06-25 11:28:27 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::StartWebsocketData() %p", this));
|
|
|
|
|
|
|
|
if (sWebSocketAdmissions &&
|
|
|
|
sWebSocketAdmissions->ConnectedCount() > mMaxConcurrentConnections) {
|
|
|
|
LOG(("WebSocketChannel max concurrency %d exceeded "
|
|
|
|
"in OnTransportAvailable()", mMaxConcurrentConnections));
|
|
|
|
AbortSession(NS_ERROR_SOCKET_CREATE_FAILED);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-06-25 11:28:27 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
|
2011-06-25 11:28:27 -07:00
|
|
|
}
|
|
|
|
|
2011-05-21 18:27:52 -07:00
|
|
|
// nsIDNSListener
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsIDNSRecord *aRecord,
|
|
|
|
nsresult aStatus)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
|
|
|
|
this, aRequest, aRecord, aStatus));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-08-07 06:43:53 -07:00
|
|
|
NS_ABORT_IF_FALSE(aRequest == mDNSRequest || mStopped,
|
|
|
|
"wrong dns request");
|
|
|
|
|
|
|
|
if (mStopped)
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mDNSRequest = nsnull;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// These failures are not fatal - we just use the hostname as the key
|
|
|
|
if (NS_FAILED(aStatus)) {
|
|
|
|
LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
|
|
|
|
} else {
|
|
|
|
nsresult rv = aRecord->GetNextAddrAsString(mAddress);
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sWebSocketAdmissions->ConditionallyConnect(mAddress, this)) {
|
|
|
|
LOG(("WebSocketChannel::OnLookupComplete: Proceeding with Open\n"));
|
|
|
|
} else {
|
|
|
|
LOG(("WebSocketChannel::OnLookupComplete: Deferring Open\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsIInterfaceRequestor
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::GetInterface(const nsIID & iid, void **result NS_OUTPARAM)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::GetInterface() %p\n", this));
|
|
|
|
|
|
|
|
if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
|
|
|
|
return QueryInterface(iid, result);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mCallbacks)
|
|
|
|
return mCallbacks->GetInterface(iid, result);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_ERROR_FAILURE;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsIChannelEventSink
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::AsyncOnChannelRedirect(
|
2011-07-04 21:18:34 -07:00
|
|
|
nsIChannel *oldChannel,
|
|
|
|
nsIChannel *newChannel,
|
|
|
|
PRUint32 flags,
|
|
|
|
nsIAsyncVerifyRedirectCallback *callback)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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(("WebSocketChannel: 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(("WebSocketChannel: Redirect to %s violates encryption rule\n",
|
|
|
|
spec.get()));
|
|
|
|
callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
|
|
|
|
callback->OnRedirectVerifyCallback(rv);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
|
|
|
|
do_QueryInterface(newChannel, &rv);
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
|
|
|
|
callback->OnRedirectVerifyCallback(rv);
|
2011-05-21 18:27:52 -07:00
|
|
|
return NS_OK;
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// The redirect is likely OK
|
|
|
|
|
|
|
|
newChannel->SetNotificationCallbacks(this);
|
|
|
|
mURI = newuri;
|
|
|
|
mHttpChannel = newHttpChannel;
|
|
|
|
mChannel = newUpgradeChannel;
|
|
|
|
rv = SetupRequest();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
|
|
|
|
callback->OnRedirectVerifyCallback(rv);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
sWebSocketAdmissions->Complete(this);
|
2011-07-04 21:18:34 -07:00
|
|
|
mAddress.Truncate();
|
|
|
|
mRedirectCallback = callback;
|
|
|
|
|
2011-08-07 06:44:19 -07:00
|
|
|
mChannelWasOpened = 0;
|
2011-08-07 06:43:53 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = ApplyForAdmission();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
|
|
|
|
callback->OnRedirectVerifyCallback(rv);
|
|
|
|
mRedirectCallback = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsITimerCallback
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::Notify(nsITimer *timer)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (timer == mCloseTimer) {
|
|
|
|
NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close");
|
|
|
|
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
|
2011-05-21 18:27:52 -07:00
|
|
|
"not socket thread");
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mCloseTimer = nsnull;
|
|
|
|
if (mStopped || mServerClosed) /* no longer relevant */
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel:: 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");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mOpenTimer = nsnull;
|
|
|
|
LOG(("WebSocketChannel:: 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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!mPingOutstanding) {
|
|
|
|
LOG(("nsWebSocketChannel:: Generating Ping\n"));
|
|
|
|
mPingOutstanding = 1;
|
|
|
|
GeneratePing();
|
|
|
|
mPingTimer->InitWithCallback(this, mPingResponseTimeout,
|
|
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
} else {
|
|
|
|
LOG(("nsWebSocketChannel:: Timed out Ping\n"));
|
|
|
|
mPingTimer = nsnull;
|
|
|
|
AbortSession(NS_ERROR_NET_TIMEOUT);
|
|
|
|
}
|
|
|
|
} else if (timer == mLingeringCloseTimer) {
|
|
|
|
LOG(("WebSocketChannel:: Lingering Close Timer"));
|
|
|
|
CleanupConnection();
|
|
|
|
} else {
|
|
|
|
NS_ABORT_IF_FALSE(0, "Unknown Timer");
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::AsyncOpen(nsIURI *aURI,
|
2011-07-04 21:18:34 -07:00
|
|
|
const nsACString &aOrigin,
|
|
|
|
nsIWebSocketListener *aListener,
|
|
|
|
nsISupports *aContext)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (!aURI || !aListener) {
|
|
|
|
LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mListener)
|
|
|
|
return NS_ERROR_ALREADY_OPENED;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsresult rv;
|
2011-06-02 17:48:06 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("unable to continue without socket transport service");
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mRandomGenerator =
|
|
|
|
do_GetService("@mozilla.org/security/random-generator;1", &rv);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("unable to continue without random number generator");
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
mOpenTimeout = NS_CLAMP(intpref, 1, 1800) * 1000;
|
2011-06-25 11:28:27 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
|
|
|
|
&intpref);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
mPingTimeout = NS_CLAMP(intpref, 0, 86400) * 1000;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
|
|
|
|
&intpref);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
mPingResponseTimeout = NS_CLAMP(intpref, 1, 3600) * 1000;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = prefService->GetIntPref
|
|
|
|
("network.websocket.max-connections", &intpref);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
mMaxConcurrentConnections = NS_CLAMP(intpref, 1, 0xffff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sWebSocketAdmissions &&
|
|
|
|
sWebSocketAdmissions->ConnectedCount() >= mMaxConcurrentConnections)
|
|
|
|
{
|
|
|
|
// Checking this early creates an optimal fast-fail, but it is
|
|
|
|
// also a time-of-check-time-of-use problem. So we will check again
|
|
|
|
// after the handshake is complete to catch anything that sneaks
|
|
|
|
// through the race condition.
|
|
|
|
LOG(("WebSocketChannel: max concurrency %d exceeded",
|
|
|
|
mMaxConcurrentConnections));
|
|
|
|
|
|
|
|
// WebSocket connections are expected to be long lived, so return
|
|
|
|
// an error here instead of queueing
|
|
|
|
return NS_ERROR_SOCKET_CREATE_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
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(("WebSocketChannel 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;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// Pass most GetInterface() requests through to our instantiator, but handle
|
|
|
|
// nsIChannelEventSink in this object in order to deal with redirects
|
|
|
|
localChannel->SetNotificationCallbacks(this);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
mChannel = do_QueryInterface(localChannel, &rv);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
|
|
|
|
mHttpChannel = do_QueryInterface(localChannel, &rv);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = SetupRequest();
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return ApplyForAdmission();
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-08-03 12:15:25 -07:00
|
|
|
WebSocketChannel::Close(PRUint16 code, const nsACString & reason)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::Close() %p\n", this));
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
2011-08-02 16:55:17 -07:00
|
|
|
|
|
|
|
if (!mTransport) {
|
|
|
|
LOG(("WebSocketChannel::Close() without transport - aborting."));
|
|
|
|
AbortSession(NS_ERROR_NOT_CONNECTED);
|
|
|
|
return NS_ERROR_NOT_CONNECTED;
|
|
|
|
}
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mRequestedClose) {
|
|
|
|
LOG(("WebSocketChannel:: Double close error\n"));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
// The API requires the UTF-8 string to be 123 or less bytes
|
|
|
|
if (reason.Length() > 123)
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-08-03 12:15:25 -07:00
|
|
|
mRequestedClose = 1;
|
|
|
|
mScriptCloseReason = reason;
|
|
|
|
mScriptCloseCode = code;
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1),
|
|
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::SendMsg(const nsACString &aMsg)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::SendMsg() %p\n", this));
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
|
|
|
|
|
|
|
if (mRequestedClose) {
|
|
|
|
LOG(("WebSocketChannel:: SendMsg when closed error\n"));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStopped) {
|
|
|
|
LOG(("WebSocketChannel:: SendMsg when stopped error\n"));
|
|
|
|
return NS_ERROR_NOT_CONNECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mSocketThread->Dispatch(
|
|
|
|
new nsPostMessage(this, new nsCString(aMsg), -1),
|
|
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
|
|
|
|
|
|
|
if (mRequestedClose) {
|
|
|
|
LOG(("WebSocketChannel:: SendBinaryMsg when closed error\n"));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStopped) {
|
|
|
|
LOG(("WebSocketChannel:: SendBinaryMsg when stopped error\n"));
|
|
|
|
return NS_ERROR_NOT_CONNECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mSocketThread->Dispatch(new nsPostMessage(this, new nsCString(aMsg),
|
|
|
|
aMsg.Length()),
|
|
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsIAsyncInputStream *aSocketIn,
|
|
|
|
nsIAsyncOutputStream *aSocketOut)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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");
|
|
|
|
NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn");
|
|
|
|
|
|
|
|
mTransport = aTransport;
|
|
|
|
mSocketIn = aSocketIn;
|
|
|
|
mSocketOut = aSocketOut;
|
|
|
|
if (sWebSocketAdmissions)
|
|
|
|
sWebSocketAdmissions->IncrementConnectedCount();
|
|
|
|
|
|
|
|
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 StartWebsocketData();
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsIRequestObserver (from nsIStreamListener)
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsISupports *aContext)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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
|
|
|
|
|
2011-08-07 06:43:53 -07:00
|
|
|
if (sWebSocketAdmissions->Complete(this))
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnStartRequest: Starting Pending Open\n"));
|
|
|
|
else
|
|
|
|
LOG(("WebSocketChannel::OnStartRequest: No More Pending Opens\n"));
|
|
|
|
|
|
|
|
if (mOpenTimer) {
|
|
|
|
mOpenTimer->Cancel();
|
|
|
|
mOpenTimer = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStopped) {
|
|
|
|
LOG(("WebSocketChannel::OnStartRequest: Channel 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(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
|
|
|
|
AbortSession(NS_ERROR_CONNECTION_REFUSED);
|
|
|
|
return NS_ERROR_CONNECTION_REFUSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG(("WebSocketChannel::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(("WebSocketChannel::OnStartRequest: "
|
|
|
|
"HTTP response header Upgrade: websocket not found\n"));
|
|
|
|
AbortSession(rv);
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsCAutoString respConnection;
|
|
|
|
rv = mHttpChannel->GetResponseHeader(
|
|
|
|
NS_LITERAL_CSTRING("Connection"), respConnection);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG(("WebSocketChannel::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(("WebSocketChannel::OnStartRequest: "
|
|
|
|
"HTTP response header Sec-WebSocket-Accept check failed\n"));
|
|
|
|
LOG(("WebSocketChannel::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;
|
2011-05-21 18:27:52 -07:00
|
|
|
rv = mHttpChannel->GetResponseHeader(
|
2011-07-04 21:18:34 -07:00
|
|
|
NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
|
|
|
|
respProtocol);
|
2011-05-21 18:27:52 -07:00
|
|
|
if (NS_SUCCEEDED(rv)) {
|
2011-07-04 21:18:34 -07:00
|
|
|
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(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
|
|
|
|
respProtocol.get()));
|
|
|
|
mProtocol = respProtocol;
|
|
|
|
} else {
|
|
|
|
LOG(("WebsocketChannel::OnStartRequest: "
|
|
|
|
"subprotocol [%s] not found - %s returned",
|
|
|
|
mProtocol.get(), respProtocol.get()));
|
|
|
|
mProtocol.Truncate();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOG(("WebsocketChannel::OnStartRequest "
|
2011-05-21 18:27:52 -07:00
|
|
|
"subprotocol [%s] not found - none returned",
|
|
|
|
mProtocol.get()));
|
2011-07-04 21:18:34 -07:00
|
|
|
mProtocol.Truncate();
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
rv = HandleExtensions();
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnStartRequest: Notifying Listener %p\n",
|
|
|
|
mListener.get()));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mListener)
|
|
|
|
mListener->OnStart(mContext);
|
|
|
|
|
|
|
|
mRecvdHttpOnStartRequest = 1;
|
|
|
|
if (mRecvdHttpUpgradeTransport)
|
|
|
|
return StartWebsocketData();
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsISupports *aContext,
|
|
|
|
nsresult aStatusCode)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
|
|
|
|
this, aRequest, aContext, aStatusCode));
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
|
|
|
|
|
|
|
// This is the end of the HTTP upgrade transaction, the
|
|
|
|
// upgraded streams live on
|
|
|
|
|
|
|
|
mChannel = nsnull;
|
|
|
|
mHttpChannel = nsnull;
|
|
|
|
mLoadGroup = nsnull;
|
|
|
|
mCallbacks = nsnull;
|
|
|
|
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsIInputStreamCallback
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
|
|
|
|
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
nsRefPtr<nsIStreamListener> deleteProtector1(mInflateReader);
|
|
|
|
nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream);
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// this is after the http upgrade - so we are speaking websockets
|
|
|
|
char buffer[2048];
|
|
|
|
PRUint32 count;
|
|
|
|
nsresult rv;
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
do {
|
|
|
|
rv = mSocketIn->Read((char *)buffer, 2048, &count);
|
|
|
|
LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
|
|
mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
mTCPClosed = PR_TRUE;
|
|
|
|
AbortSession(rv);
|
|
|
|
return rv;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (count == 0) {
|
|
|
|
mTCPClosed = PR_TRUE;
|
|
|
|
AbortSession(NS_BASE_STREAM_CLOSED);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStopped) {
|
|
|
|
NS_ABORT_IF_FALSE(mLingeringCloseTimer,
|
|
|
|
"OnInputReady after stop without linger");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// nsIOutputStreamCallback
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
|
2011-05-21 18:27:52 -07:00
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::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(("WebSocketChannel::OnOutputStreamReady: "
|
|
|
|
"Try to send %u of hdr/copybreak\n", toSend));
|
|
|
|
} else {
|
|
|
|
sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
|
|
|
|
toSend = mCurrentOut->Length() - mCurrentOutSent;
|
|
|
|
if (toSend > 0) {
|
|
|
|
LOG(("WebSocketChannel::OnOutputStreamReady: "
|
|
|
|
"Try to send %u of data\n", toSend));
|
|
|
|
}
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (toSend == 0) {
|
|
|
|
amtSent = 0;
|
|
|
|
} else {
|
|
|
|
rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
|
|
|
|
LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
|
|
|
|
amtSent, rv));
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
|
|
mSocketOut->AsyncWait(this, 0, 0, nsnull);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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) {
|
|
|
|
NS_DispatchToMainThread(new CallAcknowledge(mListener, mContext,
|
|
|
|
mCurrentOut->Length()));
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
delete mCurrentOut;
|
|
|
|
mCurrentOut = nsnull;
|
|
|
|
mCurrentOutSent = 0;
|
|
|
|
PrimeNewOutgoingMessage();
|
|
|
|
} else {
|
|
|
|
mCurrentOutSent += amtSent;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (mReleaseOnTransmit)
|
|
|
|
ReleaseSession();
|
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// nsIStreamListener
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
2011-07-04 21:18:33 -07:00
|
|
|
WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
|
2011-05-21 18:27:52 -07:00
|
|
|
nsISupports *aContext,
|
|
|
|
nsIInputStream *aInputStream,
|
|
|
|
PRUint32 aOffset,
|
|
|
|
PRUint32 aCount)
|
|
|
|
{
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %u %u]\n",
|
2011-05-21 18:27:52 -07:00
|
|
|
this, aRequest, aContext, aInputStream, aOffset, aCount));
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (aContext == mSocketIn) {
|
|
|
|
// This is the deflate decoder
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
LOG(("WebSocketChannel::OnDataAvailable: Deflate Data %u\n",
|
2011-05-21 18:27:52 -07:00
|
|
|
aCount));
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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(("WebSocketChannel::OnDataAvailable: InflateRead read %u rv %x\n",
|
|
|
|
count, rv));
|
|
|
|
if (NS_FAILED(rv) || count == 0) {
|
|
|
|
AbortSession(rv);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
aCount -= count;
|
|
|
|
rv = ProcessInput(buffer, count);
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
if (aContext == mSocketOut) {
|
|
|
|
// This is the deflate encoder
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
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(("WebSocketChannel::OnDataAvailable: DeflateWrite read %u rv %x\n",
|
|
|
|
count, rv));
|
|
|
|
if (NS_FAILED(rv) || count == 0) {
|
|
|
|
AbortSession(rv);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
mHdrOutToSend += count;
|
|
|
|
aCount -= count;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2011-05-21 18:27:52 -07:00
|
|
|
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// 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
|
2011-05-21 18:27:52 -07:00
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
// This generally should be caught by a non 101 response code in
|
|
|
|
// OnStartRequest().. so we can ignore the data here
|
|
|
|
|
|
|
|
LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
|
2011-05-21 18:27:52 -07:00
|
|
|
aCount));
|
|
|
|
|
2011-07-04 21:18:34 -07:00
|
|
|
return NS_OK;
|
2011-05-21 18:27:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace mozilla::net
|
|
|
|
} // namespace mozilla
|