/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 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/. */
// This class is responsible for proxying nsIObserver and nsIWebSocketImpl // interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on // main thread, so we can let it support weak reference. class WebSocketImplProxy final : public nsIWebSocketImpl, public GlobalTeardownObserver, public GlobalFreezeObserver { public:
NS_DECL_ISUPPORTS
NS_DECL_NSIWEBSOCKETIMPL
class WebSocketImpl final : public nsIInterfaceRequestor, public nsIWebSocketListener, public nsIRequest, public nsISerialEventTarget, public nsIWebSocketImpl, public GlobalTeardownObserver, public GlobalFreezeObserver { public:
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIWEBSOCKETLISTENER
NS_DECL_NSIREQUEST
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
NS_DECL_NSIWEBSOCKETIMPL
// ConnectionCloseEvents: 'error' event if needed, then 'close' event.
nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
nsresult aStatusCode); // 2nd half of ScheduleConnectionCloseEvents, run in its own event. void DispatchConnectionCloseEvents(const RefPtr<WebSocketImpl>& aProofOfRef);
// Web Socket owner information: // - the script file name, UTF8 encoded. // - source code line number and 1-origin column number where the Web Socket // object was constructed. // - the ID of the Web Socket owner window. Note that this may not // be the same as the inner window where the script lives. // e.g within iframes // These attributes are used for error reporting.
nsCString mScriptFile;
uint32_t mScriptLine;
uint32_t mScriptColumn;
uint64_t mInnerWindowID; bool mPrivateBrowsing; bool mIsChromeContext;
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
nsWeakPtr mWeakLoadGroup;
bool mIsMainThread;
// This mutex protects mWorkerShuttingDown.
mozilla::Mutex mMutex; bool mWorkerShuttingDown MOZ_GUARDED_BY(mMutex);
void WebSocketImpl::PrintErrorOnConsole(constchar* aBundleURI, constchar* aError,
nsTArray<nsString>&& aFormatStrings) { // This method must run on the main thread.
if (!NS_IsMainThread()) {
MOZ_ASSERT(mWorkerRef);
RefPtr<PrintErrorOnConsoleRunnable> runnable = new PrintErrorOnConsoleRunnable(this, aBundleURI, aError,
std::move(aFormatStrings));
ErrorResult rv;
runnable->Dispatch(mWorkerRef->Private(), Killing, rv); // XXXbz this seems totally broken. We should be propagating this out, but // none of our callers really propagate anything usefully. Come to think of // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget // runnable??
rv.SuppressException(); return;
}
if (mDisconnectingOrDisconnected) { return NS_OK;
}
// If this method is called because the worker is going away, we will not // receive the OnStop() method and we have to disconnect the WebSocket and // release the ThreadSafeWorkerRef.
MaybeDisconnect md(this);
// The common case... if (mChannel) {
mWebSocket->SetReadyState(WebSocket::CLOSING);
// The channel has to be closed on the main-thread.
if (NS_IsMainThread()) { return mChannel->Close(aReasonCode, aReasonString);
}
RefPtr<CancelWebSocketRunnable> runnable = new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString); return NS_DispatchToMainThread(runnable);
}
// No channel, but not disconnected: canceled or failed early
MOZ_ASSERT(readyState == WebSocket::CONNECTING, "Should only get here for early websocket cancel/error");
// Server won't be sending us a close code, so use what's passed in here.
mCloseEventCode = aReasonCode;
CopyUTF8toUTF16(aReasonString, mCloseEventReason);
if (mWebSocket->ReadyState() < WebSocket::OPEN) {
PrintErrorOnConsole("chrome://global/locale/appstrings.properties", "connectionFailure", std::move(formatStrings));
} else {
PrintErrorOnConsole("chrome://global/locale/appstrings.properties", "netInterrupt", std::move(formatStrings));
} /// todo some specific errors - like for message too large return NS_OK;
}
private: // NOTE: WebSocketImpl may be it the middle of being destroyed. // We can't just hold this as a RefPtr, since after the runnable ends // the sync caller will be released, and can finish destroying WebSocketImpl // before a ref here could be dropped.
WebSocketImpl* mImpl;
};
// DontKeepAliveAnyMore() and DisconnectInternal() can release the // object. aProofOfRef ensures we're holding a reference to this until // the end of the method.
// Disconnect can be called from some control event (such as a callback from // StrongWorkerRef). This will be scheduled before any other sync/async // runnable. In order to prevent some double Disconnect() calls, we use this // boolean.
mDisconnectingOrDisconnected = true;
// DisconnectInternal touches observers and nsILoadGroup and it must run on // the main thread.
if (NS_IsMainThread()) {
DisconnectInternal();
// If we haven't called WebSocket::DisconnectFromOwner yet, update // web socket count here. if (nsGlobalWindowInner* win = mWebSocket->GetOwnerWindow()) {
win->UpdateWebSocketCount(-1);
}
} else {
RefPtr<DisconnectInternalRunnable> runnable = new DisconnectInternalRunnable(this);
ErrorResult rv;
runnable->Dispatch(GetCurrentThreadWorkerPrivate(), Killing, rv); // XXXbz this seems totally broken. We should be propagating this out, but // where to, exactly?
rv.SuppressException();
}
nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup); if (loadGroup) {
loadGroup->RemoveRequest(this, nullptr, NS_OK); // mWeakLoadGroup has to be released on main-thread because WeakReferences // are not thread-safe.
mWeakLoadGroup = nullptr;
}
if (!mWorkerRef) {
GlobalTeardownObserver::DisconnectFromOwner();
DisconnectFreezeObserver();
}
if (mImplProxy) {
mImplProxy->Disconnect();
mImplProxy = nullptr;
}
}
if (mDisconnectingOrDisconnected) { return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState(); if (readyState == WebSocket::CLOSED) {
NS_ERROR("Received message after CLOSED"); return NS_ERROR_UNEXPECTED;
}
if (readyState == WebSocket::OPEN) { // Dispatch New Message
nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary); if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the message event");
}
return NS_OK;
}
// CLOSING should be the only other state where it's possible to get msgs // from channel: Spec says to drop them.
MOZ_ASSERT(readyState == WebSocket::CLOSING, "Received message while CONNECTING or CLOSED"); return NS_OK;
}
if (mDisconnectingOrDisconnected) { return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState();
// This is the only function that sets OPEN, and should be called only once
MOZ_ASSERT(readyState != WebSocket::OPEN, "readyState already OPEN! OnStart called twice?");
// Nothing to do if we've already closed/closing if (readyState != WebSocket::CONNECTING) { return NS_OK;
}
// Attempt to kill "ghost" websocket: but usually too early for check to fail
nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness(); if (NS_FAILED(rv)) {
RefPtr<WebSocketImpl> self(this);
CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY); return rv;
}
if (!mRequestedProtocolList.IsEmpty()) {
rv = mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// Let's keep the object alive because the webSocket can be CCed in the // onopen callback
RefPtr<WebSocket> webSocket = mWebSocket;
// Call 'onopen'
rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING); if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the open event");
}
if (mDisconnectingOrDisconnected) { return NS_OK;
}
// We can be CONNECTING here if connection failed. // We can be OPEN if we have encountered a fatal protocol error // We can be CLOSING if close() was called and/or server initiated close.
MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED, "Shouldn't already be CLOSED when OnStop called");
if (mDisconnectingOrDisconnected) { return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState();
MOZ_ASSERT(readyState != WebSocket::CONNECTING, "Received server close before connected?");
MOZ_ASSERT(readyState != WebSocket::CLOSED, "Received server close after already closed!");
// store code/string for onclose DOM event
mCloseEventCode = aCode;
CopyUTF8toUTF16(aReason, mCloseEventReason);
if (readyState == WebSocket::OPEN) { // Server initiating close. // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint // typically echos the status code it received". // But never send certain codes, per section 7.4.1
RefPtr<WebSocketImpl> self(this); if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
CloseConnection(self, 0, ""_ns);
} else {
CloseConnection(self, aCode, aReason);
}
} else { // We initiated close, and server has replied: OnStop does rest of the work.
MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
if (NS_IsMainThread()) {
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
do_QueryInterface(aGlobal.GetAsSupports()); if (!scriptPrincipal) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
principal = scriptPrincipal->GetPrincipal();
partitionedPrincipal = scriptPrincipal->PartitionedPrincipal(); if (!principal || !partitionedPrincipal) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
}
nsTArray<nsString> protocolArray;
for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) { const nsString& protocolElement = aProtocols[index];
// Repeated protocols are not allowed if (protocolArray.Contains(protocolElement)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr;
}
// Protocol string value must match constraints if (!IsValidProtocolString(protocolElement)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr;
}
protocolArray.AppendElement(protocolElement);
}
RefPtr<WebSocket> webSocket = new WebSocket(global);
RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;
bool connectionFailed = true;
if (NS_IsMainThread()) { // We're keeping track of all main thread web sockets to be able to // avoid throttling timeouts when we have active web sockets.
nsCOMPtr<nsIGlobalObject> global; if (nsGlobalWindowInner* win = webSocket->GetOwnerWindow()) {
win->UpdateWebSocketCount(1);
global = win->AsGlobal();
}
bool isSecure = principal->SchemeIs("https");
aRv = webSocketImpl->IsSecure(&isSecure); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
// the constructor should throw a SYNTAX_ERROR only if it fails to parse the // url parameter, so don't throw if InitializeConnection fails, and call // onerror/onclose asynchronously
connectionFailed = NS_FAILED(webSocketImpl->InitializeConnection(
principal, doc ? doc->CookieJarSettings() : nullptr));
} else {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
uint32_t lineno;
JS::ColumnNumberOneOrigin column;
JS::AutoFilename file; if (!JS::DescribeScriptedCaller(&file, aGlobal.Context(), &lineno,
&column)) {
NS_WARNING("Failed to get line number and filename in workers.");
}
// It can be that we have been already disconnected because the WebSocket is // gone away while we where initializing the webSocket. if (!webSocket->mImpl) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
// We don't return an error if the connection just failed. Instead we dispatch // an event. if (connectionFailed) {
webSocketImpl->FailConnection(webSocketImpl,
nsIWebSocketChannel::CLOSE_ABNORMAL);
}
// If we don't have a channel, the connection is failed and onerror() will be // called asynchrounsly. if (!webSocket->mImpl->mChannel) { return webSocket.forget();
}
// This operation must be done on the correct thread. The rest must run on the // main-thread.
aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
if (NS_IsMainThread()) {
MOZ_ASSERT(principal);
MOZ_ASSERT(partitionedPrincipal);
RefPtr<AsyncOpenRunnable> runnable = new AsyncOpenRunnable(webSocket->mImpl, std::move(stack));
runnable->Dispatch(webSocket->mImpl->mWorkerRef->Private(), Canceling, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
aRv = runnable->ErrorCode();
}
if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
// It can be that we have been already disconnected because the WebSocket is // gone away while we where initializing the webSocket. if (!webSocket->mImpl) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
// Let's inform devtools about this new active WebSocket.
webSocket->mImpl->mService->WebSocketCreated(
webSocket->mImpl->mChannel->Serial(), webSocket->mImpl->mInnerWindowID,
webSocket->mURI, webSocket->mImpl->mRequestedProtocolList);
cws.Done();
return webSocket.forget();
}
NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
DOMEventTargetHelper) if (tmp->mImpl) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
// We need to keep the implementation alive in case the init disconnects it // because of some error.
RefPtr<WebSocketImpl> kungfuDeathGrip = this;
// Attempt to kill "ghost" websocket: but usually too early for check to fail
nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
// Assign the sub protocol list and scan it for illegal values for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) { if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) { return NS_ERROR_DOM_SYNTAX_ERR;
}
if (!mRequestedProtocolList.IsEmpty()) {
mRequestedProtocolList.AppendLiteral(", ");
}
// Shut down websocket if window is frozen or destroyed (only needed for // "ghost" websockets--see bug 696085)
RefPtr<WebSocketImplProxy> proxy; if (mIsMainThread) {
proxy = new WebSocketImplProxy(this);
proxy->BindToOwner(aWindowGlobal);
}
// 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. if (aCx) { if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwnerWindow()) {
mInnerWindowID = ownerWindow->WindowID();
}
}
// 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();
}
}
// The 'real' nsHttpChannel of the websocket gets opened in the parent. // Since we don't serialize the CSP within child and parent and also not // the context, we have to perform content policy checks here instead of // AsyncOpen(). // Please note that websockets can't follow redirects, hence there is no // need to perform a CSP check after redirects.
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
aPrincipal, // loading principal
aPrincipal, // triggering principal
originDoc, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
nsIContentPolicy::TYPE_WEBSOCKET, aClientInfo);
if (aCSPEventListener) {
secCheckLoadInfo->SetCspEventListener(aCSPEventListener);
}
if (NS_CP_REJECTED(shouldLoad)) { // Disallowed by content policy return NS_ERROR_CONTENT_BLOCKED;
}
// If the HTTPS-Only mode is enabled, we need to upgrade the websocket // connection from ws:// to wss:// and mark it as secure. if (!mSecure && originDoc &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
originDoc->GetDocumentURI())) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
NS_ENSURE_SUCCESS(rv, rv);
// secCheckLoadInfo is only used for the triggering principal, so this // is okay. if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) {
mURI.ReplaceSubstring("ws://", "wss://"); if (NS_WARN_IF(mURI.Find("wss://") != 0)) { return NS_OK;
}
mSecure = true;
}
}
}
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'. // In such a case we have to upgrade ws: to wss: and also update mSecure // 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 (!mIsServerSide && !mSecure && originDoc &&
originDoc->GetUpgradeInsecureRequests(false) &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
originDoc->GetDocumentURI())) { // let's use the old specification before the upgrade for logging
AutoTArray<nsString, 2> params;
CopyUTF8toUTF16(mURI, *params.AppendElement());
// upgrade the request from ws:// to wss:// and mark as secure
mURI.ReplaceSubstring("ws://", "wss://"); if (NS_WARN_IF(mURI.Find("wss://") != 0)) { return NS_OK;
}
mSecure = true;
// Don't allow https:// to open ws:// // Check that we aren't a server side websocket or set to be upgraded to wss // or allowing ws from https or a local websocket if (!mIsServerSide && !mSecure &&
!Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", false) &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
mAsciiHost)) { // If aIsSecure is true then disallow loading ws if (aIsSecure) { return NS_ERROR_DOM_SECURITY_ERR;
}
// Obtain the precursor's URI for the loading principal if it exists // otherwise use the loading principal's URI
nsCOMPtr<nsIPrincipal> precursorPrincipal =
aPrincipal->GetPrecursorPrincipal();
nsCOMPtr<nsIURI> precursorOrLoadingURI = precursorPrincipal
? precursorPrincipal->GetURI()
: aPrincipal->GetURI();
// Check if the parent was loaded securely if we have one if (precursorOrLoadingURI) {
nsCOMPtr<nsIURI> precursorOrLoadingInnermostURI =
NS_GetInnermostURI(precursorOrLoadingURI); // If the parent was loaded securely then disallow loading ws if (precursorOrLoadingInnermostURI &&
precursorOrLoadingInnermostURI->SchemeIs("https")) { return NS_ERROR_DOM_SECURITY_ERR;
}
}
}
if (mIsMainThread) {
mImplProxy = std::move(proxy);
} return NS_OK;
}
// add ourselves to the document's load group and // provide the http stack the loadgroup info too
nsCOMPtr<nsILoadGroup> loadGroup;
rv = GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) {
rv = wsChannel->SetLoadGroup(loadGroup);
NS_ENSURE_SUCCESS(rv, rv);
rv = loadGroup->AddRequest(this, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
// manually adding loadinfo to the channel since it // was not set during channel creation.
nsCOMPtr<Document> doc(mOriginDocument);
// mOriginDocument has to be release on main-thread because WeakReferences // are not thread-safe.
mOriginDocument = nullptr;
// The TriggeringPrincipal for websockets must always be a script. // Let's make sure that the doc's principal (if a doc exists) // and aPrincipal are same origin.
MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
// Let's keep the object alive because the webSocket can be CCed in the // onerror or in the onclose callback
RefPtr<WebSocket> webSocket = mWebSocket;
// Call 'onerror' if needed if (mFailed) {
nsresult rv = webSocket->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING); if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the error event");
}
}
nsresult rv = webSocket->CreateAndDispatchCloseEvent(
mCloseEventWasClean, mCloseEventCode, mCloseEventReason); if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the close event");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.