tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }