CookieStoreSubscriptionService.cpp (13257B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "CookieStoreSubscriptionService.h" 8 9 #include "json/json.h" 10 #include "mozilla/ClearOnShutdown.h" 11 #include "mozilla/dom/PCookieStore.h" 12 #include "mozilla/dom/ServiceWorkerManager.h" 13 #include "mozilla/dom/ServiceWorkerRegistrar.h" 14 #include "mozilla/net/Cookie.h" 15 #include "mozilla/net/CookieCommons.h" 16 #include "nsAppDirectoryServiceDefs.h" 17 #include "nsICookieNotification.h" 18 19 using namespace mozilla::dom; 20 using namespace mozilla::net; 21 using mozilla::ipc::PrincipalInfo; 22 23 static mozilla::StaticRefPtr<CookieStoreSubscriptionService> gService; 24 25 NS_IMPL_ISUPPORTS(CookieStoreSubscriptionService, nsIObserver) 26 27 // static 28 void CookieStoreSubscriptionService::ServiceWorkerLoaded( 29 const ServiceWorkerRegistrationData& aData, const nsACString& aValue) { 30 MOZ_ASSERT(NS_IsMainThread()); 31 MOZ_ASSERT(XRE_IsParentProcess()); 32 33 CookieStoreSubscriptionService* service = 34 CookieStoreSubscriptionService::Instance(); 35 service->Load(aData, aValue); 36 } 37 38 // static 39 void CookieStoreSubscriptionService::ServiceWorkerUpdated( 40 const ServiceWorkerRegistrationData& aData) { 41 MOZ_ASSERT(NS_IsMainThread()); 42 MOZ_ASSERT(XRE_IsParentProcess()); 43 44 // This is a no-op 45 } 46 47 // static 48 void CookieStoreSubscriptionService::ServiceWorkerUnregistered( 49 const ServiceWorkerRegistrationData& aData) { 50 MOZ_ASSERT(NS_IsMainThread()); 51 MOZ_ASSERT(XRE_IsParentProcess()); 52 53 CookieStoreSubscriptionService* service = 54 CookieStoreSubscriptionService::Instance(); 55 service->Unregister(aData); 56 } 57 // static 58 void CookieStoreSubscriptionService::ServiceWorkerUnregistered( 59 nsIPrincipal* aPrincipal, const nsACString& aScopeURL) { 60 MOZ_ASSERT(NS_IsMainThread()); 61 MOZ_ASSERT(XRE_IsParentProcess()); 62 63 PrincipalInfo principalInfo; 64 nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); 65 if (NS_WARN_IF(NS_FAILED(rv))) { 66 return; 67 } 68 69 ServiceWorkerRegistrationData tmp; 70 tmp.principal() = principalInfo; 71 tmp.scope() = aScopeURL; 72 73 CookieStoreSubscriptionService* service = 74 CookieStoreSubscriptionService::Instance(); 75 service->Unregister(tmp); 76 } 77 78 // static 79 CookieStoreSubscriptionService* CookieStoreSubscriptionService::Instance() { 80 MOZ_ASSERT(NS_IsMainThread()); 81 MOZ_ASSERT(XRE_IsParentProcess()); 82 83 if (!gService && !PastShutdownPhase(ShutdownPhase::XPCOMShutdownFinal)) { 84 gService = new CookieStoreSubscriptionService(); 85 gService->Initialize(); 86 ClearOnShutdown(&gService, ShutdownPhase::XPCOMShutdownFinal); 87 } 88 89 return gService; 90 } 91 92 CookieStoreSubscriptionService::CookieStoreSubscriptionService() { 93 MOZ_ASSERT(NS_IsMainThread()); 94 MOZ_ASSERT(XRE_IsParentProcess()); 95 } 96 97 void CookieStoreSubscriptionService::Initialize() { 98 MOZ_ASSERT(NS_IsMainThread()); 99 MOZ_ASSERT(XRE_IsParentProcess()); 100 101 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 102 if (obs) { 103 DebugOnly<nsresult> rv = 104 obs->AddObserver(gService, "private-cookie-changed", false); 105 MOZ_ASSERT(NS_SUCCEEDED(rv)); 106 107 rv = obs->AddObserver(gService, "cookie-changed", false); 108 MOZ_ASSERT(NS_SUCCEEDED(rv)); 109 } 110 } 111 112 namespace { 113 114 bool Equivalent(const ServiceWorkerRegistrationData& aLeft, 115 const ServiceWorkerRegistrationData& aRight) { 116 MOZ_ASSERT(aLeft.principal().type() == PrincipalInfo::TContentPrincipalInfo); 117 MOZ_ASSERT(aRight.principal().type() == PrincipalInfo::TContentPrincipalInfo); 118 119 const auto& leftPrincipal = aLeft.principal().get_ContentPrincipalInfo(); 120 const auto& rightPrincipal = aRight.principal().get_ContentPrincipalInfo(); 121 122 // Only compare the attributes, not the spec part of the principal. 123 // The scope comparison above already covers the origin and codebase 124 // principals include the full path in their spec which is not what 125 // we want here. 126 return aLeft.scope() == aRight.scope() && 127 leftPrincipal.attrs() == rightPrincipal.attrs(); 128 } 129 130 } // anonymous namespace 131 132 void CookieStoreSubscriptionService::GetSubscriptions( 133 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope, 134 nsTArray<CookieSubscription>& aSubscriptions) { 135 MOZ_ASSERT(NS_IsMainThread()); 136 137 ServiceWorkerRegistrationData tmp; 138 tmp.principal() = aPrincipalInfo; 139 tmp.scope() = aScope; 140 141 for (const RegistrationData& data : mData) { 142 if (Equivalent(tmp, data.mRegistration)) { 143 aSubscriptions.AppendElements(data.mSubscriptions); 144 break; 145 } 146 } 147 } 148 149 void CookieStoreSubscriptionService::Subscribe( 150 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope, 151 const nsTArray<CookieSubscription>& aSubscriptions) { 152 MOZ_ASSERT(NS_IsMainThread()); 153 154 ServiceWorkerRegistrationData tmp; 155 tmp.principal() = aPrincipalInfo; 156 tmp.scope() = aScope; 157 158 RegistrationData* registrationData = nullptr; 159 160 for (RegistrationData& data : mData) { 161 if (Equivalent(tmp, data.mRegistration)) { 162 registrationData = &data; 163 break; 164 } 165 } 166 167 if (!registrationData) { 168 registrationData = mData.AppendElement(); 169 registrationData->mRegistration = tmp; 170 } 171 172 bool toStore = false; 173 174 for (const CookieSubscription& subscription : aSubscriptions) { 175 bool found = false; 176 for (const CookieSubscription& existingSubscription : 177 registrationData->mSubscriptions) { 178 if (existingSubscription.name() == subscription.name() && 179 existingSubscription.url() == subscription.url()) { 180 // Nothing to do. 181 found = true; 182 break; 183 } 184 } 185 186 if (!found) { 187 registrationData->mSubscriptions.AppendElement(subscription); 188 toStore = true; 189 } 190 } 191 192 if (toStore) { 193 SerializeAndSave(*registrationData); 194 } 195 } 196 197 void CookieStoreSubscriptionService::Unsubscribe( 198 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope, 199 const nsTArray<CookieSubscription>& aSubscriptions) { 200 MOZ_ASSERT(NS_IsMainThread()); 201 202 ServiceWorkerRegistrationData tmp; 203 tmp.principal() = aPrincipalInfo; 204 tmp.scope() = aScope; 205 206 RegistrationData* registrationData = nullptr; 207 uint32_t registrationDataId = 0; 208 209 for (; registrationDataId < mData.Length(); ++registrationDataId) { 210 RegistrationData& data = mData[registrationDataId]; 211 if (Equivalent(tmp, data.mRegistration)) { 212 registrationData = &data; 213 break; 214 } 215 } 216 217 if (!registrationData) { 218 return; 219 } 220 221 bool toStore = false; 222 223 for (const CookieSubscription& subscription : aSubscriptions) { 224 for (uint32_t i = 0; i < registrationData->mSubscriptions.Length(); ++i) { 225 const CookieSubscription& existingSubscription = 226 registrationData->mSubscriptions[i]; 227 if (existingSubscription.name() == subscription.name() && 228 existingSubscription.url() == subscription.url()) { 229 registrationData->mSubscriptions.RemoveElementAt(i); 230 toStore = true; 231 break; 232 } 233 } 234 } 235 236 if (toStore) { 237 SerializeAndSave(*registrationData); 238 239 if (registrationData->mSubscriptions.IsEmpty()) { 240 mData.RemoveElementAt(registrationDataId); 241 } 242 } 243 } 244 245 NS_IMETHODIMP 246 CookieStoreSubscriptionService::Observe(nsISupports* aSubject, 247 const char* aTopic, 248 const char16_t* aData) { 249 MOZ_ASSERT(NS_IsMainThread()); 250 251 nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject); 252 NS_ENSURE_TRUE(notification, NS_ERROR_FAILURE); 253 254 auto action = notification->GetAction(); 255 if (action != nsICookieNotification::COOKIE_DELETED && 256 action != nsICookieNotification::COOKIE_ADDED && 257 action != nsICookieNotification::COOKIE_CHANGED) { 258 // Other actions are user specific ones (ALL_COOKIES_CLEARED or 259 // COOKIES_BATCH_DELETED) and we don't want to expose them here. 260 return NS_OK; 261 } 262 263 nsAutoCString baseDomain; 264 nsresult rv = notification->GetBaseDomain(baseDomain); 265 if (NS_WARN_IF(NS_FAILED(rv)) || baseDomain.IsEmpty()) { 266 return rv; 267 } 268 269 nsCOMPtr<nsICookie> cookie; 270 rv = notification->GetCookie(getter_AddRefs(cookie)); 271 if (NS_WARN_IF(NS_FAILED(rv))) { 272 return rv; 273 } 274 275 bool isHttpOnly; 276 rv = cookie->GetIsHttpOnly(&isHttpOnly); 277 if (NS_WARN_IF(NS_FAILED(rv))) { 278 return rv; 279 } 280 281 if (isHttpOnly) { 282 return NS_OK; 283 } 284 285 nsAutoCString nameUtf8; 286 rv = cookie->GetName(nameUtf8); 287 if (NS_WARN_IF(NS_FAILED(rv))) { 288 return rv; 289 } 290 291 NS_ConvertUTF8toUTF16 name(nameUtf8); 292 293 bool deleteEvent = action == nsICookieNotification::COOKIE_DELETED; 294 295 nsAutoString value; 296 if (!deleteEvent) { 297 nsAutoCString valueUtf8; 298 rv = cookie->GetValue(valueUtf8); 299 if (NS_WARN_IF(NS_FAILED(rv))) { 300 return rv; 301 } 302 303 CopyUTF8toUTF16(valueUtf8, value); 304 } 305 306 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 307 if (!swm) { 308 return NS_ERROR_FAILURE; 309 } 310 311 for (const RegistrationData& data : mData) { 312 MOZ_ASSERT(data.mRegistration.principal().type() == 313 PrincipalInfo::TContentPrincipalInfo); 314 315 const auto& principalInfo = 316 data.mRegistration.principal().get_ContentPrincipalInfo(); 317 318 if (principalInfo.baseDomain() != baseDomain) { 319 continue; 320 } 321 322 if (cookie->OriginAttributesNative() != principalInfo.attrs()) { 323 continue; 324 } 325 326 for (const CookieSubscription& subscription : data.mSubscriptions) { 327 if (subscription.name().isSome() && subscription.name().value() != name) { 328 continue; 329 } 330 331 nsCOMPtr<nsIURI> uri; 332 rv = NS_NewURI(getter_AddRefs(uri), data.mRegistration.scope()); 333 if (NS_WARN_IF(NS_FAILED(rv))) { 334 return rv; 335 } 336 337 nsAutoCString filePath; 338 rv = uri->GetFilePath(filePath); 339 if (NS_WARN_IF(NS_FAILED(rv))) { 340 return rv; 341 } 342 343 if (!CookieCommons::PathMatches(cookie->AsCookie().Path(), filePath)) { 344 continue; 345 } 346 347 rv = swm->SendCookieChangeEvent(principalInfo.attrs(), 348 data.mRegistration.scope(), 349 cookie->AsCookie().ToIPC(), deleteEvent); 350 if (NS_WARN_IF(NS_FAILED(rv))) { 351 return rv; 352 } 353 354 break; 355 } 356 } 357 358 return NS_OK; 359 } 360 361 void CookieStoreSubscriptionService::Load( 362 const ServiceWorkerRegistrationData& aData, const nsACString& aValue) { 363 MOZ_ASSERT(NS_IsMainThread()); 364 365 for (RegistrationData& data : mData) { 366 if (Equivalent(aData, data.mRegistration)) { 367 ParseAndAddSubscription(data, aValue); 368 return; 369 } 370 } 371 372 RegistrationData* data = mData.AppendElement(); 373 data->mRegistration = aData; 374 ParseAndAddSubscription(*data, aValue); 375 } 376 377 void CookieStoreSubscriptionService::Unregister( 378 const ServiceWorkerRegistrationData& aData) { 379 MOZ_ASSERT(NS_IsMainThread()); 380 381 for (uint32_t i = 0; i < mData.Length(); ++i) { 382 if (Equivalent(aData, mData[i].mRegistration)) { 383 mData.RemoveElementAt(i); 384 return; 385 } 386 } 387 } 388 389 void CookieStoreSubscriptionService::ParseAndAddSubscription( 390 RegistrationData& aData, const nsACString& aValue) { 391 MOZ_ASSERT(NS_IsMainThread()); 392 393 Json::Value value; 394 Json::Reader jsonReader; 395 396 MOZ_ASSERT(jsonReader.parse(aValue.BeginReading(), value, false)); 397 MOZ_ASSERT(value.isObject()); 398 399 for (Json::ValueConstIterator iter = value.begin(); iter != value.end(); 400 ++iter) { 401 CookieSubscription* subscription = aData.mSubscriptions.AppendElement(); 402 403 for (Json::Value::const_iterator itr = iter->begin(); itr != iter->end(); 404 itr++) { 405 MOZ_ASSERT(iter.key().isString()); 406 MOZ_ASSERT(iter->isString()); 407 if (itr.key().asString().compare("name") == 0) { 408 subscription->name() = 409 Some(NS_ConvertUTF8toUTF16(iter->asString().c_str())); 410 } else if (itr.key().asString().compare("url") == 0) { 411 subscription->url() = NS_ConvertUTF8toUTF16(iter->asString().c_str()); 412 } 413 } 414 } 415 } 416 417 void CookieStoreSubscriptionService::SerializeAndSave( 418 const RegistrationData& aData) { 419 MOZ_ASSERT(NS_IsMainThread()); 420 421 RefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get(); 422 MOZ_ASSERT(swr); 423 424 if (aData.mSubscriptions.IsEmpty()) { 425 swr->UnstoreServiceWorkerExpandoOnMainThread( 426 aData.mRegistration.principal(), aData.mRegistration.scope(), 427 nsCString("cookie-store")); 428 return; 429 } 430 431 Json::Value root; 432 Json::StreamWriterBuilder builder; 433 builder["indentation"] = ""; 434 const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); 435 436 for (uint32_t i = 0; i < aData.mSubscriptions.Length(); ++i) { 437 Json::Value entry; 438 439 if (aData.mSubscriptions[i].name().isSome()) { 440 entry["name"] = 441 NS_ConvertUTF16toUTF8(aData.mSubscriptions[i].name().value()).get(); 442 } 443 444 entry["url"] = NS_ConvertUTF16toUTF8(aData.mSubscriptions[i].url()).get(); 445 root[i] = entry; 446 } 447 448 std::string document = Json::writeString(builder, root); 449 450 swr->StoreServiceWorkerExpandoOnMainThread( 451 aData.mRegistration.principal(), aData.mRegistration.scope(), 452 nsCString("cookie-store"), nsCString(document)); 453 }