1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116 |
- /* -*- Mode: C++; tab-width: 2; 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 "mozilla/Attributes.h"
- #include "mozilla/DebugOnly.h"
- #include "mozilla/Likely.h"
- #include "mozilla/Unused.h"
- #include "mozilla/net/CookieServiceChild.h"
- #include "mozilla/net/NeckoCommon.h"
- #include "nsCookieService.h"
- #include "nsContentUtils.h"
- #include "nsIServiceManager.h"
- #include "nsIIOService.h"
- #include "nsIPrefBranch.h"
- #include "nsIPrefService.h"
- #include "nsIScriptError.h"
- #include "nsICookiePermission.h"
- #include "nsIURI.h"
- #include "nsIURL.h"
- #include "nsIChannel.h"
- #include "nsIFile.h"
- #include "nsIObserverService.h"
- #include "nsILineInputStream.h"
- #include "nsIEffectiveTLDService.h"
- #include "nsIIDNService.h"
- #include "mozIThirdPartyUtil.h"
- #include "nsTArray.h"
- #include "nsCOMArray.h"
- #include "nsIMutableArray.h"
- #include "nsArrayEnumerator.h"
- #include "nsEnumeratorUtils.h"
- #include "nsAutoPtr.h"
- #include "nsReadableUtils.h"
- #include "nsCRT.h"
- #include "prprf.h"
- #include "nsNetUtil.h"
- #include "nsNetCID.h"
- #include "nsISimpleEnumerator.h"
- #include "nsIInputStream.h"
- #include "nsAppDirectoryServiceDefs.h"
- #include "nsNetCID.h"
- #include "mozilla/storage.h"
- #include "mozilla/AutoRestore.h"
- #include "mozilla/FileUtils.h"
- #include "nsIAppsService.h"
- #include "mozIApplication.h"
- #include "mozIApplicationClearPrivateDataParams.h"
- #include "nsIConsoleService.h"
- #include "nsVariant.h"
- using namespace mozilla;
- using namespace mozilla::net;
- // Create key from baseDomain that will access the default cookie namespace.
- // TODO: When we figure out what the API will look like for nsICookieManager{2}
- // on content processes (see bug 777620), change to use the appropriate app
- // namespace. For now those IDLs aren't supported on child processes.
- #define DEFAULT_APP_KEY(baseDomain) \
- nsCookieKey(baseDomain, NeckoOriginAttributes())
- /******************************************************************************
- * nsCookieService impl:
- * useful types & constants
- ******************************************************************************/
- static nsCookieService *gCookieService;
- // XXX_hack. See bug 178993.
- // This is a hack to hide HttpOnly cookies from older browsers
- #define HTTP_ONLY_PREFIX "#HttpOnly_"
- #define COOKIES_FILE "cookies.sqlite"
- #define COOKIES_SCHEMA_VERSION 7
- // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
- // ReadCookieDBListener::HandleResult
- #define IDX_NAME 0
- #define IDX_VALUE 1
- #define IDX_HOST 2
- #define IDX_PATH 3
- #define IDX_EXPIRY 4
- #define IDX_LAST_ACCESSED 5
- #define IDX_CREATION_TIME 6
- #define IDX_SECURE 7
- #define IDX_HTTPONLY 8
- #define IDX_BASE_DOMAIN 9
- #define IDX_ORIGIN_ATTRIBUTES 10
- static const int64_t kCookiePurgeAge =
- int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
- #define OLD_COOKIE_FILE_NAME "cookies.txt"
- #undef LIMIT
- #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
- #undef ADD_TEN_PERCENT
- #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
- // default limits for the cookie list. these can be tuned by the
- // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
- static const uint32_t kMaxNumberOfCookies = 3000;
- static const uint32_t kMaxCookiesPerHost = 150;
- static const uint32_t kMaxBytesPerCookie = 4096;
- static const uint32_t kMaxBytesPerPath = 1024;
- // pref string constants
- static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior";
- static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
- static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost";
- static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge";
- static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly";
- static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
- static void
- bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
- const nsCookieKey &aKey,
- const nsCookie *aCookie);
- // struct for temporarily storing cookie attributes during header parsing
- struct nsCookieAttributes
- {
- nsAutoCString name;
- nsAutoCString value;
- nsAutoCString host;
- nsAutoCString path;
- nsAutoCString expires;
- nsAutoCString maxage;
- int64_t expiryTime;
- bool isSession;
- bool isSecure;
- bool isHttpOnly;
- };
- // stores the nsCookieEntry entryclass and an index into the cookie array
- // within that entryclass, for purposes of storing an iteration state that
- // points to a certain cookie.
- struct nsListIter
- {
- // default (non-initializing) constructor.
- nsListIter() = default;
- // explicit constructor to a given iterator state with entryclass 'aEntry'
- // and index 'aIndex'.
- explicit
- nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex)
- : entry(aEntry)
- , index(aIndex)
- {
- }
- // get the nsCookie * the iterator currently points to.
- nsCookie * Cookie() const
- {
- return entry->GetCookies()[index];
- }
- nsCookieEntry *entry;
- nsCookieEntry::IndexType index;
- };
- /******************************************************************************
- * Cookie logging handlers
- * used for logging in nsCookieService
- ******************************************************************************/
- // logging handlers
- #ifdef MOZ_LOGGING
- // in order to do logging, the following environment variables need to be set:
- //
- // set MOZ_LOG=cookie:3 -- shows rejected cookies
- // set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies
- // set MOZ_LOG_FILE=cookie.log
- //
- #include "mozilla/Logging.h"
- #endif
- // define logging macros for convenience
- #define SET_COOKIE true
- #define GET_COOKIE false
- static LazyLogModule gCookieLog("cookie");
- #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d)
- #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e)
- #define COOKIE_LOGEVICTED(a, details) \
- PR_BEGIN_MACRO \
- if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \
- LogEvicted(a, details); \
- PR_END_MACRO
- #define COOKIE_LOGSTRING(lvl, fmt) \
- PR_BEGIN_MACRO \
- MOZ_LOG(gCookieLog, lvl, fmt); \
- MOZ_LOG(gCookieLog, lvl, ("\n")); \
- PR_END_MACRO
- static void
- LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason)
- {
- // if logging isn't enabled, return now to save cycles
- if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning))
- return;
- nsAutoCString spec;
- if (aHostURI)
- aHostURI->GetAsciiSpec(spec);
- MOZ_LOG(gCookieLog, LogLevel::Warning,
- ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT"));
- MOZ_LOG(gCookieLog, LogLevel::Warning,("request URL: %s\n", spec.get()));
- if (aSetCookie)
- MOZ_LOG(gCookieLog, LogLevel::Warning,("cookie string: %s\n", aCookieString));
- PRExplodedTime explodedTime;
- PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
- char timeString[40];
- PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
- MOZ_LOG(gCookieLog, LogLevel::Warning,("current time: %s", timeString));
- MOZ_LOG(gCookieLog, LogLevel::Warning,("rejected because %s\n", aReason));
- MOZ_LOG(gCookieLog, LogLevel::Warning,("\n"));
- }
- static void
- LogCookie(nsCookie *aCookie)
- {
- PRExplodedTime explodedTime;
- PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime);
- char timeString[40];
- PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
- MOZ_LOG(gCookieLog, LogLevel::Debug,("current time: %s", timeString));
- if (aCookie) {
- MOZ_LOG(gCookieLog, LogLevel::Debug,("----------------\n"));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("name: %s\n", aCookie->Name().get()));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("value: %s\n", aCookie->Value().get()));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get()));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("path: %s\n", aCookie->Path().get()));
- PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC),
- PR_GMTParameters, &explodedTime);
- PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
- MOZ_LOG(gCookieLog, LogLevel::Debug,
- ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
- PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
- PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
- MOZ_LOG(gCookieLog, LogLevel::Debug,("created: %s", timeString));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
- }
- }
- static void
- LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
- {
- // if logging isn't enabled, return now to save cycles
- if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
- return;
- }
- nsAutoCString spec;
- if (aHostURI)
- aHostURI->GetAsciiSpec(spec);
- MOZ_LOG(gCookieLog, LogLevel::Debug,
- ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT"));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("request URL: %s\n", spec.get()));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("cookie string: %s\n", aCookieString));
- if (aSetCookie)
- MOZ_LOG(gCookieLog, LogLevel::Debug,("replaces existing cookie: %s\n", aReplacing ? "true" : "false"));
- LogCookie(aCookie);
- MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
- }
- static void
- LogEvicted(nsCookie *aCookie, const char* details)
- {
- MOZ_LOG(gCookieLog, LogLevel::Debug,("===== COOKIE EVICTED =====\n"));
- MOZ_LOG(gCookieLog, LogLevel::Debug,("%s\n", details));
- LogCookie(aCookie);
- MOZ_LOG(gCookieLog, LogLevel::Debug,("\n"));
- }
- // inline wrappers to make passing in nsAFlatCStrings easier
- static inline void
- LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason)
- {
- LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason);
- }
- static inline void
- LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing)
- {
- LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing);
- }
- #ifdef DEBUG
- #define NS_ASSERT_SUCCESS(res) \
- PR_BEGIN_MACRO \
- nsresult __rv = res; /* Do not evaluate |res| more than once! */ \
- if (NS_FAILED(__rv)) { \
- char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
- #res, __rv); \
- NS_ASSERTION(NS_SUCCEEDED(__rv), msg); \
- PR_smprintf_free(msg); \
- } \
- PR_END_MACRO
- #else
- #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
- #endif
- /******************************************************************************
- * DBListenerErrorHandler impl:
- * Parent class for our async storage listeners that handles the logging of
- * errors.
- ******************************************************************************/
- class DBListenerErrorHandler : public mozIStorageStatementCallback
- {
- protected:
- explicit DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { }
- RefPtr<DBState> mDBState;
- virtual const char *GetOpType() = 0;
- public:
- NS_IMETHOD HandleError(mozIStorageError* aError) override
- {
- if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
- int32_t result = -1;
- aError->GetResult(&result);
- nsAutoCString message;
- aError->GetMessage(message);
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("DBListenerErrorHandler::HandleError(): Error %d occurred while "
- "performing operation '%s' with message '%s'; rebuilding database.",
- result, GetOpType(), message.get()));
- }
- // Rebuild the database.
- gCookieService->HandleCorruptDB(mDBState);
- return NS_OK;
- }
- };
- /******************************************************************************
- * InsertCookieDBListener impl:
- * mozIStorageStatementCallback used to track asynchronous insertion operations.
- ******************************************************************************/
- class InsertCookieDBListener final : public DBListenerErrorHandler
- {
- private:
- const char *GetOpType() override { return "INSERT"; }
- ~InsertCookieDBListener() = default;
- public:
- NS_DECL_ISUPPORTS
- explicit InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
- NS_IMETHOD HandleResult(mozIStorageResultSet*) override
- {
- NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
- return NS_OK;
- }
- NS_IMETHOD HandleCompletion(uint16_t aReason) override
- {
- // If we were rebuilding the db and we succeeded, make our corruptFlag say
- // so.
- if (mDBState->corruptFlag == DBState::REBUILDING &&
- aReason == mozIStorageStatementCallback::REASON_FINISHED) {
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
- mDBState->corruptFlag = DBState::OK;
- }
- return NS_OK;
- }
- };
- NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
- /******************************************************************************
- * UpdateCookieDBListener impl:
- * mozIStorageStatementCallback used to track asynchronous update operations.
- ******************************************************************************/
- class UpdateCookieDBListener final : public DBListenerErrorHandler
- {
- private:
- const char *GetOpType() override { return "UPDATE"; }
- ~UpdateCookieDBListener() = default;
- public:
- NS_DECL_ISUPPORTS
- explicit UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
- NS_IMETHOD HandleResult(mozIStorageResultSet*) override
- {
- NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
- return NS_OK;
- }
- NS_IMETHOD HandleCompletion(uint16_t aReason) override
- {
- return NS_OK;
- }
- };
- NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
- /******************************************************************************
- * RemoveCookieDBListener impl:
- * mozIStorageStatementCallback used to track asynchronous removal operations.
- ******************************************************************************/
- class RemoveCookieDBListener final : public DBListenerErrorHandler
- {
- private:
- const char *GetOpType() override { return "REMOVE"; }
- ~RemoveCookieDBListener() = default;
- public:
- NS_DECL_ISUPPORTS
- explicit RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { }
- NS_IMETHOD HandleResult(mozIStorageResultSet*) override
- {
- NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
- return NS_OK;
- }
- NS_IMETHOD HandleCompletion(uint16_t aReason) override
- {
- return NS_OK;
- }
- };
- NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
- /******************************************************************************
- * ReadCookieDBListener impl:
- * mozIStorageStatementCallback used to track asynchronous removal operations.
- ******************************************************************************/
- class ReadCookieDBListener final : public DBListenerErrorHandler
- {
- private:
- const char *GetOpType() override { return "READ"; }
- bool mCanceled;
- ~ReadCookieDBListener() = default;
- public:
- NS_DECL_ISUPPORTS
- explicit ReadCookieDBListener(DBState* dbState)
- : DBListenerErrorHandler(dbState)
- , mCanceled(false)
- {
- }
- void Cancel() { mCanceled = true; }
- NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) override
- {
- nsCOMPtr<mozIStorageRow> row;
- while (true) {
- DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row));
- NS_ASSERT_SUCCESS(rv);
- if (!row)
- break;
- CookieDomainTuple *tuple = mDBState->hostArray.AppendElement();
- row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain);
- nsAutoCString suffix;
- row->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
- DebugOnly<bool> success = tuple->key.mOriginAttributes.PopulateFromSuffix(suffix);
- MOZ_ASSERT(success);
- tuple->cookie =
- gCookieService->GetCookieFromRow(row, tuple->key.mOriginAttributes);
- }
- return NS_OK;
- }
- NS_IMETHOD HandleCompletion(uint16_t aReason) override
- {
- // Process the completion of the read operation. If we have been canceled,
- // we cannot assume that the cookieservice still has an open connection
- // or that it even refers to the same database, so we must return early.
- // Conversely, the cookieservice guarantees that if we have not been
- // canceled, the database connection is still alive and we can safely
- // operate on it.
- if (mCanceled) {
- // We may receive a REASON_FINISHED after being canceled;
- // tweak the reason accordingly.
- aReason = mozIStorageStatementCallback::REASON_CANCELED;
- }
- switch (aReason) {
- case mozIStorageStatementCallback::REASON_FINISHED:
- gCookieService->AsyncReadComplete();
- break;
- case mozIStorageStatementCallback::REASON_CANCELED:
- // Nothing more to do here. The partially read data has already been
- // thrown away.
- COOKIE_LOGSTRING(LogLevel::Debug, ("Read canceled"));
- break;
- case mozIStorageStatementCallback::REASON_ERROR:
- // Nothing more to do here. DBListenerErrorHandler::HandleError()
- // can handle it.
- COOKIE_LOGSTRING(LogLevel::Debug, ("Read error"));
- break;
- default:
- NS_NOTREACHED("invalid reason");
- }
- return NS_OK;
- }
- };
- NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback)
- /******************************************************************************
- * CloseCookieDBListener imp:
- * Static mozIStorageCompletionCallback used to notify when the database is
- * successfully closed.
- ******************************************************************************/
- class CloseCookieDBListener final : public mozIStorageCompletionCallback
- {
- ~CloseCookieDBListener() = default;
- public:
- explicit CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
- RefPtr<DBState> mDBState;
- NS_DECL_ISUPPORTS
- NS_IMETHOD Complete(nsresult, nsISupports*) override
- {
- gCookieService->HandleDBClosed(mDBState);
- return NS_OK;
- }
- };
- NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
- namespace {
- class AppClearDataObserver final : public nsIObserver {
- ~AppClearDataObserver() = default;
- public:
- NS_DECL_ISUPPORTS
- // nsIObserver implementation.
- NS_IMETHOD
- Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
- {
- MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA));
- MOZ_ASSERT(XRE_IsParentProcess());
- nsCOMPtr<nsICookieManager2> cookieManager
- = do_GetService(NS_COOKIEMANAGER_CONTRACTID);
- MOZ_ASSERT(cookieManager);
- return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString());
- }
- };
- NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
- } // namespace
- size_t
- nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
- {
- return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
- }
- size_t
- nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
- {
- size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
- amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
- for (uint32_t i = 0; i < mCookies.Length(); ++i) {
- amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf);
- }
- return amount;
- }
- size_t
- CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
- {
- size_t amount = 0;
- amount += key.SizeOfExcludingThis(aMallocSizeOf);
- amount += cookie->SizeOfIncludingThis(aMallocSizeOf);
- return amount;
- }
- size_t
- DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
- {
- size_t amount = 0;
- amount += aMallocSizeOf(this);
- amount += hostTable.SizeOfExcludingThis(aMallocSizeOf);
- amount += hostArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
- for (uint32_t i = 0; i < hostArray.Length(); ++i) {
- amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf);
- }
- amount += readSet.SizeOfExcludingThis(aMallocSizeOf);
- return amount;
- }
- /******************************************************************************
- * nsCookieService impl:
- * singleton instance ctor/dtor methods
- ******************************************************************************/
- nsICookieService*
- nsCookieService::GetXPCOMSingleton()
- {
- if (IsNeckoChild())
- return CookieServiceChild::GetSingleton();
- return GetSingleton();
- }
- nsCookieService*
- nsCookieService::GetSingleton()
- {
- NS_ASSERTION(!IsNeckoChild(), "not a parent process");
- if (gCookieService) {
- NS_ADDREF(gCookieService);
- return gCookieService;
- }
- // Create a new singleton nsCookieService.
- // We AddRef only once since XPCOM has rules about the ordering of module
- // teardowns - by the time our module destructor is called, it's too late to
- // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC
- // cycles have already been completed and would result in serious leaks.
- // See bug 209571.
- gCookieService = new nsCookieService();
- if (gCookieService) {
- NS_ADDREF(gCookieService);
- if (NS_FAILED(gCookieService->Init())) {
- NS_RELEASE(gCookieService);
- }
- }
- return gCookieService;
- }
- /* static */ void
- nsCookieService::AppClearDataObserverInit()
- {
- nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
- nsCOMPtr<nsIObserver> obs = new AppClearDataObserver();
- observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA,
- /* ownsWeak= */ false);
- }
- /******************************************************************************
- * nsCookieService impl:
- * public methods
- ******************************************************************************/
- NS_IMPL_ISUPPORTS(nsCookieService,
- nsICookieService,
- nsICookieManager,
- nsICookieManager2,
- nsIObserver,
- nsISupportsWeakReference,
- nsIMemoryReporter)
- nsCookieService::nsCookieService()
- : mDBState(nullptr)
- , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
- , mThirdPartySession(false)
- , mLeaveSecureAlone(true)
- , mMaxNumberOfCookies(kMaxNumberOfCookies)
- , mMaxCookiesPerHost(kMaxCookiesPerHost)
- , mCookiePurgeAge(kCookiePurgeAge)
- {
- }
- nsresult
- nsCookieService::Init()
- {
- nsresult rv;
- mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
- NS_ENSURE_SUCCESS(rv, rv);
- // init our pref and observer
- nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
- if (prefBranch) {
- prefBranch->AddObserver(kPrefCookieBehavior, this, true);
- prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
- prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true);
- prefBranch->AddObserver(kPrefCookiePurgeAge, this, true);
- prefBranch->AddObserver(kPrefThirdPartySession, this, true);
- prefBranch->AddObserver(kCookieLeaveSecurityAlone, this, true);
- PrefChanged(prefBranch);
- }
- mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- // Init our default, and possibly private DBStates.
- InitDBStates();
- RegisterWeakMemoryReporter(this);
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- NS_ENSURE_STATE(os);
- os->AddObserver(this, "profile-before-change", true);
- os->AddObserver(this, "profile-do-change", true);
- os->AddObserver(this, "last-pb-context-exited", true);
- mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
- if (!mPermissionService) {
- NS_WARNING("nsICookiePermission implementation not available - some features won't work!");
- COOKIE_LOGSTRING(LogLevel::Warning, ("Init(): nsICookiePermission implementation not available"));
- }
- return NS_OK;
- }
- void
- nsCookieService::InitDBStates()
- {
- NS_ASSERTION(!mDBState, "already have a DBState");
- NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
- NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
- // Create a new default DBState and set our current one.
- mDefaultDBState = new DBState();
- mDBState = mDefaultDBState;
- mPrivateDBState = new DBState();
- // Get our cookie file.
- nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(mDefaultDBState->cookieFile));
- if (NS_FAILED(rv)) {
- // We've already set up our DBStates appropriately; nothing more to do.
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("InitDBStates(): couldn't get cookie file"));
- return;
- }
- mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
- // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
- // do so.
- OpenDBResult result = TryInitDB(false);
- if (result == RESULT_RETRY) {
- // Database may be corrupt. Synchronously close the connection, clean up the
- // default DBState, and try again.
- COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()"));
- CleanupCachedStatements();
- CleanupDefaultDBConnection();
- result = TryInitDB(true);
- if (result == RESULT_RETRY) {
- // We're done. Change the code to failure so we clean up below.
- result = RESULT_FAILURE;
- }
- }
- if (result == RESULT_FAILURE) {
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("InitDBStates(): TryInitDB() failed, closing connection"));
- // Connection failure is unrecoverable. Clean up our connection. We can run
- // fine without persistent storage -- e.g. if there's no profile.
- CleanupCachedStatements();
- CleanupDefaultDBConnection();
- }
- }
- namespace {
- class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction
- {
- ~ConvertAppIdToOriginAttrsSQLFunction() = default;
- NS_DECL_ISUPPORTS
- NS_DECL_MOZISTORAGEFUNCTION
- };
- NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
- NS_IMETHODIMP
- ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
- mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
- {
- nsresult rv;
- int32_t appId, inIsolatedMozBrowser;
- rv = aFunctionArguments->GetInt32(0, &appId);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
- NS_ENSURE_SUCCESS(rv, rv);
- // Create an originAttributes object by appId and inIsolatedMozBrowser.
- // Then create the originSuffix string from this object.
- NeckoOriginAttributes attrs(appId, (inIsolatedMozBrowser ? 1 : 0));
- nsAutoCString suffix;
- attrs.CreateSuffix(suffix);
- RefPtr<nsVariant> outVar(new nsVariant());
- rv = outVar->SetAsAUTF8String(suffix);
- NS_ENSURE_SUCCESS(rv, rv);
- outVar.forget(aResult);
- return NS_OK;
- }
- class SetAppIdFromOriginAttributesSQLFunction final : public mozIStorageFunction
- {
- ~SetAppIdFromOriginAttributesSQLFunction() = default;
- NS_DECL_ISUPPORTS
- NS_DECL_MOZISTORAGEFUNCTION
- };
- NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
- NS_IMETHODIMP
- SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
- mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
- {
- nsresult rv;
- nsAutoCString suffix;
- NeckoOriginAttributes attrs;
- rv = aFunctionArguments->GetUTF8String(0, suffix);
- NS_ENSURE_SUCCESS(rv, rv);
- bool success = attrs.PopulateFromSuffix(suffix);
- NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
- RefPtr<nsVariant> outVar(new nsVariant());
- rv = outVar->SetAsInt32(attrs.mAppId);
- NS_ENSURE_SUCCESS(rv, rv);
- outVar.forget(aResult);
- return NS_OK;
- }
- class SetInBrowserFromOriginAttributesSQLFunction final :
- public mozIStorageFunction
- {
- ~SetInBrowserFromOriginAttributesSQLFunction() = default;
- NS_DECL_ISUPPORTS
- NS_DECL_MOZISTORAGEFUNCTION
- };
- NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
- mozIStorageFunction);
- NS_IMETHODIMP
- SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
- mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
- {
- nsresult rv;
- nsAutoCString suffix;
- NeckoOriginAttributes attrs;
- rv = aFunctionArguments->GetUTF8String(0, suffix);
- NS_ENSURE_SUCCESS(rv, rv);
- bool success = attrs.PopulateFromSuffix(suffix);
- NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
- RefPtr<nsVariant> outVar(new nsVariant());
- rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser);
- NS_ENSURE_SUCCESS(rv, rv);
- outVar.forget(aResult);
- return NS_OK;
- }
- } // namespace
- /* Attempt to open and read the database. If 'aRecreateDB' is true, try to
- * move the existing database file out of the way and create a new one.
- *
- * @returns RESULT_OK if opening or creating the database succeeded;
- * RESULT_RETRY if the database cannot be opened, is corrupt, or some
- * other failure occurred that might be resolved by recreating the
- * database; or RESULT_FAILED if there was an unrecoverable error and
- * we must run without a database.
- *
- * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
- * cleanup of the default DBState.
- */
- OpenDBResult
- nsCookieService::TryInitDB(bool aRecreateDB)
- {
- NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
- NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
- NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
- NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
- // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
- // want to delete it outright, since it may be useful for debugging purposes,
- // so we move it out of the way.
- nsresult rv;
- if (aRecreateDB) {
- nsCOMPtr<nsIFile> backupFile;
- mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
- rv = backupFile->MoveToNative(nullptr,
- NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
- NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
- }
- ReadAheadFile(mDefaultDBState->cookieFile);
- // open a connection to the cookie database, and only cache our connection
- // and statements upon success. The connection is opened unshared to eliminate
- // cache contention between the main and background threads.
- rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
- getter_AddRefs(mDefaultDBState->dbConn));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Set up our listeners.
- mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
- mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState);
- mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState);
- mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState);
- // Grow cookie db in 512KB increments
- mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString());
- bool tableExists = false;
- mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"),
- &tableExists);
- if (!tableExists) {
- rv = CreateTable();
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- } else {
- // table already exists; check the schema version before reading
- int32_t dbSchemaVersion;
- rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Start a transaction for the whole migration block.
- mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
- switch (dbSchemaVersion) {
- // Upgrading.
- // Every time you increment the database schema, you need to implement
- // the upgrading code from the previous version to the new one. If migration
- // fails for any reason, it's a bug -- so we return RESULT_RETRY such that
- // the original database will be saved, in the hopes that we might one day
- // see it and fix it.
- case 1:
- {
- // Add the lastAccessed column to the table.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // Fall through to the next upgrade.
- MOZ_FALLTHROUGH;
- case 2:
- {
- // Add the baseDomain column and index to the table.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies ADD baseDomain TEXT"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Compute the baseDomains for the table. This must be done eagerly
- // otherwise we won't be able to synchronously read in individual
- // domains on demand.
- const int64_t SCHEMA2_IDX_ID = 0;
- const int64_t SCHEMA2_IDX_HOST = 1;
- nsCOMPtr<mozIStorageStatement> select;
- rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- nsCOMPtr<mozIStorageStatement> update;
- rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
- getter_AddRefs(update));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- nsCString baseDomain, host;
- bool hasResult;
- while (true) {
- rv = select->ExecuteStep(&hasResult);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- if (!hasResult)
- break;
- int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
- select->GetUTF8String(SCHEMA2_IDX_HOST, host);
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- mozStorageStatementScoper scoper(update);
- rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
- baseDomain);
- NS_ASSERT_SUCCESS(rv);
- rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"),
- id);
- NS_ASSERT_SUCCESS(rv);
- rv = update->ExecuteStep(&hasResult);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // Create an index on baseDomain.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // Fall through to the next upgrade.
- MOZ_FALLTHROUGH;
- case 3:
- {
- // Add the creationTime column to the table, and create a unique index
- // on (name, host, path). Before we do this, we have to purge the table
- // of expired cookies such that we know that the (name, host, path)
- // index is truly unique -- otherwise we can't create the index. Note
- // that we can't just execute a statement to delete all rows where the
- // expiry column is in the past -- doing so would rely on the clock
- // (both now and when previous cookies were set) being monotonic.
- // Select the whole table, and order by the fields we're interested in.
- // This means we can simply do a linear traversal of the results and
- // check for duplicates as we go.
- const int64_t SCHEMA3_IDX_ID = 0;
- const int64_t SCHEMA3_IDX_NAME = 1;
- const int64_t SCHEMA3_IDX_HOST = 2;
- const int64_t SCHEMA3_IDX_PATH = 3;
- nsCOMPtr<mozIStorageStatement> select;
- rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT id, name, host, path FROM moz_cookies "
- "ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
- getter_AddRefs(select));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- nsCOMPtr<mozIStorageStatement> deleteExpired;
- rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "DELETE FROM moz_cookies WHERE id = :id"),
- getter_AddRefs(deleteExpired));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Read the first row.
- bool hasResult;
- rv = select->ExecuteStep(&hasResult);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- if (hasResult) {
- nsCString name1, host1, path1;
- int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
- select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
- select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
- select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
- nsCString name2, host2, path2;
- while (true) {
- // Read the second row.
- rv = select->ExecuteStep(&hasResult);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- if (!hasResult)
- break;
- int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
- select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
- select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
- select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
- // If the two rows match in (name, host, path), we know the earlier
- // row has an earlier expiry time. Delete it.
- if (name1 == name2 && host1 == host2 && path1 == path2) {
- mozStorageStatementScoper scoper(deleteExpired);
- rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"),
- id1);
- NS_ASSERT_SUCCESS(rv);
- rv = deleteExpired->ExecuteStep(&hasResult);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // Make the second row the first for the next iteration.
- name1 = name2;
- host1 = host2;
- path1 = path2;
- id1 = id2;
- }
- }
- // Add the creationTime column to the table.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies ADD creationTime INTEGER"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Copy the id of each row into the new creationTime column.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "UPDATE moz_cookies SET creationTime = "
- "(SELECT id WHERE id = moz_cookies.id)"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Create a unique index on (name, host, path) to allow fast lookup.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE UNIQUE INDEX moz_uniqueid "
- "ON moz_cookies (name, host, path)"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // Fall through to the next upgrade.
- MOZ_FALLTHROUGH;
- case 4:
- {
- // We need to add appId/inBrowserElement, plus change a constraint on
- // the table (unique entries now include appId/inBrowserElement):
- // this requires creating a new table and copying the data to it. We
- // then rename the new table to the old name.
- //
- // Why we made this change: appId/inBrowserElement allow "cookie jars"
- // for Firefox OS. We create a separate cookie namespace per {appId,
- // inBrowserElement}. When upgrading, we convert existing cookies
- // (which imply we're on desktop/mobile) to use {0, false}, as that is
- // the only namespace used by a non-Firefox-OS implementation.
- // Rename existing table
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Drop existing index (CreateTable will create new one for new table)
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "DROP INDEX moz_basedomain"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Create new table (with new fields and new unique constraint)
- rv = CreateTableForSchemaVersion5();
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Copy data from old table, using appId/inBrowser=0 for existing rows
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "INSERT INTO moz_cookies "
- "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry,"
- " lastAccessed, creationTime, isSecure, isHttpOnly) "
- "SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
- " lastAccessed, creationTime, isSecure, isHttpOnly "
- "FROM moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Drop old table
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "DROP TABLE moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("Upgraded database to schema version 5"));
- }
- // Fall through to the next upgrade.
- MOZ_FALLTHROUGH;
- case 5:
- {
- // Change in the version: Replace the columns |appId| and
- // |inBrowserElement| by a single column |originAttributes|.
- //
- // Why we made this change: FxOS new security model (NSec) encapsulates
- // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to make
- // it easier to modify the contents of this structure in the future.
- //
- // We do the migration in several steps:
- // 1. Rename the old table.
- // 2. Create a new table.
- // 3. Copy data from the old table to the new table; convert appId and
- // inBrowserElement to originAttributes in the meantime.
- // Rename existing table.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Drop existing index (CreateTable will create new one for new table).
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "DROP INDEX moz_basedomain"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Create new table with new fields and new unique constraint.
- rv = CreateTableForSchemaVersion6();
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Copy data from old table without the two deprecated columns appId and
- // inBrowserElement.
- nsCOMPtr<mozIStorageFunction>
- convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction());
- NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
- NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName,
- "CONVERT_TO_ORIGIN_ATTRIBUTES");
- rv = mDefaultDBState->dbConn->CreateFunction(convertToOriginAttrsName,
- 2, convertToOriginAttrs);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "INSERT INTO moz_cookies "
- "(baseDomain, originAttributes, name, value, host, path, expiry,"
- " lastAccessed, creationTime, isSecure, isHttpOnly) "
- "SELECT baseDomain, "
- " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
- " name, value, host, path, expiry, lastAccessed, creationTime, "
- " isSecure, isHttpOnly "
- "FROM moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->RemoveFunction(convertToOriginAttrsName);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Drop old table
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "DROP TABLE moz_cookies_old"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("Upgraded database to schema version 6"));
- }
- MOZ_FALLTHROUGH;
- case 6:
- {
- // We made a mistake in schema version 6. We cannot remove expected
- // columns of any version (checked in the default case) from cookie
- // database, because doing this would destroy the possibility of
- // downgrading database.
- //
- // This version simply restores appId and inBrowserElement columns in
- // order to fix downgrading issue even though these two columns are no
- // longer used in the latest schema.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Compute and populate the values of appId and inBrwoserElement from
- // originAttributes.
- nsCOMPtr<mozIStorageFunction>
- setAppId(new SetAppIdFromOriginAttributesSQLFunction());
- NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
- NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID");
- rv = mDefaultDBState->dbConn->CreateFunction(setAppIdName, 1, setAppId);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- nsCOMPtr<mozIStorageFunction>
- setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction());
- NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
- NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER");
- rv = mDefaultDBState->dbConn->CreateFunction(setInBrowserName, 1,
- setInBrowser);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
- "inBrowserElement = SET_IN_BROWSER(originAttributes);"
- ));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->RemoveFunction(setAppIdName);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("Upgraded database to schema version 7"));
- }
- // No more upgrades. Update the schema version.
- rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- MOZ_FALLTHROUGH;
- case COOKIES_SCHEMA_VERSION:
- break;
- case 0:
- {
- NS_WARNING("couldn't get schema version!");
- // the table may be usable; someone might've just clobbered the schema
- // version. we can treat this case like a downgrade using the codepath
- // below, by verifying the columns we care about are all there. for now,
- // re-set the schema version in the db, in case the checks succeed (if
- // they don't, we're dropping the table anyway).
- rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- // fall through to downgrade check
- MOZ_FALLTHROUGH;
- // downgrading.
- // if columns have been added to the table, we can still use the ones we
- // understand safely. if columns have been deleted or altered, just
- // blow away the table and start from scratch! if you change the way
- // a column is interpreted, make sure you also change its name so this
- // check will catch it.
- default:
- {
- // check if all the expected columns exist
- nsCOMPtr<mozIStorageStatement> stmt;
- rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT "
- "id, "
- "baseDomain, "
- "originAttributes, "
- "name, "
- "value, "
- "host, "
- "path, "
- "expiry, "
- "lastAccessed, "
- "creationTime, "
- "isSecure, "
- "isHttpOnly "
- "FROM moz_cookies"), getter_AddRefs(stmt));
- if (NS_SUCCEEDED(rv))
- break;
- // our columns aren't there - drop the table!
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "DROP TABLE moz_cookies"));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = CreateTable();
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- }
- break;
- }
- }
- // make operations on the table asynchronous, for performance
- mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "PRAGMA synchronous = OFF"));
- // Use write-ahead-logging for performance. We cap the autocheckpoint limit at
- // 16 pages (around 500KB).
- mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL"));
- mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "PRAGMA wal_autocheckpoint = 16"));
- // cache frequently used statements (for insertion, deletion, and updating)
- rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "INSERT INTO moz_cookies ("
- "baseDomain, "
- "originAttributes, "
- "name, "
- "value, "
- "host, "
- "path, "
- "expiry, "
- "lastAccessed, "
- "creationTime, "
- "isSecure, "
- "isHttpOnly"
- ") VALUES ("
- ":baseDomain, "
- ":originAttributes, "
- ":name, "
- ":value, "
- ":host, "
- ":path, "
- ":expiry, "
- ":lastAccessed, "
- ":creationTime, "
- ":isSecure, "
- ":isHttpOnly"
- ")"),
- getter_AddRefs(mDefaultDBState->stmtInsert));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "DELETE FROM moz_cookies "
- "WHERE name = :name AND host = :host AND path = :path"),
- getter_AddRefs(mDefaultDBState->stmtDelete));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "UPDATE moz_cookies SET lastAccessed = :lastAccessed "
- "WHERE name = :name AND host = :host AND path = :path"),
- getter_AddRefs(mDefaultDBState->stmtUpdate));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // if we deleted a corrupt db, don't attempt to import - return now
- if (aRecreateDB)
- return RESULT_OK;
- // check whether to import or just read in the db
- if (tableExists)
- return Read();
- nsCOMPtr<nsIFile> oldCookieFile;
- rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(oldCookieFile));
- if (NS_FAILED(rv)) return RESULT_OK;
- // Import cookies, and clean up the old file regardless of success or failure.
- // Note that we have to switch out our DBState temporarily, in case we're in
- // private browsing mode; otherwise ImportCookies() won't be happy.
- DBState* initialState = mDBState;
- mDBState = mDefaultDBState;
- oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
- ImportCookies(oldCookieFile);
- oldCookieFile->Remove(false);
- mDBState = initialState;
- return RESULT_OK;
- }
- // Sets the schema version and creates the moz_cookies table.
- nsresult
- nsCookieService::CreateTable()
- {
- // Set the schema version, before creating the table.
- nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(
- COOKIES_SCHEMA_VERSION);
- if (NS_FAILED(rv)) return rv;
- // Create the table.
- // We default originAttributes to empty string: this is so if users revert to
- // an older Firefox version that doesn't know about this field, any cookies
- // set will still work once they upgrade back.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE TABLE moz_cookies ("
- "id INTEGER PRIMARY KEY, "
- "baseDomain TEXT, "
- "originAttributes TEXT NOT NULL DEFAULT '', "
- "name TEXT, "
- "value TEXT, "
- "host TEXT, "
- "path TEXT, "
- "expiry INTEGER, "
- "lastAccessed INTEGER, "
- "creationTime INTEGER, "
- "isSecure INTEGER, "
- "isHttpOnly INTEGER, "
- "appId INTEGER DEFAULT 0, "
- "inBrowserElement INTEGER DEFAULT 0, "
- "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
- ")"));
- if (NS_FAILED(rv)) return rv;
- // Create an index on baseDomain.
- return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
- "originAttributes)"));
- }
- // Sets the schema version and creates the moz_cookies table.
- nsresult
- nsCookieService::CreateTableForSchemaVersion6()
- {
- // Set the schema version, before creating the table.
- nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(6);
- if (NS_FAILED(rv)) return rv;
- // Create the table.
- // We default originAttributes to empty string: this is so if users revert to
- // an older Firefox version that doesn't know about this field, any cookies
- // set will still work once they upgrade back.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE TABLE moz_cookies ("
- "id INTEGER PRIMARY KEY, "
- "baseDomain TEXT, "
- "originAttributes TEXT NOT NULL DEFAULT '', "
- "name TEXT, "
- "value TEXT, "
- "host TEXT, "
- "path TEXT, "
- "expiry INTEGER, "
- "lastAccessed INTEGER, "
- "creationTime INTEGER, "
- "isSecure INTEGER, "
- "isHttpOnly INTEGER, "
- "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
- ")"));
- if (NS_FAILED(rv)) return rv;
- // Create an index on baseDomain.
- return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
- "originAttributes)"));
- }
- // Sets the schema version and creates the moz_cookies table.
- nsresult
- nsCookieService::CreateTableForSchemaVersion5()
- {
- // Set the schema version, before creating the table.
- nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(5);
- if (NS_FAILED(rv)) return rv;
- // Create the table. We default appId/inBrowserElement to 0: this is so if
- // users revert to an older Firefox version that doesn't know about these
- // fields, any cookies set will still work once they upgrade back.
- rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE TABLE moz_cookies ("
- "id INTEGER PRIMARY KEY, "
- "baseDomain TEXT, "
- "appId INTEGER DEFAULT 0, "
- "inBrowserElement INTEGER DEFAULT 0, "
- "name TEXT, "
- "value TEXT, "
- "host TEXT, "
- "path TEXT, "
- "expiry INTEGER, "
- "lastAccessed INTEGER, "
- "creationTime INTEGER, "
- "isSecure INTEGER, "
- "isHttpOnly INTEGER, "
- "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)"
- ")"));
- if (NS_FAILED(rv)) return rv;
- // Create an index on baseDomain.
- return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
- "appId, "
- "inBrowserElement)"));
- }
- void
- nsCookieService::CloseDBStates()
- {
- // Null out our private and pointer DBStates regardless.
- mPrivateDBState = nullptr;
- mDBState = nullptr;
- // If we don't have a default DBState, we're done.
- if (!mDefaultDBState)
- return;
- // Cleanup cached statements before we can close anything.
- CleanupCachedStatements();
- if (mDefaultDBState->dbConn) {
- // Cancel any pending read. No further results will be received by our
- // read listener.
- if (mDefaultDBState->pendingRead) {
- CancelAsyncRead(true);
- }
- // Asynchronously close the connection. We will null it below.
- mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
- }
- CleanupDefaultDBConnection();
- mDefaultDBState = nullptr;
- }
- // Null out the statements.
- // This must be done before closing the connection.
- void
- nsCookieService::CleanupCachedStatements()
- {
- mDefaultDBState->stmtInsert = nullptr;
- mDefaultDBState->stmtDelete = nullptr;
- mDefaultDBState->stmtUpdate = nullptr;
- }
- // Null out the listeners, and the database connection itself. This
- // will not null out the statements, cancel a pending read or
- // asynchronously close the connection -- these must be done
- // beforehand if necessary.
- void
- nsCookieService::CleanupDefaultDBConnection()
- {
- MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up");
- MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up");
- MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up");
- // Null out the database connections. If 'dbConn' has not been used for any
- // asynchronous operations yet, this will synchronously close it; otherwise,
- // it's expected that the caller has performed an AsyncClose prior.
- mDefaultDBState->dbConn = nullptr;
- mDefaultDBState->syncConn = nullptr;
- // Manually null out our listeners. This is necessary because they hold a
- // strong ref to the DBState itself. They'll stay alive until whatever
- // statements are still executing complete.
- mDefaultDBState->readListener = nullptr;
- mDefaultDBState->insertListener = nullptr;
- mDefaultDBState->updateListener = nullptr;
- mDefaultDBState->removeListener = nullptr;
- mDefaultDBState->closeListener = nullptr;
- }
- void
- nsCookieService::HandleDBClosed(DBState* aDBState)
- {
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("HandleDBClosed(): DBState %x closed", aDBState));
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- switch (aDBState->corruptFlag) {
- case DBState::OK: {
- // Database is healthy. Notify of closure.
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
- }
- break;
- }
- case DBState::CLOSING_FOR_REBUILD: {
- // Our close finished. Start the rebuild, and notify of db closure later.
- RebuildCorruptDB(aDBState);
- break;
- }
- case DBState::REBUILDING: {
- // We encountered an error during rebuild, closed the database, and now
- // here we are. We already have a 'cookies.sqlite.bak' from the original
- // dead database; we don't want to overwrite it, so let's move this one to
- // 'cookies.sqlite.bak-rebuild'.
- nsCOMPtr<nsIFile> backupFile;
- aDBState->cookieFile->Clone(getter_AddRefs(backupFile));
- nsresult rv = backupFile->MoveToNative(nullptr,
- NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild"));
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to "
- "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv));
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
- }
- break;
- }
- }
- }
- void
- nsCookieService::HandleCorruptDB(DBState* aDBState)
- {
- if (mDefaultDBState != aDBState) {
- // We've either closed the state or we've switched profiles. It's getting
- // a bit late to rebuild -- bail instead.
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState));
- return;
- }
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState,
- aDBState->corruptFlag));
- // Mark the database corrupt, so the close listener can begin reconstructing
- // it.
- switch (mDefaultDBState->corruptFlag) {
- case DBState::OK: {
- // Move to 'closing' state.
- mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD;
- // Cancel any pending read and close the database. If we do have an
- // in-flight read we want to throw away all the results so far -- we have no
- // idea how consistent the database is. Note that we may have already
- // canceled the read but not emptied our readSet; do so now.
- mDefaultDBState->readSet.Clear();
- if (mDefaultDBState->pendingRead) {
- CancelAsyncRead(true);
- mDefaultDBState->syncConn = nullptr;
- }
- CleanupCachedStatements();
- mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
- CleanupDefaultDBConnection();
- break;
- }
- case DBState::CLOSING_FOR_REBUILD: {
- // We had an error while waiting for close completion. That's OK, just
- // ignore it -- we're rebuilding anyway.
- return;
- }
- case DBState::REBUILDING: {
- // We had an error while rebuilding the DB. Game over. Close the database
- // and let the close handler do nothing; then we'll move it out of the way.
- CleanupCachedStatements();
- if (mDefaultDBState->dbConn) {
- mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
- }
- CleanupDefaultDBConnection();
- break;
- }
- }
- }
- void
- nsCookieService::RebuildCorruptDB(DBState* aDBState)
- {
- NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
- NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
- "should be in CLOSING_FOR_REBUILD state");
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- aDBState->corruptFlag = DBState::REBUILDING;
- if (mDefaultDBState != aDBState) {
- // We've either closed the state or we've switched profiles. It's getting
- // a bit late to rebuild -- bail instead. In any case, we were waiting
- // on rebuild completion to notify of the db closure, which won't happen --
- // do so now.
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState));
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
- }
- return;
- }
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("RebuildCorruptDB(): creating new database"));
- // The database has been closed, and we're ready to rebuild. Open a
- // connection.
- OpenDBResult result = TryInitDB(true);
- if (result != RESULT_OK) {
- // We're done. Reset our DB connection and statements, and notify of
- // closure.
- COOKIE_LOGSTRING(LogLevel::Warning,
- ("RebuildCorruptDB(): TryInitDB() failed with result %u", result));
- CleanupCachedStatements();
- CleanupDefaultDBConnection();
- mDefaultDBState->corruptFlag = DBState::OK;
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
- }
- return;
- }
- // Notify observers that we're beginning the rebuild.
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
- }
- // Enumerate the hash, and add cookies to the params array.
- mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
- stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
- for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
- nsCookieEntry* entry = iter.Get();
- const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- nsCookie* cookie = cookies[i];
- if (!cookie->IsSession()) {
- bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
- }
- }
- }
- // Make sure we've got something to write. If we don't, we're done.
- uint32_t length;
- paramsArray->GetLength(&length);
- if (length == 0) {
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("RebuildCorruptDB(): nothing to write, rebuild complete"));
- mDefaultDBState->corruptFlag = DBState::OK;
- return;
- }
- // Execute the statement. If any errors crop up, we won't try again.
- DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- nsCookieService::~nsCookieService()
- {
- CloseDBStates();
- UnregisterWeakMemoryReporter(this);
- gCookieService = nullptr;
- }
- NS_IMETHODIMP
- nsCookieService::Observe(nsISupports *aSubject,
- const char *aTopic,
- const char16_t *aData)
- {
- // check the topic
- if (!strcmp(aTopic, "profile-before-change")) {
- // The profile is about to change,
- // or is going away because the application is shutting down.
- // Close the default DB connection and null out our DBStates before
- // changing.
- CloseDBStates();
- } else if (!strcmp(aTopic, "profile-do-change")) {
- NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState");
- NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState");
- // the profile has already changed; init the db from the new location.
- // if we are in the private browsing state, however, we do not want to read
- // data into it - we should instead put it into the default state, so it's
- // ready for us if and when we switch back to it.
- InitDBStates();
- } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
- nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
- if (prefBranch)
- PrefChanged(prefBranch);
- } else if (!strcmp(aTopic, "last-pb-context-exited")) {
- // Flush all the cookies stored by private browsing contexts
- mPrivateDBState = new DBState();
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::GetCookieString(nsIURI *aHostURI,
- nsIChannel *aChannel,
- char **aCookie)
- {
- return GetCookieStringCommon(aHostURI, aChannel, false, aCookie);
- }
- NS_IMETHODIMP
- nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI,
- nsIURI *aFirstURI,
- nsIChannel *aChannel,
- char **aCookie)
- {
- return GetCookieStringCommon(aHostURI, aChannel, true, aCookie);
- }
- nsresult
- nsCookieService::GetCookieStringCommon(nsIURI *aHostURI,
- nsIChannel *aChannel,
- bool aHttpBound,
- char** aCookie)
- {
- NS_ENSURE_ARG(aHostURI);
- NS_ENSURE_ARG(aCookie);
- // Determine whether the request is foreign. Failure is acceptable.
- bool isForeign = true;
- mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
- // Get originAttributes.
- NeckoOriginAttributes attrs;
- if (aChannel) {
- NS_GetOriginAttributes(aChannel, attrs);
- }
- bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
- nsAutoCString result;
- GetCookieStringInternal(aHostURI, isForeign, aHttpBound, attrs,
- isPrivate, result);
- *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result);
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::SetCookieString(nsIURI *aHostURI,
- nsIPrompt *aPrompt,
- const char *aCookieHeader,
- nsIChannel *aChannel)
- {
- // The aPrompt argument is deprecated and unused. Avoid introducing new
- // code that uses this argument by warning if the value is non-null.
- MOZ_ASSERT(!aPrompt);
- if (aPrompt) {
- nsCOMPtr<nsIConsoleService> aConsoleService =
- do_GetService("@mozilla.org/consoleservice;1");
- if (aConsoleService) {
- aConsoleService->LogStringMessage(
- u"Non-null prompt ignored by nsCookieService.");
- }
- }
- return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel,
- false);
- }
- NS_IMETHODIMP
- nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI,
- nsIURI *aFirstURI,
- nsIPrompt *aPrompt,
- const char *aCookieHeader,
- const char *aServerTime,
- nsIChannel *aChannel)
- {
- // The aPrompt argument is deprecated and unused. Avoid introducing new
- // code that uses this argument by warning if the value is non-null.
- MOZ_ASSERT(!aPrompt);
- if (aPrompt) {
- nsCOMPtr<nsIConsoleService> aConsoleService =
- do_GetService("@mozilla.org/consoleservice;1");
- if (aConsoleService) {
- aConsoleService->LogStringMessage(
- u"Non-null prompt ignored by nsCookieService.");
- }
- }
- return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel,
- true);
- }
- nsresult
- nsCookieService::SetCookieStringCommon(nsIURI *aHostURI,
- const char *aCookieHeader,
- const char *aServerTime,
- nsIChannel *aChannel,
- bool aFromHttp)
- {
- NS_ENSURE_ARG(aHostURI);
- NS_ENSURE_ARG(aCookieHeader);
- // Determine whether the request is foreign. Failure is acceptable.
- bool isForeign = true;
- mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
- // Get originAttributes.
- NeckoOriginAttributes attrs;
- if (aChannel) {
- NS_GetOriginAttributes(aChannel, attrs);
- }
- bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel);
- nsDependentCString cookieString(aCookieHeader);
- nsDependentCString serverTime(aServerTime ? aServerTime : "");
- SetCookieStringInternal(aHostURI, isForeign, cookieString,
- serverTime, aFromHttp, attrs,
- isPrivate, aChannel);
- return NS_OK;
- }
- void
- nsCookieService::SetCookieStringInternal(nsIURI *aHostURI,
- bool aIsForeign,
- nsDependentCString &aCookieHeader,
- const nsCString &aServerTime,
- bool aFromHttp,
- const NeckoOriginAttributes &aOriginAttrs,
- bool aIsPrivate,
- nsIChannel *aChannel)
- {
- NS_ASSERTION(aHostURI, "null host!");
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return;
- }
- AutoRestore<DBState*> savePrevDBState(mDBState);
- mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
- // get the base domain for the host URI.
- // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
- // file:// URI's (i.e. with an empty host) are allowed, but any other
- // scheme must have a non-empty host. A trailing dot in the host
- // is acceptable.
- bool requireHostMatch;
- nsAutoCString baseDomain;
- nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
- if (NS_FAILED(rv)) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "couldn't get base domain from URI");
- return;
- }
- nsCookieKey key(baseDomain, aOriginAttrs);
- // check default prefs
- CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, aCookieHeader.get());
- // fire a notification if third party or if cookie was rejected
- // (but not if there was an error)
- switch (cookieStatus) {
- case STATUS_REJECTED:
- NotifyRejected(aHostURI);
- if (aIsForeign) {
- NotifyThirdParty(aHostURI, false, aChannel);
- }
- return; // Stop here
- case STATUS_REJECTED_WITH_ERROR:
- return;
- case STATUS_ACCEPTED: // Fallthrough
- case STATUS_ACCEPT_SESSION:
- if (aIsForeign) {
- NotifyThirdParty(aHostURI, true, aChannel);
- }
- break;
- default:
- break;
- }
- // parse server local time. this is not just done here for efficiency
- // reasons - if there's an error parsing it, and we need to default it
- // to the current time, we must do it here since the current time in
- // SetCookieInternal() will change for each cookie processed (e.g. if the
- // user is prompted).
- PRTime tempServerTime;
- int64_t serverTime;
- PRStatus result = PR_ParseTimeString(aServerTime.get(), true,
- &tempServerTime);
- if (result == PR_SUCCESS) {
- serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC);
- } else {
- serverTime = PR_Now() / PR_USEC_PER_SEC;
- }
- // process each cookie in the header
- while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus,
- aCookieHeader, serverTime, aFromHttp, aChannel)) {
- // document.cookie can only set one cookie at a time
- if (!aFromHttp)
- break;
- }
- }
- // notify observers that a cookie was rejected due to the users' prefs.
- void
- nsCookieService::NotifyRejected(nsIURI *aHostURI)
- {
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- os->NotifyObservers(aHostURI, "cookie-rejected", nullptr);
- }
- }
- // notify observers that a third-party cookie was accepted/rejected
- // if the cookie issuer is unknown, it defaults to "?"
- void
- nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel)
- {
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (!os) {
- return;
- }
- const char* topic;
- if (mDBState != mPrivateDBState) {
- // Regular (non-private) browsing
- if (aIsAccepted) {
- topic = "third-party-cookie-accepted";
- } else {
- topic = "third-party-cookie-rejected";
- }
- } else {
- // Private browsing
- if (aIsAccepted) {
- topic = "private-third-party-cookie-accepted";
- } else {
- topic = "private-third-party-cookie-rejected";
- }
- }
- do {
- // Attempt to find the host of aChannel.
- if (!aChannel) {
- break;
- }
- nsCOMPtr<nsIURI> channelURI;
- nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
- if (NS_FAILED(rv)) {
- break;
- }
- nsAutoCString referringHost;
- rv = channelURI->GetHost(referringHost);
- if (NS_FAILED(rv)) {
- break;
- }
- nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost);
- os->NotifyObservers(aHostURI, topic, referringHostUTF16.get());
- return;
- } while (false);
- // This can fail for a number of reasons, in which kind we fallback to "?"
- os->NotifyObservers(aHostURI, topic, u"?");
- }
- // notify observers that the cookie list changed. there are five possible
- // values for aData:
- // "deleted" means a cookie was deleted. aSubject is the deleted cookie.
- // "added" means a cookie was added. aSubject is the added cookie.
- // "changed" means a cookie was altered. aSubject is the new cookie.
- // "cleared" means the entire cookie list was cleared. aSubject is null.
- // "batch-deleted" means a set of cookies was purged. aSubject is the list of
- // cookies.
- void
- nsCookieService::NotifyChanged(nsISupports *aSubject,
- const char16_t *aData)
- {
- const char* topic = mDBState == mPrivateDBState ?
- "private-cookie-changed" : "cookie-changed";
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- os->NotifyObservers(aSubject, topic, aData);
- }
- }
- already_AddRefed<nsIArray>
- nsCookieService::CreatePurgeList(nsICookie2* aCookie)
- {
- nsCOMPtr<nsIMutableArray> removedList =
- do_CreateInstance(NS_ARRAY_CONTRACTID);
- removedList->AppendElement(aCookie, false);
- return removedList.forget();
- }
- /******************************************************************************
- * nsCookieService:
- * pref observer impl
- ******************************************************************************/
- void
- nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
- {
- int32_t val;
- if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
- mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0);
- if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
- mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
- if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
- mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
- if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
- mCookiePurgeAge =
- int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
- }
- bool boolval;
- if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
- mThirdPartySession = boolval;
- if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
- mLeaveSecureAlone = boolval;
- }
- /******************************************************************************
- * nsICookieManager impl:
- * nsICookieManager
- ******************************************************************************/
- NS_IMETHODIMP
- nsCookieService::RemoveAll()
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- RemoveAllFromMemory();
- // clear the cookie file
- if (mDBState->dbConn) {
- NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state");
- // Cancel any pending read. No further results will be received by our
- // read listener.
- if (mDefaultDBState->pendingRead) {
- CancelAsyncRead(true);
- }
- nsCOMPtr<mozIStorageAsyncStatement> stmt;
- nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "DELETE FROM moz_cookies"), getter_AddRefs(stmt));
- if (NS_SUCCEEDED(rv)) {
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(mDefaultDBState->removeListener,
- getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- } else {
- // Recreate the database.
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("RemoveAll(): corruption detected with rv 0x%x", rv));
- HandleCorruptDB(mDefaultDBState);
- }
- }
- NotifyChanged(nullptr, u"cleared");
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- EnsureReadComplete();
- nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
- for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
- const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- cookieList.AppendObject(cookies[i]);
- }
- }
- return NS_NewArrayEnumerator(aEnumerator, cookieList);
- }
- static nsresult
- InitializeOriginAttributes(NeckoOriginAttributes* aAttrs,
- JS::HandleValue aOriginAttributes,
- JSContext* aCx,
- uint8_t aArgc,
- const char16_t* aAPI,
- const char16_t* aInterfaceSuffix)
- {
- MOZ_ASSERT(aAttrs);
- MOZ_ASSERT(aCx);
- MOZ_ASSERT(aAPI);
- MOZ_ASSERT(aInterfaceSuffix);
- if (aArgc == 0) {
- const char16_t* params[] = {
- aAPI,
- aInterfaceSuffix
- };
- // This is supposed to be temporary and in 1 or 2 releases we want to
- // have originAttributes param as mandatory. But for now, we don't want to
- // break existing addons, so we write a console message to inform the addon
- // developers about it.
- nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
- NS_LITERAL_CSTRING("Cookie Manager"),
- nullptr,
- nsContentUtils::eNECKO_PROPERTIES,
- "nsICookieManagerAPIDeprecated",
- params, ArrayLength(params));
- } else if (aArgc == 1) {
- if (!aOriginAttributes.isObject() ||
- !aAttrs->Init(aCx, aOriginAttributes)) {
- return NS_ERROR_INVALID_ARG;
- }
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::Add(const nsACString &aHost,
- const nsACString &aPath,
- const nsACString &aName,
- const nsACString &aValue,
- bool aIsSecure,
- bool aIsHttpOnly,
- bool aIsSession,
- int64_t aExpiry,
- JS::HandleValue aOriginAttributes,
- JSContext* aCx,
- uint8_t aArgc)
- {
- MOZ_ASSERT(aArgc == 0 || aArgc == 1);
- NeckoOriginAttributes attrs;
- nsresult rv = InitializeOriginAttributes(&attrs,
- aOriginAttributes,
- aCx,
- aArgc,
- u"nsICookieManager2.add()",
- u"2");
- NS_ENSURE_SUCCESS(rv, rv);
- return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
- aIsSession, aExpiry, &attrs);
- }
- NS_IMETHODIMP_(nsresult)
- nsCookieService::AddNative(const nsACString &aHost,
- const nsACString &aPath,
- const nsACString &aName,
- const nsACString &aValue,
- bool aIsSecure,
- bool aIsHttpOnly,
- bool aIsSession,
- int64_t aExpiry,
- NeckoOriginAttributes* aOriginAttributes)
- {
- if (NS_WARN_IF(!aOriginAttributes)) {
- return NS_ERROR_FAILURE;
- }
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // first, normalize the hostname, and fail if it contains illegal characters.
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- // get the base domain for the host URI.
- // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk".
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- int64_t currentTimeInUsec = PR_Now();
- nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes);
- RefPtr<nsCookie> cookie =
- nsCookie::Create(aName, aValue, host, aPath,
- aExpiry,
- currentTimeInUsec,
- nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
- aIsSession,
- aIsSecure,
- aIsHttpOnly,
- key.mOriginAttributes);
- if (!cookie) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
- return NS_OK;
- }
- nsresult
- nsCookieService::Remove(const nsACString& aHost, const NeckoOriginAttributes& aAttrs,
- const nsACString& aName, const nsACString& aPath,
- bool aBlocked)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // first, normalize the hostname, and fail if it contains illegal characters.
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- nsListIter matchIter;
- RefPtr<nsCookie> cookie;
- if (FindCookie(nsCookieKey(baseDomain, aAttrs),
- host,
- PromiseFlatCString(aName),
- PromiseFlatCString(aPath),
- matchIter)) {
- cookie = matchIter.Cookie();
- RemoveCookieFromList(matchIter);
- }
- // check if we need to add the host to the permissions blacklist.
- if (aBlocked && mPermissionService) {
- // strip off the domain dot, if necessary
- if (!host.IsEmpty() && host.First() == '.')
- host.Cut(0, 1);
- host.Insert(NS_LITERAL_CSTRING("http://"), 0);
- nsCOMPtr<nsIURI> uri;
- NS_NewURI(getter_AddRefs(uri), host);
- if (uri)
- mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY);
- }
- if (cookie) {
- // Everything's done. Notify observers.
- NotifyChanged(cookie, u"deleted");
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::Remove(const nsACString &aHost,
- const nsACString &aName,
- const nsACString &aPath,
- bool aBlocked,
- JS::HandleValue aOriginAttributes,
- JSContext* aCx,
- uint8_t aArgc)
- {
- MOZ_ASSERT(aArgc == 0 || aArgc == 1);
- NeckoOriginAttributes attrs;
- nsresult rv = InitializeOriginAttributes(&attrs,
- aOriginAttributes,
- aCx,
- aArgc,
- u"nsICookieManager.remove()",
- u"");
- NS_ENSURE_SUCCESS(rv, rv);
- return RemoveNative(aHost, aName, aPath, aBlocked, &attrs);
- }
- NS_IMETHODIMP_(nsresult)
- nsCookieService::RemoveNative(const nsACString &aHost,
- const nsACString &aName,
- const nsACString &aPath,
- bool aBlocked,
- NeckoOriginAttributes* aOriginAttributes)
- {
- if (NS_WARN_IF(!aOriginAttributes)) {
- return NS_ERROR_FAILURE;
- }
- nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- return NS_OK;
- }
- NS_IMETHODIMP
- nsCookieService::UsePrivateMode(bool aIsPrivate,
- nsIPrivateModeCallback* aCallback)
- {
- if (!aCallback) {
- return NS_ERROR_INVALID_ARG;
- }
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- AutoRestore<DBState*> savePrevDBState(mDBState);
- mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
- return aCallback->Callback();
- }
- /******************************************************************************
- * nsCookieService impl:
- * private file I/O functions
- ******************************************************************************/
- // Begin an asynchronous read from the database.
- OpenDBResult
- nsCookieService::Read()
- {
- // Set up a statement for the read. Note that our query specifies that
- // 'baseDomain' not be nullptr -- see below for why.
- nsCOMPtr<mozIStorageAsyncStatement> stmtRead;
- nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "SELECT "
- "name, "
- "value, "
- "host, "
- "path, "
- "expiry, "
- "lastAccessed, "
- "creationTime, "
- "isSecure, "
- "isHttpOnly, "
- "baseDomain, "
- "originAttributes "
- "FROM moz_cookies "
- "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Set up a statement to delete any rows with a nullptr 'baseDomain'
- // column. This takes care of any cookies set by browsers that don't
- // understand the 'baseDomain' column, where the database schema version
- // is from one that does. (This would occur when downgrading.)
- nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull;
- rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
- "DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
- getter_AddRefs(stmtDeleteNull));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Start a new connection for sync reads, to reduce contention with the
- // background thread. We need to do this before we kick off write statements,
- // since they can lock the database and prevent connections from being opened.
- rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
- getter_AddRefs(mDefaultDBState->syncConn));
- NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
- // Init our readSet hash and execute the statements. Note that, after this
- // point, we cannot fail without altering the cleanup code in InitDBStates()
- // to handle closing of the now-asynchronous connection.
- mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies);
- mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState);
- rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener,
- getter_AddRefs(mDefaultDBState->pendingRead));
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener,
- getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- return RESULT_OK;
- }
- // Extract data from a single result row and create an nsCookie.
- // This is templated since 'T' is different for sync vs async results.
- template<class T> nsCookie*
- nsCookieService::GetCookieFromRow(T &aRow, const OriginAttributes& aOriginAttributes)
- {
- // Skip reading 'baseDomain' -- up to the caller.
- nsCString name, value, host, path;
- DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
- NS_ASSERT_SUCCESS(rv);
- rv = aRow->GetUTF8String(IDX_VALUE, value);
- NS_ASSERT_SUCCESS(rv);
- rv = aRow->GetUTF8String(IDX_HOST, host);
- NS_ASSERT_SUCCESS(rv);
- rv = aRow->GetUTF8String(IDX_PATH, path);
- NS_ASSERT_SUCCESS(rv);
- int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
- int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
- int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
- bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
- bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
- // Create a new nsCookie and assign the data.
- return nsCookie::Create(name, value, host, path,
- expiry,
- lastAccessed,
- creationTime,
- false,
- isSecure,
- isHttpOnly,
- aOriginAttributes);
- }
- void
- nsCookieService::AsyncReadComplete()
- {
- // We may be in the private browsing DB state, with a pending read on the
- // default DB state. (This would occur if we started up in private browsing
- // mode.) As long as we do all our operations on the default state, we're OK.
- NS_ASSERTION(mDefaultDBState, "no default DBState");
- NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
- NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
- // Merge the data read on the background thread with the data synchronously
- // read on the main thread. Note that transactions on the cookie table may
- // have occurred on the main thread since, making the background data stale.
- for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) {
- const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i];
- // Tiebreak: if the given base domain has already been read in, ignore
- // the background data. Note that readSet may contain domains that were
- // queried but found not to be in the db -- that's harmless.
- if (mDefaultDBState->readSet.GetEntry(tuple.key))
- continue;
- AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false);
- }
- mDefaultDBState->stmtReadDomain = nullptr;
- mDefaultDBState->pendingRead = nullptr;
- mDefaultDBState->readListener = nullptr;
- mDefaultDBState->syncConn = nullptr;
- mDefaultDBState->hostArray.Clear();
- mDefaultDBState->readSet.Clear();
- COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %ld cookies read",
- mDefaultDBState->cookieCount));
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
- }
- }
- void
- nsCookieService::CancelAsyncRead(bool aPurgeReadSet)
- {
- // We may be in the private browsing DB state, with a pending read on the
- // default DB state. (This would occur if we started up in private browsing
- // mode.) As long as we do all our operations on the default state, we're OK.
- NS_ASSERTION(mDefaultDBState, "no default DBState");
- NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read");
- NS_ASSERTION(mDefaultDBState->readListener, "no read listener");
- // Cancel the pending read, kill the read listener, and empty the array
- // of data already read in on the background thread.
- mDefaultDBState->readListener->Cancel();
- DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel();
- NS_ASSERT_SUCCESS(rv);
- mDefaultDBState->stmtReadDomain = nullptr;
- mDefaultDBState->pendingRead = nullptr;
- mDefaultDBState->readListener = nullptr;
- mDefaultDBState->hostArray.Clear();
- // Only clear the 'readSet' table if we no longer need to know what set of
- // data is already accounted for.
- if (aPurgeReadSet)
- mDefaultDBState->readSet.Clear();
- }
- void
- nsCookieService::EnsureReadDomain(const nsCookieKey &aKey)
- {
- NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
- "not in default db state");
- // Fast path 1: nothing to read, or we've already finished reading.
- if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
- return;
- // Fast path 2: already read in this particular domain.
- if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey)))
- return;
- // Read in the data synchronously.
- // see IDX_NAME, etc. for parameter indexes
- nsresult rv;
- if (!mDefaultDBState->stmtReadDomain) {
- // Cache the statement, since it's likely to be used again.
- rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT "
- "name, "
- "value, "
- "host, "
- "path, "
- "expiry, "
- "lastAccessed, "
- "creationTime, "
- "isSecure, "
- "isHttpOnly "
- "FROM moz_cookies "
- "WHERE baseDomain = :baseDomain "
- " AND originAttributes = :originAttributes"),
- getter_AddRefs(mDefaultDBState->stmtReadDomain));
- if (NS_FAILED(rv)) {
- // Recreate the database.
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadDomain(): corruption detected when creating statement "
- "with rv 0x%x", rv));
- HandleCorruptDB(mDefaultDBState);
- return;
- }
- }
- NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection");
- mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain);
- rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
- NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain);
- NS_ASSERT_SUCCESS(rv);
- nsAutoCString suffix;
- aKey.mOriginAttributes.CreateSuffix(suffix);
- rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName(
- NS_LITERAL_CSTRING("originAttributes"), suffix);
- NS_ASSERT_SUCCESS(rv);
- bool hasResult;
- nsCString name, value, host, path;
- AutoTArray<RefPtr<nsCookie>, kMaxCookiesPerHost> array;
- while (true) {
- rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult);
- if (NS_FAILED(rv)) {
- // Recreate the database.
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadDomain(): corruption detected when reading result "
- "with rv 0x%x", rv));
- HandleCorruptDB(mDefaultDBState);
- return;
- }
- if (!hasResult)
- break;
- array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain,
- aKey.mOriginAttributes));
- }
- // Add the cookies to the table in a single operation. This makes sure that
- // either all the cookies get added, or in the case of corruption, none.
- for (uint32_t i = 0; i < array.Length(); ++i) {
- AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false);
- }
- // Add it to the hashset of read entries, so we don't read it again.
- mDefaultDBState->readSet.PutEntry(aKey);
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadDomain(): %ld cookies read for base domain %s, "
- " originAttributes = %s", array.Length(), aKey.mBaseDomain.get(),
- suffix.get()));
- }
- void
- nsCookieService::EnsureReadComplete()
- {
- NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState,
- "not in default db state");
- // Fast path 1: nothing to read, or we've already finished reading.
- if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead))
- return;
- // Cancel the pending read, so we don't get any more results.
- CancelAsyncRead(false);
- // Read in the data synchronously.
- // see IDX_NAME, etc. for parameter indexes
- nsCOMPtr<mozIStorageStatement> stmt;
- nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING(
- "SELECT "
- "name, "
- "value, "
- "host, "
- "path, "
- "expiry, "
- "lastAccessed, "
- "creationTime, "
- "isSecure, "
- "isHttpOnly, "
- "baseDomain, "
- "originAttributes "
- "FROM moz_cookies "
- "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
- if (NS_FAILED(rv)) {
- // Recreate the database.
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadComplete(): corruption detected when creating statement "
- "with rv 0x%x", rv));
- HandleCorruptDB(mDefaultDBState);
- return;
- }
- nsCString baseDomain, name, value, host, path;
- bool hasResult;
- nsTArray<CookieDomainTuple> array(kMaxNumberOfCookies);
- while (true) {
- rv = stmt->ExecuteStep(&hasResult);
- if (NS_FAILED(rv)) {
- // Recreate the database.
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadComplete(): corruption detected when reading result "
- "with rv 0x%x", rv));
- HandleCorruptDB(mDefaultDBState);
- return;
- }
- if (!hasResult)
- break;
- // Make sure we haven't already read the data.
- stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain);
- nsAutoCString suffix;
- NeckoOriginAttributes attrs;
- stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
- // If PopulateFromSuffix failed we just ignore the OA attributes
- // that we don't support
- Unused << attrs.PopulateFromSuffix(suffix);
- nsCookieKey key(baseDomain, attrs);
- if (mDefaultDBState->readSet.GetEntry(key))
- continue;
- CookieDomainTuple* tuple = array.AppendElement();
- tuple->key = key;
- tuple->cookie = GetCookieFromRow(stmt, attrs);
- }
- // Add the cookies to the table in a single operation. This makes sure that
- // either all the cookies get added, or in the case of corruption, none.
- for (uint32_t i = 0; i < array.Length(); ++i) {
- CookieDomainTuple& tuple = array[i];
- AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr,
- false);
- }
- mDefaultDBState->syncConn = nullptr;
- mDefaultDBState->readSet.Clear();
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("EnsureReadComplete(): %ld cookies read", array.Length()));
- }
- NS_IMETHODIMP
- nsCookieService::ImportCookies(nsIFile *aCookieFile)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // Make sure we're in the default DB state. We don't want people importing
- // cookies into a private browsing session!
- if (mDBState != mDefaultDBState) {
- NS_WARNING("Trying to import cookies in a private browsing session!");
- return NS_ERROR_NOT_AVAILABLE;
- }
- nsresult rv;
- nsCOMPtr<nsIInputStream> fileInputStream;
- rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
- if (NS_FAILED(rv)) return rv;
- nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
- if (NS_FAILED(rv)) return rv;
- // First, ensure we've read in everything from the database, if we have one.
- EnsureReadComplete();
- static const char kTrue[] = "TRUE";
- nsAutoCString buffer, baseDomain;
- bool isMore = true;
- int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
- nsASingleFragmentCString::char_iterator iter;
- int32_t numInts;
- int64_t expires;
- bool isDomain, isHttpOnly = false;
- uint32_t originalCookieCount = mDefaultDBState->cookieCount;
- int64_t currentTimeInUsec = PR_Now();
- int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
- // we use lastAccessedCounter to keep cookies in recently-used order,
- // so we start by initializing to currentTime (somewhat arbitrary)
- int64_t lastAccessedCounter = currentTimeInUsec;
- /* file format is:
- *
- * host \t isDomain \t path \t secure \t expires \t name \t cookie
- *
- * if this format isn't respected we move onto the next line in the file.
- * isDomain is "TRUE" or "FALSE" (default to "FALSE")
- * isSecure is "TRUE" or "FALSE" (default to "TRUE")
- * expires is a int64_t integer
- * note 1: cookie can contain tabs.
- * note 2: cookies will be stored in order of lastAccessed time:
- * most-recently used come first; least-recently-used come last.
- */
- /*
- * ...but due to bug 178933, we hide HttpOnly cookies from older code
- * in a comment, so they don't expose HttpOnly cookies to JS.
- *
- * The format for HttpOnly cookies is
- *
- * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
- *
- */
- // We will likely be adding a bunch of cookies to the DB, so we use async
- // batching with storage to make this super fast.
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
- if (originalCookieCount == 0 && mDefaultDBState->dbConn) {
- mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
- }
- while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
- if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) {
- isHttpOnly = true;
- hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1;
- } else if (buffer.IsEmpty() || buffer.First() == '#') {
- continue;
- } else {
- isHttpOnly = false;
- hostIndex = 0;
- }
- // this is a cheap, cheesy way of parsing a tab-delimited line into
- // string indexes, which can be lopped off into substrings. just for
- // purposes of obfuscation, it also checks that each token was found.
- // todo: use iterators?
- if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 ||
- (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 ||
- (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 ||
- (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 ||
- (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 ||
- (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) {
- continue;
- }
- // check the expirytime first - if it's expired, ignore
- // nullstomp the trailing tab, to avoid copying the string
- buffer.BeginWriting(iter);
- *(iter += nameIndex - 1) = char(0);
- numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires);
- if (numInts != 1 || expires < currentTime) {
- continue;
- }
- isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue);
- const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1);
- // check for bad legacy cookies (domain not starting with a dot, or containing a port),
- // and discard
- if ((isDomain && !host.IsEmpty() && host.First() != '.') ||
- host.Contains(':')) {
- continue;
- }
- // compute the baseDomain from the host
- rv = GetBaseDomainFromHost(host, baseDomain);
- if (NS_FAILED(rv))
- continue;
- // pre-existing cookies have appId=0, inIsolatedMozBrowser=false set by default
- // constructor of NeckoOriginAttributes().
- nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
- // Create a new nsCookie and assign the data. We don't know the cookie
- // creation time, so just use the current time to generate a unique one.
- RefPtr<nsCookie> newCookie =
- nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1),
- Substring(buffer, cookieIndex, buffer.Length() - cookieIndex),
- host,
- Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
- expires,
- lastAccessedCounter,
- nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
- false,
- Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
- isHttpOnly,
- key.mOriginAttributes);
- if (!newCookie) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // trick: preserve the most-recently-used cookie ordering,
- // by successively decrementing the lastAccessed time
- lastAccessedCounter--;
- if (originalCookieCount == 0) {
- AddCookieToList(key, newCookie, mDefaultDBState, paramsArray);
- }
- else {
- AddInternal(key, newCookie, currentTimeInUsec,
- nullptr, nullptr, true);
- }
- }
- // If we need to write to disk, do so now.
- if (paramsArray) {
- uint32_t length;
- paramsArray->GetLength(&length);
- if (length) {
- rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = mDefaultDBState->stmtInsert->ExecuteAsync(
- mDefaultDBState->insertListener, getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- }
- COOKIE_LOGSTRING(LogLevel::Debug, ("ImportCookies(): %ld cookies imported",
- mDefaultDBState->cookieCount));
- return NS_OK;
- }
- /******************************************************************************
- * nsCookieService impl:
- * private GetCookie/SetCookie helpers
- ******************************************************************************/
- // helper function for GetCookieList
- static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; }
- // Comparator class for sorting cookies before sending to a server.
- class CompareCookiesForSending
- {
- public:
- bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const
- {
- return aCookie1->CreationTime() == aCookie2->CreationTime() &&
- aCookie2->Path().Length() == aCookie1->Path().Length();
- }
- bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const
- {
- // compare by cookie path length in accordance with RFC2109
- int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length();
- if (result != 0)
- return result < 0;
- // when path lengths match, older cookies should be listed first. this is
- // required for backwards compatibility since some websites erroneously
- // depend on receiving cookies in the order in which they were sent to the
- // browser! see bug 236772.
- return aCookie1->CreationTime() < aCookie2->CreationTime();
- }
- };
- static bool
- DomainMatches(nsCookie* aCookie, const nsACString& aHost) {
- // first, check for an exact host or domain cookie match, e.g. "google.com"
- // or ".google.com"; second a subdomain match, e.g.
- // host = "mail.google.com", cookie domain = ".google.com".
- return aCookie->RawHost() == aHost ||
- (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host()));
- }
- static bool
- PathMatches(nsCookie* aCookie, const nsACString& aPath) {
- // calculate cookie path length, excluding trailing '/'
- uint32_t cookiePathLen = aCookie->Path().Length();
- if (cookiePathLen > 0 && aCookie->Path().Last() == '/')
- --cookiePathLen;
- // if the given path is shorter than the cookie path, it doesn't match
- // if the given path doesn't start with the cookie path, it doesn't match.
- if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen)))
- return false;
- // if the given path is longer than the cookie path, and the first char after
- // the cookie path is not a path delimiter, it doesn't match.
- if (aPath.Length() > cookiePathLen &&
- !ispathdelimiter(aPath.CharAt(cookiePathLen))) {
- /*
- * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'.
- * '/' is the "standard" case; the '?' test allows a site at host/abc?def
- * to receive a cookie that has a path attribute of abc. this seems
- * strange but at least one major site (citibank, bug 156725) depends
- * on it. The test for # and ; are put in to proactively avoid problems
- * with other sites - these are the only other chars allowed in the path.
- */
- return false;
- }
- // either the paths match exactly, or the cookie path is a prefix of
- // the given path.
- return true;
- }
- void
- nsCookieService::GetCookieStringInternal(nsIURI *aHostURI,
- bool aIsForeign,
- bool aHttpBound,
- const NeckoOriginAttributes aOriginAttrs,
- bool aIsPrivate,
- nsCString &aCookieString)
- {
- NS_ASSERTION(aHostURI, "null host!");
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return;
- }
- AutoRestore<DBState*> savePrevDBState(mDBState);
- mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState;
- // get the base domain, host, and path from the URI.
- // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk".
- // file:// URI's (i.e. with an empty host) are allowed, but any other
- // scheme must have a non-empty host. A trailing dot in the host
- // is acceptable.
- bool requireHostMatch;
- nsAutoCString baseDomain, hostFromURI, pathFromURI;
- nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch);
- if (NS_SUCCEEDED(rv))
- rv = aHostURI->GetAsciiHost(hostFromURI);
- if (NS_SUCCEEDED(rv))
- rv = aHostURI->GetPath(pathFromURI);
- if (NS_FAILED(rv)) {
- COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI");
- return;
- }
- // check default prefs
- CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, nullptr);
- // for GetCookie(), we don't fire rejection notifications.
- switch (cookieStatus) {
- case STATUS_REJECTED:
- case STATUS_REJECTED_WITH_ERROR:
- return;
- default:
- break;
- }
- // Note: The following permissions logic is mirrored in
- // toolkit/modules/addons/MatchPattern.jsm:MatchPattern.matchesCookie().
- // If it changes, please update that function, or file a bug for someone
- // else to do so.
- // check if aHostURI is using an https secure protocol.
- // if it isn't, then we can't send a secure cookie over the connection.
- // if SchemeIs fails, assume an insecure connection, to be on the safe side
- bool isSecure;
- if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
- isSecure = false;
- }
- nsCookie *cookie;
- AutoTArray<nsCookie*, 8> foundCookieList;
- int64_t currentTimeInUsec = PR_Now();
- int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
- bool stale = false;
- nsCookieKey key(baseDomain, aOriginAttrs);
- EnsureReadDomain(key);
- // perform the hash lookup
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
- if (!entry)
- return;
- // iterate the cookies!
- const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- cookie = cookies[i];
- // check the host, since the base domain lookup is conservative.
- if (!DomainMatches(cookie, hostFromURI))
- continue;
- // if the cookie is secure and the host scheme isn't, we can't send it
- if (cookie->IsSecure() && !isSecure)
- continue;
- // if the cookie is httpOnly and it's not going directly to the HTTP
- // connection, don't send it
- if (cookie->IsHttpOnly() && !aHttpBound)
- continue;
- // if the nsIURI path doesn't match the cookie path, don't send it back
- if (!PathMatches(cookie, pathFromURI))
- continue;
- // check if the cookie has expired
- if (cookie->Expiry() <= currentTime) {
- continue;
- }
- // all checks passed - add to list and check if lastAccessed stamp needs updating
- foundCookieList.AppendElement(cookie);
- if (cookie->IsStale()) {
- stale = true;
- }
- }
- int32_t count = foundCookieList.Length();
- if (count == 0)
- return;
- // update lastAccessed timestamps. we only do this if the timestamp is stale
- // by a certain amount, to avoid thrashing the db during pageload.
- if (stale) {
- // Create an array of parameters to bind to our update statement. Batching
- // is OK here since we're updating cookies with no interleaved operations.
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
- mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate;
- if (mDBState->dbConn) {
- stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
- }
- for (int32_t i = 0; i < count; ++i) {
- cookie = foundCookieList.ElementAt(i);
- if (cookie->IsStale()) {
- UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
- }
- }
- // Update the database now if necessary.
- if (paramsArray) {
- uint32_t length;
- paramsArray->GetLength(&length);
- if (length) {
- DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(mDBState->updateListener,
- getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- }
- }
- // return cookies in order of path length; longest to shortest.
- // this is required per RFC2109. if cookies match in length,
- // then sort by creation time (see bug 236772).
- foundCookieList.Sort(CompareCookiesForSending());
- for (int32_t i = 0; i < count; ++i) {
- cookie = foundCookieList.ElementAt(i);
- // check if we have anything to write
- if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
- // if we've already added a cookie to the return list, append a "; " so
- // that subsequent cookies are delimited in the final list.
- if (!aCookieString.IsEmpty()) {
- aCookieString.AppendLiteral("; ");
- }
- if (!cookie->Name().IsEmpty()) {
- // we have a name and value - write both
- aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value();
- } else {
- // just write value
- aCookieString += cookie->Value();
- }
- }
- }
- if (!aCookieString.IsEmpty())
- COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false);
- }
- // processes a single cookie, and returns true if there are more cookies
- // to be processed
- bool
- nsCookieService::SetCookieInternal(nsIURI *aHostURI,
- const nsCookieKey &aKey,
- bool aRequireHostMatch,
- CookieStatus aStatus,
- nsDependentCString &aCookieHeader,
- int64_t aServerTime,
- bool aFromHttp,
- nsIChannel *aChannel)
- {
- NS_ASSERTION(aHostURI, "null host!");
- // create a stack-based nsCookieAttributes, to store all the
- // attributes parsed from the cookie
- nsCookieAttributes cookieAttributes;
- // init expiryTime such that session cookies won't prematurely expire
- cookieAttributes.expiryTime = INT64_MAX;
- // aCookieHeader is an in/out param to point to the next cookie, if
- // there is one. Save the present value for logging purposes
- nsDependentCString savedCookieHeader(aCookieHeader);
- // newCookie says whether there are multiple cookies in the header;
- // so we can handle them separately.
- bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes);
- bool isHTTPS;
- nsresult rv = aHostURI->SchemeIs("https", &isHTTPS);
- int64_t currentTimeInUsec = PR_Now();
- // calculate expiry time of cookie.
- cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime,
- currentTimeInUsec / PR_USEC_PER_SEC);
- if (aStatus == STATUS_ACCEPT_SESSION) {
- // force lifetime to session. note that the expiration time, if set above,
- // will still apply.
- cookieAttributes.isSession = true;
- }
- // reject cookie if it's over the size limit, per RFC2109
- if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)");
- return newCookie;
- }
- // Note: For now we allow 0x20 (Space) in cookie names.
- // Some websites apparently use cookie names with spaces in them, and the RFC
- // doesn't exactly specify what to do in that case, so it's better to keep
- // wider compatibility.
- const char illegalNameCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
- 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
- 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
- 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
- 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
- 0x1F, /* 0x20, */ 0x00 };
- if (cookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character");
- return newCookie;
- }
- // domain & path checks
- if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
- return newCookie;
- }
- if (!CheckPath(cookieAttributes, aHostURI)) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
- return newCookie;
- }
- // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
- if (!CheckPrefixes(cookieAttributes, isHTTPS)) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the prefix tests");
- return newCookie;
- }
- // Reject cookie if value contains an RFC 6265 disallowed character.
- // See RFC 6265 section 4.1.1
- // XXX: For now we allow for web compatibility (see issue #357):
- // 0x20 (Space)
- // 0x22 (DQUOTE)
- // 0x2C (Comma)
- // 0x5C (Backslash)
- //
- // FIXME: Before removing DQUOTE from the exceptions list:
- // DQUOTE *cookie-octet DQUOTE is permitted and would fail if just removed.
- // This needs better checking for first and last character allowing
- // DQUOTE but not in the actual value.
- //
- // This only applies to cookies set via the Set-Cookie header, since
- // document.cookie is defined to be UTF-8.
- const char illegalCharacters[] = {
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
- 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
- 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, /* 0x20, 0x22, */
- /* 0x2C, */ 0x3B, /* 0x5C, */ 0x7F, 0x00 };
- if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character");
- return newCookie;
- }
- // create a new nsCookie and copy attributes
- RefPtr<nsCookie> cookie =
- nsCookie::Create(cookieAttributes.name,
- cookieAttributes.value,
- cookieAttributes.host,
- cookieAttributes.path,
- cookieAttributes.expiryTime,
- currentTimeInUsec,
- nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
- cookieAttributes.isSession,
- cookieAttributes.isSecure,
- cookieAttributes.isHttpOnly,
- aKey.mOriginAttributes);
- if (!cookie)
- return newCookie;
- // check permissions from site permission list, or ask the user,
- // to determine if we can set the cookie
- if (mPermissionService) {
- bool permission;
- mPermissionService->CanSetCookie(aHostURI,
- aChannel,
- static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)),
- &cookieAttributes.isSession,
- &cookieAttributes.expiryTime,
- &permission);
- if (!permission) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager");
- NotifyRejected(aHostURI);
- return newCookie;
- }
- // update isSession and expiry attributes, in case they changed
- cookie->SetIsSession(cookieAttributes.isSession);
- cookie->SetExpiry(cookieAttributes.expiryTime);
- }
- // add the cookie to the list. AddInternal() takes care of logging.
- // we get the current time again here, since it may have changed during prompting
- AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
- aFromHttp);
- return newCookie;
- }
- // this is a backend function for adding a cookie to the list, via SetCookie.
- // also used in the cookie manager, for profile migration from IE.
- // it either replaces an existing cookie; or adds the cookie to the hashtable,
- // and deletes a cookie (if maximum number of cookies has been
- // reached). also performs list maintenance by removing expired cookies.
- void
- nsCookieService::AddInternal(const nsCookieKey &aKey,
- nsCookie *aCookie,
- int64_t aCurrentTimeInUsec,
- nsIURI *aHostURI,
- const char *aCookieHeader,
- bool aFromHttp)
- {
- int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
- // if the new cookie is httponly, make sure we're not coming from script
- if (!aFromHttp && aCookie->IsHttpOnly()) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "cookie is httponly; coming from script");
- return;
- }
- bool isSecure = true;
- if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
- isSecure = false;
- }
- // If the new cookie is non-https and wants to set secure flag,
- // browser have to ignore this new cookie.
- // (draft-ietf-httpbis-cookie-alone section 3.1)
- if (mLeaveSecureAlone && aCookie->IsSecure() && !isSecure) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "non-https cookie can't set secure flag");
- return;
- }
- nsListIter exactIter;
- bool foundCookie = false;
- if (mLeaveSecureAlone) {
- // Step1, call FindSecureCookie(). FindSecureCookie() would
- // find the existing cookie with the security flag and has
- // the same name, host and path of the new cookie, if there is any.
- // Step2, Confirm new cookie's security setting. If any targeted
- // cookie had been found in Step1, then confirm whether the
- // new cookie could modify it. If the new created cookie’s
- // "secure-only-flag" is not set, and the "scheme" component
- // of the "request-uri" does not denote a "secure" protocol,
- // then ignore the new cookie.
- // (draft-ietf-httpbis-cookie-alone section 3.2)
- foundCookie = FindSecureCookie(aKey, aCookie);
- if (foundCookie && !aCookie->IsSecure()) {
- if (!isSecure) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
- return;
- }
- }
- }
- foundCookie = FindCookie(aKey, aCookie->Host(),
- aCookie->Name(), aCookie->Path(), exactIter);
- RefPtr<nsCookie> oldCookie;
- nsCOMPtr<nsIArray> purgedList;
- if (foundCookie) {
- oldCookie = exactIter.Cookie();
- // Check if the old cookie is stale (i.e. has already expired). If so, we
- // need to be careful about the semantics of removing it and adding the new
- // cookie: we want the behavior wrt adding the new cookie to be the same as
- // if it didn't exist, but we still want to fire a removal notification.
- if (oldCookie->Expiry() <= currentTime) {
- if (aCookie->Expiry() <= currentTime) {
- // The new cookie has expired and the old one is stale. Nothing to do.
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "cookie has already expired");
- return;
- }
- // Remove the stale cookie. We save notification for later, once all list
- // modifications are complete.
- RemoveCookieFromList(exactIter);
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "stale cookie was purged");
- purgedList = CreatePurgeList(oldCookie);
- // We've done all we need to wrt removing and notifying the stale cookie.
- // From here on out, we pretend pretend it didn't exist, so that we
- // preserve expected notification semantics when adding the new cookie.
- foundCookie = false;
- } else {
- // If the old cookie is httponly, make sure we're not coming from script.
- if (!aFromHttp && oldCookie->IsHttpOnly()) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "previously stored cookie is httponly; coming from script");
- return;
- }
- // If the new cookie has the same value, expiry date, and isSecure,
- // isSession, and isHttpOnly flags then we can just keep the old one.
- // Only if any of these differ we would want to override the cookie.
- if (oldCookie->Value().Equals(aCookie->Value()) &&
- oldCookie->Expiry() == aCookie->Expiry() &&
- oldCookie->IsSecure() == aCookie->IsSecure() &&
- oldCookie->IsSession() == aCookie->IsSession() &&
- oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() &&
- // We don't want to perform this optimization if the cookie is
- // considered stale, since in this case we would need to update the
- // database.
- !oldCookie->IsStale()) {
- // Update the last access time on the old cookie.
- oldCookie->SetLastAccessed(aCookie->LastAccessed());
- UpdateCookieOldestTime(mDBState, oldCookie);
- return;
- }
- // Remove the old cookie.
- RemoveCookieFromList(exactIter);
- // If the new cookie has expired -- i.e. the intent was simply to delete
- // the old cookie -- then we're done.
- if (aCookie->Expiry() <= currentTime) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "previously stored cookie was deleted");
- NotifyChanged(oldCookie, u"deleted");
- return;
- }
- // Preserve creation time of cookie for ordering purposes.
- aCookie->SetCreationTime(oldCookie->CreationTime());
- }
- } else {
- // check if cookie has already expired
- if (aCookie->Expiry() <= currentTime) {
- COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
- "cookie has already expired");
- return;
- }
- // check if we have to delete an old cookie.
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
- if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
- nsListIter iter;
- // Prioritize evicting insecure cookies.
- // (draft-ietf-httpbis-cookie-alone section 3.3)
- mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
- int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
- if (iter.entry == nullptr) {
- if (aCookie->IsSecure()) {
- // It's valid to evict a secure cookie for another secure cookie.
- oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
- } else {
- COOKIE_LOGEVICTED(aCookie,
- "Too many cookies for this domain and the new cookie is not a secure cookie");
- return;
- }
- }
- MOZ_ASSERT(iter.entry);
- oldCookie = iter.Cookie();
- // remove the oldest cookie from the domain
- RemoveCookieFromList(iter);
- COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
- purgedList = CreatePurgeList(oldCookie);
- } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
- int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
- int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
- if (maxAge >= purgeAge) {
- // we're over both size and age limits by 10%; time to purge the table!
- // do this by:
- // 1) removing expired cookies;
- // 2) evicting the balance of old cookies until we reach the size limit.
- // note that the cookieOldestTime indicator can be pessimistic - if it's
- // older than the actual oldest cookie, we'll just purge more eagerly.
- purgedList = PurgeCookies(aCurrentTimeInUsec);
- }
- }
- }
- // Add the cookie to the db. We do not supply a params array for batching
- // because this might result in removals and additions being out of order.
- AddCookieToList(aKey, aCookie, mDBState, nullptr);
- COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie);
- // Now that list mutations are complete, notify observers. We do it here
- // because observers may themselves attempt to mutate the list.
- if (purgedList) {
- NotifyChanged(purgedList, u"batch-deleted");
- }
- NotifyChanged(aCookie, foundCookie ? u"changed" : u"added");
- }
- /******************************************************************************
- * nsCookieService impl:
- * private cookie header parsing functions
- ******************************************************************************/
- // The following comment block elucidates the function of ParseAttributes.
- /******************************************************************************
- ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1
- ** please note: this BNF deviates from both specifications, and reflects this
- ** implementation. <bnf> indicates a reference to the defined grammar "bnf".
- ** Differences from RFC2109/2616 and explanations:
- 1. implied *LWS
- The grammar described by this specification is word-based. Except
- where noted otherwise, linear white space (<LWS>) can be included
- between any two adjacent words (token or quoted-string), and
- between adjacent words and separators, without changing the
- interpretation of a field.
- <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT.
- 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in
- common use inside values.
- 3. tokens and values have looser restrictions on allowed characters than
- spec. This is also due to certain characters being in common use inside
- values. We allow only '=' to separate token/value pairs, and ';' to
- terminate tokens or values. <LWS> is allowed within tokens and values
- (see bug 206022).
- 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to
- reject control chars or non-ASCII chars. This is erring on the loose
- side, since there's probably no good reason to enforce this strictness.
- 5. cookie <NAME> is optional, where spec requires it. This is a fairly
- trivial case, but allows the flexibility of setting only a cookie <VALUE>
- with a blank <NAME> and is required by some sites (see bug 169091).
- 6. Attribute "HttpOnly", not covered in the RFCs, is supported
- (see bug 178993).
- ** Begin BNF:
- token = 1*<any allowed-chars except separators>
- value = 1*<any allowed-chars except value-sep>
- separators = ";" | "="
- value-sep = ";"
- cookie-sep = CR | LF
- allowed-chars = <any OCTET except NUL or cookie-sep>
- OCTET = <any 8-bit sequence of data>
- LWS = SP | HT
- NUL = <US-ASCII NUL, null control character (0)>
- CR = <US-ASCII CR, carriage return (13)>
- LF = <US-ASCII LF, linefeed (10)>
- SP = <US-ASCII SP, space (32)>
- HT = <US-ASCII HT, horizontal-tab (9)>
- set-cookie = "Set-Cookie:" cookies
- cookies = cookie *( cookie-sep cookie )
- cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first
- NAME = token ; cookie name
- VALUE = value ; cookie value
- cookie-av = token ["=" value]
- valid values for cookie-av (checked post-parsing) are:
- cookie-av = "Path" "=" value
- | "Domain" "=" value
- | "Expires" "=" value
- | "Max-Age" "=" value
- | "Comment" "=" value
- | "Version" "=" value
- | "Secure"
- | "HttpOnly"
- ******************************************************************************/
- // helper functions for GetTokenValue
- static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; }
- static inline bool isterminator (char c) { return c == '\n' || c == '\r'; }
- static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; }
- static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; }
- // Parse a single token/value pair.
- // Returns true if a cookie terminator is found, so caller can parse new cookie.
- bool
- nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter,
- nsASingleFragmentCString::const_char_iterator &aEndIter,
- nsDependentCSubstring &aTokenString,
- nsDependentCSubstring &aTokenValue,
- bool &aEqualsFound)
- {
- nsASingleFragmentCString::const_char_iterator start, lastSpace;
- // initialize value string to clear garbage
- aTokenValue.Rebind(aIter, aIter);
- // find <token>, including any <LWS> between the end-of-token and the
- // token separator. we'll remove trailing <LWS> next
- while (aIter != aEndIter && iswhitespace(*aIter))
- ++aIter;
- start = aIter;
- while (aIter != aEndIter && !istokenseparator(*aIter))
- ++aIter;
- // remove trailing <LWS>; first check we're not at the beginning
- lastSpace = aIter;
- if (lastSpace != start) {
- while (--lastSpace != start && iswhitespace(*lastSpace))
- continue;
- ++lastSpace;
- }
- aTokenString.Rebind(start, lastSpace);
- aEqualsFound = (*aIter == '=');
- if (aEqualsFound) {
- // find <value>
- while (++aIter != aEndIter && iswhitespace(*aIter))
- continue;
- start = aIter;
- // process <token>
- // just look for ';' to terminate ('=' allowed)
- while (aIter != aEndIter && !isvalueseparator(*aIter))
- ++aIter;
- // remove trailing <LWS>; first check we're not at the beginning
- if (aIter != start) {
- lastSpace = aIter;
- while (--lastSpace != start && iswhitespace(*lastSpace))
- continue;
- aTokenValue.Rebind(start, ++lastSpace);
- }
- }
- // aIter is on ';', or terminator, or EOS
- if (aIter != aEndIter) {
- // if on terminator, increment past & return true to process new cookie
- if (isterminator(*aIter)) {
- ++aIter;
- return true;
- }
- // fall-through: aIter is on ';', increment and return false
- ++aIter;
- }
- return false;
- }
- // Parses attributes from cookie header. expires/max-age attributes aren't folded into the
- // cookie struct here, because we don't know which one to use until we've parsed the header.
- bool
- nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader,
- nsCookieAttributes &aCookieAttributes)
- {
- static const char kPath[] = "path";
- static const char kDomain[] = "domain";
- static const char kExpires[] = "expires";
- static const char kMaxage[] = "max-age";
- static const char kSecure[] = "secure";
- static const char kHttpOnly[] = "httponly";
- nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
- nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
- aCookieHeader.BeginReading(cookieStart);
- aCookieHeader.EndReading(cookieEnd);
- aCookieAttributes.isSecure = false;
- aCookieAttributes.isHttpOnly = false;
- nsDependentCSubstring tokenString(cookieStart, cookieStart);
- nsDependentCSubstring tokenValue (cookieStart, cookieStart);
- bool newCookie, equalsFound;
- // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
- // if we find multiple cookies, return for processing
- // note: if there's no '=', we assume token is <VALUE>. this is required by
- // some sites (see bug 169091).
- // XXX fix the parser to parse according to <VALUE> grammar for this case
- newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
- if (equalsFound) {
- aCookieAttributes.name = tokenString;
- aCookieAttributes.value = tokenValue;
- } else {
- aCookieAttributes.value = tokenString;
- }
- // extract remaining attributes
- while (cookieStart != cookieEnd && !newCookie) {
- newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound);
- if (!tokenValue.IsEmpty()) {
- tokenValue.BeginReading(tempBegin);
- tokenValue.EndReading(tempEnd);
- }
- // decide which attribute we have, and copy the string
- if (tokenString.LowerCaseEqualsLiteral(kPath))
- aCookieAttributes.path = tokenValue;
- else if (tokenString.LowerCaseEqualsLiteral(kDomain))
- aCookieAttributes.host = tokenValue;
- else if (tokenString.LowerCaseEqualsLiteral(kExpires))
- aCookieAttributes.expires = tokenValue;
- else if (tokenString.LowerCaseEqualsLiteral(kMaxage))
- aCookieAttributes.maxage = tokenValue;
- // ignore any tokenValue for isSecure; just set the boolean
- else if (tokenString.LowerCaseEqualsLiteral(kSecure))
- aCookieAttributes.isSecure = true;
- // ignore any tokenValue for isHttpOnly (see bug 178993);
- // just set the boolean
- else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
- aCookieAttributes.isHttpOnly = true;
- }
- // rebind aCookieHeader, in case we need to process another cookie
- aCookieHeader.Rebind(cookieStart, cookieEnd);
- return newCookie;
- }
- /******************************************************************************
- * nsCookieService impl:
- * private domain & permission compliance enforcement functions
- ******************************************************************************/
- // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
- // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
- // dot may be present. If aHostURI is an IP address, an alias such as
- // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
- // be the exact host, and aRequireHostMatch will be true to indicate that
- // substring matches should not be performed.
- nsresult
- nsCookieService::GetBaseDomain(nsIURI *aHostURI,
- nsCString &aBaseDomain,
- bool &aRequireHostMatch)
- {
- // get the base domain. this will fail if the host contains a leading dot,
- // more than one trailing dot, or is otherwise malformed.
- nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
- aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
- rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
- if (aRequireHostMatch) {
- // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
- // such as 'co.uk', or the empty string. use the host as a key in such
- // cases.
- rv = aHostURI->GetAsciiHost(aBaseDomain);
- }
- NS_ENSURE_SUCCESS(rv, rv);
- // aHost (and thus aBaseDomain) may be the string '.'. If so, fail.
- if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
- return NS_ERROR_INVALID_ARG;
- // block any URIs without a host that aren't file:// URIs.
- if (aBaseDomain.IsEmpty()) {
- bool isFileURI = false;
- aHostURI->SchemeIs("file", &isFileURI);
- if (!isFileURI)
- return NS_ERROR_INVALID_ARG;
- }
- return NS_OK;
- }
- // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be
- // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed
- // that aHost is already normalized, and it may contain a leading dot
- // (indicating that it represents a domain). A trailing dot may be present.
- // If aHost is an IP address, an alias such as 'localhost', an eTLD such as
- // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a
- // leading dot will be treated as an error.
- nsresult
- nsCookieService::GetBaseDomainFromHost(const nsACString &aHost,
- nsCString &aBaseDomain)
- {
- // aHost must not be the string '.'.
- if (aHost.Length() == 1 && aHost.Last() == '.')
- return NS_ERROR_INVALID_ARG;
- // aHost may contain a leading dot; if so, strip it now.
- bool domain = !aHost.IsEmpty() && aHost.First() == '.';
- // get the base domain. this will fail if the host contains a leading dot,
- // more than one trailing dot, or is otherwise malformed.
- nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain);
- if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
- rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
- // aHost is either an IP address, an alias such as 'localhost', an eTLD
- // such as 'co.uk', or the empty string. use the host as a key in such
- // cases; however, we reject any such hosts with a leading dot, since it
- // doesn't make sense for them to be domain cookies.
- if (domain)
- return NS_ERROR_INVALID_ARG;
- aBaseDomain = aHost;
- return NS_OK;
- }
- return rv;
- }
- // Normalizes the given hostname, component by component. ASCII/ACE
- // components are lower-cased, and UTF-8 components are normalized per
- // RFC 3454 and converted to ACE.
- nsresult
- nsCookieService::NormalizeHost(nsCString &aHost)
- {
- if (!IsASCII(aHost)) {
- nsAutoCString host;
- nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host);
- if (NS_FAILED(rv))
- return rv;
- aHost = host;
- }
- ToLowerCase(aHost);
- return NS_OK;
- }
- // returns true if 'a' is equal to or a subdomain of 'b',
- // assuming no leading dots are present.
- static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b)
- {
- if (a == b)
- return true;
- if (a.Length() > b.Length())
- return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b);
- return false;
- }
- CookieStatus
- nsCookieService::CheckPrefs(nsIURI *aHostURI,
- bool aIsForeign,
- const char *aCookieHeader)
- {
- nsresult rv;
- // don't let ftp sites get/set cookies (could be a security issue)
- bool ftp;
- if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies");
- return STATUS_REJECTED_WITH_ERROR;
- }
- // check the permission list first; if we find an entry, it overrides
- // default prefs. see bug 184059.
- if (mPermissionService) {
- nsCookieAccess access;
- // Not passing an nsIChannel here is probably OK; our implementation
- // doesn't do anything with it anyway.
- rv = mPermissionService->CanAccess(aHostURI, nullptr, &access);
- // if we found an entry, use it
- if (NS_SUCCEEDED(rv)) {
- switch (access) {
- case nsICookiePermission::ACCESS_DENY:
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
- aCookieHeader, "cookies are blocked for this site");
- return STATUS_REJECTED;
- case nsICookiePermission::ACCESS_ALLOW:
- return STATUS_ACCEPTED;
- case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
- if (aIsForeign) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
- aCookieHeader, "third party cookies are blocked "
- "for this site");
- return STATUS_REJECTED;
- }
- return STATUS_ACCEPTED;
- case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
- if (!aIsForeign)
- return STATUS_ACCEPTED;
- uint32_t priorCookieCount = 0;
- nsAutoCString hostFromURI;
- aHostURI->GetHost(hostFromURI);
- CountCookiesFromHost(hostFromURI, &priorCookieCount);
- if (priorCookieCount == 0) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
- aCookieHeader, "third party cookies are blocked "
- "for this site");
- return STATUS_REJECTED;
- }
- return STATUS_ACCEPTED;
- }
- }
- }
- // check default prefs
- if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled");
- return STATUS_REJECTED;
- }
- // check if cookie is foreign
- if (aIsForeign) {
- if (mCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT && mThirdPartySession)
- return STATUS_ACCEPT_SESSION;
- if (mCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
- return STATUS_REJECTED;
- }
- if (mCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) {
- uint32_t priorCookieCount = 0;
- nsAutoCString hostFromURI;
- aHostURI->GetHost(hostFromURI);
- CountCookiesFromHost(hostFromURI, &priorCookieCount);
- if (priorCookieCount == 0) {
- COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party");
- return STATUS_REJECTED;
- }
- if (mThirdPartySession)
- return STATUS_ACCEPT_SESSION;
- }
- }
- // if nothing has complained, accept cookie
- return STATUS_ACCEPTED;
- }
- // processes domain attribute, and returns true if host has permission to set for this domain.
- bool
- nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes,
- nsIURI *aHostURI,
- const nsCString &aBaseDomain,
- bool aRequireHostMatch)
- {
- // Note: The logic in this function is mirrored in
- // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions().
- // If it changes, please update that function, or file a bug for someone
- // else to do so.
- // get host from aHostURI
- nsAutoCString hostFromURI;
- aHostURI->GetAsciiHost(hostFromURI);
- // if a domain is given, check the host has permission
- if (!aCookieAttributes.host.IsEmpty()) {
- // Tolerate leading '.' characters, but not if it's otherwise an empty host.
- if (aCookieAttributes.host.Length() > 1 &&
- aCookieAttributes.host.First() == '.') {
- aCookieAttributes.host.Cut(0, 1);
- }
- // switch to lowercase now, to avoid case-insensitive compares everywhere
- ToLowerCase(aCookieAttributes.host);
- // check whether the host is either an IP address, an alias such as
- // 'localhost', an eTLD such as 'co.uk', or the empty string. in these
- // cases, require an exact string match for the domain, and leave the cookie
- // as a non-domain one. bug 105917 originally noted the requirement to deal
- // with IP addresses.
- if (aRequireHostMatch)
- return hostFromURI.Equals(aCookieAttributes.host);
- // ensure the proposed domain is derived from the base domain; and also
- // that the host domain is derived from the proposed domain (per RFC2109).
- if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) &&
- IsSubdomainOf(hostFromURI, aCookieAttributes.host)) {
- // prepend a dot to indicate a domain cookie
- aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0);
- return true;
- }
- /*
- * note: RFC2109 section 4.3.2 requires that we check the following:
- * that the portion of host not in domain does not contain a dot.
- * this prevents hosts of the form x.y.co.nz from setting cookies in the
- * entire .co.nz domain. however, it's only a only a partial solution and
- * it breaks sites (IE doesn't enforce it), so we don't perform this check.
- */
- return false;
- }
- // no domain specified, use hostFromURI
- aCookieAttributes.host = hostFromURI;
- return true;
- }
- nsCString
- GetPathFromURI(nsIURI* aHostURI)
- {
- // strip down everything after the last slash to get the path,
- // ignoring slashes in the query string part.
- // if we can QI to nsIURL, that'll take care of the query string portion.
- // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash.
- nsAutoCString path;
- nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI);
- if (hostURL) {
- hostURL->GetDirectory(path);
- } else {
- aHostURI->GetPath(path);
- int32_t slash = path.RFindChar('/');
- if (slash != kNotFound) {
- path.Truncate(slash + 1);
- }
- }
- return path;
- }
- bool
- nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes,
- nsIURI *aHostURI)
- {
- // if a path is given, check the host has permission
- if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') {
- aCookieAttributes.path = GetPathFromURI(aHostURI);
- #if 0
- } else {
- /**
- * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
- * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
- * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
- * been disabled, unless we can evangelize these sites.
- */
- // get path from aHostURI
- nsAutoCString pathFromURI;
- if (NS_FAILED(aHostURI->GetPath(pathFromURI)) ||
- !StringBeginsWith(pathFromURI, aCookieAttributes.path)) {
- return false;
- }
- #endif
- }
- if (aCookieAttributes.path.Length() > kMaxBytesPerPath ||
- aCookieAttributes.path.Contains('\t'))
- return false;
- return true;
- }
- // CheckPrefixes
- //
- // Reject cookies whose name starts with the magic prefixes from
- // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
- // if they do not meet the criteria required by the prefix.
- //
- // Must not be called until after CheckDomain() and CheckPath() have
- // regularized and validated the nsCookieAttributes values!
- bool
- nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes,
- bool aSecureRequest)
- {
- static const char kSecure[] = "__Secure-";
- static const char kHost[] = "__Host-";
- static const int kSecureLen = sizeof( kSecure ) - 1;
- static const int kHostLen = sizeof( kHost ) - 1;
- bool isSecure = strncmp( aCookieAttributes.name.get(), kSecure, kSecureLen ) == 0;
- bool isHost = strncmp( aCookieAttributes.name.get(), kHost, kHostLen ) == 0;
- if ( !isSecure && !isHost ) {
- // not one of the magic prefixes: carry on
- return true;
- }
- if ( !aSecureRequest || !aCookieAttributes.isSecure ) {
- // the magic prefixes may only be used from a secure request and
- // the secure attribute must be set on the cookie
- return false;
- }
- if ( isHost ) {
- // The host prefix requires that the path is "/" and that the cookie
- // had no domain attribute. CheckDomain() and CheckPath() MUST be run
- // first to make sure invalid attributes are rejected and to regularlize
- // them. In particular all explicit domain attributes result in a host
- // that starts with a dot, and if the host doesn't start with a dot it
- // correctly matches the true host.
- if ( aCookieAttributes.host[0] == '.' ||
- !aCookieAttributes.path.EqualsLiteral( "/" )) {
- return false;
- }
- }
- return true;
- }
- bool
- nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes,
- int64_t aServerTime,
- int64_t aCurrentTime)
- {
- /* Determine when the cookie should expire. This is done by taking the difference between
- * the server time and the time the server wants the cookie to expire, and adding that
- * difference to the client time. This localizes the client time regardless of whether or
- * not the TZ environment variable was set on the client.
- *
- * Note: We need to consider accounting for network lag here, per RFC.
- */
- // check for max-age attribute first; this overrides expires attribute
- if (!aCookieAttributes.maxage.IsEmpty()) {
- // obtain numeric value of maxageAttribute
- int64_t maxage;
- int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage);
- // default to session cookie if the conversion failed
- if (numInts != 1) {
- return true;
- }
- // if this addition overflows, expiryTime will be less than currentTime
- // and the cookie will be expired - that's okay.
- aCookieAttributes.expiryTime = aCurrentTime + maxage;
- // check for expires attribute
- } else if (!aCookieAttributes.expires.IsEmpty()) {
- PRTime expires;
- // parse expiry time
- if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) {
- return true;
- }
- // If set-cookie used absolute time to set expiration, and it can't use
- // client time to set expiration.
- // Because if current time be set in the future, but the cookie expire
- // time be set less than current time and more than server time.
- // The cookie item have to be used to the expired cookie.
- aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC);
- // default to session cookie if no attributes found
- } else {
- return true;
- }
- return false;
- }
- /******************************************************************************
- * nsCookieService impl:
- * private cookielist management functions
- ******************************************************************************/
- void
- nsCookieService::RemoveAllFromMemory()
- {
- // clearing the hashtable will call each nsCookieEntry's dtor,
- // which releases all their respective children.
- mDBState->hostTable.Clear();
- mDBState->cookieCount = 0;
- mDBState->cookieOldestTime = INT64_MAX;
- }
- // comparator class for lastaccessed times of cookies.
- class CompareCookiesByAge {
- public:
- bool Equals(const nsListIter &a, const nsListIter &b) const
- {
- return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
- a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
- }
- bool LessThan(const nsListIter &a, const nsListIter &b) const
- {
- // compare by lastAccessed time, and tiebreak by creationTime.
- int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
- if (result != 0)
- return result < 0;
- return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
- }
- };
- // comparator class for sorting cookies by entry and index.
- class CompareCookiesByIndex {
- public:
- bool Equals(const nsListIter &a, const nsListIter &b) const
- {
- NS_ASSERTION(a.entry != b.entry || a.index != b.index,
- "cookie indexes should never be equal");
- return false;
- }
- bool LessThan(const nsListIter &a, const nsListIter &b) const
- {
- // compare by entryclass pointer, then by index.
- if (a.entry != b.entry)
- return a.entry < b.entry;
- return a.index < b.index;
- }
- };
- // purges expired and old cookies in a batch operation.
- already_AddRefed<nsIArray>
- nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
- {
- NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
- EnsureReadComplete();
- uint32_t initialCookieCount = mDBState->cookieCount;
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
- mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
- typedef nsTArray<nsListIter> PurgeList;
- PurgeList purgeList(kMaxNumberOfCookies);
- nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
- // Create a params array to batch the removals. This is OK here because
- // all the removals are in order, and there are no interleaved additions.
- mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
- if (mDBState->dbConn) {
- stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
- }
- int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
- int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
- int64_t oldestTime = INT64_MAX;
- for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
- nsCookieEntry* entry = iter.Get();
- const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
- auto length = cookies.Length();
- for (nsCookieEntry::IndexType i = 0; i < length; ) {
- nsListIter iter(entry, i);
- nsCookie* cookie = cookies[i];
- // check if the cookie has expired
- if (cookie->Expiry() <= currentTime) {
- removedList->AppendElement(cookie, false);
- COOKIE_LOGEVICTED(cookie, "Cookie expired");
- // remove from list; do not increment our iterator unless we're the last
- // in the list already.
- gCookieService->RemoveCookieFromList(iter, paramsArray);
- if (i == --length) {
- break;
- }
- } else {
- // check if the cookie is over the age limit
- if (cookie->LastAccessed() <= purgeTime) {
- purgeList.AppendElement(iter);
- } else if (cookie->LastAccessed() < oldestTime) {
- // reset our indicator
- oldestTime = cookie->LastAccessed();
- }
- ++i;
- }
- MOZ_ASSERT(length == cookies.Length());
- }
- }
- uint32_t postExpiryCookieCount = mDBState->cookieCount;
- // now we have a list of iterators for cookies over the age limit.
- // sort them by age, and then we'll see how many to remove...
- purgeList.Sort(CompareCookiesByAge());
- // only remove old cookies until we reach the max cookie limit, no more.
- uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
- mDBState->cookieCount - mMaxNumberOfCookies : 0;
- if (purgeList.Length() > excess) {
- // We're not purging everything in the list, so update our indicator.
- oldestTime = purgeList[excess].Cookie()->LastAccessed();
- purgeList.SetLength(excess);
- }
- // sort the list again, this time grouping cookies with a common entryclass
- // together, and with ascending index. this allows us to iterate backwards
- // over the list removing cookies, without having to adjust indexes as we go.
- purgeList.Sort(CompareCookiesByIndex());
- for (PurgeList::index_type i = purgeList.Length(); i--; ) {
- nsCookie *cookie = purgeList[i].Cookie();
- removedList->AppendElement(cookie, false);
- COOKIE_LOGEVICTED(cookie, "Cookie too old");
- RemoveCookieFromList(purgeList[i], paramsArray);
- }
- // Update the database if we have entries to purge.
- if (paramsArray) {
- uint32_t length;
- paramsArray->GetLength(&length);
- if (length) {
- DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- }
- // reset the oldest time indicator
- mDBState->cookieOldestTime = oldestTime;
- COOKIE_LOGSTRING(LogLevel::Debug,
- ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
- initialCookieCount - postExpiryCookieCount,
- postExpiryCookieCount - mDBState->cookieCount,
- mDBState->cookieCount,
- aCurrentTimeInUsec - mDBState->cookieOldestTime));
- return removedList.forget();
- }
- // find whether a given cookie has been previously set. this is provided by the
- // nsICookieManager2 interface.
- NS_IMETHODIMP
- nsCookieService::CookieExists(nsICookie2* aCookie,
- JS::HandleValue aOriginAttributes,
- JSContext* aCx,
- uint8_t aArgc,
- bool* aFoundCookie)
- {
- NS_ENSURE_ARG_POINTER(aCookie);
- NS_ENSURE_ARG_POINTER(aCx);
- NS_ENSURE_ARG_POINTER(aFoundCookie);
- MOZ_ASSERT(aArgc == 0 || aArgc == 1);
- NeckoOriginAttributes attrs;
- nsresult rv = InitializeOriginAttributes(&attrs,
- aOriginAttributes,
- aCx,
- aArgc,
- u"nsICookieManager2.cookieExists()",
- u"2");
- NS_ENSURE_SUCCESS(rv, rv);
- return CookieExistsNative(aCookie, &attrs, aFoundCookie);
- }
- NS_IMETHODIMP_(nsresult)
- nsCookieService::CookieExistsNative(nsICookie2* aCookie,
- NeckoOriginAttributes* aOriginAttributes,
- bool* aFoundCookie)
- {
- NS_ENSURE_ARG_POINTER(aCookie);
- NS_ENSURE_ARG_POINTER(aOriginAttributes);
- NS_ENSURE_ARG_POINTER(aFoundCookie);
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- nsAutoCString host, name, path;
- nsresult rv = aCookie->GetHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = aCookie->GetName(name);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = aCookie->GetPath(path);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- nsListIter iter;
- *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
- host, name, path, iter);
- return NS_OK;
- }
- // For a given base domain, find either an expired cookie or the oldest cookie
- // by lastAccessed time.
- int64_t
- nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
- int64_t aCurrentTime,
- nsIURI* aSource,
- mozilla::Maybe<bool> aIsSecure,
- nsListIter &aIter)
- {
- aIter.entry = nullptr;
- bool requireHostMatch = true;
- nsAutoCString baseDomain, sourceHost, sourcePath;
- if (aSource) {
- GetBaseDomain(aSource, baseDomain, requireHostMatch);
- aSource->GetAsciiHost(sourceHost);
- sourcePath = GetPathFromURI(aSource);
- }
- const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
- int64_t oldestNonMatchingSessionCookieTime = 0;
- nsListIter oldestNonMatchingSessionCookie;
- oldestNonMatchingSessionCookie.entry = nullptr;
- int64_t oldestSessionCookieTime = 0;
- nsListIter oldestSessionCookie;
- oldestSessionCookie.entry = nullptr;
- int64_t oldestNonMatchingNonSessionCookieTime = 0;
- nsListIter oldestNonMatchingNonSessionCookie;
- oldestNonMatchingNonSessionCookie.entry = nullptr;
- int64_t oldestCookieTime = 0;
- nsListIter oldestCookie;
- oldestCookie.entry = nullptr;
- int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- nsCookie *cookie = cookies[i];
- // If we found an expired cookie, we're done.
- if (cookie->Expiry() <= aCurrentTime) {
- aIter.entry = aEntry;
- aIter.index = i;
- return -1;
- }
- int64_t lastAccessed = cookie->LastAccessed();
- // Record the age of the oldest cookie that is stored for this host.
- // oldestCookieTime is the age of the oldest cookie with a matching
- // secure flag, which may be more recent than an older cookie with
- // a non-matching secure flag.
- if (actualOldestCookieTime > lastAccessed) {
- actualOldestCookieTime = lastAccessed;
- }
- if (aIsSecure.isSome() && !aIsSecure.value()) {
- // We want to look for the oldest non-secure cookie first time through,
- // then find the oldest secure cookie the second time we are called.
- if (cookie->IsSecure()) {
- continue;
- }
- }
- // Update our various records of oldest cookies fitting several restrictions:
- // * session cookies
- // * non-session cookies
- // * cookies with paths and domains that don't match the cookie triggering this purge
- // This cookie is a candidate for eviction if we have no information about
- // the source request, or if it is not a path or domain match against the
- // source request.
- bool isPrimaryEvictionCandidate = true;
- if (aSource) {
- isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
- }
- if (cookie->IsSession()) {
- if (!oldestSessionCookie.entry || oldestSessionCookieTime > lastAccessed) {
- oldestSessionCookieTime = lastAccessed;
- oldestSessionCookie.entry = aEntry;
- oldestSessionCookie.index = i;
- }
- if (isPrimaryEvictionCandidate &&
- (!oldestNonMatchingSessionCookie.entry ||
- oldestNonMatchingSessionCookieTime > lastAccessed)) {
- oldestNonMatchingSessionCookieTime = lastAccessed;
- oldestNonMatchingSessionCookie.entry = aEntry;
- oldestNonMatchingSessionCookie.index = i;
- }
- } else if (isPrimaryEvictionCandidate &&
- (!oldestNonMatchingNonSessionCookie.entry ||
- oldestNonMatchingNonSessionCookieTime > lastAccessed)) {
- oldestNonMatchingNonSessionCookieTime = lastAccessed;
- oldestNonMatchingNonSessionCookie.entry = aEntry;
- oldestNonMatchingNonSessionCookie.index = i;
- }
- // Check if we've found the oldest cookie so far.
- if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
- oldestCookieTime = lastAccessed;
- oldestCookie.entry = aEntry;
- oldestCookie.index = i;
- }
- }
- // Prefer to evict the oldest session cookies with a non-matching path/domain,
- // followed by the oldest session cookie with a matching path/domain,
- // followed by the oldest non-session cookie with a non-matching path/domain,
- // resorting to the oldest non-session cookie with a matching path/domain.
- if (oldestNonMatchingSessionCookie.entry) {
- aIter = oldestNonMatchingSessionCookie;
- } else if (oldestSessionCookie.entry) {
- aIter = oldestSessionCookie;
- } else if (oldestNonMatchingNonSessionCookie.entry) {
- aIter = oldestNonMatchingNonSessionCookie;
- } else {
- aIter = oldestCookie;
- }
- return actualOldestCookieTime;
- }
- // count the number of cookies stored by a particular host. this is provided by the
- // nsICookieManager2 interface.
- NS_IMETHODIMP
- nsCookieService::CountCookiesFromHost(const nsACString &aHost,
- uint32_t *aCountFromHost)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // first, normalize the hostname, and fail if it contains illegal characters.
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- nsCookieKey key = DEFAULT_APP_KEY(baseDomain);
- EnsureReadDomain(key);
- // Return a count of all cookies, including expired.
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
- *aCountFromHost = entry ? entry->GetCookies().Length() : 0;
- return NS_OK;
- }
- // get an enumerator of cookies stored by a particular host. this is provided by the
- // nsICookieManager2 interface.
- NS_IMETHODIMP
- nsCookieService::GetCookiesFromHost(const nsACString &aHost,
- JS::HandleValue aOriginAttributes,
- JSContext* aCx,
- uint8_t aArgc,
- nsISimpleEnumerator **aEnumerator)
- {
- MOZ_ASSERT(aArgc == 0 || aArgc == 1);
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // first, normalize the hostname, and fail if it contains illegal characters.
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- NeckoOriginAttributes attrs;
- rv = InitializeOriginAttributes(&attrs,
- aOriginAttributes,
- aCx,
- aArgc,
- u"nsICookieManager2.getCookiesFromHost()",
- u"2");
- NS_ENSURE_SUCCESS(rv, rv);
- nsCookieKey key = nsCookieKey(baseDomain, attrs);
- EnsureReadDomain(key);
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(key);
- if (!entry)
- return NS_NewEmptyEnumerator(aEnumerator);
- nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
- const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- cookieList.AppendObject(cookies[i]);
- }
- return NS_NewArrayEnumerator(aEnumerator, cookieList);
- }
- NS_IMETHODIMP
- nsCookieService::GetCookiesWithOriginAttributes(const nsAString& aPattern,
- const nsACString& aHost,
- nsISimpleEnumerator **aEnumerator)
- {
- mozilla::OriginAttributesPattern pattern;
- if (!pattern.Init(aPattern)) {
- return NS_ERROR_INVALID_ARG;
- }
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator);
- }
- nsresult
- nsCookieService::GetCookiesWithOriginAttributes(
- const mozilla::OriginAttributesPattern& aPattern,
- const nsCString& aBaseDomain,
- nsISimpleEnumerator **aEnumerator)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already closed?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- if (aPattern.mAppId.WasPassed() && aPattern.mAppId.Value() == NECKO_UNKNOWN_APP_ID) {
- return NS_ERROR_INVALID_ARG;
- }
- nsCOMArray<nsICookie> cookies;
- for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
- nsCookieEntry* entry = iter.Get();
- if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
- continue;
- }
- if (!aPattern.Matches(entry->mOriginAttributes)) {
- continue;
- }
- const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
- cookies.AppendObject(entryCookies[i]);
- }
- }
- return NS_NewArrayEnumerator(aEnumerator, cookies);
- }
- NS_IMETHODIMP
- nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern,
- const nsACString& aHost)
- {
- MOZ_ASSERT(XRE_IsParentProcess());
- mozilla::OriginAttributesPattern pattern;
- if (!pattern.Init(aPattern)) {
- return NS_ERROR_INVALID_ARG;
- }
- nsAutoCString host(aHost);
- nsresult rv = NormalizeHost(host);
- NS_ENSURE_SUCCESS(rv, rv);
- nsAutoCString baseDomain;
- rv = GetBaseDomainFromHost(host, baseDomain);
- NS_ENSURE_SUCCESS(rv, rv);
- return RemoveCookiesWithOriginAttributes(pattern, baseDomain);
- }
- nsresult
- nsCookieService::RemoveCookiesWithOriginAttributes(
- const mozilla::OriginAttributesPattern& aPattern,
- const nsCString& aBaseDomain)
- {
- if (!mDBState) {
- NS_WARNING("No DBState! Profile already close?");
- return NS_ERROR_NOT_AVAILABLE;
- }
- // Iterate the hash table of nsCookieEntry.
- for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
- nsCookieEntry* entry = iter.Get();
- if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) {
- continue;
- }
- if (!aPattern.Matches(entry->mOriginAttributes)) {
- continue;
- }
- // Pattern matches. Delete all cookies within this nsCookieEntry.
- uint32_t cookiesCount = entry->GetCookies().Length();
- for (nsCookieEntry::IndexType i = 0 ; i < cookiesCount; ++i) {
- // Remove the first cookie from the list.
- nsListIter iter(entry, 0);
- RefPtr<nsCookie> cookie = iter.Cookie();
- // Remove the cookie.
- RemoveCookieFromList(iter);
- if (cookie) {
- NotifyChanged(cookie, u"deleted");
- }
- }
- }
- return NS_OK;
- }
- // find an secure cookie specified by host and name
- bool
- nsCookieService::FindSecureCookie(const nsCookieKey &aKey,
- nsCookie *aCookie)
- {
- EnsureReadDomain(aKey);
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
- if (!entry)
- return false;
- const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- nsCookie *cookie = cookies[i];
- // isn't a match if insecure or a different name
- if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name()))
- continue;
- // The host must "domain-match" an existing cookie or vice-versa
- if (DomainMatches(cookie, aCookie->Host()) ||
- DomainMatches(aCookie, cookie->Host())) {
- // If the path of new cookie and the path of existing cookie
- // aren't "/", then this situation needs to compare paths to
- // ensure only that a newly-created non-secure cookie does not
- // overlay an existing secure cookie.
- if (PathMatches(cookie, aCookie->Path())) {
- return true;
- }
- }
- }
- return false;
- }
- // find an exact cookie specified by host, name, and path that hasn't expired.
- bool
- nsCookieService::FindCookie(const nsCookieKey &aKey,
- const nsAFlatCString &aHost,
- const nsAFlatCString &aName,
- const nsAFlatCString &aPath,
- nsListIter &aIter)
- {
- EnsureReadDomain(aKey);
- nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
- if (!entry)
- return false;
- const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
- for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
- nsCookie *cookie = cookies[i];
- if (aHost.Equals(cookie->Host()) &&
- aPath.Equals(cookie->Path()) &&
- aName.Equals(cookie->Name())) {
- aIter = nsListIter(entry, i);
- return true;
- }
- }
- return false;
- }
- // remove a cookie from the hashtable, and update the iterator state.
- void
- nsCookieService::RemoveCookieFromList(const nsListIter &aIter,
- mozIStorageBindingParamsArray *aParamsArray)
- {
- // if it's a non-session cookie, remove it from the db
- if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
- // Use the asynchronous binding methods to ensure that we do not acquire
- // the database lock.
- mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
- if (!paramsArray) {
- stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
- }
- nsCOMPtr<mozIStorageBindingParams> params;
- paramsArray->NewBindingParams(getter_AddRefs(params));
- DebugOnly<nsresult> rv =
- params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
- aIter.Cookie()->Name());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
- aIter.Cookie()->Host());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
- aIter.Cookie()->Path());
- NS_ASSERT_SUCCESS(rv);
- rv = paramsArray->AddParams(params);
- NS_ASSERT_SUCCESS(rv);
- // If we weren't given a params array, we'll need to remove it ourselves.
- if (!aParamsArray) {
- rv = stmt->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- }
- if (aIter.entry->GetCookies().Length() == 1) {
- // we're removing the last element in the array - so just remove the entry
- // from the hash. note that the entryclass' dtor will take care of
- // releasing this last element for us!
- mDBState->hostTable.RawRemoveEntry(aIter.entry);
- } else {
- // just remove the element from the list
- aIter.entry->GetCookies().RemoveElementAt(aIter.index);
- }
- --mDBState->cookieCount;
- }
- void
- bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
- const nsCookieKey &aKey,
- const nsCookie *aCookie)
- {
- NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
- NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
- // Use the asynchronous binding methods to ensure that we do not acquire the
- // database lock.
- nsCOMPtr<mozIStorageBindingParams> params;
- DebugOnly<nsresult> rv =
- aParamsArray->NewBindingParams(getter_AddRefs(params));
- NS_ASSERT_SUCCESS(rv);
- // Bind our values to params
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"),
- aKey.mBaseDomain);
- NS_ASSERT_SUCCESS(rv);
- nsAutoCString suffix;
- aKey.mOriginAttributes.CreateSuffix(suffix);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
- suffix);
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
- aCookie->Name());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"),
- aCookie->Value());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
- aCookie->Host());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
- aCookie->Path());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"),
- aCookie->Expiry());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
- aCookie->LastAccessed());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"),
- aCookie->CreationTime());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
- aCookie->IsSecure());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
- aCookie->IsHttpOnly());
- NS_ASSERT_SUCCESS(rv);
- // Bind the params to the array.
- rv = aParamsArray->AddParams(params);
- NS_ASSERT_SUCCESS(rv);
- }
- void
- nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
- nsCookie* aCookie)
- {
- if (aCookie->LastAccessed() < aDBState->cookieOldestTime) {
- aDBState->cookieOldestTime = aCookie->LastAccessed();
- }
- }
- void
- nsCookieService::AddCookieToList(const nsCookieKey &aKey,
- nsCookie *aCookie,
- DBState *aDBState,
- mozIStorageBindingParamsArray *aParamsArray,
- bool aWriteToDB)
- {
- NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray),
- "Not writing to the DB but have a params array?");
- NS_ASSERTION(!(!aDBState->dbConn && aParamsArray),
- "Do not have a DB connection but have a params array?");
- nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey);
- NS_ASSERTION(entry, "can't insert element into a null entry!");
- entry->GetCookies().AppendElement(aCookie);
- ++aDBState->cookieCount;
- // keep track of the oldest cookie, for when it comes time to purge
- UpdateCookieOldestTime(aDBState, aCookie);
- // if it's a non-session cookie and hasn't just been read from the db, write it out.
- if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) {
- mozIStorageAsyncStatement *stmt = aDBState->stmtInsert;
- nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
- if (!paramsArray) {
- stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
- }
- bindCookieParameters(paramsArray, aKey, aCookie);
- // If we were supplied an array to store parameters, we shouldn't call
- // executeAsync - someone up the stack will do this for us.
- if (!aParamsArray) {
- DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
- NS_ASSERT_SUCCESS(rv);
- nsCOMPtr<mozIStoragePendingStatement> handle;
- rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle));
- NS_ASSERT_SUCCESS(rv);
- }
- }
- }
- void
- nsCookieService::UpdateCookieInList(nsCookie *aCookie,
- int64_t aLastAccessed,
- mozIStorageBindingParamsArray *aParamsArray)
- {
- NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
- // udpate the lastAccessed timestamp
- aCookie->SetLastAccessed(aLastAccessed);
- // if it's a non-session cookie, update it in the db too
- if (!aCookie->IsSession() && aParamsArray) {
- // Create our params holder.
- nsCOMPtr<mozIStorageBindingParams> params;
- aParamsArray->NewBindingParams(getter_AddRefs(params));
- // Bind our parameters.
- DebugOnly<nsresult> rv =
- params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"),
- aLastAccessed);
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
- aCookie->Name());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"),
- aCookie->Host());
- NS_ASSERT_SUCCESS(rv);
- rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"),
- aCookie->Path());
- NS_ASSERT_SUCCESS(rv);
- // Add our bound parameters to the array.
- rv = aParamsArray->AddParams(params);
- NS_ASSERT_SUCCESS(rv);
- }
- }
- size_t
- nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
- {
- size_t n = aMallocSizeOf(this);
- if (mDefaultDBState) {
- n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf);
- }
- if (mPrivateDBState) {
- n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf);
- }
- return n;
- }
- MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf)
- NS_IMETHODIMP
- nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport,
- nsISupports* aData, bool aAnonymize)
- {
- MOZ_COLLECT_REPORT(
- "explicit/cookie-service", KIND_HEAP, UNITS_BYTES,
- SizeOfIncludingThis(CookieServiceMallocSizeOf),
- "Memory used by the cookie service.");
- return NS_OK;
- }
|