Files
palemoon27/dom/base/EventSource.cpp
T
roytam1 2ce8c88a9b import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1265072 part 1. Add GetWindowIfCurrent and GetDocumentIfCurrent helpers to DOMEventTargetHelper. r=smaug (238cf93592)
- Bug 1265072 part 2. Get rid of uses of GetDocumentFromScriptContext in XMLHttpRequest code. r=smaug (263f063149)
- Bug 1265072 part 3. Get rid of the use of GetDocumentFromScriptContext in DOMEventTargetHelper. r=smaug (8dd8dca53d)
- Bug 1265072 part 4. Get rid of uses of GetDocumentFromScriptContext in WebSocket code. r=smaug (be9c18769b)
- Bug 1265072 part 5. Get rid of uses of GetDocumentFromScriptContext in EventSource code. r=smaug (5fbcf73629)
- Bug 1265072 part 6. Get rid of nsContentUtils::GetDocumentFromScriptContext. r=smaug (752aa67986)
- Bug 1258576 part.1 nsContentIterator should give up to find next/previous node if it reached the root node unexpectedly r=smaug (7175ce5477)
- Bug 1230660 - Remove verbose warnings emitted from nsContentIterator. r=smaug (ef0b8a3a4c)
- Bug 1260908 - Fix type of kMinTelemetryMessageSize. r=smaug (8817839277)
- Bug 1209461 - Remove compilation warnings in nsFrameMessageManager, r=smaug (da95cf91ab)
- Bug 1251361 - "Assertion failure: cache->PreservingWrapper()" with <marquee>, navigation, adoptNode. r=smaug. (630cff1943)
- Bug 1144204. Stop returning things for non-plug-in MIME types from the navigator.mimeTypes getter. r=smaug (329eac72b1)
- Bug 1150709 - Add mForceContentDispatch to nsInProcessTabChildGlobal (r=smaug) (d7e2887457)
- Bug 233705 - remove mDontWrapAnyQuotes, mWrapToWindow and pref wrap_to_window_width. r=ehsan (c7e98bc307)
- Bug 1105556 - nsPerformance::CheckAllowedOrigin should return early for TYPE_DOCUMENT loads. TYPE_DOCUMENT loads don't go through a TimingAllowedCheck(). r=bz, vgosu (5da5530caf)
- Bug 1241183 - Make nsScriptLoadRequest non-threadsafe now it's no longer used as a context for network loads r=sicking (206d763af8)
- Bug 962251 - Add relatedTarget in FocusEvent, r=smaug (f66018e5a4)
- Bug 1248806 - Splitting out protocol handlers from nsLayoutModule. r=bholley (572243ca92)
- Bug 1256488 - Use Base64 URL-encoding in CryptoBuffer. r=ttaubert (9d28ca5f9c)
- Bug 1243311 - Add structured cloning tests for CryptoKeys r=rbarnes (5cde35d811)
- Bug 842818 - Enable structured cloning for CryptoKeys across threads r=baku,keeler (8863b23dc3)
- Bug 1257325 - Silence VS2015 compiler warnings in CryptoKey.cpp f=gps r=rbarnes (0d93bdc950)
- Bug 1188750 - Add test to ensure NSS is initialized before the WebCrypto API tries to deserialize a key f=keeler r=khuey (8d08363e2c)
- Bug 842818 - Run WebCrypto tests in Workers r=mt,rbarnes (48477dfeb4)
- Bug 1205177 - call fileHandleQueue->Finish if aFinish in FileHandleThreadPool::Enqueue. r=janv (f5d6737f27)
- Bug 1206166 - Move FetchUtil::Consume methods into separate BodyUtil class and update Fetch.cpp and ServiceWorkerEvents.cpp accordingly. r=kitcambridge (967f2f58e1)
- Bug 1250930 - Use SubtleCrypto's global when creating keys for an ImportKeyTask r=bz (6227fb14b3)
- Bug 1250930 - Use correct global when creating a key in GenerateSymmetricKeyTask r=bz (92d7faa773)
- Bug 1240436 - Part1: Convert UTF16 to UTF8 before generating nsStringInputStream. r=khuey (1919accaf8)
- Bug 1240436 - Part2: Lossy convert UTF16 to ASCII before generating nsStringInputStream. r=mayhemer (03aa1b6dbe)
- Bug 1240436 - Part3: Remove NS_NewStringInputStream to prevent misuse. r=froydnj (cff40c1b2e)
- Bug 1263405 - Some headers missing in dom/base, r=smaug (12043c5368)
- Bug 964583 - Revert Web IDL [EnforceRange] (unsigned) long long boundary conditions to match ES6. r=bz (6e235bb6b7)
- Bug 1260838 - Assert that ScriptSource's reference count is zero upon destruction; r=jimb a=kwierso (8455465c92)
- Bug 1257164 - Check for interrupts in a few loops in JSON.stringify to eliminate feedback-less hangs. r=evilpie (dbe1336aa8)
- Bug 837192 followup: In js::FunctionToString, fold variable into its only remaining usage-site (an assertion) to fix opt Werror build failures. rs=Waldo (e6b4f52d6a)
- Bug 1258436 - Remove GC suppression in JSFunction::createScriptForLazilyInterpretedFunction. r=sfink (206023942d)
- Declare and define ExecutableAllocator::reprotectRegion only #ifdef NON_WRITABLE_JIT_CODE, to eliminate MOZ_ASSERT of a constant condition that makes some compilers warn. No bug, r=efaust over IRC (5a4d3ab11b)
- Bug 1254369 - IonMonkey: MIPS: Fix ma_b(Register, Imm32, wasm::JumpTarget) missing. r=arai (2f906fec70)
- Bug 1256502 - Use a uint32_t cast to avoid C4319 on VS2015; r=botond (93ba380002)
- Bug 1236043 - Use TiledRegion for the invalid region of a layer. r=jrmuizel (37b87bc355)
- Bug 1248044 - Add PingPongRegion for faster region operations for 2x memory usage. r=jrmuizel (fbd73fb879)
- Bug 1236043 - Add a TiledRegion class. r=jrmuizel (3056f641a4)
- Bug 1116473 - [3.2] Use RefPtr for AndroidSurfaceTexture references. r=snorp (c6e80d0d84)
- Bug 1116473 - [1.1] Handle AndroidSurfaceTexture mapping in thread-safe class. r=snorp (ee8b1d0736)
- Bug 1116473 - [2.1] Const-correctness fixes. r=snorp (5f5fb8fbc4)
- Bug 1251163 - Clear android surface texture before widget shutdown, r=nical (b71d849297)
- Bug 1245813 - Make TextureHost bullet-proof against changing its compositor. r=dvander (b0ef2492fc)
- Recreate GLTextureSources after changing compositors on Mac. (bug 1247611, r=mattwoodrow) (26bd0e69aa)
- Bug 1258768 - Check compositor backends before casting. r=dvander (98929ca492)
- Bug 1245813 - Fix a trivial inverted null check in TextureHostOGL.cpp. r=me (956cfd2e5c)
- Bug 1245813 - Add a missing parenthesis on NS_SUCCEEDED, on a CLOSED TREE. (8e13cc3e1b)
- Bug 1262601 - Handle video content as opaque in PostProcessLayers() r=mattwoodrow (ba578d7394)
- Bug 1258768 - Remove the remaining unsafe compositor casts. r=dvander (8ec58c0ce8)
- Bug 1229946 - report GL_ARB_texture_rg extension to SkiaGL to avoid using GL_ALPHA render targets. r=jgilbert (cf0f3a9fec)
- Bug 1238541 - Don't die in SharedSurface_EGLImage::ProducerReadReleaseImpl() if there is an existing fence r=jgilbert (c1ea4891f7)
- Bug 1240806 - Remove some dead code in GLContextProviderEGL. r=jgilbert (40484e9039)
- Bug 1258094 - Use SurfaceFormat::B8G8R8A8 as back buffer surface r=jrmuizel (cec7a31ffd)
- Bug 1254897 - Recycle back buffer in BasicCompositor r=jrmuizel (0359698b68)
2024-05-27 16:55:14 +08:00

1378 lines
38 KiB
C++

/* -*- 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/. */
#include "mozilla/dom/EventSource.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/EventSourceBinding.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsNetUtil.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsIInputStream.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsMimeTypes.h"
#include "nsIPromptFactory.h"
#include "nsIWindowWatcher.h"
#include "nsPresContext.h"
#include "nsContentPolicyUtils.h"
#include "nsIStringBundle.h"
#include "nsIConsoleService.h"
#include "nsIObserverService.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsJSUtils.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIScriptError.h"
#include "mozilla/dom/EncodingUtils.h"
#include "nsIContentSecurityPolicy.h"
#include "nsContentUtils.h"
#include "mozilla/Preferences.h"
#include "xpcpublic.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/Attributes.h"
#include "nsError.h"
namespace mozilla {
namespace dom {
#define REPLACEMENT_CHAR (char16_t)0xFFFD
#define BOM_CHAR (char16_t)0xFEFF
#define SPACE_CHAR (char16_t)0x0020
#define CR_CHAR (char16_t)0x000D
#define LF_CHAR (char16_t)0x000A
#define COLON_CHAR (char16_t)0x003A
#define DEFAULT_BUFFER_SIZE 4096
// Reconnection time related values in milliseconds. The default one is equal
// to the default value of the pref dom.server-events.default-reconnection-time
#define MIN_RECONNECTION_TIME_VALUE 500
#define DEFAULT_RECONNECTION_TIME_VALUE 5000
#define MAX_RECONNECTION_TIME_VALUE PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
EventSource::EventSource(nsPIDOMWindow* aOwnerWindow) :
DOMEventTargetHelper(aOwnerWindow),
mStatus(PARSE_STATE_OFF),
mFrozen(false),
mErrorLoadOnRedirect(false),
mGoingToDispatchAllMessages(false),
mWithCredentials(false),
mWaitingForOnStopRequest(false),
mLastConvertionResult(NS_OK),
mReadyState(CONNECTING),
mScriptLine(0),
mScriptColumn(0),
mInnerWindowID(0)
{
}
EventSource::~EventSource()
{
Close();
}
//-----------------------------------------------------------------------------
// EventSource::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(EventSource)
bool isBlack = tmp->IsBlack();
if (isBlack || tmp->mWaitingForOnStopRequest) {
if (tmp->mListenerManager) {
tmp->mListenerManager->MarkForCC();
}
if (!isBlack && tmp->PreservingWrapper()) {
// This marks the wrapper black.
tmp->GetWrapper();
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(EventSource)
return tmp->IsBlack();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(EventSource)
return tmp->IsBlack();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EventSource,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadGroup)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHttpChannel)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnicodeDecoder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
DOMEventTargetHelper)
tmp->Close();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(EventSource)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
void
EventSource::DisconnectFromOwner()
{
DOMEventTargetHelper::DisconnectFromOwner();
Close();
}
void
EventSource::Close()
{
if (mReadyState == CLOSED) {
return;
}
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
}
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
ResetConnection();
ClearFields();
while (mMessagesToDispatch.GetSize() != 0) {
delete static_cast<Message*>(mMessagesToDispatch.PopFront());
}
mSrc = nullptr;
mFrozen = false;
mUnicodeDecoder = nullptr;
mReadyState = CLOSED;
}
nsresult
EventSource::Init(nsISupports* aOwner,
const nsAString& aURL,
bool aWithCredentials)
{
if (mReadyState != CONNECTING || !PrefEnabled()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
NS_ENSURE_STATE(sgo);
// XXXbz why are we checking this? This doesn't match anything in the spec.
nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
NS_ENSURE_STATE(scriptContext);
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
do_QueryInterface(aOwner);
NS_ENSURE_STATE(scriptPrincipal);
nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
NS_ENSURE_STATE(principal);
mPrincipal = principal;
mWithCredentials = aWithCredentials;
// The conditional here is historical and not necessarily sane.
if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
&mScriptColumn);
mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
}
// Get the load group for the page. When requesting we'll add ourselves to it.
// This way any pending requests will be automatically aborted if the user
// leaves the page.
nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
if (doc) {
mLoadGroup = doc->GetDocumentLoadGroup();
}
// get the src
nsCOMPtr<nsIURI> baseURI;
nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> srcURI;
rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
// we observe when the window freezes and thaws
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
NS_ENSURE_STATE(os);
rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString origin;
rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString spec;
rv = srcURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
mOriginalURL = NS_ConvertUTF8toUTF16(spec);
mSrc = srcURI;
mOrigin = origin;
mReconnectionTime =
Preferences::GetInt("dom.server-events.default-reconnection-time",
DEFAULT_RECONNECTION_TIME_VALUE);
mUnicodeDecoder = EncodingUtils::DecoderForEncoding("UTF-8");
// the constructor should throw a SYNTAX_ERROR only if it fails resolving the
// url parameter, so we don't care about the InitChannelAndRequestEventSource
// result.
InitChannelAndRequestEventSource();
return NS_OK;
}
/* virtual */ JSObject*
EventSource::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return EventSourceBinding::Wrap(aCx, this, aGivenProto);
}
/* static */ already_AddRefed<EventSource>
EventSource::Constructor(const GlobalObject& aGlobal,
const nsAString& aURL,
const EventSourceInit& aEventSourceInitDict,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> ownerWindow =
do_QueryInterface(aGlobal.GetAsSupports());
if (!ownerWindow) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
MOZ_ASSERT(ownerWindow->IsInnerWindow());
RefPtr<EventSource> eventSource = new EventSource(ownerWindow);
aRv = eventSource->Init(aGlobal.GetAsSupports(), aURL,
aEventSourceInitDict.mWithCredentials);
return eventSource.forget();
}
//-----------------------------------------------------------------------------
// EventSource::nsIObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EventSource::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
if (mReadyState == CLOSED) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
if (!GetOwner() || window != GetOwner()) {
return NS_OK;
}
DebugOnly<nsresult> rv;
if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
rv = Freeze();
NS_ASSERTION(NS_SUCCEEDED(rv), "Freeze() failed");
} else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
rv = Thaw();
NS_ASSERTION(NS_SUCCEEDED(rv), "Thaw() failed");
} else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
Close();
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// EventSource::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EventSource::OnStartRequest(nsIRequest *aRequest,
nsISupports *ctxt)
{
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsresult status;
rv = aRequest->GetStatus(&status);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(status)) {
// EventSource::OnStopRequest will evaluate if it shall either reestablish
// or fail the connection
return NS_ERROR_ABORT;
}
uint32_t httpStatus;
rv = httpChannel->GetResponseStatus(&httpStatus);
NS_ENSURE_SUCCESS(rv, rv);
if (httpStatus != 200) {
DispatchFailConnection();
return NS_ERROR_ABORT;
}
nsAutoCString contentType;
rv = httpChannel->GetContentType(contentType);
NS_ENSURE_SUCCESS(rv, rv);
if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
DispatchFailConnection();
return NS_ERROR_ABORT;
}
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &EventSource::AnnounceConnection);
NS_ENSURE_STATE(event);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_BEGIN_OF_STREAM;
return NS_OK;
}
// this method parses the characters as they become available instead of
// buffering them.
NS_METHOD
EventSource::StreamReaderFunc(nsIInputStream *aInputStream,
void *aClosure,
const char *aFromRawSegment,
uint32_t aToOffset,
uint32_t aCount,
uint32_t *aWriteCount)
{
EventSource* thisObject = static_cast<EventSource*>(aClosure);
if (!thisObject || !aWriteCount) {
NS_WARNING("EventSource cannot read from stream: no aClosure or aWriteCount");
return NS_ERROR_FAILURE;
}
*aWriteCount = 0;
int32_t srcCount, outCount;
char16_t out[2];
nsresult rv;
const char *p = aFromRawSegment,
*end = aFromRawSegment + aCount;
do {
srcCount = aCount - (p - aFromRawSegment);
outCount = 2;
thisObject->mLastConvertionResult =
thisObject->mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
MOZ_ASSERT(thisObject->mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
for (int32_t i = 0; i < outCount; ++i) {
rv = thisObject->ParseCharacter(out[i]);
NS_ENSURE_SUCCESS(rv, rv);
}
p = p + srcCount;
} while (p < end &&
thisObject->mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
thisObject->mLastConvertionResult != NS_OK);
*aWriteCount = aCount;
return NS_OK;
}
NS_IMETHODIMP
EventSource::OnDataAvailable(nsIRequest *aRequest,
nsISupports *aContext,
nsIInputStream *aInputStream,
uint64_t aOffset,
uint32_t aCount)
{
NS_ENSURE_ARG_POINTER(aInputStream);
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t totalRead;
return aInputStream->ReadSegments(EventSource::StreamReaderFunc, this,
aCount, &totalRead);
}
NS_IMETHODIMP
EventSource::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatusCode)
{
mWaitingForOnStopRequest = false;
if (mReadyState == CLOSED) {
return NS_ERROR_ABORT;
}
// "Network errors that prevents the connection from being established in the
// first place (e.g. DNS errors), must cause the user agent to asynchronously
// reestablish the connection.
//
// (...) the cancelation of the fetch algorithm by the user agent (e.g. in
// response to window.stop() or the user canceling the network connection
// manually) must cause the user agent to fail the connection.
if (NS_FAILED(aStatusCode) &&
aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
aStatusCode != NS_ERROR_NET_TIMEOUT &&
aStatusCode != NS_ERROR_NET_RESET &&
aStatusCode != NS_ERROR_NET_INTERRUPT &&
aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
DispatchFailConnection();
return NS_ERROR_ABORT;
}
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
ClearFields();
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &EventSource::ReestablishConnection);
NS_ENSURE_STATE(event);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
//-----------------------------------------------------------------------------
// EventSource::nsIChannelEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
uint32_t aFlags,
nsIAsyncVerifyRedirectCallback *aCallback)
{
nsCOMPtr<nsIRequest> aOldRequest = do_QueryInterface(aOldChannel);
NS_PRECONDITION(aOldRequest, "Redirect from a null request?");
nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
NS_ENSURE_SUCCESS(rv, rv);
NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
nsCOMPtr<nsIURI> newURI;
rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
bool isValidScheme =
(NS_SUCCEEDED(newURI->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
(NS_SUCCEEDED(newURI->SchemeIs("https", &isValidScheme)) && isValidScheme);
rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv) || !isValidScheme) {
DispatchFailConnection();
return NS_ERROR_DOM_SECURITY_ERR;
}
// update our channel
mHttpChannel = do_QueryInterface(aNewChannel);
NS_ENSURE_STATE(mHttpChannel);
SetupHttpChannel();
// The HTTP impl already copies over the referrer and referrer policy on
// redirects, so we don't need to SetupReferrerPolicy().
if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
NS_ENSURE_SUCCESS(rv, rv);
}
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
//-----------------------------------------------------------------------------
// EventSource::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EventSource::GetInterface(const nsIID & aIID,
void **aResult)
{
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
*aResult = static_cast<nsIChannelEventSink*>(this);
NS_ADDREF_THIS();
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
nsresult rv = CheckInnerWindowCorrectness();
NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIPromptFactory> wwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Get the an auth prompter for our window so that the parenting
// of the dialogs works as it should when using tabs.
nsCOMPtr<nsIDOMWindow> window;
if (GetOwner()) {
window = GetOwner()->GetOuterWindow();
}
return wwatch->GetPrompt(window, aIID, aResult);
}
return QueryInterface(aIID, aResult);
}
// static
bool
EventSource::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
{
return Preferences::GetBool("dom.server-events.enabled", false);
}
nsresult
EventSource::GetBaseURI(nsIURI **aBaseURI)
{
NS_ENSURE_ARG_POINTER(aBaseURI);
*aBaseURI = nullptr;
nsCOMPtr<nsIURI> baseURI;
// first we try from document->GetBaseURI()
nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
if (doc) {
baseURI = doc->GetBaseURI();
}
// otherwise we get from the doc's principal
if (!baseURI) {
nsresult rv = mPrincipal->GetURI(getter_AddRefs(baseURI));
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ENSURE_STATE(baseURI);
baseURI.forget(aBaseURI);
return NS_OK;
}
void
EventSource::SetupHttpChannel()
{
mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
/* set the http request headers */
mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING(TEXT_EVENT_STREAM), false);
// LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
if (!mLastEventID.IsEmpty()) {
mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
NS_ConvertUTF16toUTF8(mLastEventID), false);
}
}
nsresult
EventSource::SetupReferrerPolicy()
{
nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
if (doc) {
nsresult rv = mHttpChannel->SetReferrerWithPolicy(doc->GetDocumentURI(),
doc->GetReferrerPolicy());
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
EventSource::InitChannelAndRequestEventSource()
{
if (mReadyState == CLOSED) {
return NS_ERROR_ABORT;
}
bool isValidScheme =
(NS_SUCCEEDED(mSrc->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
(NS_SUCCEEDED(mSrc->SchemeIs("https", &isValidScheme)) && isValidScheme);
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv) || !isValidScheme) {
DispatchFailConnection();
return NS_ERROR_DOM_SECURITY_ERR;
}
nsLoadFlags loadFlags;
loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE;
nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
if (mWithCredentials) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
nsCOMPtr<nsIChannel> channel;
// If we have the document, use it
if (doc) {
rv = NS_NewChannel(getter_AddRefs(channel),
mSrc,
doc,
securityFlags,
nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
mLoadGroup, // loadGroup
nullptr, // aCallbacks
loadFlags); // aLoadFlags
} else {
// otherwise use the principal
rv = NS_NewChannel(getter_AddRefs(channel),
mSrc,
mPrincipal,
securityFlags,
nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
mLoadGroup, // loadGroup
nullptr, // aCallbacks
loadFlags); // aLoadFlags
}
NS_ENSURE_SUCCESS(rv, rv);
mHttpChannel = do_QueryInterface(channel);
NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
SetupHttpChannel();
rv = SetupReferrerPolicy();
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DEBUG
{
nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
mHttpChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
MOZ_ASSERT(!notificationCallbacks);
}
#endif
mHttpChannel->SetNotificationCallbacks(this);
// Start reading from the channel
rv = mHttpChannel->AsyncOpen2(this);
if (NS_FAILED(rv)) {
DispatchFailConnection();
return rv;
}
mWaitingForOnStopRequest = true;
return rv;
}
void
EventSource::AnnounceConnection()
{
if (mReadyState == CLOSED) {
return;
}
if (mReadyState != CONNECTING) {
NS_WARNING("Unexpected mReadyState!!!");
return;
}
// When a user agent is to announce the connection, the user agent must set
// the readyState attribute to OPEN and queue a task to fire a simple event
// named open at the EventSource object.
mReadyState = OPEN;
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
// it doesn't bubble, and it isn't cancelable
event->InitEvent(NS_LITERAL_STRING("open"), false, false);
event->SetTrusted(true);
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the open event!!!");
return;
}
}
nsresult
EventSource::ResetConnection()
{
if (mHttpChannel) {
mHttpChannel->Cancel(NS_ERROR_ABORT);
}
if (mUnicodeDecoder) {
mUnicodeDecoder->Reset();
}
mLastConvertionResult = NS_OK;
mHttpChannel = nullptr;
mStatus = PARSE_STATE_OFF;
mReadyState = CONNECTING;
return NS_OK;
}
void
EventSource::ReestablishConnection()
{
if (mReadyState == CLOSED) {
return;
}
nsresult rv = ResetConnection();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to reset the connection!!!");
return;
}
rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
// it doesn't bubble, and it isn't cancelable
event->InitEvent(NS_LITERAL_STRING("error"), false, false);
event->SetTrusted(true);
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the error event!!!");
return;
}
rv = SetReconnectionTimeout();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to set the timeout for reestablishing the connection!!!");
return;
}
}
nsresult
EventSource::SetReconnectionTimeout()
{
if (mReadyState == CLOSED) {
return NS_ERROR_ABORT;
}
// the timer will be used whenever the requests are going finished.
if (!mTimer) {
mTimer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_STATE(mTimer);
}
nsresult rv = mTimer->InitWithFuncCallback(TimerCallback, this,
mReconnectionTime,
nsITimer::TYPE_ONE_SHOT);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
EventSource::PrintErrorOnConsole(const char *aBundleURI,
const char16_t *aError,
const char16_t **aFormatStrings,
uint32_t aFormatStringsLen)
{
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
NS_ENSURE_STATE(bundleService);
nsCOMPtr<nsIStringBundle> strBundle;
nsresult rv =
bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIConsoleService> console(
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIScriptError> errObj(
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
// Localize the error message
nsXPIDLString message;
if (aFormatStrings) {
rv = strBundle->FormatStringFromName(aError, aFormatStrings,
aFormatStringsLen,
getter_Copies(message));
} else {
rv = strBundle->GetStringFromName(aError, getter_Copies(message));
}
NS_ENSURE_SUCCESS(rv, rv);
rv = errObj->InitWithWindowID(message,
mScriptFile,
EmptyString(),
mScriptLine, mScriptColumn,
nsIScriptError::errorFlag,
"Event Source", mInnerWindowID);
NS_ENSURE_SUCCESS(rv, rv);
// print the error message directly to the JS console
rv = console->LogMessage(errObj);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
EventSource::ConsoleError()
{
nsAutoCString targetSpec;
nsresult rv = mSrc->GetSpec(targetSpec);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
const char16_t *formatStrings[] = { specUTF16.get() };
if (mReadyState == CONNECTING) {
rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
MOZ_UTF16("connectionFailure"),
formatStrings, ArrayLength(formatStrings));
} else {
rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
MOZ_UTF16("netInterrupt"),
formatStrings, ArrayLength(formatStrings));
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
EventSource::DispatchFailConnection()
{
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &EventSource::FailConnection);
NS_ENSURE_STATE(event);
return NS_DispatchToMainThread(event);
}
void
EventSource::FailConnection()
{
if (mReadyState == CLOSED) {
return;
}
nsresult rv = ConsoleError();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to print to the console error");
}
// When a user agent is to fail the connection, the user agent must set the
// readyState attribute to CLOSED and queue a task to fire a simple event
// named error at the EventSource object.
Close(); // it sets mReadyState to CLOSED
rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
// it doesn't bubble, and it isn't cancelable
event->InitEvent(NS_LITERAL_STRING("error"), false, false);
event->SetTrusted(true);
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the error event!!!");
return;
}
}
// static
void
EventSource::TimerCallback(nsITimer* aTimer, void* aClosure)
{
RefPtr<EventSource> thisObject = static_cast<EventSource*>(aClosure);
if (thisObject->mReadyState == CLOSED) {
return;
}
NS_PRECONDITION(!thisObject->mHttpChannel,
"the channel hasn't been cancelled!!");
if (!thisObject->mFrozen) {
nsresult rv = thisObject->InitChannelAndRequestEventSource();
if (NS_FAILED(rv)) {
NS_WARNING("thisObject->InitChannelAndRequestEventSource() failed");
return;
}
}
}
nsresult
EventSource::Thaw()
{
if (mReadyState == CLOSED || !mFrozen) {
return NS_OK;
}
NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
mFrozen = false;
nsresult rv;
if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
NS_ENSURE_STATE(event);
mGoingToDispatchAllMessages = true;
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = InitChannelAndRequestEventSource();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
EventSource::Freeze()
{
if (mReadyState == CLOSED || mFrozen) {
return NS_OK;
}
NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
mFrozen = true;
return NS_OK;
}
nsresult
EventSource::DispatchCurrentMessageEvent()
{
nsAutoPtr<Message> message(new Message());
*message = mCurrentMessage;
ClearFields();
if (message->mData.IsEmpty()) {
return NS_OK;
}
// removes the trailing LF from mData
NS_ASSERTION(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
"Invalid trailing character! LF was expected instead.");
message->mData.SetLength(message->mData.Length() - 1);
if (message->mEventName.IsEmpty()) {
message->mEventName.AssignLiteral("message");
}
if (message->mLastEventID.IsEmpty() && !mLastEventID.IsEmpty()) {
message->mLastEventID.Assign(mLastEventID);
}
size_t sizeBefore = mMessagesToDispatch.GetSize();
mMessagesToDispatch.Push(message.forget());
NS_ENSURE_TRUE(mMessagesToDispatch.GetSize() == sizeBefore + 1,
NS_ERROR_OUT_OF_MEMORY);
if (!mGoingToDispatchAllMessages) {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
NS_ENSURE_STATE(event);
mGoingToDispatchAllMessages = true;
return NS_DispatchToMainThread(event);
}
return NS_OK;
}
void
EventSource::DispatchAllMessageEvents()
{
if (mReadyState == CLOSED || mFrozen) {
return;
}
mGoingToDispatchAllMessages = false;
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
return;
}
JSContext* cx = jsapi.cx();
while (mMessagesToDispatch.GetSize() > 0) {
nsAutoPtr<Message>
message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
// Now we can turn our string into a jsval
JS::Rooted<JS::Value> jsData(cx);
{
JSString* jsString;
jsString = JS_NewUCStringCopyN(cx,
message->mData.get(),
message->mData.Length());
NS_ENSURE_TRUE_VOID(jsString);
jsData.setString(jsString);
}
// create an event that uses the MessageEvent interface,
// which does not bubble, is not cancelable, and has no default action
RefPtr<MessageEvent> event =
NS_NewDOMMessageEvent(this, nullptr, nullptr);
rv = event->InitMessageEvent(message->mEventName, false, false, jsData,
mOrigin, message->mLastEventID, nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to init the message event!!!");
return;
}
event->SetTrusted(true);
rv = DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the message event!!!");
return;
}
mLastEventID.Assign(message->mLastEventID);
}
}
nsresult
EventSource::ClearFields()
{
// mLastEventID and mReconnectionTime must be cached
mCurrentMessage.mEventName.Truncate();
mCurrentMessage.mLastEventID.Truncate();
mCurrentMessage.mData.Truncate();
mLastFieldName.Truncate();
mLastFieldValue.Truncate();
return NS_OK;
}
nsresult
EventSource::SetFieldAndClear()
{
if (mLastFieldName.IsEmpty()) {
mLastFieldValue.Truncate();
return NS_OK;
}
char16_t first_char;
first_char = mLastFieldName.CharAt(0);
switch (first_char) // with no case folding performed
{
case char16_t('d'):
if (mLastFieldName.EqualsLiteral("data")) {
// If the field name is "data" append the field value to the data
// buffer, then append a single U+000A LINE FEED (LF) character
// to the data buffer.
mCurrentMessage.mData.Append(mLastFieldValue);
mCurrentMessage.mData.Append(LF_CHAR);
}
break;
case char16_t('e'):
if (mLastFieldName.EqualsLiteral("event")) {
mCurrentMessage.mEventName.Assign(mLastFieldValue);
}
break;
case char16_t('i'):
if (mLastFieldName.EqualsLiteral("id")) {
mCurrentMessage.mLastEventID.Assign(mLastFieldValue);
}
break;
case char16_t('r'):
if (mLastFieldName.EqualsLiteral("retry")) {
uint32_t newValue=0;
uint32_t i = 0; // we must ensure that there are only digits
bool assign = true;
for (i = 0; i < mLastFieldValue.Length(); ++i) {
if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
mLastFieldValue.CharAt(i) > (char16_t)'9') {
assign = false;
break;
}
newValue = newValue*10 +
(((uint32_t)mLastFieldValue.CharAt(i))-
((uint32_t)((char16_t)'0')));
}
if (assign) {
if (newValue < MIN_RECONNECTION_TIME_VALUE) {
mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
} else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
} else {
mReconnectionTime = newValue;
}
}
break;
}
break;
}
mLastFieldName.Truncate();
mLastFieldValue.Truncate();
return NS_OK;
}
nsresult
EventSource::CheckHealthOfRequestCallback(nsIRequest *aRequestCallback)
{
// check if we have been closed or if the request has been canceled
// or if we have been frozen
if (mReadyState == CLOSED || !mHttpChannel ||
mFrozen || mErrorLoadOnRedirect) {
return NS_ERROR_ABORT;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
NS_ENSURE_STATE(httpChannel);
if (httpChannel != mHttpChannel) {
NS_WARNING("wrong channel from request callback");
return NS_ERROR_ABORT;
}
return NS_OK;
}
nsresult
EventSource::ParseCharacter(char16_t aChr)
{
nsresult rv;
if (mReadyState == CLOSED) {
return NS_ERROR_ABORT;
}
switch (mStatus)
{
case PARSE_STATE_OFF:
NS_ERROR("Invalid state");
return NS_ERROR_FAILURE;
break;
case PARSE_STATE_BEGIN_OF_STREAM:
if (aChr == BOM_CHAR) {
mStatus = PARSE_STATE_BOM_WAS_READ; // ignore it
} else if (aChr == CR_CHAR) {
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == COLON_CHAR) {
mStatus = PARSE_STATE_COMMENT;
} else {
mLastFieldName += aChr;
mStatus = PARSE_STATE_FIELD_NAME;
}
break;
case PARSE_STATE_BOM_WAS_READ:
if (aChr == CR_CHAR) {
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == COLON_CHAR) {
mStatus = PARSE_STATE_COMMENT;
} else {
mLastFieldName += aChr;
mStatus = PARSE_STATE_FIELD_NAME;
}
break;
case PARSE_STATE_CR_CHAR:
if (aChr == CR_CHAR) {
rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
NS_ENSURE_SUCCESS(rv, rv);
} else if (aChr == LF_CHAR) {
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == COLON_CHAR) {
mStatus = PARSE_STATE_COMMENT;
} else {
mLastFieldName += aChr;
mStatus = PARSE_STATE_FIELD_NAME;
}
break;
case PARSE_STATE_COMMENT:
if (aChr == CR_CHAR) {
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
mStatus = PARSE_STATE_BEGIN_OF_LINE;
}
break;
case PARSE_STATE_FIELD_NAME:
if (aChr == CR_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == COLON_CHAR) {
mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
} else {
mLastFieldName += aChr;
}
break;
case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
if (aChr == CR_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == SPACE_CHAR) {
mStatus = PARSE_STATE_FIELD_VALUE;
} else {
mLastFieldValue += aChr;
mStatus = PARSE_STATE_FIELD_VALUE;
}
break;
case PARSE_STATE_FIELD_VALUE:
if (aChr == CR_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
rv = SetFieldAndClear();
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else {
mLastFieldValue += aChr;
}
break;
case PARSE_STATE_BEGIN_OF_LINE:
if (aChr == CR_CHAR) {
rv = DispatchCurrentMessageEvent(); // there is an empty line
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_CR_CHAR;
} else if (aChr == LF_CHAR) {
rv = DispatchCurrentMessageEvent(); // there is an empty line
NS_ENSURE_SUCCESS(rv, rv);
mStatus = PARSE_STATE_BEGIN_OF_LINE;
} else if (aChr == COLON_CHAR) {
mStatus = PARSE_STATE_COMMENT;
} else {
mLastFieldName += aChr;
mStatus = PARSE_STATE_FIELD_NAME;
}
break;
}
return NS_OK;
}
} // namespace dom
} // namespace mozilla