123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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 "WebSocketFrame.h"
- #include "WebSocketLog.h"
- #include "WebSocketChannel.h"
- #include "mozilla/Atomics.h"
- #include "mozilla/Attributes.h"
- #include "mozilla/EndianUtils.h"
- #include "mozilla/MathAlgorithms.h"
- #include "mozilla/net/WebSocketEventService.h"
- #include "nsIURI.h"
- #include "nsIChannel.h"
- #include "nsICryptoHash.h"
- #include "nsIRunnable.h"
- #include "nsIPrefBranch.h"
- #include "nsIPrefService.h"
- #include "nsICancelable.h"
- #include "nsIClassOfService.h"
- #include "nsIDNSRecord.h"
- #include "nsIDNSService.h"
- #include "nsIStreamConverterService.h"
- #include "nsIIOService2.h"
- #include "nsIProtocolProxyService.h"
- #include "nsIProxyInfo.h"
- #include "nsIProxiedChannel.h"
- #include "nsIAsyncVerifyRedirectCallback.h"
- #include "nsIDashboardEventNotifier.h"
- #include "nsIEventTarget.h"
- #include "nsIHttpChannel.h"
- #include "nsILoadGroup.h"
- #include "nsIProtocolHandler.h"
- #include "nsIRandomGenerator.h"
- #include "nsISocketTransport.h"
- #include "nsThreadUtils.h"
- #include "nsINetworkLinkService.h"
- #include "nsIObserverService.h"
- #include "nsITransportProvider.h"
- #include "nsCharSeparatedTokenizer.h"
- #include "nsAutoPtr.h"
- #include "nsNetCID.h"
- #include "nsServiceManagerUtils.h"
- #include "nsCRT.h"
- #include "nsThreadUtils.h"
- #include "nsError.h"
- #include "nsStringStream.h"
- #include "nsAlgorithm.h"
- #include "nsProxyRelease.h"
- #include "nsNetUtil.h"
- #include "nsINode.h"
- #include "mozilla/StaticMutex.h"
- #include "mozilla/TimeStamp.h"
- #include "nsSocketTransportService2.h"
- #include "plbase64.h"
- #include "prmem.h"
- #include "prnetdb.h"
- #include "zlib.h"
- #include <algorithm>
- // rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
- // dupe one constant we need from it
- #define CLOSE_GOING_AWAY 1001
- using namespace mozilla;
- using namespace mozilla::net;
- namespace mozilla {
- namespace net {
- NS_IMPL_ISUPPORTS(WebSocketChannel,
- nsIWebSocketChannel,
- nsIHttpUpgradeListener,
- nsIRequestObserver,
- nsIStreamListener,
- nsIProtocolHandler,
- nsIInputStreamCallback,
- nsIOutputStreamCallback,
- nsITimerCallback,
- nsIDNSListener,
- nsIProtocolProxyCallback,
- nsIInterfaceRequestor,
- nsIChannelEventSink,
- nsIThreadRetargetableRequest,
- nsIObserver)
- // We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
- #define SEC_WEBSOCKET_VERSION "13"
- /*
- * About SSL unsigned certificates
- *
- * wss will not work to a host using an unsigned certificate unless there
- * is already an exception (i.e. it cannot popup a dialog asking for
- * a security exception). This is similar to how an inlined img will
- * fail without a dialog if fails for the same reason. This should not
- * be a problem in practice as it is expected the websocket javascript
- * is served from the same host as the websocket server (or of course,
- * a valid cert could just be provided).
- *
- */
- // some helper classes
- //-----------------------------------------------------------------------------
- // FailDelayManager
- //
- // Stores entries (searchable by {host, port}) of connections that have recently
- // failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
- //-----------------------------------------------------------------------------
- // Initial reconnect delay is randomly chosen between 200-400 ms.
- // This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
- const uint32_t kWSReconnectInitialBaseDelay = 200;
- const uint32_t kWSReconnectInitialRandomDelay = 200;
- // Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
- const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
- // Maximum reconnect delay (in ms)
- const uint32_t kWSReconnectMaxDelay = 60 * 1000;
- // hold record of failed connections, and calculates needed delay for reconnects
- // to same host/port.
- class FailDelay
- {
- public:
- FailDelay(nsCString address, int32_t port)
- : mAddress(address), mPort(port)
- {
- mLastFailure = TimeStamp::Now();
- mNextDelay = kWSReconnectInitialBaseDelay +
- (rand() % kWSReconnectInitialRandomDelay);
- }
- // Called to update settings when connection fails again.
- void FailedAgain()
- {
- mLastFailure = TimeStamp::Now();
- // We use a truncated exponential backoff as suggested by RFC 6455,
- // but multiply by 1.5 instead of 2 to be more gradual.
- mNextDelay = static_cast<uint32_t>(
- std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
- LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu",
- mAddress.get(), mPort, mNextDelay));
- }
- // returns 0 if there is no need to delay (i.e. delay interval is over)
- uint32_t RemainingDelay(TimeStamp rightNow)
- {
- TimeDuration dur = rightNow - mLastFailure;
- uint32_t sinceFail = (uint32_t) dur.ToMilliseconds();
- if (sinceFail > mNextDelay)
- return 0;
- return mNextDelay - sinceFail;
- }
- bool IsExpired(TimeStamp rightNow)
- {
- return (mLastFailure +
- TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay))
- <= rightNow;
- }
- nsCString mAddress; // IP address (or hostname if using proxy)
- int32_t mPort;
- private:
- TimeStamp mLastFailure; // Time of last failed attempt
- // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
- uint32_t mNextDelay; // milliseconds
- };
- class FailDelayManager
- {
- public:
- FailDelayManager()
- {
- MOZ_COUNT_CTOR(FailDelayManager);
- mDelaysDisabled = false;
- nsCOMPtr<nsIPrefBranch> prefService =
- do_GetService(NS_PREFSERVICE_CONTRACTID);
- if (!prefService) {
- return;
- }
- bool boolpref = true;
- nsresult rv;
- rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
- &boolpref);
- if (NS_SUCCEEDED(rv) && !boolpref) {
- mDelaysDisabled = true;
- }
- }
- ~FailDelayManager()
- {
- MOZ_COUNT_DTOR(FailDelayManager);
- for (uint32_t i = 0; i < mEntries.Length(); i++) {
- delete mEntries[i];
- }
- }
- void Add(nsCString &address, int32_t port)
- {
- if (mDelaysDisabled)
- return;
- FailDelay *record = new FailDelay(address, port);
- mEntries.AppendElement(record);
- }
- // Element returned may not be valid after next main thread event: don't keep
- // pointer to it around
- FailDelay* Lookup(nsCString &address, int32_t port,
- uint32_t *outIndex = nullptr)
- {
- if (mDelaysDisabled)
- return nullptr;
- FailDelay *result = nullptr;
- TimeStamp rightNow = TimeStamp::Now();
- // We also remove expired entries during search: iterate from end to make
- // indexing simpler
- for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
- FailDelay *fail = mEntries[i];
- if (fail->mAddress.Equals(address) && fail->mPort == port) {
- if (outIndex)
- *outIndex = i;
- result = fail;
- // break here: removing more entries would mess up *outIndex.
- // Any remaining expired entries will be deleted next time Lookup
- // finds nothing, which is the most common case anyway.
- break;
- } else if (fail->IsExpired(rightNow)) {
- mEntries.RemoveElementAt(i);
- delete fail;
- }
- }
- return result;
- }
- // returns true if channel connects immediately, or false if it's delayed
- void DelayOrBegin(WebSocketChannel *ws)
- {
- if (!mDelaysDisabled) {
- uint32_t failIndex = 0;
- FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
- if (fail) {
- TimeStamp rightNow = TimeStamp::Now();
- uint32_t remainingDelay = fail->RemainingDelay(rightNow);
- if (remainingDelay) {
- // reconnecting within delay interval: delay by remaining time
- nsresult rv;
- ws->mReconnectDelayTimer =
- do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_SUCCEEDED(rv)) {
- rv = ws->mReconnectDelayTimer->InitWithCallback(
- ws, remainingDelay, nsITimer::TYPE_ONE_SHOT);
- if (NS_SUCCEEDED(rv)) {
- LOG(("WebSocket: delaying websocket [this=%p] by %lu ms, changing"
- " state to CONNECTING_DELAYED", ws,
- (unsigned long)remainingDelay));
- ws->mConnecting = CONNECTING_DELAYED;
- return;
- }
- }
- // if timer fails (which is very unlikely), drop down to BeginOpen call
- } else if (fail->IsExpired(rightNow)) {
- mEntries.RemoveElementAt(failIndex);
- delete fail;
- }
- }
- }
- // Delays disabled, or no previous failure, or we're reconnecting after scheduled
- // delay interval has passed: connect.
- ws->BeginOpen(true);
- }
- // Remove() also deletes all expired entries as it iterates: better for
- // battery life than using a periodic timer.
- void Remove(nsCString &address, int32_t port)
- {
- TimeStamp rightNow = TimeStamp::Now();
- // iterate from end, to make deletion indexing easier
- for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
- FailDelay *entry = mEntries[i];
- if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
- entry->IsExpired(rightNow)) {
- mEntries.RemoveElementAt(i);
- delete entry;
- }
- }
- }
- private:
- nsTArray<FailDelay *> mEntries;
- bool mDelaysDisabled;
- };
- //-----------------------------------------------------------------------------
- // nsWSAdmissionManager
- //
- // 1) Ensures that only one websocket at a time is CONNECTING to a given IP
- // address (or hostname, if using proxy), per RFC 6455 Section 4.1.
- // 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
- //-----------------------------------------------------------------------------
- class nsWSAdmissionManager
- {
- public:
- static void Init()
- {
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- sManager = new nsWSAdmissionManager();
- }
- }
- static void Shutdown()
- {
- StaticMutexAutoLock lock(sLock);
- delete sManager;
- sManager = nullptr;
- }
- // Determine if we will open connection immediately (returns true), or
- // delay/queue the connection (returns false)
- static void ConditionallyConnect(WebSocketChannel *ws)
- {
- LOG(("Websocket: ConditionallyConnect: [this=%p]", ws));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state");
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- // If there is already another WS channel connecting to this IP address,
- // defer BeginOpen and mark as waiting in queue.
- bool found = (sManager->IndexOf(ws->mAddress) >= 0);
- // Always add ourselves to queue, even if we'll connect immediately
- nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
- LOG(("Websocket: adding conn %p to the queue", newdata));
- sManager->mQueue.AppendElement(newdata);
- if (found) {
- LOG(("Websocket: some other channel is connecting, changing state to "
- "CONNECTING_QUEUED"));
- ws->mConnecting = CONNECTING_QUEUED;
- } else {
- sManager->mFailures.DelayOrBegin(ws);
- }
- }
- static void OnConnected(WebSocketChannel *aChannel)
- {
- LOG(("Websocket: OnConnected: [this=%p]", aChannel));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
- "Channel completed connect, but not connecting?");
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- LOG(("Websocket: changing state to NOT_CONNECTING"));
- aChannel->mConnecting = NOT_CONNECTING;
- // Remove from queue
- sManager->RemoveFromQueue(aChannel);
- // Connection succeeded, so stop keeping track of any previous failures
- sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
- // Check for queued connections to same host.
- // Note: still need to check for failures, since next websocket with same
- // host may have different port
- sManager->ConnectNext(aChannel->mAddress);
- }
- // Called every time a websocket channel ends its session (including going away
- // w/o ever successfully creating a connection)
- static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason)
- {
- LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08x]", aChannel,
- aReason));
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- if (NS_FAILED(aReason)) {
- // Have we seen this failure before?
- FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress,
- aChannel->mPort);
- if (knownFailure) {
- if (aReason == NS_ERROR_NOT_CONNECTED) {
- // Don't count close() before connection as a network error
- LOG(("Websocket close() before connection to %s, %d completed"
- " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
- aChannel));
- } else {
- // repeated failure to connect: increase delay for next connection
- knownFailure->FailedAgain();
- }
- } else {
- // new connection failure: record it.
- LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
- aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
- sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
- }
- }
- if (aChannel->mConnecting) {
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- // Only way a connecting channel may get here w/o failing is if it was
- // closed with GOING_AWAY (1001) because of navigation, tab close, etc.
- MOZ_ASSERT(NS_FAILED(aReason) ||
- aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
- "websocket closed while connecting w/o failing?");
- sManager->RemoveFromQueue(aChannel);
- bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
- LOG(("Websocket: changing state to NOT_CONNECTING"));
- aChannel->mConnecting = NOT_CONNECTING;
- if (wasNotQueued) {
- sManager->ConnectNext(aChannel->mAddress);
- }
- }
- }
- static void IncrementSessionCount()
- {
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- sManager->mSessionCount++;
- }
- static void DecrementSessionCount()
- {
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- sManager->mSessionCount--;
- }
- static void GetSessionCount(int32_t &aSessionCount)
- {
- StaticMutexAutoLock lock(sLock);
- if (!sManager) {
- return;
- }
- aSessionCount = sManager->mSessionCount;
- }
- private:
- nsWSAdmissionManager() : mSessionCount(0)
- {
- MOZ_COUNT_CTOR(nsWSAdmissionManager);
- }
- ~nsWSAdmissionManager()
- {
- MOZ_COUNT_DTOR(nsWSAdmissionManager);
- for (uint32_t i = 0; i < mQueue.Length(); i++)
- delete mQueue[i];
- }
- class nsOpenConn
- {
- public:
- nsOpenConn(nsCString &addr, WebSocketChannel *channel)
- : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
- ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
- nsCString mAddress;
- WebSocketChannel *mChannel;
- };
- void ConnectNext(nsCString &hostName)
- {
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- int32_t index = IndexOf(hostName);
- if (index >= 0) {
- WebSocketChannel *chan = mQueue[index]->mChannel;
- MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED,
- "transaction not queued but in queue");
- LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
- mFailures.DelayOrBegin(chan);
- }
- }
- void RemoveFromQueue(WebSocketChannel *aChannel)
- {
- LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel));
- int32_t index = IndexOf(aChannel);
- MOZ_ASSERT(index >= 0, "connection to remove not in queue");
- if (index >= 0) {
- nsOpenConn *olddata = mQueue[index];
- mQueue.RemoveElementAt(index);
- LOG(("Websocket: removing conn %p from the queue", olddata));
- delete olddata;
- }
- }
- int32_t IndexOf(nsCString &aStr)
- {
- for (uint32_t i = 0; i < mQueue.Length(); i++)
- if (aStr == (mQueue[i])->mAddress)
- return i;
- return -1;
- }
- int32_t IndexOf(WebSocketChannel *aChannel)
- {
- for (uint32_t i = 0; i < mQueue.Length(); i++)
- if (aChannel == (mQueue[i])->mChannel)
- return i;
- return -1;
- }
- // SessionCount might be decremented from the main or the socket
- // thread, so manage it with atomic counters
- Atomic<int32_t> mSessionCount;
- // Queue for websockets that have not completed connecting yet.
- // The first nsOpenConn with a given address will be either be
- // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same
- // hostname must be CONNECTING_QUEUED.
- //
- // We could hash hostnames instead of using a single big vector here, but the
- // dataset is expected to be small.
- nsTArray<nsOpenConn *> mQueue;
- FailDelayManager mFailures;
- static nsWSAdmissionManager *sManager;
- static StaticMutex sLock;
- };
- nsWSAdmissionManager *nsWSAdmissionManager::sManager;
- StaticMutex nsWSAdmissionManager::sLock;
- //-----------------------------------------------------------------------------
- // CallOnMessageAvailable
- //-----------------------------------------------------------------------------
- class CallOnMessageAvailable final : public nsIRunnable
- {
- public:
- NS_DECL_THREADSAFE_ISUPPORTS
- CallOnMessageAvailable(WebSocketChannel* aChannel,
- nsACString& aData,
- int32_t aLen)
- : mChannel(aChannel),
- mListenerMT(aChannel->mListenerMT),
- mData(aData),
- mLen(aLen) {}
- NS_IMETHOD Run() override
- {
- MOZ_ASSERT(mChannel->IsOnTargetThread());
- if (mListenerMT) {
- if (mLen < 0) {
- mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext,
- mData);
- } else {
- mListenerMT->mListener->OnBinaryMessageAvailable(mListenerMT->mContext,
- mData);
- }
- }
- return NS_OK;
- }
- private:
- ~CallOnMessageAvailable() {}
- RefPtr<WebSocketChannel> mChannel;
- RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
- nsCString mData;
- int32_t mLen;
- };
- NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
- //-----------------------------------------------------------------------------
- // CallOnStop
- //-----------------------------------------------------------------------------
- class CallOnStop final : public nsIRunnable
- {
- public:
- NS_DECL_THREADSAFE_ISUPPORTS
- CallOnStop(WebSocketChannel* aChannel,
- nsresult aReason)
- : mChannel(aChannel),
- mListenerMT(mChannel->mListenerMT),
- mReason(aReason)
- {}
- NS_IMETHOD Run() override
- {
- MOZ_ASSERT(mChannel->IsOnTargetThread());
- if (mListenerMT) {
- mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason);
- mChannel->mListenerMT = nullptr;
- }
- return NS_OK;
- }
- private:
- ~CallOnStop() {}
- RefPtr<WebSocketChannel> mChannel;
- RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
- nsresult mReason;
- };
- NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
- //-----------------------------------------------------------------------------
- // CallOnServerClose
- //-----------------------------------------------------------------------------
- class CallOnServerClose final : public nsIRunnable
- {
- public:
- NS_DECL_THREADSAFE_ISUPPORTS
- CallOnServerClose(WebSocketChannel* aChannel,
- uint16_t aCode,
- nsACString& aReason)
- : mChannel(aChannel),
- mListenerMT(mChannel->mListenerMT),
- mCode(aCode),
- mReason(aReason) {}
- NS_IMETHOD Run() override
- {
- MOZ_ASSERT(mChannel->IsOnTargetThread());
- if (mListenerMT) {
- mListenerMT->mListener->OnServerClose(mListenerMT->mContext, mCode,
- mReason);
- }
- return NS_OK;
- }
- private:
- ~CallOnServerClose() {}
- RefPtr<WebSocketChannel> mChannel;
- RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
- uint16_t mCode;
- nsCString mReason;
- };
- NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
- //-----------------------------------------------------------------------------
- // CallAcknowledge
- //-----------------------------------------------------------------------------
- class CallAcknowledge final : public CancelableRunnable
- {
- public:
- CallAcknowledge(WebSocketChannel* aChannel,
- uint32_t aSize)
- : mChannel(aChannel),
- mListenerMT(mChannel->mListenerMT),
- mSize(aSize) {}
- NS_IMETHOD Run() override
- {
- MOZ_ASSERT(mChannel->IsOnTargetThread());
- LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
- if (mListenerMT) {
- mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize);
- }
- return NS_OK;
- }
- private:
- ~CallAcknowledge() {}
- RefPtr<WebSocketChannel> mChannel;
- RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT;
- uint32_t mSize;
- };
- //-----------------------------------------------------------------------------
- // CallOnTransportAvailable
- //-----------------------------------------------------------------------------
- class CallOnTransportAvailable final : public nsIRunnable
- {
- public:
- NS_DECL_THREADSAFE_ISUPPORTS
- CallOnTransportAvailable(WebSocketChannel *aChannel,
- nsISocketTransport *aTransport,
- nsIAsyncInputStream *aSocketIn,
- nsIAsyncOutputStream *aSocketOut)
- : mChannel(aChannel),
- mTransport(aTransport),
- mSocketIn(aSocketIn),
- mSocketOut(aSocketOut) {}
- NS_IMETHOD Run() override
- {
- LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
- return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
- }
- private:
- ~CallOnTransportAvailable() {}
- RefPtr<WebSocketChannel> mChannel;
- nsCOMPtr<nsISocketTransport> mTransport;
- nsCOMPtr<nsIAsyncInputStream> mSocketIn;
- nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
- };
- NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
- //-----------------------------------------------------------------------------
- // PMCECompression
- //-----------------------------------------------------------------------------
- class PMCECompression
- {
- public:
- PMCECompression(bool aNoContextTakeover,
- int32_t aLocalMaxWindowBits,
- int32_t aRemoteMaxWindowBits)
- : mActive(false)
- , mNoContextTakeover(aNoContextTakeover)
- , mResetDeflater(false)
- , mMessageDeflated(false)
- {
- MOZ_COUNT_CTOR(PMCECompression);
- mDeflater.zalloc = mInflater.zalloc = Z_NULL;
- mDeflater.zfree = mInflater.zfree = Z_NULL;
- mDeflater.opaque = mInflater.opaque = Z_NULL;
- if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
- -aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
- if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
- mActive = true;
- } else {
- deflateEnd(&mDeflater);
- }
- }
- }
- ~PMCECompression()
- {
- MOZ_COUNT_DTOR(PMCECompression);
- if (mActive) {
- inflateEnd(&mInflater);
- deflateEnd(&mDeflater);
- }
- }
- bool Active()
- {
- return mActive;
- }
- void SetMessageDeflated()
- {
- MOZ_ASSERT(!mMessageDeflated);
- mMessageDeflated = true;
- }
- bool IsMessageDeflated()
- {
- return mMessageDeflated;
- }
- bool UsingContextTakeover()
- {
- return !mNoContextTakeover;
- }
- nsresult Deflate(uint8_t *data, uint32_t dataLen, nsACString &_retval)
- {
- if (mResetDeflater || mNoContextTakeover) {
- if (deflateReset(&mDeflater) != Z_OK) {
- return NS_ERROR_UNEXPECTED;
- }
- mResetDeflater = false;
- }
- mDeflater.avail_out = kBufferLen;
- mDeflater.next_out = mBuffer;
- mDeflater.avail_in = dataLen;
- mDeflater.next_in = data;
- while (true) {
- int zerr = deflate(&mDeflater, Z_SYNC_FLUSH);
- if (zerr != Z_OK) {
- mResetDeflater = true;
- return NS_ERROR_UNEXPECTED;
- }
- uint32_t deflated = kBufferLen - mDeflater.avail_out;
- if (deflated > 0) {
- _retval.Append(reinterpret_cast<char *>(mBuffer), deflated);
- }
- mDeflater.avail_out = kBufferLen;
- mDeflater.next_out = mBuffer;
- if (mDeflater.avail_in > 0) {
- continue; // There is still some data to deflate
- }
- if (deflated == kBufferLen) {
- continue; // There was not enough space in the buffer
- }
- break;
- }
- if (_retval.Length() < 4) {
- MOZ_ASSERT(false, "Expected trailing not found in deflated data!");
- mResetDeflater = true;
- return NS_ERROR_UNEXPECTED;
- }
- _retval.Truncate(_retval.Length() - 4);
- return NS_OK;
- }
- nsresult Inflate(uint8_t *data, uint32_t dataLen, nsACString &_retval)
- {
- mMessageDeflated = false;
- Bytef trailingData[] = { 0x00, 0x00, 0xFF, 0xFF };
- bool trailingDataUsed = false;
- mInflater.avail_out = kBufferLen;
- mInflater.next_out = mBuffer;
- mInflater.avail_in = dataLen;
- mInflater.next_in = data;
- while (true) {
- int zerr = inflate(&mInflater, Z_NO_FLUSH);
- if (zerr == Z_STREAM_END) {
- Bytef *saveNextIn = mInflater.next_in;
- uint32_t saveAvailIn = mInflater.avail_in;
- Bytef *saveNextOut = mInflater.next_out;
- uint32_t saveAvailOut = mInflater.avail_out;
- inflateReset(&mInflater);
- mInflater.next_in = saveNextIn;
- mInflater.avail_in = saveAvailIn;
- mInflater.next_out = saveNextOut;
- mInflater.avail_out = saveAvailOut;
- } else if (zerr != Z_OK && zerr != Z_BUF_ERROR) {
- return NS_ERROR_INVALID_CONTENT_ENCODING;
- }
- uint32_t inflated = kBufferLen - mInflater.avail_out;
- if (inflated > 0) {
- _retval.Append(reinterpret_cast<char *>(mBuffer), inflated);
- }
- mInflater.avail_out = kBufferLen;
- mInflater.next_out = mBuffer;
- if (mInflater.avail_in > 0) {
- continue; // There is still some data to inflate
- }
- if (inflated == kBufferLen) {
- continue; // There was not enough space in the buffer
- }
- if (!trailingDataUsed) {
- trailingDataUsed = true;
- mInflater.avail_in = sizeof(trailingData);
- mInflater.next_in = trailingData;
- continue;
- }
- return NS_OK;
- }
- }
- private:
- bool mActive;
- bool mNoContextTakeover;
- bool mResetDeflater;
- bool mMessageDeflated;
- z_stream mDeflater;
- z_stream mInflater;
- const static uint32_t kBufferLen = 4096;
- uint8_t mBuffer[kBufferLen];
- };
- //-----------------------------------------------------------------------------
- // OutboundMessage
- //-----------------------------------------------------------------------------
- enum WsMsgType {
- kMsgTypeString = 0,
- kMsgTypeBinaryString,
- kMsgTypeStream,
- kMsgTypePing,
- kMsgTypePong,
- kMsgTypeFin
- };
- static const char* msgNames[] = {
- "text",
- "binaryString",
- "binaryStream",
- "ping",
- "pong",
- "close"
- };
- class OutboundMessage
- {
- public:
- OutboundMessage(WsMsgType type, nsCString *str)
- : mMsgType(type), mDeflated(false), mOrigLength(0)
- {
- MOZ_COUNT_CTOR(OutboundMessage);
- mMsg.pString.mValue = str;
- mMsg.pString.mOrigValue = nullptr;
- mLength = str ? str->Length() : 0;
- }
- OutboundMessage(nsIInputStream *stream, uint32_t length)
- : mMsgType(kMsgTypeStream), mLength(length), mDeflated(false)
- , mOrigLength(0)
- {
- MOZ_COUNT_CTOR(OutboundMessage);
- mMsg.pStream = stream;
- mMsg.pStream->AddRef();
- }
- ~OutboundMessage() {
- MOZ_COUNT_DTOR(OutboundMessage);
- switch (mMsgType) {
- case kMsgTypeString:
- case kMsgTypeBinaryString:
- case kMsgTypePing:
- case kMsgTypePong:
- delete mMsg.pString.mValue;
- if (mMsg.pString.mOrigValue)
- delete mMsg.pString.mOrigValue;
- break;
- case kMsgTypeStream:
- // for now this only gets hit if msg deleted w/o being sent
- if (mMsg.pStream) {
- mMsg.pStream->Close();
- mMsg.pStream->Release();
- }
- break;
- case kMsgTypeFin:
- break; // do-nothing: avoid compiler warning
- }
- }
- WsMsgType GetMsgType() const { return mMsgType; }
- int32_t Length() const { return mLength; }
- int32_t OrigLength() const { return mDeflated ? mOrigLength : mLength; }
- uint8_t* BeginWriting() {
- MOZ_ASSERT(mMsgType != kMsgTypeStream,
- "Stream should have been converted to string by now");
- return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginWriting() : nullptr);
- }
- uint8_t* BeginReading() {
- MOZ_ASSERT(mMsgType != kMsgTypeStream,
- "Stream should have been converted to string by now");
- return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginReading() : nullptr);
- }
- uint8_t* BeginOrigReading() {
- MOZ_ASSERT(mMsgType != kMsgTypeStream,
- "Stream should have been converted to string by now");
- if (!mDeflated)
- return BeginReading();
- return (uint8_t *)(mMsg.pString.mOrigValue ? mMsg.pString.mOrigValue->BeginReading() : nullptr);
- }
- nsresult ConvertStreamToString()
- {
- MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
- #ifdef DEBUG
- // Make sure we got correct length from Blob
- uint64_t bytes;
- mMsg.pStream->Available(&bytes);
- NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
- #endif
- nsAutoPtr<nsCString> temp(new nsCString());
- nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
- NS_ENSURE_SUCCESS(rv, rv);
- mMsg.pStream->Close();
- mMsg.pStream->Release();
- mMsg.pString.mValue = temp.forget();
- mMsg.pString.mOrigValue = nullptr;
- mMsgType = kMsgTypeBinaryString;
- return NS_OK;
- }
- bool DeflatePayload(PMCECompression *aCompressor)
- {
- MOZ_ASSERT(mMsgType != kMsgTypeStream,
- "Stream should have been converted to string by now");
- MOZ_ASSERT(!mDeflated);
- nsresult rv;
- if (mLength == 0) {
- // Empty message
- return false;
- }
- nsAutoPtr<nsCString> temp(new nsCString());
- rv = aCompressor->Deflate(BeginReading(), mLength, *temp);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::OutboundMessage: Deflating payload failed "
- "[rv=0x%08x]\n", rv));
- return false;
- }
- if (!aCompressor->UsingContextTakeover() && temp->Length() > mLength) {
- // 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
- // we would need to empty the sliding window which could affect the
- // compression of the subsequent messages.
- LOG(("WebSocketChannel::OutboundMessage: Not deflating message since the "
- "deflated payload is larger than the original one [deflated=%d, "
- "original=%d]", temp->Length(), mLength));
- return false;
- }
- mOrigLength = mLength;
- mDeflated = true;
- mLength = temp->Length();
- mMsg.pString.mOrigValue = mMsg.pString.mValue;
- mMsg.pString.mValue = temp.forget();
- return true;
- }
- private:
- union {
- struct {
- nsCString *mValue;
- nsCString *mOrigValue;
- } pString;
- nsIInputStream *pStream;
- } mMsg;
- WsMsgType mMsgType;
- uint32_t mLength;
- bool mDeflated;
- uint32_t mOrigLength;
- };
- //-----------------------------------------------------------------------------
- // OutboundEnqueuer
- //-----------------------------------------------------------------------------
- class OutboundEnqueuer final : public nsIRunnable
- {
- public:
- NS_DECL_THREADSAFE_ISUPPORTS
- OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
- : mChannel(aChannel), mMessage(aMsg) {}
- NS_IMETHOD Run() override
- {
- mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
- return NS_OK;
- }
- private:
- ~OutboundEnqueuer() {}
- RefPtr<WebSocketChannel> mChannel;
- OutboundMessage *mMessage;
- };
- NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
- //-----------------------------------------------------------------------------
- // WebSocketChannel
- //-----------------------------------------------------------------------------
- WebSocketChannel::WebSocketChannel() :
- mPort(0),
- mCloseTimeout(20000),
- mOpenTimeout(20000),
- mConnecting(NOT_CONNECTING),
- mMaxConcurrentConnections(200),
- mGotUpgradeOK(0),
- mRecvdHttpUpgradeTransport(0),
- mAutoFollowRedirects(0),
- mAllowPMCE(1),
- mPingOutstanding(0),
- mReleaseOnTransmit(0),
- mDataStarted(0),
- mRequestedClose(0),
- mClientClosed(0),
- mServerClosed(0),
- mStopped(0),
- mCalledOnStop(0),
- mTCPClosed(0),
- mOpenedHttpChannel(0),
- mIncrementedSessionCount(0),
- mDecrementedSessionCount(0),
- mMaxMessageSize(INT32_MAX),
- mStopOnClose(NS_OK),
- mServerCloseCode(CLOSE_ABNORMAL),
- mScriptCloseCode(0),
- mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
- mFragmentAccumulator(0),
- mBuffered(0),
- mBufferSize(kIncomingBufferInitialSize),
- mCurrentOut(nullptr),
- mCurrentOutSent(0),
- mDynamicOutputSize(0),
- mDynamicOutput(nullptr),
- mPrivateBrowsing(false),
- mConnectionLogService(nullptr),
- mCountRecv(0),
- mCountSent(0),
- mAppId(NECKO_NO_APP_ID),
- mIsInIsolatedMozBrowser(false)
- {
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
- nsWSAdmissionManager::Init();
- mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
- nsresult rv;
- mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
- if (NS_FAILED(rv))
- LOG(("Failed to initiate dashboard service."));
- mService = WebSocketEventService::GetOrCreate();
- }
- WebSocketChannel::~WebSocketChannel()
- {
- LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
- if (mWasOpened) {
- MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
- MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
- }
- MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
- MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
- free(mBuffer);
- free(mDynamicOutput);
- delete mCurrentOut;
- while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
- delete mCurrentOut;
- while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
- delete mCurrentOut;
- while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
- delete mCurrentOut;
- NS_ReleaseOnMainThread(mURI.forget());
- NS_ReleaseOnMainThread(mOriginalURI.forget());
- mListenerMT = nullptr;
- NS_ReleaseOnMainThread(mLoadGroup.forget());
- NS_ReleaseOnMainThread(mLoadInfo.forget());
- NS_ReleaseOnMainThread(mService.forget());
- }
- NS_IMETHODIMP
- WebSocketChannel::Observe(nsISupports *subject,
- const char *topic,
- const char16_t *data)
- {
- LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
- if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
- nsCString converted = NS_ConvertUTF16toUTF8(data);
- const char *state = converted.get();
- if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
- LOG(("WebSocket: received network CHANGED event"));
- if (!mSocketThread) {
- // there has not been an asyncopen yet on the object and then we need
- // no ping.
- LOG(("WebSocket: early object, no ping needed"));
- } else {
- // Next we check mDataStarted, which we need to do on mTargetThread.
- if (!IsOnTargetThread()) {
- mTargetThread->Dispatch(
- NewRunnableMethod(this, &WebSocketChannel::OnNetworkChangedTargetThread),
- NS_DISPATCH_NORMAL);
- } else {
- OnNetworkChangedTargetThread();
- }
- }
- }
- }
- return NS_OK;
- }
- nsresult
- WebSocketChannel::OnNetworkChangedTargetThread()
- {
- LOG(("WebSocketChannel::OnNetworkChangedTargetThread() - on target thread %p", this));
- if (!mDataStarted) {
- LOG(("WebSocket: data not started yet, no ping needed"));
- return NS_OK;
- }
- return mSocketThread->Dispatch(
- NewRunnableMethod(this, &WebSocketChannel::OnNetworkChanged),
- NS_DISPATCH_NORMAL);
- }
- nsresult
- WebSocketChannel::OnNetworkChanged()
- {
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this));
- if (mPingOutstanding) {
- // If there's an outstanding ping that's expected to get a pong back
- // we let that do its thing.
- LOG(("WebSocket: pong already pending"));
- return NS_OK;
- }
- if (mPingForced) {
- // avoid more than one
- LOG(("WebSocket: forced ping timer already fired"));
- return NS_OK;
- }
- LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
- if (!mPingTimer) {
- // The ping timer is only conditionally running already. If it wasn't
- // already created do it here.
- nsresult rv;
- mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_FAILED(rv)) {
- LOG(("WebSocket: unable to create ping timer!"));
- NS_WARNING("unable to create ping timer!");
- return rv;
- }
- }
- // Trigger the ping timeout asap to fire off a new ping. Wait just
- // a little bit to better avoid multi-triggers.
- mPingForced = 1;
- mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
- return NS_OK;
- }
- void
- WebSocketChannel::Shutdown()
- {
- nsWSAdmissionManager::Shutdown();
- }
- bool
- WebSocketChannel::IsOnTargetThread()
- {
- MOZ_ASSERT(mTargetThread);
- bool isOnTargetThread = false;
- nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- return NS_FAILED(rv) ? false : isOnTargetThread;
- }
- void
- WebSocketChannel::GetEffectiveURL(nsAString& aEffectiveURL) const
- {
- aEffectiveURL = mEffectiveURL;
- }
- bool
- WebSocketChannel::IsEncrypted() const
- {
- return mEncrypted;
- }
- void
- WebSocketChannel::BeginOpen(bool aCalledFromAdmissionManager)
- {
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- LOG(("WebSocketChannel::BeginOpen() %p\n", this));
- // Important that we set CONNECTING_IN_PROGRESS before any call to
- // AbortSession here: ensures that any remaining queued connection(s) are
- // scheduled in OnStopSession
- LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS"));
- mConnecting = CONNECTING_IN_PROGRESS;
- if (aCalledFromAdmissionManager) {
- // When called from nsWSAdmissionManager post an event to avoid potential
- // re-entering of nsWSAdmissionManager and its lock.
- NS_DispatchToMainThread(
- NewRunnableMethod(this, &WebSocketChannel::BeginOpenInternal),
- NS_DISPATCH_NORMAL);
- } else {
- BeginOpenInternal();
- }
- }
- void
- WebSocketChannel::BeginOpenInternal()
- {
- LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this));
- nsresult rv;
- if (mRedirectCallback) {
- LOG(("WebSocketChannel::BeginOpenInternal: Resuming Redirect\n"));
- rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
- mRedirectCallback = nullptr;
- return;
- }
- nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
- AbortSession(NS_ERROR_UNEXPECTED);
- return;
- }
- if (localChannel) {
- NS_GetAppInfo(localChannel, &mAppId, &mIsInIsolatedMozBrowser);
- }
- rv = NS_MaybeOpenChannelUsingAsyncOpen2(localChannel, this);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
- AbortSession(NS_ERROR_CONNECTION_REFUSED);
- return;
- }
- mOpenedHttpChannel = 1;
- mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::BeginOpenInternal: cannot create open timer\n"));
- AbortSession(NS_ERROR_UNEXPECTED);
- return;
- }
- rv = mOpenTimer->InitWithCallback(this, mOpenTimeout,
- nsITimer::TYPE_ONE_SHOT);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::BeginOpenInternal: cannot initialize open "
- "timer\n"));
- AbortSession(NS_ERROR_UNEXPECTED);
- return;
- }
- }
- bool
- WebSocketChannel::IsPersistentFramePtr()
- {
- return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
- }
- // Extends the internal buffer by count and returns the total
- // amount of data available for read
- //
- // Accumulated fragment size is passed in instead of using the member
- // variable beacuse when transitioning from the stack to the persistent
- // read buffer we want to explicitly include them in the buffer instead
- // of as already existing data.
- bool
- WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count,
- uint32_t accumulatedFragments,
- uint32_t *available)
- {
- LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
- this, buffer, count));
- if (!mBuffered)
- mFramePtr = mBuffer;
- MOZ_ASSERT(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
- MOZ_ASSERT(mFramePtr - accumulatedFragments >= mBuffer,
- "reserved FramePtr bad");
- if (mBuffered + count <= mBufferSize) {
- // append to existing buffer
- LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
- } else if (mBuffered + count -
- (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
- // make room in existing buffer by shifting unused data to start
- mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
- LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
- ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
- mFramePtr = mBuffer + accumulatedFragments;
- } else {
- // existing buffer is not sufficient, extend it
- mBufferSize += count + 8192 + mBufferSize/3;
- LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
- uint8_t *old = mBuffer;
- mBuffer = (uint8_t *)realloc(mBuffer, mBufferSize);
- if (!mBuffer) {
- mBuffer = old;
- return false;
- }
- mFramePtr = mBuffer + (mFramePtr - old);
- }
- ::memcpy(mBuffer + mBuffered, buffer, count);
- mBuffered += count;
- if (available)
- *available = mBuffered - (mFramePtr - mBuffer);
- return true;
- }
- nsresult
- WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
- {
- LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- nsresult rv;
- // The purpose of ping/pong is to actively probe the peer so that an
- // unreachable peer is not mistaken for a period of idleness. This
- // implementation accepts any application level read activity as a sign of
- // life, it does not necessarily have to be a pong.
- ResetPingTimer();
- uint32_t avail;
- if (!mBuffered) {
- // Most of the time we can process right off the stack buffer without
- // having to accumulate anything
- mFramePtr = buffer;
- avail = count;
- } else {
- if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
- return NS_ERROR_FILE_TOO_BIG;
- }
- }
- uint8_t *payload;
- uint32_t totalAvail = avail;
- while (avail >= 2) {
- int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
- uint8_t finBit = mFramePtr[0] & kFinalFragBit;
- uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
- uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
- uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
- uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
- uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
- uint8_t maskBit = mFramePtr[1] & kMaskBit;
- uint32_t mask = 0;
- uint32_t framingLength = 2;
- if (maskBit)
- framingLength += 4;
- if (payloadLength64 < 126) {
- if (avail < framingLength)
- break;
- } else if (payloadLength64 == 126) {
- // 16 bit length field
- framingLength += 2;
- if (avail < framingLength)
- break;
- payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
- } else {
- // 64 bit length
- framingLength += 8;
- if (avail < framingLength)
- break;
- if (mFramePtr[2] & 0x80) {
- // Section 4.2 says that the most significant bit MUST be
- // 0. (i.e. this is really a 63 bit value)
- LOG(("WebSocketChannel:: high bit of 64 bit length set"));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- // copy this in case it is unaligned
- payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
- }
- payload = mFramePtr + framingLength;
- avail -= framingLength;
- LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
- payloadLength64, avail));
- CheckedInt<int64_t> payloadLengthChecked(payloadLength64);
- payloadLengthChecked += mFragmentAccumulator;
- if (!payloadLengthChecked.isValid() || payloadLengthChecked.value() >
- mMaxMessageSize) {
- return NS_ERROR_FILE_TOO_BIG;
- }
- uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
- if (avail < payloadLength)
- break;
- 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) {
- 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);
- }
- 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));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- if (rsvBits) {
- // PMCE sets RSV1 bit in the first fragment when the non-control frame
- // is deflated
- if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 &&
- !(opcode & kControlFrameMask)) {
- mPMCECompressor->SetMessageDeflated();
- LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
- } else {
- LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
- rsvBits));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- }
- if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
- // This is part of a fragment response
- // Only the first frame has a non zero op code: Make sure we don't see a
- // first frame while some old fragments are open
- if ((mFragmentAccumulator != 0) &&
- (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
- LOG(("WebSocketChannel:: nested fragments\n"));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
- if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
- // Make sure this continuation fragment isn't the first fragment
- if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
- LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- // For frag > 1 move the data body back on top of the headers
- // so we have contiguous stream of data
- MOZ_ASSERT(mFramePtr + framingLength == payload,
- "payload offset from frameptr wrong");
- ::memmove(mFramePtr, payload, avail);
- payload = mFramePtr;
- if (mBuffered)
- mBuffered -= framingLength;
- } else {
- mFragmentOpcode = opcode;
- }
- if (finBit) {
- LOG(("WebSocketChannel:: Finalizing Fragment\n"));
- payload -= mFragmentAccumulator;
- payloadLength += mFragmentAccumulator;
- avail += mFragmentAccumulator;
- mFragmentAccumulator = 0;
- opcode = mFragmentOpcode;
- // reset to detect if next message illegally starts with continuation
- mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
- } else {
- opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
- mFragmentAccumulator += payloadLength;
- }
- } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
- // This frame is not part of a fragment sequence but we
- // have an open fragment.. it must be a control code or else
- // we have a problem
- LOG(("WebSocketChannel:: illegal fragment sequence\n"));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- if (mServerClosed) {
- LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
- opcode));
- // nop
- } else if (mStopped) {
- LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
- opcode));
- } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
- bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
- LOG(("WebSocketChannel:: %stext frame received\n",
- isDeflated ? "deflated " : ""));
- if (mListenerMT) {
- nsCString utf8Data;
- if (isDeflated) {
- rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data);
- if (NS_FAILED(rv)) {
- return rv;
- }
- LOG(("WebSocketChannel:: message successfully inflated "
- "[origLength=%d, newLength=%d]\n", payloadLength,
- utf8Data.Length()));
- } else {
- if (!utf8Data.Assign((const char *)payload, payloadLength,
- mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- }
- // Section 8.1 says to fail connection if invalid utf-8 in text message
- if (!IsUTF8(utf8Data, false)) {
- LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
- return NS_ERROR_CANNOT_CONVERT_DATA;
- }
- RefPtr<WebSocketFrame> frame =
- mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
- opcode, maskBit, mask, utf8Data);
- if (frame) {
- mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
- }
- mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
- NS_DISPATCH_NORMAL);
- if (mConnectionLogService && !mPrivateBrowsing) {
- mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
- LOG(("Added new msg received for %s", mHost.get()));
- }
- }
- } else if (opcode & kControlFrameMask) {
- // control frames
- if (payloadLength > 125) {
- LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
- opcode, payloadLength));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- RefPtr<WebSocketFrame> frame =
- mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
- opcode, maskBit, mask, payload,
- payloadLength);
- if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
- LOG(("WebSocketChannel:: close received\n"));
- mServerClosed = 1;
- mServerCloseCode = CLOSE_NO_STATUS;
- if (payloadLength >= 2) {
- mServerCloseCode = NetworkEndian::readUint16(payload);
- LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
- uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
- if (msglen > 0) {
- mServerCloseReason.SetLength(msglen);
- memcpy(mServerCloseReason.BeginWriting(),
- (const char *)payload + 2, msglen);
- // section 8.1 says to replace received non utf-8 sequences
- // (which are non-conformant to send) with u+fffd,
- // but secteam feels that silently rewriting messages is
- // inappropriate - so we will fail the connection instead.
- if (!IsUTF8(mServerCloseReason, false)) {
- LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
- return NS_ERROR_CANNOT_CONVERT_DATA;
- }
- LOG(("WebSocketChannel:: close msg %s\n",
- mServerCloseReason.get()));
- }
- }
- if (mCloseTimer) {
- mCloseTimer->Cancel();
- mCloseTimer = nullptr;
- }
- if (frame) {
- // We send the frame immediately becuase we want to have it dispatched
- // before the CallOnServerClose.
- mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
- frame = nullptr;
- }
- if (mListenerMT) {
- mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
- mServerCloseReason),
- NS_DISPATCH_NORMAL);
- }
- if (mClientClosed)
- ReleaseSession();
- } else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
- LOG(("WebSocketChannel:: ping received\n"));
- GeneratePong(payload, payloadLength);
- } else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
- // opcode OPCODE_PONG: the mere act of receiving the packet is all we
- // need to do for the pong to trigger the activity timers
- LOG(("WebSocketChannel:: pong received\n"));
- } else {
- /* unknown control frame opcode */
- LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- if (mFragmentAccumulator) {
- // Remove the control frame from the stream so we have a contiguous
- // data buffer of reassembled fragments
- LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
- MOZ_ASSERT(mFramePtr + framingLength == payload,
- "payload offset from frameptr wrong");
- ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
- payload = mFramePtr;
- avail -= payloadLength;
- if (mBuffered)
- mBuffered -= framingLength + payloadLength;
- payloadLength = 0;
- }
- if (frame) {
- mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
- }
- } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
- bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
- LOG(("WebSocketChannel:: %sbinary frame received\n",
- isDeflated ? "deflated " : ""));
- if (mListenerMT) {
- nsCString binaryData;
- if (isDeflated) {
- rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData);
- if (NS_FAILED(rv)) {
- return rv;
- }
- LOG(("WebSocketChannel:: message successfully inflated "
- "[origLength=%d, newLength=%d]\n", payloadLength,
- binaryData.Length()));
- } else {
- if (!binaryData.Assign((const char *)payload, payloadLength,
- mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- }
- RefPtr<WebSocketFrame> frame =
- mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
- opcode, maskBit, mask, binaryData);
- if (frame) {
- mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
- }
- mTargetThread->Dispatch(
- new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
- NS_DISPATCH_NORMAL);
- // To add the header to 'Networking Dashboard' log
- if (mConnectionLogService && !mPrivateBrowsing) {
- mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
- LOG(("Added new received msg for %s", mHost.get()));
- }
- }
- } else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
- /* unknown opcode */
- LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
- return NS_ERROR_ILLEGAL_VALUE;
- }
- mFramePtr = payload + payloadLength;
- avail -= payloadLength;
- totalAvail = avail;
- }
- // Adjust the stateful buffer. If we were operating off the stack and
- // now have a partial message then transition to the buffer, or if
- // we were working off the buffer but no longer have any active state
- // then transition to the stack
- if (!IsPersistentFramePtr()) {
- mBuffered = 0;
- if (mFragmentAccumulator) {
- LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
- if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
- totalAvail + mFragmentAccumulator, 0, nullptr)) {
- return NS_ERROR_FILE_TOO_BIG;
- }
- // UpdateReadBuffer will reset the frameptr to the beginning
- // of new saved state, so we need to skip past processed framgents
- mFramePtr += mFragmentAccumulator;
- } else if (totalAvail) {
- LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
- if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
- return NS_ERROR_FILE_TOO_BIG;
- }
- }
- } else if (!mFragmentAccumulator && !totalAvail) {
- // If we were working off a saved buffer state and there is no partial
- // frame or fragment in process, then revert to stack behavior
- LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
- mBuffered = 0;
- // release memory if we've been processing a large message
- if (mBufferSize > kIncomingBufferStableSize) {
- mBufferSize = kIncomingBufferStableSize;
- free(mBuffer);
- mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
- }
- }
- return NS_OK;
- }
- /* static */ void
- WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
- {
- if (!data || len == 0)
- return;
- // Optimally we want to apply the mask 32 bits at a time,
- // but the buffer might not be alligned. So we first deal with
- // 0 to 3 bytes of preamble individually
- while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
- *data ^= mask >> 24;
- mask = RotateLeft(mask, 8);
- data++;
- len--;
- }
- // perform mask on full words of data
- uint32_t *iData = (uint32_t *) data;
- uint32_t *end = iData + (len / 4);
- NetworkEndian::writeUint32(&mask, mask);
- for (; iData < end; iData++)
- *iData ^= mask;
- mask = NetworkEndian::readUint32(&mask);
- data = (uint8_t *)iData;
- len = len % 4;
- // There maybe up to 3 trailing bytes that need to be dealt with
- // individually
- while (len) {
- *data ^= mask >> 24;
- mask = RotateLeft(mask, 8);
- data++;
- len--;
- }
- }
- void
- WebSocketChannel::GeneratePing()
- {
- nsCString *buf = new nsCString();
- buf->AssignLiteral("PING");
- EnqueueOutgoingMessage(mOutgoingPingMessages,
- new OutboundMessage(kMsgTypePing, buf));
- }
- void
- WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
- {
- nsCString *buf = new nsCString();
- buf->SetLength(len);
- if (buf->Length() < len) {
- LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
- delete buf;
- return;
- }
- memcpy(buf->BeginWriting(), payload, len);
- EnqueueOutgoingMessage(mOutgoingPongMessages,
- new OutboundMessage(kMsgTypePong, buf));
- }
- void
- WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
- OutboundMessage *aMsg)
- {
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- LOG(("WebSocketChannel::EnqueueOutgoingMessage %p "
- "queueing msg %p [type=%s len=%d]\n",
- this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
- aQueue.Push(aMsg);
- OnOutputStreamReady(mSocketOut);
- }
- uint16_t
- WebSocketChannel::ResultToCloseCode(nsresult resultCode)
- {
- if (NS_SUCCEEDED(resultCode))
- return CLOSE_NORMAL;
- switch (resultCode) {
- case NS_ERROR_FILE_TOO_BIG:
- case NS_ERROR_OUT_OF_MEMORY:
- return CLOSE_TOO_LARGE;
- case NS_ERROR_CANNOT_CONVERT_DATA:
- return CLOSE_INVALID_PAYLOAD;
- case NS_ERROR_UNEXPECTED:
- return CLOSE_INTERNAL_ERROR;
- default:
- return CLOSE_PROTOCOL_ERROR;
- }
- }
- void
- WebSocketChannel::PrimeNewOutgoingMessage()
- {
- LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- MOZ_ASSERT(!mCurrentOut, "Current message in progress");
- nsresult rv = NS_OK;
- mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
- if (mCurrentOut) {
- MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong,
- "Not pong message!");
- } else {
- mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
- if (mCurrentOut)
- MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing,
- "Not ping message!");
- else
- mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
- }
- if (!mCurrentOut)
- return;
- WsMsgType msgType = mCurrentOut->GetMsgType();
- LOG(("WebSocketChannel::PrimeNewOutgoingMessage "
- "%p found queued msg %p [type=%s len=%d]\n",
- this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
- mCurrentOutSent = 0;
- mHdrOut = mOutHeader;
- uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
- uint8_t maskSize = mIsServerSide ? 0 : 4;
- uint8_t *payload = nullptr;
- if (msgType == kMsgTypeFin) {
- // This is a demand to create a close message
- if (mClientClosed) {
- DeleteCurrentOutGoingMessage();
- PrimeNewOutgoingMessage();
- return;
- }
- mClientClosed = 1;
- mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
- mOutHeader[1] = maskBit;
- // 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()
- // and there isn't an internal error, use that.
- if (NS_SUCCEEDED(mStopOnClose)) {
- if (mScriptCloseCode) {
- NetworkEndian::writeUint16(payload, mScriptCloseCode);
- mOutHeader[1] += 2;
- mHdrOutToSend = 4 + maskSize;
- if (!mScriptCloseReason.IsEmpty()) {
- MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
- "Close Reason Too Long");
- mOutHeader[1] += mScriptCloseReason.Length();
- mHdrOutToSend += mScriptCloseReason.Length();
- memcpy (payload + 2,
- mScriptCloseReason.BeginReading(),
- mScriptCloseReason.Length());
- }
- } else {
- // 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 = 2 + maskSize;
- }
- } else {
- NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
- mOutHeader[1] += 2;
- mHdrOutToSend = 4 + maskSize;
- }
- if (mServerClosed) {
- /* bidi close complete */
- mReleaseOnTransmit = 1;
- } else if (NS_FAILED(mStopOnClose)) {
- /* result of abort session - give up */
- StopSession(mStopOnClose);
- } else {
- /* wait for reciprocal close from server */
- mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_SUCCEEDED(rv)) {
- mCloseTimer->InitWithCallback(this, mCloseTimeout,
- nsITimer::TYPE_ONE_SHOT);
- } else {
- StopSession(rv);
- }
- }
- } else {
- switch (msgType) {
- case kMsgTypePong:
- mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
- break;
- case kMsgTypePing:
- mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
- break;
- case kMsgTypeString:
- mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
- break;
- case kMsgTypeStream:
- // HACK ALERT: read in entire stream into string.
- // Will block socket transport thread if file is blocking.
- // TODO: bug 704447: don't block socket thread!
- rv = mCurrentOut->ConvertStreamToString();
- if (NS_FAILED(rv)) {
- AbortSession(NS_ERROR_FILE_TOO_BIG);
- return;
- }
- // Now we're a binary string
- msgType = kMsgTypeBinaryString;
- // no break: fall down into binary string case
- MOZ_FALLTHROUGH;
- case kMsgTypeBinaryString:
- mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
- break;
- case kMsgTypeFin:
- MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
- break;
- }
- // deflate the payload if PMCE is negotiated
- if (mPMCECompressor &&
- (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) {
- if (mCurrentOut->DeflatePayload(mPMCECompressor)) {
- // The payload was deflated successfully, set RSV1 bit
- mOutHeader[0] |= kRsv1Bit;
- LOG(("WebSocketChannel::PrimeNewOutgoingMessage %p current msg %p was "
- "deflated [origLength=%d, newLength=%d].\n", this, mCurrentOut,
- mCurrentOut->OrigLength(), mCurrentOut->Length()));
- }
- }
- if (mCurrentOut->Length() < 126) {
- mOutHeader[1] = mCurrentOut->Length() | maskBit;
- mHdrOutToSend = 2 + maskSize;
- } else if (mCurrentOut->Length() <= 0xffff) {
- mOutHeader[1] = 126 | maskBit;
- NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
- mCurrentOut->Length());
- mHdrOutToSend = 4 + maskSize;
- } else {
- mOutHeader[1] = 127 | maskBit;
- NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
- mHdrOutToSend = 10 + maskSize;
- }
- payload = mOutHeader + mHdrOutToSend;
- }
- MOZ_ASSERT(payload, "payload offset not found");
- uint32_t mask = 0;
- if (!mIsServerSide) {
- // Perform the sending mask. Never use a zero mask
- do {
- uint8_t *buffer;
- static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
- nsresult rv = mRandomGenerator->GenerateRandomBytes(sizeof(mask),
- &buffer);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
- "GenerateRandomBytes failure %x\n", rv));
- StopSession(rv);
- return;
- }
- memcpy(&mask, buffer, sizeof(mask));
- free(buffer);
- } while (!mask);
- NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
- }
- LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
- // We don't mask the framing, but occasionally we stick a little payload
- // data in the buffer used for the framing. Close frames are the current
- // example. This data needs to be masked, but it is never more than a
- // handful of bytes and might rotate the mask, so we can just do it locally.
- // For real data frames we ship the bulk of the payload off to ApplyMask()
- RefPtr<WebSocketFrame> frame =
- mService->CreateFrameIfNeeded(
- mOutHeader[0] & WebSocketChannel::kFinalFragBit,
- mOutHeader[0] & WebSocketChannel::kRsv1Bit,
- mOutHeader[0] & WebSocketChannel::kRsv2Bit,
- mOutHeader[0] & WebSocketChannel::kRsv3Bit,
- mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
- mOutHeader[1] & WebSocketChannel::kMaskBit,
- mask,
- payload, mHdrOutToSend - (payload - mOutHeader),
- mCurrentOut->BeginOrigReading(),
- mCurrentOut->OrigLength());
- if (frame) {
- mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
- }
- 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());
- }
- int32_t len = mCurrentOut->Length();
- // for small frames, copy it all together for a contiguous write
- if (len && len <= kCopyBreak) {
- memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
- mHdrOutToSend += len;
- mCurrentOutSent = len;
- }
- // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
- // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
- // coaleseced into the former for small messages or as the result of the
- // compression process.
- }
- void
- WebSocketChannel::DeleteCurrentOutGoingMessage()
- {
- delete mCurrentOut;
- mCurrentOut = nullptr;
- mCurrentOutSent = 0;
- }
- void
- WebSocketChannel::EnsureHdrOut(uint32_t size)
- {
- LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
- if (mDynamicOutputSize < size) {
- mDynamicOutputSize = size;
- mDynamicOutput =
- (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
- }
- mHdrOut = mDynamicOutput;
- }
- namespace {
- class RemoveObserverRunnable : public Runnable
- {
- RefPtr<WebSocketChannel> mChannel;
- public:
- explicit RemoveObserverRunnable(WebSocketChannel* aChannel)
- : mChannel(aChannel)
- {}
- NS_IMETHOD Run() override
- {
- nsCOMPtr<nsIObserverService> observerService =
- mozilla::services::GetObserverService();
- if (!observerService) {
- NS_WARNING("failed to get observer service");
- return NS_OK;
- }
- observerService->RemoveObserver(mChannel, NS_NETWORK_LINK_TOPIC);
- return NS_OK;
- }
- };
- } // namespace
- void
- WebSocketChannel::CleanupConnection()
- {
- LOG(("WebSocketChannel::CleanupConnection() %p", this));
- if (mLingeringCloseTimer) {
- mLingeringCloseTimer->Cancel();
- mLingeringCloseTimer = nullptr;
- }
- if (mSocketIn) {
- mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
- mSocketIn = nullptr;
- }
- if (mSocketOut) {
- mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
- mSocketOut = nullptr;
- }
- if (mTransport) {
- mTransport->SetSecurityCallbacks(nullptr);
- mTransport->SetEventSink(nullptr, nullptr);
- mTransport->Close(NS_BASE_STREAM_CLOSED);
- mTransport = nullptr;
- }
- if (mConnectionLogService && !mPrivateBrowsing) {
- mConnectionLogService->RemoveHost(mHost, mSerial);
- }
- // This method can run in any thread, but the observer has to be removed on
- // the main-thread.
- NS_DispatchToMainThread(new RemoveObserverRunnable(this));
- DecrementSessionCount();
- }
- void
- WebSocketChannel::StopSession(nsresult reason)
- {
- LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason));
- // normally this should be called on socket thread, but it is ok to call it
- // from OnStartRequest before the socket thread machine has gotten underway
- mStopped = 1;
- if (!mOpenedHttpChannel) {
- // The HTTP channel information will never be used in this case
- NS_ReleaseOnMainThread(mChannel.forget());
- NS_ReleaseOnMainThread(mHttpChannel.forget());
- NS_ReleaseOnMainThread(mLoadGroup.forget());
- NS_ReleaseOnMainThread(mCallbacks.forget());
- }
- if (mCloseTimer) {
- mCloseTimer->Cancel();
- mCloseTimer = nullptr;
- }
- if (mOpenTimer) {
- mOpenTimer->Cancel();
- mOpenTimer = nullptr;
- }
- if (mReconnectDelayTimer) {
- mReconnectDelayTimer->Cancel();
- mReconnectDelayTimer = nullptr;
- }
- if (mPingTimer) {
- mPingTimer->Cancel();
- mPingTimer = nullptr;
- }
- if (mSocketIn && !mTCPClosed) {
- // Drain, within reason, this socket. if we leave any data
- // unconsumed (including the tcp fin) a RST will be generated
- // The right thing to do here is shutdown(SHUT_WR) and then wait
- // a little while to see if any data comes in.. but there is no
- // reason to delay things for that when the websocket handshake
- // is supposed to guarantee a quiet connection except for that fin.
- char buffer[512];
- uint32_t count = 0;
- uint32_t total = 0;
- nsresult rv;
- do {
- total += count;
- rv = mSocketIn->Read(buffer, 512, &count);
- if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
- (NS_FAILED(rv) || count == 0))
- mTCPClosed = true;
- } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
- }
- int32_t sessionCount = kLingeringCloseThreshold;
- nsWSAdmissionManager::GetSessionCount(sessionCount);
- if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) {
- // 7.1.1 says that the client SHOULD wait for the server to close the TCP
- // connection. This is so we can reuse port numbers before 2 MSL expires,
- // which is not really as much of a concern for us as the amount of state
- // that might be accrued by keeping this channel object around waiting for
- // the server. We handle the SHOULD by waiting a short time in the common
- // case, but not waiting in the case of high concurrency.
- //
- // Normally this will be taken care of in AbortSession() after mTCPClosed
- // is set when the server close arrives without waiting for the timeout to
- // expire.
- LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
- nsresult rv;
- mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_SUCCEEDED(rv))
- mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
- nsITimer::TYPE_ONE_SHOT);
- else
- CleanupConnection();
- } else {
- CleanupConnection();
- }
- if (mCancelable) {
- mCancelable->Cancel(NS_ERROR_UNEXPECTED);
- mCancelable = nullptr;
- }
- mPMCECompressor = nullptr;
- if (!mCalledOnStop) {
- mCalledOnStop = 1;
- nsWSAdmissionManager::OnStopSession(this, reason);
- RefPtr<CallOnStop> runnable = new CallOnStop(this, reason);
- mTargetThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
- }
- }
- void
- WebSocketChannel::AbortSession(nsresult reason)
- {
- LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
- this, reason, !!mStopped));
- // normally this should be called on socket thread, but it is ok to call it
- // from the main thread before StartWebsocketData() has completed
- // When we are failing we need to close the TCP connection immediately
- // as per 7.1.1
- mTCPClosed = true;
- if (mLingeringCloseTimer) {
- MOZ_ASSERT(mStopped, "Lingering without Stop");
- LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
- CleanupConnection();
- return;
- }
- if (mStopped)
- return;
- mStopped = 1;
- if (mTransport && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose &&
- !mClientClosed && !mServerClosed && mConnecting == NOT_CONNECTING) {
- mRequestedClose = 1;
- mStopOnClose = reason;
- mSocketThread->Dispatch(
- new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
- nsIEventTarget::DISPATCH_NORMAL);
- } else {
- StopSession(reason);
- }
- }
- // ReleaseSession is called on orderly shutdown
- void
- WebSocketChannel::ReleaseSession()
- {
- LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
- this, !!mStopped));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- if (mStopped)
- return;
- StopSession(NS_OK);
- }
- void
- WebSocketChannel::IncrementSessionCount()
- {
- if (!mIncrementedSessionCount) {
- nsWSAdmissionManager::IncrementSessionCount();
- mIncrementedSessionCount = 1;
- }
- }
- void
- WebSocketChannel::DecrementSessionCount()
- {
- // Make sure we decrement session count only once, and only if we incremented it.
- // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is
- // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
- // times when they'll never be a race condition for checking/setting them.
- if (mIncrementedSessionCount && !mDecrementedSessionCount) {
- nsWSAdmissionManager::DecrementSessionCount();
- mDecrementedSessionCount = 1;
- }
- }
- 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()
- {
- LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
- nsresult rv;
- nsAutoCString extensions;
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- rv = mHttpChannel->GetResponseHeader(
- NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
- extensions.CompressWhitespace();
- if (extensions.IsEmpty()) {
- return NS_OK;
- }
- LOG(("WebSocketChannel::HandleExtensions: received "
- "Sec-WebSocket-Extensions header: %s\n", extensions.get()));
- bool clientNoContextTakeover;
- bool serverNoContextTakeover;
- int32_t clientMaxWindowBits;
- int32_t serverMaxWindowBits;
- rv = ParseWebSocketExtension(extensions,
- eParseClientSide,
- clientNoContextTakeover,
- serverNoContextTakeover,
- clientMaxWindowBits,
- serverMaxWindowBits);
- if (NS_FAILED(rv)) {
- AbortSession(rv);
- return rv;
- }
- if (!mAllowPMCE) {
- LOG(("WebSocketChannel::HandleExtensions: "
- "Recvd permessage-deflate which wasn't offered\n"));
- 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;
- }
- 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()
- {
- LOG(("WebSocketChannel::SetupRequest() %p\n", this));
- nsresult rv;
- if (mLoadGroup) {
- rv = mHttpChannel->SetLoadGroup(mLoadGroup);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND |
- nsIRequest::INHIBIT_CACHING |
- nsIRequest::LOAD_BYPASS_CACHE |
- nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
- NS_ENSURE_SUCCESS(rv, rv);
- // we never let websockets be blocked by head CSS/JS loads to avoid
- // potential deadlock where server generation of CSS/JS requires
- // an XHR signal.
- nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
- if (cos) {
- cos->AddClassFlags(nsIClassOfService::Unblocked);
- }
- // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
- // in lower case, so go with that. It is technically case insensitive.
- rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this);
- NS_ENSURE_SUCCESS(rv, rv);
- mHttpChannel->SetRequestHeader(
- NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
- NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
- if (!mOrigin.IsEmpty())
- mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
- false);
- if (!mProtocol.IsEmpty())
- mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
- mProtocol, true);
- if (mAllowPMCE)
- mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
- NS_LITERAL_CSTRING("permessage-deflate"),
- false);
- uint8_t *secKey;
- nsAutoCString secKeyString;
- rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
- NS_ENSURE_SUCCESS(rv, rv);
- char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
- free(secKey);
- if (!b64)
- return NS_ERROR_OUT_OF_MEMORY;
- secKeyString.Assign(b64);
- PR_Free(b64);
- mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
- secKeyString, false);
- LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
- // prepare the value we expect to see in
- // the sec-websocket-accept response header
- rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
- NS_ENSURE_SUCCESS(rv, rv);
- LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
- mHashedSecret.get()));
- return NS_OK;
- }
- nsresult
- WebSocketChannel::DoAdmissionDNS()
- {
- nsresult rv;
- nsCString hostName;
- rv = mURI->GetHost(hostName);
- NS_ENSURE_SUCCESS(rv, rv);
- mAddress = hostName;
- rv = mURI->GetPort(&mPort);
- NS_ENSURE_SUCCESS(rv, rv);
- if (mPort == -1)
- mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
- nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIThread> mainThread;
- NS_GetMainThread(getter_AddRefs(mainThread));
- MOZ_ASSERT(!mCancelable);
- return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable));
- }
- nsresult
- WebSocketChannel::ApplyForAdmission()
- {
- LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
- // Websockets has a policy of 1 session at a time being allowed in the
- // CONNECTING state per server IP address (not hostname)
- // Check to see if a proxy is being used before making DNS call
- nsCOMPtr<nsIProtocolProxyService> pps =
- do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
- if (!pps) {
- // go straight to DNS
- // expect the callback in ::OnLookupComplete
- LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
- return DoAdmissionDNS();
- }
- MOZ_ASSERT(!mCancelable);
- nsresult rv;
- rv = pps->AsyncResolve(mHttpChannel,
- nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
- nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
- this, getter_AddRefs(mCancelable));
- NS_ASSERTION(NS_FAILED(rv) || mCancelable,
- "nsIProtocolProxyService::AsyncResolve succeeded but didn't "
- "return a cancelable object!");
- return rv;
- }
- // Called after both OnStartRequest and OnTransportAvailable have
- // executed. This essentially ends the handshake and starts the websockets
- // protocol state machine.
- nsresult
- WebSocketChannel::StartWebsocketData()
- {
- nsresult rv;
- if (!IsOnTargetThread()) {
- return mTargetThread->Dispatch(
- NewRunnableMethod(this, &WebSocketChannel::StartWebsocketData),
- NS_DISPATCH_NORMAL);
- }
- LOG(("WebSocketChannel::StartWebsocketData() %p", this));
- MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice");
- mDataStarted = 1;
- rv = mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::StartWebsocketData mSocketIn->AsyncWait() failed "
- "with error 0x%08x", rv));
- return mSocketThread->Dispatch(
- NewRunnableMethod<nsresult>(this,
- &WebSocketChannel::AbortSession,
- rv),
- NS_DISPATCH_NORMAL);
- }
- if (mPingInterval) {
- rv = mSocketThread->Dispatch(
- NewRunnableMethod(this, &WebSocketChannel::StartPinging),
- NS_DISPATCH_NORMAL);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::StartWebsocketData Could not start pinging, "
- "rv=0x%08x", rv));
- return rv;
- }
- }
- LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p",
- mListenerMT ? mListenerMT->mListener.get() : nullptr));
- if (mListenerMT) {
- mListenerMT->mListener->OnStart(mListenerMT->mContext);
- }
- return NS_OK;
- }
- nsresult
- WebSocketChannel::StartPinging()
- {
- LOG(("WebSocketChannel::StartPinging() %p", this));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- MOZ_ASSERT(mPingInterval);
- MOZ_ASSERT(!mPingTimer);
- nsresult rv;
- mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_FAILED(rv)) {
- NS_WARNING("unable to create ping timer. Carrying on.");
- } else {
- LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
- mPingInterval));
- mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
- }
- return NS_OK;
- }
- void
- WebSocketChannel::ReportConnectionTelemetry()
- {
- // 3 bits are used. high bit is for wss, middle bit for failed,
- // and low bit for proxy..
- // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
- // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
- bool didProxy = false;
- nsCOMPtr<nsIProxyInfo> pi;
- nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
- if (pc)
- pc->GetProxyInfo(getter_AddRefs(pi));
- if (pi) {
- nsAutoCString proxyType;
- pi->GetType(proxyType);
- if (!proxyType.IsEmpty() &&
- !proxyType.EqualsLiteral("direct"))
- didProxy = true;
- }
- uint8_t value = (mEncrypted ? (1 << 2) : 0) |
- (!mGotUpgradeOK ? (1 << 1) : 0) |
- (didProxy ? (1 << 0) : 0);
- LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
- }
- // nsIDNSListener
- NS_IMETHODIMP
- WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
- nsIDNSRecord *aRecord,
- nsresult aStatus)
- {
- LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
- this, aRequest, aRecord, aStatus));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- if (mStopped) {
- LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
- mCancelable = nullptr;
- return NS_OK;
- }
- mCancelable = nullptr;
- // These failures are not fatal - we just use the hostname as the key
- if (NS_FAILED(aStatus)) {
- LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
- // set host in case we got here without calling DoAdmissionDNS()
- mURI->GetHost(mAddress);
- } else {
- nsresult rv = aRecord->GetNextAddrAsString(mAddress);
- if (NS_FAILED(rv))
- LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
- }
- LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
- nsWSAdmissionManager::ConditionallyConnect(this);
- return NS_OK;
- }
- // nsIProtocolProxyCallback
- NS_IMETHODIMP
- WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
- nsIProxyInfo *pi, nsresult status)
- {
- if (mStopped) {
- LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
- mCancelable = nullptr;
- return NS_OK;
- }
- MOZ_ASSERT(!mCancelable || (aRequest == mCancelable));
- mCancelable = nullptr;
- nsAutoCString type;
- if (NS_SUCCEEDED(status) && pi &&
- NS_SUCCEEDED(pi->GetType(type)) &&
- !type.EqualsLiteral("direct")) {
- LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this));
- // call DNS callback directly without DNS resolver
- OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
- } else {
- LOG(("WebSocketChannel::OnProxyAvailable[%p] checking DNS resolution\n", this));
- nsresult rv = DoAdmissionDNS();
- if (NS_FAILED(rv)) {
- LOG(("WebSocket OnProxyAvailable [%p] DNS lookup failed\n", this));
- // call DNS callback directly without DNS resolver
- OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
- }
- }
- return NS_OK;
- }
- // nsIInterfaceRequestor
- NS_IMETHODIMP
- WebSocketChannel::GetInterface(const nsIID & iid, void **result)
- {
- LOG(("WebSocketChannel::GetInterface() %p\n", this));
- if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
- return QueryInterface(iid, result);
- if (mCallbacks)
- return mCallbacks->GetInterface(iid, result);
- return NS_ERROR_FAILURE;
- }
- // nsIChannelEventSink
- NS_IMETHODIMP
- WebSocketChannel::AsyncOnChannelRedirect(
- nsIChannel *oldChannel,
- nsIChannel *newChannel,
- uint32_t flags,
- nsIAsyncVerifyRedirectCallback *callback)
- {
- LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- nsresult rv;
- nsCOMPtr<nsIURI> newuri;
- rv = newChannel->GetURI(getter_AddRefs(newuri));
- NS_ENSURE_SUCCESS(rv, rv);
- // newuri is expected to be http or https
- bool newuriIsHttps = false;
- rv = newuri->SchemeIs("https", &newuriIsHttps);
- NS_ENSURE_SUCCESS(rv, rv);
- if (!mAutoFollowRedirects) {
- // Even if redirects configured off, still allow them for HTTP Strict
- // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
- if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
- nsIChannelEventSink::REDIRECT_STS_UPGRADE))) {
- nsAutoCString newSpec;
- rv = newuri->GetSpec(newSpec);
- NS_ENSURE_SUCCESS(rv, rv);
- LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
- newSpec.get()));
- return NS_ERROR_FAILURE;
- }
- }
- if (mEncrypted && !newuriIsHttps) {
- nsAutoCString spec;
- if (NS_SUCCEEDED(newuri->GetSpec(spec)))
- LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
- spec.get()));
- return NS_ERROR_FAILURE;
- }
- nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
- return rv;
- }
- nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
- do_QueryInterface(newChannel, &rv);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
- return rv;
- }
- // The redirect is likely OK
- newChannel->SetNotificationCallbacks(this);
- mEncrypted = newuriIsHttps;
- newuri->Clone(getter_AddRefs(mURI));
- if (mEncrypted)
- rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
- else
- rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
- mHttpChannel = newHttpChannel;
- mChannel = newUpgradeChannel;
- rv = SetupRequest();
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
- return rv;
- }
- // Redirected-to URI may need to be delayed by 1-connecting-per-host and
- // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback
- // until BeginOpen, when we know it's OK to proceed with new channel.
- mRedirectCallback = callback;
- // Mark old channel as successfully connected so we'll clear any FailDelay
- // associated with the old URI. Note: no need to also call OnStopSession:
- // it's a no-op for successful, already-connected channels.
- nsWSAdmissionManager::OnConnected(this);
- // ApplyForAdmission as if we were starting from fresh...
- mAddress.Truncate();
- mOpenedHttpChannel = 0;
- rv = ApplyForAdmission();
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
- mRedirectCallback = nullptr;
- return rv;
- }
- return NS_OK;
- }
- // nsITimerCallback
- NS_IMETHODIMP
- WebSocketChannel::Notify(nsITimer *timer)
- {
- LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
- if (timer == mCloseTimer) {
- MOZ_ASSERT(mClientClosed, "Close Timeout without local close");
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread,
- "not socket thread");
- mCloseTimer = nullptr;
- if (mStopped || mServerClosed) /* no longer relevant */
- return NS_OK;
- LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
- AbortSession(NS_ERROR_NET_TIMEOUT);
- } else if (timer == mOpenTimer) {
- MOZ_ASSERT(!mGotUpgradeOK,
- "Open Timer after open complete");
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- mOpenTimer = nullptr;
- LOG(("WebSocketChannel:: Connection Timed Out\n"));
- if (mStopped || mServerClosed) /* no longer relevant */
- return NS_OK;
- AbortSession(NS_ERROR_NET_TIMEOUT);
- } else if (timer == mReconnectDelayTimer) {
- MOZ_ASSERT(mConnecting == CONNECTING_DELAYED,
- "woke up from delay w/o being delayed?");
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- mReconnectDelayTimer = nullptr;
- LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
- BeginOpen(false);
- } else if (timer == mPingTimer) {
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread,
- "not socket thread");
- if (mClientClosed || mServerClosed || mRequestedClose) {
- // no point in worrying about ping now
- mPingTimer = nullptr;
- return NS_OK;
- }
- if (!mPingOutstanding) {
- // Ping interval must be non-null or PING was forced by OnNetworkChanged()
- MOZ_ASSERT(mPingInterval || mPingForced);
- LOG(("nsWebSocketChannel:: Generating Ping\n"));
- mPingOutstanding = 1;
- mPingForced = 0;
- mPingTimer->InitWithCallback(this, mPingResponseTimeout,
- nsITimer::TYPE_ONE_SHOT);
- GeneratePing();
- } else {
- LOG(("nsWebSocketChannel:: Timed out Ping\n"));
- mPingTimer = nullptr;
- AbortSession(NS_ERROR_NET_TIMEOUT);
- }
- } else if (timer == mLingeringCloseTimer) {
- LOG(("WebSocketChannel:: Lingering Close Timer"));
- CleanupConnection();
- } else {
- MOZ_ASSERT(0, "Unknown Timer");
- }
- return NS_OK;
- }
- // nsIWebSocketChannel
- NS_IMETHODIMP
- WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
- {
- LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- if (mTransport) {
- if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
- *aSecurityInfo = nullptr;
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- WebSocketChannel::AsyncOpen(nsIURI *aURI,
- const nsACString &aOrigin,
- uint64_t aInnerWindowID,
- nsIWebSocketListener *aListener,
- nsISupports *aContext)
- {
- LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
- if (!NS_IsMainThread()) {
- MOZ_ASSERT(false, "not main thread");
- LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));
- return NS_ERROR_UNEXPECTED;
- }
- if ((!aURI && !mIsServerSide) || !aListener) {
- LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
- return NS_ERROR_UNEXPECTED;
- }
- if (mListenerMT || mWasOpened)
- return NS_ERROR_ALREADY_OPENED;
- nsresult rv;
- // Ensure target thread is set.
- if (!mTargetThread) {
- mTargetThread = do_GetMainThread();
- }
- mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
- if (NS_FAILED(rv)) {
- NS_WARNING("unable to continue without socket transport service");
- return rv;
- }
- nsCOMPtr<nsIPrefBranch> prefService;
- prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
- if (prefService) {
- int32_t intpref;
- bool boolpref;
- rv = prefService->GetIntPref("network.websocket.max-message-size",
- &intpref);
- if (NS_SUCCEEDED(rv)) {
- mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
- }
- rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
- if (NS_SUCCEEDED(rv)) {
- mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
- }
- rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
- if (NS_SUCCEEDED(rv)) {
- mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
- }
- rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
- &intpref);
- if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
- mPingInterval = clamped(intpref, 0, 86400) * 1000;
- }
- rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
- &intpref);
- if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
- mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
- }
- rv = prefService->GetBoolPref("network.websocket.extensions.permessage-deflate",
- &boolpref);
- if (NS_SUCCEEDED(rv)) {
- mAllowPMCE = boolpref ? 1 : 0;
- }
- rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
- &boolpref);
- if (NS_SUCCEEDED(rv)) {
- mAutoFollowRedirects = boolpref ? 1 : 0;
- }
- rv = prefService->GetIntPref
- ("network.websocket.max-connections", &intpref);
- if (NS_SUCCEEDED(rv)) {
- mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
- }
- }
- int32_t sessionCount = -1;
- nsWSAdmissionManager::GetSessionCount(sessionCount);
- if (sessionCount >= 0) {
- LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
- sessionCount, mMaxConcurrentConnections));
- }
- if (sessionCount >= mMaxConcurrentConnections) {
- LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
- mMaxConcurrentConnections,
- sessionCount));
- // WebSocket connections are expected to be long lived, so return
- // an error here instead of queueing
- return NS_ERROR_SOCKET_CREATE_FAILED;
- }
- mInnerWindowID = aInnerWindowID;
- mOriginalURI = aURI;
- mURI = mOriginalURI;
- mOrigin = aOrigin;
- 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;
- mURI->Clone(getter_AddRefs(localURI));
- if (mEncrypted)
- rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
- else
- rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
- NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIIOService> ioService;
- ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
- if (NS_FAILED(rv)) {
- NS_WARNING("unable to continue without io service");
- return rv;
- }
- nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
- if (NS_FAILED(rv)) {
- NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
- return rv;
- }
- // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
- // allow setting proxy uri/flags
- rv = io2->NewChannelFromURIWithProxyFlags2(
- localURI,
- mURI,
- nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
- nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
- mLoadInfo->LoadingNode() ?
- mLoadInfo->LoadingNode()->AsDOMNode() : nullptr,
- mLoadInfo->LoadingPrincipal(),
- mLoadInfo->TriggeringPrincipal(),
- mLoadInfo->GetSecurityFlags(),
- mLoadInfo->InternalContentPolicyType(),
- getter_AddRefs(localChannel));
- NS_ENSURE_SUCCESS(rv, rv);
- // Please note that we still call SetLoadInfo on the channel because
- // we want the same instance of the loadInfo to be set on the channel.
- rv = localChannel->SetLoadInfo(mLoadInfo);
- NS_ENSURE_SUCCESS(rv, rv);
- // Pass most GetInterface() requests through to our instantiator, but handle
- // nsIChannelEventSink in this object in order to deal with redirects
- localChannel->SetNotificationCallbacks(this);
- class MOZ_STACK_CLASS CleanUpOnFailure
- {
- public:
- explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel)
- : mWebSocketChannel(aWebSocketChannel)
- {}
- ~CleanUpOnFailure()
- {
- if (!mWebSocketChannel->mWasOpened) {
- mWebSocketChannel->mChannel = nullptr;
- mWebSocketChannel->mHttpChannel = nullptr;
- }
- }
- WebSocketChannel *mWebSocketChannel;
- };
- CleanUpOnFailure cuof(this);
- mChannel = do_QueryInterface(localChannel, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- mHttpChannel = do_QueryInterface(localChannel, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = SetupRequest();
- if (NS_FAILED(rv))
- return rv;
- mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
- if (mConnectionLogService && !mPrivateBrowsing) {
- mConnectionLogService->AddHost(mHost, mSerial,
- BaseWebSocketChannel::mEncrypted);
- }
- rv = ApplyForAdmission();
- if (NS_FAILED(rv))
- return rv;
- // Register for prefs change notifications
- nsCOMPtr<nsIObserverService> observerService =
- mozilla::services::GetObserverService();
- if (!observerService) {
- NS_WARNING("failed to get observer service");
- return NS_ERROR_FAILURE;
- }
- rv = observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- // Only set these if the open was successful:
- //
- mWasOpened = 1;
- mListenerMT = new ListenerAndContextContainer(aListener, aContext);
- IncrementSessionCount();
- return rv;
- }
- NS_IMETHODIMP
- WebSocketChannel::Close(uint16_t code, const nsACString & reason)
- {
- LOG(("WebSocketChannel::Close() %p\n", this));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- // save the networkstats (bug 855949)
- SaveNetworkStats(true);
- if (mRequestedClose) {
- return NS_OK;
- }
- // The API requires the UTF-8 string to be 123 or less bytes
- if (reason.Length() > 123)
- return NS_ERROR_ILLEGAL_VALUE;
- mRequestedClose = 1;
- mScriptCloseReason = reason;
- mScriptCloseCode = code;
- if (!mTransport || mConnecting != NOT_CONNECTING) {
- nsresult rv;
- if (code == CLOSE_GOING_AWAY) {
- // Not an error: for example, tab has closed or navigated away
- LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
- rv = NS_OK;
- } else {
- LOG(("WebSocketChannel::Close() without transport - error."));
- rv = NS_ERROR_NOT_CONNECTED;
- }
- StopSession(rv);
- return rv;
- }
- return mSocketThread->Dispatch(
- new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
- nsIEventTarget::DISPATCH_NORMAL);
- }
- NS_IMETHODIMP
- WebSocketChannel::SendMsg(const nsACString &aMsg)
- {
- LOG(("WebSocketChannel::SendMsg() %p\n", this));
- return SendMsgCommon(&aMsg, false, aMsg.Length());
- }
- NS_IMETHODIMP
- WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
- {
- LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
- return SendMsgCommon(&aMsg, true, aMsg.Length());
- }
- NS_IMETHODIMP
- WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength)
- {
- LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
- return SendMsgCommon(nullptr, true, aLength, aStream);
- }
- nsresult
- WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
- uint32_t aLength, nsIInputStream *aStream)
- {
- MOZ_ASSERT(IsOnTargetThread(), "not target thread");
- if (!mDataStarted) {
- LOG(("WebSocketChannel:: Error: data not started yet\n"));
- return NS_ERROR_UNEXPECTED;
- }
- if (mRequestedClose) {
- LOG(("WebSocketChannel:: Error: send when closed\n"));
- return NS_ERROR_UNEXPECTED;
- }
- if (mStopped) {
- LOG(("WebSocketChannel:: Error: send when stopped\n"));
- return NS_ERROR_NOT_CONNECTED;
- }
- MOZ_ASSERT(mMaxMessageSize >= 0, "max message size negative");
- if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
- LOG(("WebSocketChannel:: Error: message too big\n"));
- return NS_ERROR_FILE_TOO_BIG;
- }
- if (mConnectionLogService && !mPrivateBrowsing) {
- mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
- LOG(("Added new msg sent for %s", mHost.get()));
- }
- return mSocketThread->Dispatch(
- aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
- : new OutboundEnqueuer(this,
- new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
- : kMsgTypeString,
- new nsCString(*aMsg))),
- nsIEventTarget::DISPATCH_NORMAL);
- }
- // nsIHttpUpgradeListener
- NS_IMETHODIMP
- WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
- nsIAsyncInputStream *aSocketIn,
- nsIAsyncOutputStream *aSocketOut)
- {
- if (!NS_IsMainThread()) {
- return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
- aTransport,
- aSocketIn,
- aSocketOut));
- }
- LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
- this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
- if (mStopped) {
- LOG(("WebSocketChannel::OnTransportAvailable: Already stopped"));
- return NS_OK;
- }
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA duplicated");
- MOZ_ASSERT(aSocketIn, "OTA with invalid socketIn");
- mTransport = aTransport;
- mSocketIn = aSocketIn;
- mSocketOut = aSocketOut;
- nsresult rv;
- rv = mTransport->SetEventSink(nullptr, nullptr);
- if (NS_FAILED(rv)) return rv;
- rv = mTransport->SetSecurityCallbacks(this);
- if (NS_FAILED(rv)) return rv;
- mRecvdHttpUpgradeTransport = 1;
- if (mGotUpgradeOK) {
- // We're now done CONNECTING, which means we can now open another,
- // perhaps parallel, connection to the same host if one
- // is pending
- nsWSAdmissionManager::OnConnected(this);
- 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;
- }
- // nsIRequestObserver (from nsIStreamListener)
- NS_IMETHODIMP
- WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
- nsISupports *aContext)
- {
- LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
- this, aRequest, mHttpChannel.get(), mRecvdHttpUpgradeTransport));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- MOZ_ASSERT(!mGotUpgradeOK, "OTA duplicated");
- if (mOpenTimer) {
- mOpenTimer->Cancel();
- mOpenTimer = nullptr;
- }
- if (mStopped) {
- LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
- AbortSession(NS_ERROR_CONNECTION_REFUSED);
- return NS_ERROR_CONNECTION_REFUSED;
- }
- nsresult rv;
- uint32_t status;
- char *val, *token;
- rv = mHttpChannel->GetResponseStatus(&status);
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
- AbortSession(NS_ERROR_CONNECTION_REFUSED);
- return NS_ERROR_CONNECTION_REFUSED;
- }
- LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
- if (status != 101) {
- AbortSession(NS_ERROR_CONNECTION_REFUSED);
- return NS_ERROR_CONNECTION_REFUSED;
- }
- nsAutoCString respUpgrade;
- rv = mHttpChannel->GetResponseHeader(
- NS_LITERAL_CSTRING("Upgrade"), respUpgrade);
- if (NS_SUCCEEDED(rv)) {
- rv = NS_ERROR_ILLEGAL_VALUE;
- if (!respUpgrade.IsEmpty()) {
- val = respUpgrade.BeginWriting();
- while ((token = nsCRT::strtok(val, ", \t", &val))) {
- if (PL_strcasecmp(token, "Websocket") == 0) {
- rv = NS_OK;
- break;
- }
- }
- }
- }
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::OnStartRequest: "
- "HTTP response header Upgrade: websocket not found\n"));
- AbortSession(NS_ERROR_ILLEGAL_VALUE);
- return rv;
- }
- nsAutoCString respConnection;
- rv = mHttpChannel->GetResponseHeader(
- NS_LITERAL_CSTRING("Connection"), respConnection);
- if (NS_SUCCEEDED(rv)) {
- rv = NS_ERROR_ILLEGAL_VALUE;
- if (!respConnection.IsEmpty()) {
- val = respConnection.BeginWriting();
- while ((token = nsCRT::strtok(val, ", \t", &val))) {
- if (PL_strcasecmp(token, "Upgrade") == 0) {
- rv = NS_OK;
- break;
- }
- }
- }
- }
- if (NS_FAILED(rv)) {
- LOG(("WebSocketChannel::OnStartRequest: "
- "HTTP response header 'Connection: Upgrade' not found\n"));
- AbortSession(NS_ERROR_ILLEGAL_VALUE);
- return rv;
- }
- nsAutoCString respAccept;
- rv = mHttpChannel->GetResponseHeader(
- NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
- respAccept);
- if (NS_FAILED(rv) ||
- respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) {
- LOG(("WebSocketChannel::OnStartRequest: "
- "HTTP response header Sec-WebSocket-Accept check failed\n"));
- LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
- mHashedSecret.get(), respAccept.get()));
- AbortSession(NS_ERROR_ILLEGAL_VALUE);
- return NS_ERROR_ILLEGAL_VALUE;
- }
- // If we sent a sub protocol header, verify the response matches
- // If it does not, set mProtocol to "" so the protocol attribute
- // of the WebSocket JS object reflects that
- if (!mProtocol.IsEmpty()) {
- nsAutoCString respProtocol;
- rv = mHttpChannel->GetResponseHeader(
- NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
- respProtocol);
- if (NS_SUCCEEDED(rv)) {
- rv = NS_ERROR_ILLEGAL_VALUE;
- val = mProtocol.BeginWriting();
- while ((token = nsCRT::strtok(val, ", \t", &val))) {
- if (PL_strcasecmp(token, respProtocol.get()) == 0) {
- rv = NS_OK;
- break;
- }
- }
- if (NS_SUCCEEDED(rv)) {
- LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
- respProtocol.get()));
- mProtocol = respProtocol;
- } else {
- LOG(("WebsocketChannel::OnStartRequest: "
- "subprotocol [%s] not found - %s returned",
- mProtocol.get(), respProtocol.get()));
- mProtocol.Truncate();
- }
- } else {
- LOG(("WebsocketChannel::OnStartRequest "
- "subprotocol [%s] not found - none returned",
- mProtocol.get()));
- mProtocol.Truncate();
- }
- }
- rv = HandleExtensions();
- if (NS_FAILED(rv))
- return rv;
- // Update mEffectiveURL for off main thread URI access.
- nsCOMPtr<nsIURI> uri = mURI ? mURI : mOriginalURI;
- nsAutoCString spec;
- rv = uri->GetSpec(spec);
- MOZ_ASSERT(NS_SUCCEEDED(rv));
- CopyUTF8toUTF16(spec, mEffectiveURL);
- mGotUpgradeOK = 1;
- if (mRecvdHttpUpgradeTransport) {
- // We're now done CONNECTING, which means we can now open another,
- // perhaps parallel, connection to the same host if one
- // is pending
- nsWSAdmissionManager::OnConnected(this);
- return StartWebsocketData();
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
- nsISupports *aContext,
- nsresult aStatusCode)
- {
- LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
- this, aRequest, mHttpChannel.get(), aStatusCode));
- MOZ_ASSERT(NS_IsMainThread(), "not main thread");
- ReportConnectionTelemetry();
- // This is the end of the HTTP upgrade transaction, the
- // upgraded streams live on
- mChannel = nullptr;
- mHttpChannel = nullptr;
- mLoadGroup = nullptr;
- mCallbacks = nullptr;
- return NS_OK;
- }
- // nsIInputStreamCallback
- NS_IMETHODIMP
- WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
- {
- LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- if (!mSocketIn) // did we we clean up the socket after scheduling InputReady?
- return NS_OK;
- // this is after the http upgrade - so we are speaking websockets
- char buffer[2048];
- uint32_t count;
- nsresult rv;
- do {
- rv = mSocketIn->Read((char *)buffer, 2048, &count);
- LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
- // accumulate received bytes
- CountRecvBytes(count);
- if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
- mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
- return NS_OK;
- }
- if (NS_FAILED(rv)) {
- mTCPClosed = true;
- AbortSession(rv);
- return rv;
- }
- if (count == 0) {
- mTCPClosed = true;
- AbortSession(NS_BASE_STREAM_CLOSED);
- return NS_OK;
- }
- if (mStopped) {
- continue;
- }
- rv = ProcessInput((uint8_t *)buffer, count);
- if (NS_FAILED(rv)) {
- AbortSession(rv);
- return rv;
- }
- } while (NS_SUCCEEDED(rv) && mSocketIn);
- return NS_OK;
- }
- // nsIOutputStreamCallback
- NS_IMETHODIMP
- WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
- {
- LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
- MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "not socket thread");
- nsresult rv;
- if (!mCurrentOut)
- PrimeNewOutgoingMessage();
- while (mCurrentOut && mSocketOut) {
- const char *sndBuf;
- uint32_t toSend;
- uint32_t amtSent;
- if (mHdrOut) {
- sndBuf = (const char *)mHdrOut;
- toSend = mHdrOutToSend;
- LOG(("WebSocketChannel::OnOutputStreamReady: "
- "Try to send %u of hdr/copybreak\n", toSend));
- } else {
- sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
- toSend = mCurrentOut->Length() - mCurrentOutSent;
- if (toSend > 0) {
- LOG(("WebSocketChannel::OnOutputStreamReady: "
- "Try to send %u of data\n", toSend));
- }
- }
- if (toSend == 0) {
- amtSent = 0;
- } else {
- rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
- LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
- amtSent, rv));
- // accumulate sent bytes
- CountSentBytes(amtSent);
- if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
- mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
- return NS_OK;
- }
- if (NS_FAILED(rv)) {
- AbortSession(rv);
- return NS_OK;
- }
- }
- if (mHdrOut) {
- if (amtSent == toSend) {
- mHdrOut = nullptr;
- mHdrOutToSend = 0;
- } else {
- mHdrOut += amtSent;
- mHdrOutToSend -= amtSent;
- mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
- }
- } else {
- if (amtSent == toSend) {
- if (!mStopped) {
- mTargetThread->Dispatch(
- new CallAcknowledge(this, mCurrentOut->OrigLength()),
- NS_DISPATCH_NORMAL);
- }
- DeleteCurrentOutGoingMessage();
- PrimeNewOutgoingMessage();
- } else {
- mCurrentOutSent += amtSent;
- mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
- }
- }
- }
- if (mReleaseOnTransmit)
- ReleaseSession();
- return NS_OK;
- }
- // nsIStreamListener
- NS_IMETHODIMP
- WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
- nsISupports *aContext,
- nsIInputStream *aInputStream,
- uint64_t aOffset,
- uint32_t aCount)
- {
- LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n",
- this, aRequest, mHttpChannel.get(), aInputStream, aOffset, aCount));
- // This is the HTTP OnDataAvailable Method, which means this is http data in
- // response to the upgrade request and there should be no http response body
- // if the upgrade succeeded. This generally should be caught by a non 101
- // response code in OnStartRequest().. so we can ignore the data here
- LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
- aCount));
- return NS_OK;
- }
- nsresult
- WebSocketChannel::SaveNetworkStats(bool enforce)
- {
- return NS_ERROR_NOT_IMPLEMENTED;
- }
- } // namespace net
- } // namespace mozilla
- #undef CLOSE_GOING_AWAY
|