Line data Source code
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 "StorageDBThread.h"
11 : #include "StorageUtils.h"
12 :
13 : #include "mozilla/BasePrincipal.h"
14 : #include "nsIObserverService.h"
15 : #include "nsIURI.h"
16 : #include "nsIURL.h"
17 : #include "nsIScriptSecurityManager.h"
18 : #include "nsIPermission.h"
19 : #include "nsIIDNService.h"
20 : #include "nsICookiePermission.h"
21 :
22 : #include "nsPrintfCString.h"
23 : #include "nsXULAppAPI.h"
24 : #include "nsEscape.h"
25 : #include "nsNetCID.h"
26 : #include "mozilla/Preferences.h"
27 : #include "mozilla/Services.h"
28 : #include "nsServiceManagerUtils.h"
29 :
30 : namespace mozilla {
31 : namespace dom {
32 :
33 : using namespace StorageUtils;
34 :
35 : static const char kStartupTopic[] = "sessionstore-windows-restored";
36 : static const uint32_t kStartupDelay = 0;
37 :
38 : const char kTestingPref[] = "dom.storage.testing";
39 :
40 133 : NS_IMPL_ISUPPORTS(StorageObserver,
41 : nsIObserver,
42 : nsISupportsWeakReference)
43 :
44 : StorageObserver* StorageObserver::sSelf = nullptr;
45 :
46 : // static
47 : nsresult
48 3 : StorageObserver::Init()
49 : {
50 3 : if (sSelf) {
51 0 : return NS_OK;
52 : }
53 :
54 6 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
55 3 : if (!obs) {
56 0 : return NS_ERROR_UNEXPECTED;
57 : }
58 :
59 3 : sSelf = new StorageObserver();
60 3 : NS_ADDREF(sSelf);
61 :
62 : // Chrome clear operations.
63 3 : obs->AddObserver(sSelf, kStartupTopic, true);
64 3 : obs->AddObserver(sSelf, "cookie-changed", true);
65 3 : obs->AddObserver(sSelf, "perm-changed", true);
66 3 : obs->AddObserver(sSelf, "browser:purge-domain-data", true);
67 3 : obs->AddObserver(sSelf, "last-pb-context-exited", true);
68 3 : obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
69 :
70 : // Shutdown
71 3 : obs->AddObserver(sSelf, "profile-after-change", true);
72 3 : obs->AddObserver(sSelf, "profile-before-change", true);
73 3 : obs->AddObserver(sSelf, "xpcom-shutdown", true);
74 :
75 : // Observe low device storage notifications.
76 3 : obs->AddObserver(sSelf, "disk-space-watcher", true);
77 :
78 : // Testing
79 : #ifdef DOM_STORAGE_TESTS
80 3 : Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
81 : #endif
82 :
83 3 : return NS_OK;
84 : }
85 :
86 : // static
87 : nsresult
88 0 : StorageObserver::Shutdown()
89 : {
90 0 : if (!sSelf) {
91 0 : return NS_ERROR_NOT_INITIALIZED;
92 : }
93 :
94 0 : NS_RELEASE(sSelf);
95 0 : return NS_OK;
96 : }
97 :
98 : // static
99 : void
100 3 : StorageObserver::TestingPrefChanged(const char* aPrefName, void* aClosure)
101 : {
102 6 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
103 3 : if (!obs) {
104 0 : return;
105 : }
106 :
107 3 : if (Preferences::GetBool(kTestingPref)) {
108 0 : obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
109 0 : if (XRE_IsParentProcess()) {
110 : // Only to forward to child process.
111 0 : obs->AddObserver(sSelf, "domstorage-test-flushed", true);
112 : }
113 0 : obs->AddObserver(sSelf, "domstorage-test-reload", true);
114 : } else {
115 3 : obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
116 3 : if (XRE_IsParentProcess()) {
117 : // Only to forward to child process.
118 1 : obs->RemoveObserver(sSelf, "domstorage-test-flushed");
119 : }
120 3 : obs->RemoveObserver(sSelf, "domstorage-test-reload");
121 : }
122 : }
123 :
124 : void
125 4 : StorageObserver::AddSink(StorageObserverSink* aObs)
126 : {
127 4 : mSinks.AppendElement(aObs);
128 4 : }
129 :
130 : void
131 0 : StorageObserver::RemoveSink(StorageObserverSink* aObs)
132 : {
133 0 : mSinks.RemoveElement(aObs);
134 0 : }
135 :
136 : void
137 1 : StorageObserver::Notify(const char* aTopic,
138 : const nsAString& aOriginAttributesPattern,
139 : const nsACString& aOriginScope)
140 : {
141 1 : for (uint32_t i = 0; i < mSinks.Length(); ++i) {
142 0 : StorageObserverSink* sink = mSinks[i];
143 0 : sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
144 : }
145 1 : }
146 :
147 : NS_IMETHODIMP
148 5 : StorageObserver::Observe(nsISupports* aSubject,
149 : const char* aTopic,
150 : const char16_t* aData)
151 : {
152 : nsresult rv;
153 :
154 : // Start the thread that opens the database.
155 5 : if (!strcmp(aTopic, kStartupTopic)) {
156 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
157 0 : obs->RemoveObserver(this, kStartupTopic);
158 :
159 0 : mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
160 0 : if (!mDBThreadStartDelayTimer) {
161 0 : return NS_ERROR_UNEXPECTED;
162 : }
163 :
164 0 : mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay);
165 :
166 0 : return NS_OK;
167 : }
168 :
169 : // Timer callback used to start the database a short timer after startup
170 5 : if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
171 0 : nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
172 0 : if (!timer) {
173 0 : return NS_ERROR_UNEXPECTED;
174 : }
175 :
176 0 : if (timer == mDBThreadStartDelayTimer) {
177 0 : mDBThreadStartDelayTimer = nullptr;
178 :
179 0 : StorageDBBridge* db = LocalStorageCache::StartDatabase();
180 0 : NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
181 : }
182 :
183 0 : return NS_OK;
184 : }
185 :
186 : // Clear everything, caches + database
187 5 : if (!strcmp(aTopic, "cookie-changed")) {
188 0 : if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
189 0 : return NS_OK;
190 : }
191 :
192 0 : StorageDBBridge* db = LocalStorageCache::StartDatabase();
193 0 : NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
194 :
195 0 : db->AsyncClearAll();
196 :
197 0 : Notify("cookie-cleared");
198 :
199 0 : return NS_OK;
200 : }
201 :
202 : // Clear from caches everything that has been stored
203 : // while in session-only mode
204 5 : if (!strcmp(aTopic, "perm-changed")) {
205 : // Check for cookie permission change
206 8 : nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
207 4 : if (!perm) {
208 0 : return NS_OK;
209 : }
210 :
211 8 : nsAutoCString type;
212 4 : perm->GetType(type);
213 4 : if (type != NS_LITERAL_CSTRING("cookie")) {
214 4 : return NS_OK;
215 : }
216 :
217 0 : uint32_t cap = 0;
218 0 : perm->GetCapability(&cap);
219 0 : if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
220 0 : !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) {
221 0 : return NS_OK;
222 : }
223 :
224 0 : nsCOMPtr<nsIPrincipal> principal;
225 0 : perm->GetPrincipal(getter_AddRefs(principal));
226 0 : if (!principal) {
227 0 : return NS_OK;
228 : }
229 :
230 0 : nsAutoCString originSuffix;
231 0 : BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(originSuffix);
232 :
233 0 : nsCOMPtr<nsIURI> origin;
234 0 : principal->GetURI(getter_AddRefs(origin));
235 0 : if (!origin) {
236 0 : return NS_OK;
237 : }
238 :
239 0 : nsAutoCString host;
240 0 : origin->GetHost(host);
241 0 : if (host.IsEmpty()) {
242 0 : return NS_OK;
243 : }
244 :
245 0 : nsAutoCString originScope;
246 0 : rv = CreateReversedDomain(host, originScope);
247 0 : NS_ENSURE_SUCCESS(rv, rv);
248 :
249 0 : Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
250 0 : originScope);
251 :
252 0 : return NS_OK;
253 : }
254 :
255 : // Clear everything (including so and pb data) from caches and database
256 : // for the gived domain and subdomains.
257 1 : if (!strcmp(aTopic, "browser:purge-domain-data")) {
258 : // Convert the domain name to the ACE format
259 0 : nsAutoCString aceDomain;
260 0 : nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
261 0 : if (converter) {
262 0 : rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);
263 0 : NS_ENSURE_SUCCESS(rv, rv);
264 : } else {
265 : // In case the IDN service is not available, this is the best we can come
266 : // up with!
267 0 : rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
268 : esc_OnlyNonASCII | esc_AlwaysCopy,
269 : aceDomain,
270 0 : fallible);
271 0 : NS_ENSURE_SUCCESS(rv, rv);
272 : }
273 :
274 0 : nsAutoCString originScope;
275 0 : rv = CreateReversedDomain(aceDomain, originScope);
276 0 : NS_ENSURE_SUCCESS(rv, rv);
277 :
278 0 : StorageDBBridge* db = LocalStorageCache::StartDatabase();
279 0 : NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
280 :
281 0 : db->AsyncClearMatchingOrigin(originScope);
282 :
283 0 : Notify("domain-data-cleared", EmptyString(), originScope);
284 :
285 0 : return NS_OK;
286 : }
287 :
288 : // Clear all private-browsing caches
289 1 : if (!strcmp(aTopic, "last-pb-context-exited")) {
290 0 : Notify("private-browsing-data-cleared");
291 :
292 0 : return NS_OK;
293 : }
294 :
295 : // Clear data of the origins whose prefixes will match the suffix.
296 1 : if (!strcmp(aTopic, "clear-origin-attributes-data")) {
297 0 : OriginAttributesPattern pattern;
298 0 : if (!pattern.Init(nsDependentString(aData))) {
299 0 : NS_ERROR("Cannot parse origin attributes pattern");
300 0 : return NS_ERROR_FAILURE;
301 : }
302 :
303 0 : StorageDBBridge* db = LocalStorageCache::StartDatabase();
304 0 : NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
305 :
306 0 : db->AsyncClearMatchingOriginAttributes(pattern);
307 :
308 0 : Notify("origin-attr-pattern-cleared", nsDependentString(aData));
309 :
310 0 : return NS_OK;
311 : }
312 :
313 1 : if (!strcmp(aTopic, "profile-after-change")) {
314 2 : Notify("profile-change");
315 :
316 1 : return NS_OK;
317 : }
318 :
319 0 : if (!strcmp(aTopic, "profile-before-change") ||
320 0 : !strcmp(aTopic, "xpcom-shutdown")) {
321 0 : rv = LocalStorageCache::StopDatabase();
322 0 : if (NS_FAILED(rv)) {
323 0 : NS_WARNING("Error while stopping Storage DB background thread");
324 : }
325 :
326 0 : return NS_OK;
327 : }
328 :
329 0 : if (!strcmp(aTopic, "disk-space-watcher")) {
330 0 : if (NS_LITERAL_STRING("full").Equals(aData)) {
331 0 : Notify("low-disk-space");
332 0 : } else if (NS_LITERAL_STRING("free").Equals(aData)) {
333 0 : Notify("no-low-disk-space");
334 : }
335 :
336 0 : return NS_OK;
337 : }
338 :
339 : #ifdef DOM_STORAGE_TESTS
340 0 : if (!strcmp(aTopic, "domstorage-test-flush-force")) {
341 0 : StorageDBBridge* db = LocalStorageCache::GetDatabase();
342 0 : if (db) {
343 0 : db->AsyncFlush();
344 : }
345 :
346 0 : return NS_OK;
347 : }
348 :
349 0 : if (!strcmp(aTopic, "domstorage-test-flushed")) {
350 : // Only used to propagate to IPC children
351 0 : Notify("test-flushed");
352 :
353 0 : return NS_OK;
354 : }
355 :
356 0 : if (!strcmp(aTopic, "domstorage-test-reload")) {
357 0 : Notify("test-reload");
358 :
359 0 : return NS_OK;
360 : }
361 : #endif
362 :
363 0 : NS_ERROR("Unexpected topic");
364 0 : return NS_ERROR_UNEXPECTED;
365 : }
366 :
367 : } // namespace dom
368 : } // namespace mozilla
|