tor-browser

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

StorageObserver.cpp (15288B)


      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 "StorageObserver.h"
      8 
      9 #include "LocalStorageCache.h"
     10 #include "StorageCommon.h"
     11 #include "StorageDBThread.h"
     12 #include "StorageIPC.h"
     13 #include "StorageUtils.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/Preferences.h"
     16 #include "mozilla/Services.h"
     17 #include "mozilla/SpinEventLoopUntil.h"
     18 #include "mozilla/dom/LocalStorageCommon.h"
     19 #include "nsCOMPtr.h"
     20 #include "nsEscape.h"
     21 #include "nsIClearBySiteEntry.h"
     22 #include "nsICookieNotification.h"
     23 #include "nsICookiePermission.h"
     24 #include "nsIObserverService.h"
     25 #include "nsIPermission.h"
     26 #include "nsIURI.h"
     27 #include "nsNetCID.h"
     28 #include "nsNetUtil.h"
     29 #include "nsPrintfCString.h"
     30 #include "nsServiceManagerUtils.h"
     31 #include "nsXULAppAPI.h"
     32 
     33 namespace mozilla::dom {
     34 
     35 using namespace StorageUtils;
     36 
     37 static const char kStartupTopic[] = "sessionstore-windows-restored";
     38 static const uint32_t kStartupDelay = 0;
     39 
     40 const char kTestingPref[] = "dom.storage.testing";
     41 
     42 constexpr auto kPrivateBrowsingPattern = u"{ \"privateBrowsingId\": 1 }"_ns;
     43 
     44 NS_IMPL_ISUPPORTS(StorageObserver, nsIObserver, nsINamed,
     45                  nsISupportsWeakReference)
     46 
     47 StorageObserver* StorageObserver::sSelf = nullptr;
     48 
     49 // static
     50 nsresult StorageObserver::Init() {
     51  static_assert(kPrivateBrowsingIdCount * sizeof(mBackgroundThread[0]) ==
     52                sizeof(mBackgroundThread));
     53  if (sSelf) {
     54    return NS_OK;
     55  }
     56 
     57  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     58  if (!obs) {
     59    return NS_ERROR_UNEXPECTED;
     60  }
     61 
     62  sSelf = new StorageObserver();
     63  NS_ADDREF(sSelf);
     64 
     65  // Chrome clear operations.
     66  obs->AddObserver(sSelf, kStartupTopic, true);
     67  obs->AddObserver(sSelf, "cookie-changed", true);
     68  obs->AddObserver(sSelf, "perm-changed", true);
     69  obs->AddObserver(sSelf, "last-pb-context-exited", true);
     70  obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
     71  obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true);
     72  obs->AddObserver(sSelf, "extension:purge-localStorage", true);
     73  obs->AddObserver(sSelf, "browser:purge-sessionStorage", true);
     74  obs->AddObserver(sSelf, "extension:purge-sessionStorage", true);
     75 
     76  // Shutdown
     77  obs->AddObserver(sSelf, "profile-after-change", true);
     78  if (XRE_IsParentProcess()) {
     79    obs->AddObserver(sSelf, "profile-before-change", true);
     80  }
     81 
     82  // Testing
     83 #ifdef DOM_STORAGE_TESTS
     84  Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
     85 #endif
     86 
     87  return NS_OK;
     88 }
     89 
     90 // static
     91 nsresult StorageObserver::Shutdown() {
     92  AssertIsOnMainThread();
     93 
     94  if (!sSelf) {
     95    return NS_ERROR_NOT_INITIALIZED;  // Is this always an error?
     96  }
     97 
     98  sSelf->mSinks.Clear();
     99 
    100  NS_RELEASE(sSelf);
    101  return NS_OK;
    102 }
    103 
    104 // static
    105 void StorageObserver::TestingPrefChanged(const char* aPrefName,
    106                                         void* aClosure) {
    107  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    108  if (!obs || !sSelf) {
    109    return;
    110  }
    111 
    112  if (Preferences::GetBool(kTestingPref)) {
    113    obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
    114    if (XRE_IsParentProcess()) {
    115      // Only to forward to child process.
    116      obs->AddObserver(sSelf, "domstorage-test-flushed", true);
    117    }
    118    obs->AddObserver(sSelf, "domstorage-test-reload", true);
    119  } else {
    120    obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
    121    if (XRE_IsParentProcess()) {
    122      // Only to forward to child process.
    123      obs->RemoveObserver(sSelf, "domstorage-test-flushed");
    124    }
    125    obs->RemoveObserver(sSelf, "domstorage-test-reload");
    126  }
    127 }
    128 
    129 void StorageObserver::AddSink(StorageObserverSink* aObs) {
    130  AssertIsOnMainThread();
    131 
    132  MOZ_ASSERT(sSelf);
    133 
    134  mSinks.AppendElement(aObs);
    135 }
    136 
    137 void StorageObserver::RemoveSink(StorageObserverSink* aObs) {
    138  AssertIsOnMainThread();
    139 
    140  MOZ_ASSERT(sSelf);
    141 
    142  mSinks.RemoveElement(aObs);
    143 }
    144 
    145 void StorageObserver::Notify(const char* aTopic,
    146                             const nsAString& aOriginAttributesPattern,
    147                             const nsACString& aOriginScope) {
    148  AssertIsOnMainThread();
    149 
    150  MOZ_ASSERT(sSelf);
    151 
    152  for (auto sink : mSinks.ForwardRange()) {
    153    sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
    154  }
    155 }
    156 
    157 void StorageObserver::NoteBackgroundThread(const uint32_t aPrivateBrowsingId,
    158                                           nsIEventTarget* aBackgroundThread) {
    159  MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
    160 
    161  mBackgroundThread[aPrivateBrowsingId] = aBackgroundThread;
    162 }
    163 
    164 nsresult StorageObserver::GetOriginScope(const char16_t* aData,
    165                                         nsACString& aOriginScope) {
    166  nsresult rv;
    167 
    168  NS_ConvertUTF16toUTF8 domain(aData);
    169 
    170  nsAutoCString convertedDomain;
    171  rv = NS_DomainToASCIIAllowAnyGlyphfulASCII(domain, convertedDomain);
    172  if (NS_WARN_IF(NS_FAILED(rv))) {
    173    return rv;
    174  }
    175 
    176  nsCString originScope;
    177  rv = CreateReversedDomain(convertedDomain, originScope);
    178  if (NS_WARN_IF(NS_FAILED(rv))) {
    179    return rv;
    180  }
    181 
    182  aOriginScope = originScope;
    183  return NS_OK;
    184 }
    185 
    186 NS_IMETHODIMP
    187 StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
    188                         const char16_t* aData) {
    189  if (NS_WARN_IF(!sSelf)) {  // Shutdown took place
    190    return NS_OK;
    191  }
    192 
    193  nsresult rv;
    194 
    195  // Start the thread that opens the database.
    196  if (!strcmp(aTopic, kStartupTopic)) {
    197    MOZ_ASSERT(XRE_IsParentProcess());
    198 
    199    if (NextGenLocalStorageEnabled()) {
    200      return NS_OK;
    201    }
    202 
    203    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    204    obs->RemoveObserver(this, kStartupTopic);
    205 
    206    return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer),
    207                                   this, nsITimer::TYPE_ONE_SHOT,
    208                                   kStartupDelay);
    209  }
    210 
    211  // Timer callback used to start the database a short timer after startup
    212  if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
    213    MOZ_ASSERT(XRE_IsParentProcess());
    214    MOZ_ASSERT(!NextGenLocalStorageEnabled());
    215 
    216    nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
    217    if (!timer) {
    218      return NS_ERROR_UNEXPECTED;
    219    }
    220 
    221    if (timer == mDBThreadStartDelayTimer) {
    222      mDBThreadStartDelayTimer = nullptr;
    223 
    224      for (const uint32_t id : {0, 1}) {
    225        StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    226        if (NS_WARN_IF(!storageChild)) {
    227          return NS_ERROR_FAILURE;
    228        }
    229 
    230        storageChild->SendStartup();
    231      }
    232    }
    233 
    234    return NS_OK;
    235  }
    236 
    237  // Clear everything, caches + database
    238  if (!strcmp(aTopic, "cookie-changed")) {
    239    nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject);
    240    NS_ENSURE_TRUE(notification, NS_ERROR_FAILURE);
    241 
    242    if (notification->GetAction() !=
    243        nsICookieNotification::ALL_COOKIES_CLEARED) {
    244      return NS_OK;
    245    }
    246 
    247    if (!NextGenLocalStorageEnabled()) {
    248      for (const uint32_t id : {0, 1}) {
    249        StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    250        if (NS_WARN_IF(!storageChild)) {
    251          return NS_ERROR_FAILURE;
    252        }
    253 
    254        storageChild->AsyncClearAll();
    255 
    256        if (XRE_IsParentProcess()) {
    257          storageChild->SendClearAll();
    258        }
    259      }
    260    }
    261 
    262    Notify("cookie-cleared");
    263 
    264    return NS_OK;
    265  }
    266 
    267  // Clear from caches everything that has been stored
    268  // while in session-only mode
    269  if (!strcmp(aTopic, "perm-changed")) {
    270    // Check for cookie permission change
    271    nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
    272    if (!perm) {
    273      return NS_OK;
    274    }
    275 
    276    nsAutoCString type;
    277    perm->GetType(type);
    278    if (type != "cookie"_ns) {
    279      return NS_OK;
    280    }
    281 
    282    uint32_t cap = 0;
    283    perm->GetCapability(&cap);
    284    if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
    285        !u"deleted"_ns.Equals(nsDependentString(aData))) {
    286      return NS_OK;
    287    }
    288 
    289    nsCOMPtr<nsIPrincipal> principal;
    290    perm->GetPrincipal(getter_AddRefs(principal));
    291    if (!principal) {
    292      return NS_OK;
    293    }
    294 
    295    nsAutoCString originSuffix;
    296    BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(
    297        originSuffix);
    298 
    299    nsAutoCString host;
    300    principal->GetHost(host);
    301    if (host.IsEmpty()) {
    302      return NS_OK;
    303    }
    304 
    305    nsAutoCString originScope;
    306    rv = CreateReversedDomain(host, originScope);
    307    NS_ENSURE_SUCCESS(rv, rv);
    308 
    309    Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
    310           originScope);
    311 
    312    return NS_OK;
    313  }
    314 
    315  if (!strcmp(aTopic, "extension:purge-localStorage")) {
    316    if (NextGenLocalStorageEnabled()) {
    317      return NS_OK;
    318    }
    319 
    320    const char topic[] = "extension:purge-localStorage-caches";
    321 
    322    if (aData) {
    323      nsCString originScope;
    324 
    325      rv = GetOriginScope(aData, originScope);
    326      if (NS_WARN_IF(NS_FAILED(rv))) {
    327        return rv;
    328      }
    329 
    330      if (XRE_IsParentProcess()) {
    331        for (const uint32_t id : {0, 1}) {
    332          StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    333          if (NS_WARN_IF(!storageChild)) {
    334            return NS_ERROR_FAILURE;
    335          }
    336 
    337          storageChild->SendClearMatchingOrigin(originScope);
    338        }
    339      }
    340 
    341      Notify(topic, u""_ns, originScope);
    342    } else {
    343      for (const uint32_t id : {0, 1}) {
    344        StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    345        if (NS_WARN_IF(!storageChild)) {
    346          return NS_ERROR_FAILURE;
    347        }
    348 
    349        storageChild->AsyncClearAll();
    350 
    351        if (XRE_IsParentProcess()) {
    352          storageChild->SendClearAll();
    353        }
    354      }
    355 
    356      Notify(topic);
    357    }
    358 
    359    return NS_OK;
    360  }
    361 
    362  if (!strcmp(aTopic, "browser:purge-sessionStorage") ||
    363      !strcmp(aTopic, "extension:purge-sessionStorage")) {
    364    // The caller passed an nsIClearBySiteEntry object which consists of both
    365    // site and pattern.
    366    // If both are passed, aSubject takes precedence over aData.
    367    if (aSubject) {
    368      nsCOMPtr<nsIClearBySiteEntry> entry = do_QueryInterface(aSubject);
    369 
    370      NS_ENSURE_TRUE(entry, NS_ERROR_FAILURE);
    371 
    372      nsAutoCString schemelessSite;
    373      rv = entry->GetSchemelessSite(schemelessSite);
    374      NS_ENSURE_SUCCESS(rv, rv);
    375 
    376      nsAutoString patternJSON;
    377      rv = entry->GetPatternJSON(patternJSON);
    378      NS_ENSURE_SUCCESS(rv, rv);
    379 
    380      nsCString originScope;
    381      if (!schemelessSite.IsEmpty()) {
    382        rv = GetOriginScope(NS_ConvertUTF8toUTF16(schemelessSite).get(),
    383                            originScope);
    384        NS_ENSURE_SUCCESS(rv, rv);
    385      }
    386 
    387      Notify(aTopic, patternJSON, originScope);
    388    } else if (aData) {
    389      // The caller passed only a schemeless site, origin or host.
    390      nsCString originScope;
    391      rv = GetOriginScope(aData, originScope);
    392      if (NS_WARN_IF(NS_FAILED(rv))) {
    393        return rv;
    394      }
    395 
    396      Notify(aTopic, u""_ns, originScope);
    397    } else {
    398      // The caller passed nothing, clear everything.
    399      Notify(aTopic, u""_ns, ""_ns);
    400    }
    401 
    402    return NS_OK;
    403  }
    404 
    405  // Clear all private-browsing caches
    406  if (!strcmp(aTopic, "last-pb-context-exited")) {
    407    if (NextGenLocalStorageEnabled()) {
    408      return NS_OK;
    409    }
    410 
    411    // We get the notification in both processes (parent and content), but the
    412    // clearing of the in-memory database should be triggered from the parent
    413    // process only to avoid creation of redundant clearing operations.
    414    // Also, if we create a new StorageDBChild instance late during content
    415    // process shutdown, then it might be leaked in debug builds because it
    416    // could happen that there is no chance to properly destroy it.
    417    if (XRE_IsParentProcess()) {
    418      // This doesn't use a loop with privateBrowsingId 0 and 1, since we only
    419      // need to clear the in-memory database which is represented by
    420      // privateBrowsingId 1.
    421      static const uint32_t id = 1;
    422 
    423      StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    424      if (NS_WARN_IF(!storageChild)) {
    425        return NS_ERROR_FAILURE;
    426      }
    427 
    428      OriginAttributesPattern pattern;
    429      if (!pattern.Init(kPrivateBrowsingPattern)) {
    430        NS_ERROR("Cannot parse origin attributes pattern");
    431        return NS_ERROR_FAILURE;
    432      }
    433 
    434      storageChild->SendClearMatchingOriginAttributes(pattern);
    435    }
    436 
    437    Notify("private-browsing-data-cleared", kPrivateBrowsingPattern);
    438 
    439    return NS_OK;
    440  }
    441 
    442  // Clear data of the origins whose prefixes will match the suffix.
    443  if (!strcmp(aTopic, "clear-origin-attributes-data") ||
    444      !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
    445    MOZ_ASSERT(XRE_IsParentProcess());
    446 
    447    OriginAttributesPattern pattern;
    448    if (!pattern.Init(nsDependentString(aData))) {
    449      NS_ERROR("Cannot parse origin attributes pattern");
    450      return NS_ERROR_FAILURE;
    451    }
    452 
    453    if (NextGenLocalStorageEnabled()) {
    454      Notify("session-storage:clear-origin-attributes-data",
    455             nsDependentString(aData));
    456      return NS_OK;
    457    }
    458 
    459    for (const uint32_t id : {0, 1}) {
    460      StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    461      if (NS_WARN_IF(!storageChild)) {
    462        return NS_ERROR_FAILURE;
    463      }
    464 
    465      storageChild->SendClearMatchingOriginAttributes(pattern);
    466    }
    467 
    468    Notify(aTopic, nsDependentString(aData));
    469 
    470    return NS_OK;
    471  }
    472 
    473  if (!strcmp(aTopic, "profile-after-change")) {
    474    Notify("profile-change");
    475 
    476    return NS_OK;
    477  }
    478 
    479  if (!strcmp(aTopic, "profile-before-change")) {
    480    MOZ_ASSERT(XRE_IsParentProcess());
    481 
    482    if (NextGenLocalStorageEnabled()) {
    483      return NS_OK;
    484    }
    485 
    486    for (const uint32_t id : {0, 1}) {
    487      if (mBackgroundThread[id]) {
    488        bool done = false;
    489 
    490        RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
    491            new StorageDBThread::ShutdownRunnable(id, done);
    492        MOZ_ALWAYS_SUCCEEDS(mBackgroundThread[id]->Dispatch(
    493            shutdownRunnable, NS_DISPATCH_NORMAL));
    494 
    495        MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
    496            "StorageObserver::Observe profile-before-change"_ns,
    497            [&]() { return done; }));
    498 
    499        mBackgroundThread[id] = nullptr;
    500      }
    501    }
    502 
    503    return NS_OK;
    504  }
    505 
    506 #ifdef DOM_STORAGE_TESTS
    507  if (!strcmp(aTopic, "domstorage-test-flush-force")) {
    508    if (NextGenLocalStorageEnabled()) {
    509      return NS_OK;
    510    }
    511 
    512    for (const uint32_t id : {0, 1}) {
    513      StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
    514      if (NS_WARN_IF(!storageChild)) {
    515        return NS_ERROR_FAILURE;
    516      }
    517 
    518      storageChild->SendAsyncFlush();
    519    }
    520 
    521    return NS_OK;
    522  }
    523 
    524  if (!strcmp(aTopic, "domstorage-test-flushed")) {
    525    if (NextGenLocalStorageEnabled()) {
    526      return NS_OK;
    527    }
    528 
    529    // Only used to propagate to IPC children
    530    Notify("test-flushed");
    531 
    532    return NS_OK;
    533  }
    534 
    535  if (!strcmp(aTopic, "domstorage-test-reload")) {
    536    if (NextGenLocalStorageEnabled()) {
    537      return NS_OK;
    538    }
    539 
    540    Notify("test-reload");
    541 
    542    return NS_OK;
    543  }
    544 #endif
    545 
    546  NS_ERROR("Unexpected topic");
    547  return NS_ERROR_UNEXPECTED;
    548 }
    549 
    550 NS_IMETHODIMP
    551 StorageObserver::GetName(nsACString& aName) {
    552  aName.AssignLiteral("StorageObserver");
    553  return NS_OK;
    554 }
    555 
    556 }  // namespace mozilla::dom