mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Bug 1272100: Websocket changes in preparation of FlyWeb landing. Make websocket code support acting as the server side of a websocket connection. r=michal
This commit is contained in:
parent
1c8273e348
commit
2e9e463e19
@ -86,6 +86,7 @@ public:
|
||||
|
||||
explicit WebSocketImpl(WebSocket* aWebSocket)
|
||||
: mWebSocket(aWebSocket)
|
||||
, mIsServerSide(false)
|
||||
, mOnCloseScheduled(false)
|
||||
, mFailed(false)
|
||||
, mDisconnectingOrDisconnected(false)
|
||||
@ -118,6 +119,7 @@ public:
|
||||
|
||||
void Init(JSContext* aCx,
|
||||
nsIPrincipal* aPrincipal,
|
||||
bool aIsServerSide,
|
||||
const nsAString& aURL,
|
||||
nsTArray<nsString>& aProtocolArray,
|
||||
const nsACString& aScriptFile,
|
||||
@ -127,6 +129,8 @@ public:
|
||||
bool* aConnectionFailed);
|
||||
|
||||
void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
|
||||
nsITransportProvider* aTransportProvider,
|
||||
const nsACString& aNegotiatedExtensions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsresult ParseURL(const nsAString& aURL);
|
||||
@ -169,6 +173,9 @@ public:
|
||||
|
||||
nsCOMPtr<nsIWebSocketChannel> mChannel;
|
||||
|
||||
bool mIsServerSide; // True if we're implementing the server side of a
|
||||
// websocket connection
|
||||
|
||||
bool mSecure; // if true it is using SSL and the wss scheme,
|
||||
// otherwise it is using the ws scheme with no SSL
|
||||
|
||||
@ -951,7 +958,8 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
Sequence<nsString> protocols;
|
||||
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
|
||||
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
|
||||
EmptyCString(), aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<WebSocket>
|
||||
@ -966,7 +974,18 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
|
||||
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
|
||||
EmptyCString(), aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<WebSocket>
|
||||
WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aUrl,
|
||||
const Sequence<nsString>& aProtocols,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
|
||||
EmptyCString(), aRv);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -1027,7 +1046,8 @@ protected:
|
||||
class InitRunnable final : public WebSocketMainThreadRunnable
|
||||
{
|
||||
public:
|
||||
InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL,
|
||||
InitRunnable(WebSocketImpl* aImpl, bool aIsServerSide,
|
||||
const nsAString& aURL,
|
||||
nsTArray<nsString>& aProtocolArray,
|
||||
const nsACString& aScriptFile, uint32_t aScriptLine,
|
||||
uint32_t aScriptColumn,
|
||||
@ -1035,6 +1055,7 @@ public:
|
||||
: WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
|
||||
NS_LITERAL_CSTRING("WebSocket :: init"))
|
||||
, mImpl(aImpl)
|
||||
, mIsServerSide(aIsServerSide)
|
||||
, mURL(aURL)
|
||||
, mProtocolArray(aProtocolArray)
|
||||
, mScriptFile(aScriptFile)
|
||||
@ -1070,8 +1091,9 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
mImpl->Init(jsapi.cx(), principal, mURL, mProtocolArray, mScriptFile,
|
||||
mScriptLine, mScriptColumn, mRv, mConnectionFailed);
|
||||
mImpl->Init(jsapi.cx(), principal, mIsServerSide, mURL, mProtocolArray,
|
||||
mScriptFile, mScriptLine, mScriptColumn, mRv,
|
||||
mConnectionFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1080,15 +1102,16 @@ protected:
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
|
||||
|
||||
mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mURL,
|
||||
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn, mRv,
|
||||
mConnectionFailed);
|
||||
mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide,
|
||||
mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn,
|
||||
mRv, mConnectionFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Raw pointer. This worker runs synchronously.
|
||||
WebSocketImpl* mImpl;
|
||||
|
||||
bool mIsServerSide;
|
||||
const nsAString& mURL;
|
||||
nsTArray<nsString>& mProtocolArray;
|
||||
nsCString mScriptFile;
|
||||
@ -1140,7 +1163,7 @@ protected:
|
||||
windowID = topInner->WindowID();
|
||||
}
|
||||
|
||||
mImpl->AsyncOpen(principal, windowID, mRv);
|
||||
mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), mRv);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1149,7 +1172,8 @@ protected:
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
|
||||
|
||||
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv);
|
||||
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, nullptr,
|
||||
EmptyCString(), mRv);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1163,11 +1187,14 @@ private:
|
||||
} // namespace
|
||||
|
||||
already_AddRefed<WebSocket>
|
||||
WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aUrl,
|
||||
const Sequence<nsString>& aProtocols,
|
||||
ErrorResult& aRv)
|
||||
WebSocket::ConstructorCommon(const GlobalObject& aGlobal,
|
||||
const nsAString& aUrl,
|
||||
const Sequence<nsString>& aProtocols,
|
||||
nsITransportProvider* aTransportProvider,
|
||||
const nsACString& aNegotiatedExtensions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsCOMPtr<nsPIDOMWindowInner> ownerWindow;
|
||||
|
||||
@ -1229,8 +1256,9 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
bool connectionFailed = true;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
webSocket->mImpl->Init(aGlobal.Context(), principal, aUrl, protocolArray,
|
||||
EmptyCString(), 0, 0, aRv, &connectionFailed);
|
||||
webSocket->mImpl->Init(aGlobal.Context(), principal, !!aTransportProvider,
|
||||
aUrl, protocolArray, EmptyCString(),
|
||||
0, 0, aRv, &connectionFailed);
|
||||
} else {
|
||||
// In workers we have to keep the worker alive using a feature in order to
|
||||
// dispatch messages correctly.
|
||||
@ -1247,9 +1275,9 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
}
|
||||
|
||||
RefPtr<InitRunnable> runnable =
|
||||
new InitRunnable(webSocket->mImpl, aUrl, protocolArray,
|
||||
nsDependentCString(file.get()), lineno, column, aRv,
|
||||
&connectionFailed);
|
||||
new InitRunnable(webSocket->mImpl, !!aTransportProvider, aUrl,
|
||||
protocolArray, nsDependentCString(file.get()), lineno,
|
||||
column, aRv, &connectionFailed);
|
||||
runnable->Dispatch(aRv);
|
||||
}
|
||||
|
||||
@ -1327,8 +1355,11 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
windowID = topInner->WindowID();
|
||||
}
|
||||
|
||||
webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
|
||||
webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
|
||||
aNegotiatedExtensions, aRv);
|
||||
} else {
|
||||
MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
|
||||
"not yet implemented");
|
||||
RefPtr<AsyncOpenRunnable> runnable =
|
||||
new AsyncOpenRunnable(webSocket->mImpl, aRv);
|
||||
runnable->Dispatch(aRv);
|
||||
@ -1425,6 +1456,7 @@ WebSocket::DisconnectFromOwner()
|
||||
void
|
||||
WebSocketImpl::Init(JSContext* aCx,
|
||||
nsIPrincipal* aPrincipal,
|
||||
bool aIsServerSide,
|
||||
const nsAString& aURL,
|
||||
nsTArray<nsString>& aProtocolArray,
|
||||
const nsACString& aScriptFile,
|
||||
@ -1484,6 +1516,8 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
}
|
||||
}
|
||||
|
||||
mIsServerSide = aIsServerSide;
|
||||
|
||||
// If we don't have aCx, we are window-less, so we don't have a
|
||||
// inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
|
||||
// DedicateWorkers created by JSM.
|
||||
@ -1497,19 +1531,6 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
{
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
|
||||
|
||||
// We crash here because we are sure that mURI is a valid URI, so either we
|
||||
// are OOM'ing or something else bad is happening.
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
// Check content policy.
|
||||
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
nsCOMPtr<nsIDocument> originDoc = mWebSocket->GetDocumentIfCurrent();
|
||||
if (!originDoc) {
|
||||
nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
|
||||
@ -1518,25 +1539,40 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mOriginDocument = do_GetWeakReference(originDoc);
|
||||
aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
|
||||
uri,
|
||||
aPrincipal,
|
||||
originDoc,
|
||||
EmptyCString(),
|
||||
nullptr,
|
||||
&shouldLoad,
|
||||
nsContentUtils::GetContentPolicy(),
|
||||
nsContentUtils::GetSecurityManager());
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_CP_REJECTED(shouldLoad)) {
|
||||
// Disallowed by content policy.
|
||||
aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
|
||||
return;
|
||||
if (!mIsServerSide) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
{
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
|
||||
|
||||
// We crash here because we are sure that mURI is a valid URI, so either we
|
||||
// are OOM'ing or something else bad is happening.
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
// Check content policy.
|
||||
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
|
||||
uri,
|
||||
aPrincipal,
|
||||
originDoc,
|
||||
EmptyCString(),
|
||||
nullptr,
|
||||
&shouldLoad,
|
||||
nsContentUtils::GetContentPolicy(),
|
||||
nsContentUtils::GetSecurityManager());
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NS_CP_REJECTED(shouldLoad)) {
|
||||
// Disallowed by content policy.
|
||||
aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
|
||||
@ -1544,7 +1580,8 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
// to reflect that upgrade. Please note that we can not upgrade from ws:
|
||||
// to wss: before performing content policy checks because CSP needs to
|
||||
// send reports in case the scheme is about to be upgraded.
|
||||
if (!mSecure && originDoc && originDoc->GetUpgradeInsecureRequests(false)) {
|
||||
if (!mIsServerSide && !mSecure && originDoc &&
|
||||
originDoc->GetUpgradeInsecureRequests(false)) {
|
||||
// let's use the old specification before the upgrade for logging
|
||||
NS_ConvertUTF8toUTF16 reportSpec(mURI);
|
||||
|
||||
@ -1567,7 +1604,7 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
}
|
||||
|
||||
// Don't allow https:// to open ws://
|
||||
if (!mSecure &&
|
||||
if (!mIsServerSide && !mSecure &&
|
||||
!Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
|
||||
false)) {
|
||||
// Confirmed we are opening plain ws:// and want to prevent this from a
|
||||
@ -1704,9 +1741,12 @@ WebSocketImpl::Init(JSContext* aCx,
|
||||
|
||||
void
|
||||
WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
|
||||
nsITransportProvider* aTransportProvider,
|
||||
const nsACString& aNegotiatedExtensions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
|
||||
MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
|
||||
|
||||
nsCString asciiOrigin;
|
||||
aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
|
||||
@ -1714,11 +1754,20 @@ WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTransportProvider) {
|
||||
aRv = mChannel->SetServerParameters(aTransportProvider, aNegotiatedExtensions);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ToLowerCase(asciiOrigin);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
|
||||
MOZ_ASSERT(!aRv.Failed());
|
||||
if (!aTransportProvider) {
|
||||
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
|
||||
MOZ_ASSERT(!aRv.Failed());
|
||||
}
|
||||
|
||||
aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
@ -1984,6 +2033,13 @@ WebSocketImpl::ParseURL(const nsAString& aURL)
|
||||
AssertIsOnMainThread();
|
||||
NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
|
||||
|
||||
if (mIsServerSide) {
|
||||
mWebSocket->mURI = aURL;
|
||||
CopyUTF16toUTF8(mWebSocket->mURI, mURI);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#define DEFAULT_WSS_SCHEME_PORT 443
|
||||
|
||||
class nsIInputStream;
|
||||
class nsITransportProvider;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -82,6 +83,13 @@ public: // WebIDL interface:
|
||||
const Sequence<nsString>& aProtocols,
|
||||
ErrorResult& rv);
|
||||
|
||||
static already_AddRefed<WebSocket> ConstructorCommon(const GlobalObject& aGlobal,
|
||||
const nsAString& aUrl,
|
||||
const Sequence<nsString>& aProtocols,
|
||||
nsITransportProvider* aTransportProvider,
|
||||
const nsACString& aNegotiatedExtensions,
|
||||
ErrorResult& rv);
|
||||
|
||||
// webIDL: readonly attribute DOMString url
|
||||
void GetUrl(nsAString& aResult);
|
||||
|
||||
|
@ -950,6 +950,7 @@ GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
|
||||
GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
|
||||
GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
|
||||
GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
|
||||
GK_ATOM(onwebsocket, "onwebsocket")
|
||||
GK_ATOM(onwheel, "onwheel")
|
||||
GK_ATOM(open, "open")
|
||||
GK_ATOM(optgroup, "optgroup")
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "LoadInfo.h"
|
||||
#include "nsIDOMNode.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "nsITransportProvider.h"
|
||||
|
||||
using mozilla::dom::ContentChild;
|
||||
|
||||
@ -39,6 +40,7 @@ BaseWebSocketChannel::BaseWebSocketChannel()
|
||||
, mClientSetPingTimeout(0)
|
||||
, mEncrypted(0)
|
||||
, mPingForced(0)
|
||||
, mIsServerSide(false)
|
||||
, mPingInterval(0)
|
||||
, mPingResponseTimeout(10000)
|
||||
{
|
||||
@ -243,6 +245,17 @@ BaseWebSocketChannel::SetSerial(uint32_t aSerial)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BaseWebSocketChannel::SetServerParameters(nsITransportProvider* aProvider,
|
||||
const nsACString& aNegotiatedExtensions)
|
||||
{
|
||||
MOZ_ASSERT(aProvider);
|
||||
mServerTransportProvider = aProvider;
|
||||
mNegotiatedExtensions = aNegotiatedExtensions;
|
||||
mIsServerSide = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// BaseWebSocketChannel::nsIProtocolHandler
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -57,6 +57,8 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
|
||||
uint32_t aContentPolicyType) override;
|
||||
NS_IMETHOD GetSerial(uint32_t* aSerial) override;
|
||||
NS_IMETHOD SetSerial(uint32_t aSerial) override;
|
||||
NS_IMETHOD SetServerParameters(nsITransportProvider* aProvider,
|
||||
const nsACString& aNegotiatedExtensions) override;
|
||||
|
||||
// Off main thread URI access.
|
||||
virtual void GetEffectiveURL(nsAString& aEffectiveURL) const = 0;
|
||||
@ -85,6 +87,7 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
|
||||
nsCOMPtr<nsILoadGroup> mLoadGroup;
|
||||
nsCOMPtr<nsILoadInfo> mLoadInfo;
|
||||
nsCOMPtr<nsIEventTarget> mTargetThread;
|
||||
nsCOMPtr<nsITransportProvider> mServerTransportProvider;
|
||||
|
||||
nsCString mProtocol;
|
||||
nsCString mOrigin;
|
||||
@ -97,6 +100,7 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
|
||||
|
||||
Atomic<bool> mEncrypted;
|
||||
bool mPingForced;
|
||||
bool mIsServerSide;
|
||||
|
||||
uint32_t mPingInterval; /* milliseconds */
|
||||
uint32_t mPingResponseTimeout; /* milliseconds */
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsINetworkLinkService.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsITransportProvider.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsNetCID.h"
|
||||
@ -761,8 +763,8 @@ class PMCECompression
|
||||
{
|
||||
public:
|
||||
PMCECompression(bool aNoContextTakeover,
|
||||
int32_t aClientMaxWindowBits,
|
||||
int32_t aServerMaxWindowBits)
|
||||
int32_t aLocalMaxWindowBits,
|
||||
int32_t aRemoteMaxWindowBits)
|
||||
: mActive(false)
|
||||
, mNoContextTakeover(aNoContextTakeover)
|
||||
, mResetDeflater(false)
|
||||
@ -775,8 +777,8 @@ public:
|
||||
mDeflater.opaque = mInflater.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
||||
-aClientMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
|
||||
if (inflateInit2(&mInflater, -aServerMaxWindowBits) == Z_OK) {
|
||||
-aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
|
||||
if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
|
||||
mActive = true;
|
||||
} else {
|
||||
deflateEnd(&mDeflater);
|
||||
@ -1072,7 +1074,7 @@ public:
|
||||
}
|
||||
|
||||
if (!aCompressor->UsingContextTakeover() && temp->Length() > mLength) {
|
||||
// When "client_no_context_takeover" was negotiated, do not send deflated
|
||||
// When "<local>_no_context_takeover" was negotiated, do not send deflated
|
||||
// payload if it's larger that the original one. OTOH, it makes sense
|
||||
// to send the larger deflated payload when the sliding window is not
|
||||
// reset between messages because if we would skip some deflated block
|
||||
@ -1574,15 +1576,32 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
|
||||
opcode));
|
||||
|
||||
if (!maskBit && mIsServerSide) {
|
||||
LOG(("WebSocketChannel::ProcessInput: unmasked frame received "
|
||||
"from client\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
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."));
|
||||
if (!mIsServerSide) {
|
||||
// The server should not be allowed to send masked frames to clients.
|
||||
// But we've been allowing it for some time, so this should be
|
||||
// deprecated with care.
|
||||
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
|
||||
}
|
||||
|
||||
mask = NetworkEndian::readUint32(payload - 4);
|
||||
ApplyMask(mask, payload, payloadLength);
|
||||
}
|
||||
|
||||
if (mask) {
|
||||
ApplyMask(mask, payload, payloadLength);
|
||||
} else if (mIsServerSide) {
|
||||
LOG(("WebSocketChannel::ProcessInput: masked frame with mask 0 received"
|
||||
"from client\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
|
||||
// Control codes are required to have the fin bit set
|
||||
if (!finBit && (opcode & kControlFrameMask)) {
|
||||
LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
|
||||
@ -2021,6 +2040,9 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
mCurrentOutSent = 0;
|
||||
mHdrOut = mOutHeader;
|
||||
|
||||
uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
|
||||
uint8_t maskSize = mIsServerSide ? 0 : 4;
|
||||
|
||||
uint8_t *payload = nullptr;
|
||||
|
||||
if (msgType == kMsgTypeFin) {
|
||||
@ -2033,10 +2055,10 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
|
||||
mClientClosed = 1;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
|
||||
mOutHeader[1] = kMaskBit;
|
||||
mOutHeader[1] = maskBit;
|
||||
|
||||
// payload is offset 6 including 4 for the mask
|
||||
payload = mOutHeader + 6;
|
||||
// payload is offset 2 plus size of the mask
|
||||
payload = mOutHeader + 2 + maskSize;
|
||||
|
||||
// The close reason code sits in the first 2 bytes of payload
|
||||
// If the channel user provided a code and reason during Close()
|
||||
@ -2045,7 +2067,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
if (mScriptCloseCode) {
|
||||
NetworkEndian::writeUint16(payload, mScriptCloseCode);
|
||||
mOutHeader[1] += 2;
|
||||
mHdrOutToSend = 8;
|
||||
mHdrOutToSend = 4 + maskSize;
|
||||
if (!mScriptCloseReason.IsEmpty()) {
|
||||
MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
|
||||
"Close Reason Too Long");
|
||||
@ -2059,12 +2081,12 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
// No close code/reason, so payload length = 0. We must still send mask
|
||||
// even though it's not used. Keep payload offset so we write mask
|
||||
// below.
|
||||
mHdrOutToSend = 6;
|
||||
mHdrOutToSend = 2 + maskSize;
|
||||
}
|
||||
} else {
|
||||
NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
|
||||
mOutHeader[1] += 2;
|
||||
mHdrOutToSend = 8;
|
||||
mHdrOutToSend = 4 + maskSize;
|
||||
}
|
||||
|
||||
if (mServerClosed) {
|
||||
@ -2131,38 +2153,40 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
}
|
||||
|
||||
if (mCurrentOut->Length() < 126) {
|
||||
mOutHeader[1] = mCurrentOut->Length() | kMaskBit;
|
||||
mHdrOutToSend = 6;
|
||||
mOutHeader[1] = mCurrentOut->Length() | maskBit;
|
||||
mHdrOutToSend = 2 + maskSize;
|
||||
} else if (mCurrentOut->Length() <= 0xffff) {
|
||||
mOutHeader[1] = 126 | kMaskBit;
|
||||
mOutHeader[1] = 126 | maskBit;
|
||||
NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
|
||||
mCurrentOut->Length());
|
||||
mHdrOutToSend = 8;
|
||||
mHdrOutToSend = 4 + maskSize;
|
||||
} else {
|
||||
mOutHeader[1] = 127 | kMaskBit;
|
||||
mOutHeader[1] = 127 | maskBit;
|
||||
NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
|
||||
mHdrOutToSend = 14;
|
||||
mHdrOutToSend = 10 + maskSize;
|
||||
}
|
||||
payload = mOutHeader + mHdrOutToSend;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(payload, "payload offset not found");
|
||||
|
||||
// Perform the sending mask. Never use a zero mask
|
||||
uint32_t mask;
|
||||
do {
|
||||
uint8_t *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<uint32_t *>(buffer);
|
||||
free(buffer);
|
||||
} while (!mask);
|
||||
NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
|
||||
uint32_t mask = 0;
|
||||
if (!mIsServerSide) {
|
||||
// Perform the sending mask. Never use a zero mask
|
||||
do {
|
||||
uint8_t *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<uint32_t *>(buffer);
|
||||
free(buffer);
|
||||
} while (!mask);
|
||||
NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
|
||||
}
|
||||
|
||||
LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
|
||||
|
||||
@ -2189,16 +2213,17 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
|
||||
}
|
||||
|
||||
while (payload < (mOutHeader + mHdrOutToSend)) {
|
||||
*payload ^= mask >> 24;
|
||||
mask = RotateLeft(mask, 8);
|
||||
payload++;
|
||||
if (mask) {
|
||||
while (payload < (mOutHeader + mHdrOutToSend)) {
|
||||
*payload ^= mask >> 24;
|
||||
mask = RotateLeft(mask, 8);
|
||||
payload++;
|
||||
}
|
||||
|
||||
// Mask the real message payloads
|
||||
ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
|
||||
}
|
||||
|
||||
// Mask the real message payloads
|
||||
|
||||
ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
|
||||
|
||||
int32_t len = mCurrentOut->Length();
|
||||
|
||||
// for small frames, copy it all together for a contiguous write
|
||||
@ -2313,10 +2338,10 @@ WebSocketChannel::StopSession(nsresult reason)
|
||||
|
||||
if (!mOpenedHttpChannel) {
|
||||
// The HTTP channel information will never be used in this case
|
||||
mChannel = nullptr;
|
||||
mHttpChannel = nullptr;
|
||||
mLoadGroup = nullptr;
|
||||
mCallbacks = nullptr;
|
||||
NS_ReleaseOnMainThread(mChannel.forget());
|
||||
NS_ReleaseOnMainThread(mHttpChannel.forget());
|
||||
NS_ReleaseOnMainThread(mLoadGroup.forget());
|
||||
NS_ReleaseOnMainThread(mCallbacks.forget());
|
||||
}
|
||||
|
||||
if (mCloseTimer) {
|
||||
@ -2477,6 +2502,128 @@ WebSocketChannel::DecrementSessionCount()
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
enum ExtensionParseMode { eParseServerSide, eParseClientSide };
|
||||
}
|
||||
|
||||
static nsresult
|
||||
ParseWebSocketExtension(const nsACString& aExtension,
|
||||
ExtensionParseMode aMode,
|
||||
bool& aClientNoContextTakeover,
|
||||
bool& aServerNoContextTakeover,
|
||||
int32_t& aClientMaxWindowBits,
|
||||
int32_t& aServerMaxWindowBits)
|
||||
{
|
||||
nsCCharSeparatedTokenizer tokens(aExtension, ';');
|
||||
|
||||
if (!tokens.hasMoreTokens() ||
|
||||
!tokens.nextToken().Equals(NS_LITERAL_CSTRING("permessage-deflate"))) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: "
|
||||
"HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
|
||||
PromiseFlatCString(aExtension).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
aClientNoContextTakeover = aServerNoContextTakeover = false;
|
||||
aClientMaxWindowBits = aServerMaxWindowBits = -1;
|
||||
|
||||
while (tokens.hasMoreTokens()) {
|
||||
auto token = tokens.nextToken();
|
||||
|
||||
int32_t nameEnd, valueStart;
|
||||
int32_t delimPos = token.FindChar('=');
|
||||
if (delimPos == kNotFound) {
|
||||
nameEnd = token.Length();
|
||||
valueStart = token.Length();
|
||||
} else {
|
||||
nameEnd = delimPos;
|
||||
valueStart = delimPos + 1;
|
||||
}
|
||||
|
||||
auto paramName = Substring(token, 0, nameEnd);
|
||||
auto paramValue = Substring(token, valueStart);
|
||||
|
||||
if (paramName.EqualsLiteral("client_no_context_takeover")) {
|
||||
if (!paramValue.IsEmpty()) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
|
||||
"client_no_context_takeover must not have value, found %s\n",
|
||||
PromiseFlatCString(paramValue).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (aClientNoContextTakeover) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
|
||||
"parameters client_no_context_takeover\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
aClientNoContextTakeover = true;
|
||||
} else if (paramName.EqualsLiteral("server_no_context_takeover")) {
|
||||
if (!paramValue.IsEmpty()) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
|
||||
"server_no_context_takeover must not have value, found %s\n",
|
||||
PromiseFlatCString(paramValue).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (aServerNoContextTakeover) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
|
||||
"parameters server_no_context_takeover\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
aServerNoContextTakeover = true;
|
||||
} else if (paramName.EqualsLiteral("client_max_window_bits")) {
|
||||
if (aClientMaxWindowBits != -1) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
|
||||
"parameters client_max_window_bits\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
if (aMode == eParseServerSide && paramValue.IsEmpty()) {
|
||||
// Use -2 to indicate that "client_max_window_bits" has been parsed,
|
||||
// but had no value.
|
||||
aClientMaxWindowBits = -2;
|
||||
}
|
||||
else {
|
||||
nsresult errcode;
|
||||
aClientMaxWindowBits =
|
||||
PromiseFlatCString(paramValue).ToInteger(&errcode);
|
||||
if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 ||
|
||||
aClientMaxWindowBits > 15) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
|
||||
"parameter client_max_window_bits %s\n",
|
||||
PromiseFlatCString(paramValue).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
} else if (paramName.EqualsLiteral("server_max_window_bits")) {
|
||||
if (aServerMaxWindowBits != -1) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
|
||||
"parameters server_max_window_bits\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
nsresult errcode;
|
||||
aServerMaxWindowBits =
|
||||
PromiseFlatCString(paramValue).ToInteger(&errcode);
|
||||
if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 ||
|
||||
aServerMaxWindowBits > 15) {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
|
||||
"parameter server_max_window_bits %s\n",
|
||||
PromiseFlatCString(paramValue).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
} else {
|
||||
LOG(("WebSocketChannel::ParseWebSocketExtension: found unknown "
|
||||
"parameter %s\n", PromiseFlatCString(paramName).get()));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (aClientMaxWindowBits == -2) {
|
||||
aClientMaxWindowBits = -1;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
WebSocketChannel::HandleExtensions()
|
||||
{
|
||||
@ -2489,168 +2636,137 @@ WebSocketChannel::HandleExtensions()
|
||||
|
||||
rv = mHttpChannel->GetResponseHeader(
|
||||
NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: received "
|
||||
"Sec-WebSocket-Extensions header: %s\n", extensions.get()));
|
||||
extensions.CompressWhitespace();
|
||||
if (extensions.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
extensions.CompressWhitespace();
|
||||
LOG(("WebSocketChannel::HandleExtensions: received "
|
||||
"Sec-WebSocket-Extensions header: %s\n", extensions.get()));
|
||||
|
||||
if (!extensions.IsEmpty()) {
|
||||
if (StringBeginsWith(extensions,
|
||||
NS_LITERAL_CSTRING("permessage-deflate"))) {
|
||||
if (!mAllowPMCE) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: "
|
||||
"Recvd permessage-deflate which wasn't offered\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
bool clientNoContextTakeover;
|
||||
bool serverNoContextTakeover;
|
||||
int32_t clientMaxWindowBits;
|
||||
int32_t serverMaxWindowBits;
|
||||
|
||||
bool skipExtensionName = false;
|
||||
bool clientNoContextTakeover = false;
|
||||
bool serverNoContextTakeover = false;
|
||||
int32_t clientMaxWindowBits = -1;
|
||||
int32_t serverMaxWindowBits = -1;
|
||||
rv = ParseWebSocketExtension(extensions,
|
||||
eParseClientSide,
|
||||
clientNoContextTakeover,
|
||||
serverNoContextTakeover,
|
||||
clientMaxWindowBits,
|
||||
serverMaxWindowBits);
|
||||
if (NS_FAILED(rv)) {
|
||||
AbortSession(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
while (!extensions.IsEmpty()) {
|
||||
nsAutoCString paramName;
|
||||
nsAutoCString paramValue;
|
||||
if (!mAllowPMCE) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: "
|
||||
"Recvd permessage-deflate which wasn't offered\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
int32_t delimPos = extensions.FindChar(';');
|
||||
if (delimPos != kNotFound) {
|
||||
paramName = Substring(extensions, 0, delimPos);
|
||||
extensions = Substring(extensions, delimPos + 1);
|
||||
} else {
|
||||
paramName = extensions;
|
||||
extensions.Truncate();
|
||||
}
|
||||
paramName.CompressWhitespace();
|
||||
extensions.CompressWhitespace();
|
||||
if (clientMaxWindowBits == -1) {
|
||||
clientMaxWindowBits = 15;
|
||||
}
|
||||
if (serverMaxWindowBits == -1) {
|
||||
serverMaxWindowBits = 15;
|
||||
}
|
||||
|
||||
delimPos = paramName.FindChar('=');
|
||||
if (delimPos != kNotFound) {
|
||||
paramValue = Substring(paramName, delimPos + 1);
|
||||
paramName.Truncate(delimPos);
|
||||
}
|
||||
mPMCECompressor = new PMCECompression(clientNoContextTakeover,
|
||||
clientMaxWindowBits,
|
||||
serverMaxWindowBits);
|
||||
if (mPMCECompressor->Active()) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
|
||||
"context takeover, clientMaxWindowBits=%d, "
|
||||
"serverMaxWindowBits=%d\n",
|
||||
clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
|
||||
serverMaxWindowBits));
|
||||
|
||||
if (!skipExtensionName) {
|
||||
skipExtensionName = true;
|
||||
if (!paramName.EqualsLiteral("permessage-deflate") ||
|
||||
!paramValue.IsEmpty()) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: HTTP "
|
||||
"Sec-WebSocket-Extensions negotiated unknown extension\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
} else if (paramName.EqualsLiteral("client_no_context_takeover")) {
|
||||
if (!paramValue.IsEmpty()) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: parameter "
|
||||
"client_no_context_takeover must not have value, found %s\n",
|
||||
paramValue.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (clientNoContextTakeover) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found multiple "
|
||||
"parameters client_no_context_takeover\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
clientNoContextTakeover = true;
|
||||
} else if (paramName.EqualsLiteral("server_no_context_takeover")) {
|
||||
if (!paramValue.IsEmpty()) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: parameter "
|
||||
"server_no_context_takeover must not have value, found %s\n",
|
||||
paramValue.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
if (serverNoContextTakeover) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found multiple "
|
||||
"parameters server_no_context_takeover\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
serverNoContextTakeover = true;
|
||||
} else if (paramName.EqualsLiteral("client_max_window_bits")) {
|
||||
if (clientMaxWindowBits != -1) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found multiple "
|
||||
"parameters client_max_window_bits\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
nsresult errcode;
|
||||
clientMaxWindowBits = paramValue.ToInteger(&errcode);
|
||||
if (NS_FAILED(errcode) || clientMaxWindowBits < 8 ||
|
||||
clientMaxWindowBits > 15) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found invalid "
|
||||
"parameter client_max_window_bits %s\n", paramValue.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
} else if (paramName.EqualsLiteral("server_max_window_bits")) {
|
||||
if (serverMaxWindowBits != -1) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found multiple "
|
||||
"parameters server_max_window_bits\n"));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
nsresult errcode;
|
||||
serverMaxWindowBits = paramValue.ToInteger(&errcode);
|
||||
if (NS_FAILED(errcode) || serverMaxWindowBits < 8 ||
|
||||
serverMaxWindowBits > 15) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found invalid "
|
||||
"parameter server_max_window_bits %s\n", paramValue.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
} else {
|
||||
LOG(("WebSocketChannel::HandleExtensions: found unknown "
|
||||
"parameter %s\n", paramName.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientMaxWindowBits == -1) {
|
||||
clientMaxWindowBits = 15;
|
||||
}
|
||||
if (serverMaxWindowBits == -1) {
|
||||
serverMaxWindowBits = 15;
|
||||
}
|
||||
|
||||
mPMCECompressor = new PMCECompression(clientNoContextTakeover,
|
||||
clientMaxWindowBits,
|
||||
serverMaxWindowBits);
|
||||
if (mPMCECompressor->Active()) {
|
||||
LOG(("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
|
||||
"context takeover, clientMaxWindowBits=%d, "
|
||||
"serverMaxWindowBits=%d\n",
|
||||
clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
|
||||
serverMaxWindowBits));
|
||||
|
||||
mNegotiatedExtensions = "permessage-deflate";
|
||||
} else {
|
||||
LOG(("WebSocketChannel::HandleExtensions: Cannot init PMCE "
|
||||
"compression object\n"));
|
||||
mPMCECompressor = nullptr;
|
||||
AbortSession(NS_ERROR_UNEXPECTED);
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
} else {
|
||||
LOG(("WebSocketChannel::HandleExtensions: "
|
||||
"HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
|
||||
extensions.get()));
|
||||
AbortSession(NS_ERROR_ILLEGAL_VALUE);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
mNegotiatedExtensions = "permessage-deflate";
|
||||
} else {
|
||||
LOG(("WebSocketChannel::HandleExtensions: Cannot init PMCE "
|
||||
"compression object\n"));
|
||||
mPMCECompressor = nullptr;
|
||||
AbortSession(NS_ERROR_UNEXPECTED);
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
ProcessServerWebSocketExtensions(const nsACString& aExtensions,
|
||||
nsACString& aNegotiatedExtensions)
|
||||
{
|
||||
aNegotiatedExtensions.Truncate();
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefService =
|
||||
do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
if (prefService) {
|
||||
bool boolpref;
|
||||
nsresult rv = prefService->
|
||||
GetBoolPref("network.websocket.extensions.permessage-deflate", &boolpref);
|
||||
if (NS_SUCCEEDED(rv) && !boolpref) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsCCharSeparatedTokenizer extList(aExtensions, ',');
|
||||
while (extList.hasMoreTokens()) {
|
||||
bool clientNoContextTakeover;
|
||||
bool serverNoContextTakeover;
|
||||
int32_t clientMaxWindowBits;
|
||||
int32_t serverMaxWindowBits;
|
||||
|
||||
nsresult rv = ParseWebSocketExtension(extList.nextToken(),
|
||||
eParseServerSide,
|
||||
clientNoContextTakeover,
|
||||
serverNoContextTakeover,
|
||||
clientMaxWindowBits,
|
||||
serverMaxWindowBits);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Ignore extensions that we can't parse
|
||||
continue;
|
||||
}
|
||||
|
||||
aNegotiatedExtensions.AssignLiteral("permessage-deflate");
|
||||
if (clientNoContextTakeover) {
|
||||
aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover");
|
||||
}
|
||||
if (serverNoContextTakeover) {
|
||||
aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover");
|
||||
}
|
||||
if (clientMaxWindowBits != -1) {
|
||||
aNegotiatedExtensions.AppendLiteral(";client_max_window_bits=");
|
||||
aNegotiatedExtensions.AppendInt(clientMaxWindowBits);
|
||||
}
|
||||
if (serverMaxWindowBits != -1) {
|
||||
aNegotiatedExtensions.AppendLiteral(";server_max_window_bits=");
|
||||
aNegotiatedExtensions.AppendInt(serverMaxWindowBits);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCString key =
|
||||
aKey + NS_LITERAL_CSTRING("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 uint8_t *)key.BeginWriting(), key.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return hasher->Finish(true, aHash);
|
||||
}
|
||||
|
||||
nsresult
|
||||
WebSocketChannel::SetupRequest()
|
||||
{
|
||||
@ -2716,16 +2832,7 @@ WebSocketChannel::SetupRequest()
|
||||
|
||||
// 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 uint8_t *) secKeyString.BeginWriting(),
|
||||
secKeyString.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = hasher->Finish(true, mHashedSecret);
|
||||
rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
|
||||
mHashedSecret.get()));
|
||||
@ -3182,7 +3289,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (!aURI || !aListener) {
|
||||
if ((!aURI && !mIsServerSide) || !aListener) {
|
||||
LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
@ -3203,13 +3310,6 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
|
||||
return rv;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefService;
|
||||
prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
|
||||
@ -3273,11 +3373,29 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
|
||||
return NS_ERROR_SOCKET_CREATE_FAILED;
|
||||
}
|
||||
|
||||
mInnerWindowID = aInnerWindowID;
|
||||
mOriginalURI = aURI;
|
||||
mURI = mOriginalURI;
|
||||
mURI->GetHostPort(mHost);
|
||||
mOrigin = aOrigin;
|
||||
mInnerWindowID = aInnerWindowID;
|
||||
|
||||
if (mIsServerSide) {
|
||||
//IncrementSessionCount();
|
||||
mWasOpened = 1;
|
||||
mListenerMT = new ListenerAndContextContainer(aListener, aContext);
|
||||
mServerTransportProvider->SetListener(this);
|
||||
mServerTransportProvider = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mURI->GetHostPort(mHost);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> localURI;
|
||||
nsCOMPtr<nsIChannel> localChannel;
|
||||
@ -3540,6 +3658,51 @@ WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
|
||||
return StartWebsocketData();
|
||||
}
|
||||
|
||||
if (mIsServerSide) {
|
||||
if (!mNegotiatedExtensions.IsEmpty()) {
|
||||
bool clientNoContextTakeover;
|
||||
bool serverNoContextTakeover;
|
||||
int32_t clientMaxWindowBits;
|
||||
int32_t serverMaxWindowBits;
|
||||
|
||||
rv = ParseWebSocketExtension(mNegotiatedExtensions,
|
||||
eParseServerSide,
|
||||
clientNoContextTakeover,
|
||||
serverNoContextTakeover,
|
||||
clientMaxWindowBits,
|
||||
serverMaxWindowBits);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server");
|
||||
|
||||
if (clientMaxWindowBits == -1) {
|
||||
clientMaxWindowBits = 15;
|
||||
}
|
||||
if (serverMaxWindowBits == -1) {
|
||||
serverMaxWindowBits = 15;
|
||||
}
|
||||
|
||||
mPMCECompressor = new PMCECompression(serverNoContextTakeover,
|
||||
serverMaxWindowBits,
|
||||
clientMaxWindowBits);
|
||||
if (mPMCECompressor->Active()) {
|
||||
LOG(("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing "
|
||||
"context takeover, serverMaxWindowBits=%d, "
|
||||
"clientMaxWindowBits=%d\n",
|
||||
serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits,
|
||||
clientMaxWindowBits));
|
||||
|
||||
mNegotiatedExtensions = "permessage-deflate";
|
||||
} else {
|
||||
LOG(("WebSocketChannel::OnTransportAvailable: Cannot init PMCE "
|
||||
"compression object\n"));
|
||||
mPMCECompressor = nullptr;
|
||||
AbortSession(NS_ERROR_UNEXPECTED);
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return StartWebsocketData();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,12 @@ class CallOnServerClose;
|
||||
class CallAcknowledge;
|
||||
class WebSocketEventService;
|
||||
|
||||
extern nsresult
|
||||
CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash);
|
||||
extern void
|
||||
ProcessServerWebSocketExtensions(const nsACString& aExtensions,
|
||||
nsACString& aNegotiatedExtensions);
|
||||
|
||||
// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
|
||||
enum wsConnectingState {
|
||||
NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection
|
||||
|
@ -5,6 +5,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsITransportProvider.idl',
|
||||
'nsIWebSocketChannel.idl',
|
||||
'nsIWebSocketEventService.idl',
|
||||
'nsIWebSocketListener.idl',
|
||||
|
19
netwerk/protocol/websocket/nsITransportProvider.idl
Normal file
19
netwerk/protocol/websocket/nsITransportProvider.idl
Normal file
@ -0,0 +1,19 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set sw=4 ts=4 et tw=80 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
interface nsIHttpUpgradeListener;
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* An interface which can be used to asynchronously request a nsITransport
|
||||
* together with the input and output streams that go together with it.
|
||||
*/
|
||||
[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
|
||||
interface nsITransportProvider : nsISupports
|
||||
{
|
||||
void setListener(in nsIHttpUpgradeListener listener);
|
||||
};
|
@ -12,6 +12,7 @@ interface nsIInputStream;
|
||||
interface nsILoadInfo;
|
||||
interface nsIDOMNode;
|
||||
interface nsIPrincipal;
|
||||
interface nsITransportProvider;
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
@ -229,6 +230,15 @@ interface nsIWebSocketChannel : nsISupports
|
||||
*/
|
||||
attribute unsigned long serial;
|
||||
|
||||
/**
|
||||
* Set a nsITransportProvider and negotated extensions to be used by this
|
||||
* channel. Calling this function also means that this channel will
|
||||
* implement the server-side part of a websocket connection rather than the
|
||||
* client-side part.
|
||||
*/
|
||||
void setServerParameters(in nsITransportProvider aProvider,
|
||||
in ACString aNegotiatedExtensions);
|
||||
|
||||
%{C++
|
||||
inline uint32_t Serial()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user