|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "DOMStorage.h" |
|
7 #include "DOMStorageCache.h" |
|
8 #include "DOMStorageManager.h" |
|
9 |
|
10 #include "nsIDOMStorageEvent.h" |
|
11 #include "nsIObserverService.h" |
|
12 #include "nsIScriptSecurityManager.h" |
|
13 #include "nsIPermissionManager.h" |
|
14 #include "nsIPrincipal.h" |
|
15 #include "nsICookiePermission.h" |
|
16 |
|
17 #include "nsDOMClassInfoID.h" |
|
18 #include "mozilla/Services.h" |
|
19 #include "mozilla/Preferences.h" |
|
20 #include "GeneratedEvents.h" |
|
21 #include "nsThreadUtils.h" |
|
22 #include "nsContentUtils.h" |
|
23 #include "nsServiceManagerUtils.h" |
|
24 |
|
25 DOMCI_DATA(Storage, mozilla::dom::DOMStorage) |
|
26 |
|
27 namespace mozilla { |
|
28 namespace dom { |
|
29 |
|
30 NS_IMPL_ADDREF(DOMStorage) |
|
31 NS_IMPL_RELEASE(DOMStorage) |
|
32 |
|
33 NS_INTERFACE_MAP_BEGIN(DOMStorage) |
|
34 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage) |
|
35 NS_INTERFACE_MAP_ENTRY(nsIDOMStorage) |
|
36 NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage) |
|
37 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
38 NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage) |
|
39 NS_INTERFACE_MAP_END |
|
40 |
|
41 DOMStorage::DOMStorage(DOMStorageManager* aManager, |
|
42 DOMStorageCache* aCache, |
|
43 const nsAString& aDocumentURI, |
|
44 nsIPrincipal* aPrincipal, |
|
45 bool aIsPrivate) |
|
46 : mManager(aManager) |
|
47 , mCache(aCache) |
|
48 , mDocumentURI(aDocumentURI) |
|
49 , mPrincipal(aPrincipal) |
|
50 , mIsPrivate(aIsPrivate) |
|
51 , mIsSessionOnly(false) |
|
52 { |
|
53 mCache->Preload(); |
|
54 } |
|
55 |
|
56 DOMStorage::~DOMStorage() |
|
57 { |
|
58 mCache->KeepAlive(); |
|
59 } |
|
60 |
|
61 // nsIDOMStorage (web content public API implementation) |
|
62 |
|
63 NS_IMETHODIMP |
|
64 DOMStorage::GetLength(uint32_t* aLength) |
|
65 { |
|
66 if (!CanUseStorage(this)) { |
|
67 return NS_ERROR_DOM_SECURITY_ERR; |
|
68 } |
|
69 |
|
70 return mCache->GetLength(this, aLength); |
|
71 } |
|
72 |
|
73 NS_IMETHODIMP |
|
74 DOMStorage::Key(uint32_t aIndex, nsAString& aRetval) |
|
75 { |
|
76 if (!CanUseStorage(this)) { |
|
77 return NS_ERROR_DOM_SECURITY_ERR; |
|
78 } |
|
79 |
|
80 return mCache->GetKey(this, aIndex, aRetval); |
|
81 } |
|
82 |
|
83 NS_IMETHODIMP |
|
84 DOMStorage::GetItem(const nsAString& aKey, nsAString& aRetval) |
|
85 { |
|
86 if (!CanUseStorage(this)) { |
|
87 return NS_ERROR_DOM_SECURITY_ERR; |
|
88 } |
|
89 |
|
90 return mCache->GetItem(this, aKey, aRetval); |
|
91 } |
|
92 |
|
93 NS_IMETHODIMP |
|
94 DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData) |
|
95 { |
|
96 if (!CanUseStorage(this)) { |
|
97 return NS_ERROR_DOM_SECURITY_ERR; |
|
98 } |
|
99 |
|
100 Telemetry::Accumulate(GetType() == LocalStorage |
|
101 ? Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES |
|
102 : Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES, aKey.Length()); |
|
103 Telemetry::Accumulate(GetType() == LocalStorage |
|
104 ? Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES |
|
105 : Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES, aData.Length()); |
|
106 |
|
107 nsString old; |
|
108 nsresult rv = mCache->SetItem(this, aKey, nsString(aData), old); |
|
109 if (NS_FAILED(rv)) { |
|
110 return rv; |
|
111 } |
|
112 |
|
113 if (rv != NS_SUCCESS_DOM_NO_OPERATION) { |
|
114 BroadcastChangeNotification(aKey, old, aData); |
|
115 } |
|
116 |
|
117 return NS_OK; |
|
118 } |
|
119 |
|
120 NS_IMETHODIMP |
|
121 DOMStorage::RemoveItem(const nsAString& aKey) |
|
122 { |
|
123 if (!CanUseStorage(this)) { |
|
124 return NS_ERROR_DOM_SECURITY_ERR; |
|
125 } |
|
126 |
|
127 nsAutoString old; |
|
128 nsresult rv = mCache->RemoveItem(this, aKey, old); |
|
129 if (NS_FAILED(rv)) { |
|
130 return rv; |
|
131 } |
|
132 |
|
133 if (rv != NS_SUCCESS_DOM_NO_OPERATION) { |
|
134 BroadcastChangeNotification(aKey, old, NullString()); |
|
135 } |
|
136 |
|
137 return NS_OK; |
|
138 } |
|
139 |
|
140 NS_IMETHODIMP |
|
141 DOMStorage::Clear() |
|
142 { |
|
143 if (!CanUseStorage(this)) { |
|
144 return NS_ERROR_DOM_SECURITY_ERR; |
|
145 } |
|
146 |
|
147 nsresult rv = mCache->Clear(this); |
|
148 if (NS_FAILED(rv)) { |
|
149 return rv; |
|
150 } |
|
151 |
|
152 if (rv != NS_SUCCESS_DOM_NO_OPERATION) { |
|
153 BroadcastChangeNotification(NullString(), NullString(), NullString()); |
|
154 } |
|
155 |
|
156 return NS_OK; |
|
157 } |
|
158 |
|
159 namespace { |
|
160 |
|
161 class StorageNotifierRunnable : public nsRunnable |
|
162 { |
|
163 public: |
|
164 StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aType) |
|
165 : mSubject(aSubject), mType(aType) |
|
166 { } |
|
167 |
|
168 NS_DECL_NSIRUNNABLE |
|
169 |
|
170 private: |
|
171 nsCOMPtr<nsISupports> mSubject; |
|
172 const char16_t* mType; |
|
173 }; |
|
174 |
|
175 NS_IMETHODIMP |
|
176 StorageNotifierRunnable::Run() |
|
177 { |
|
178 nsCOMPtr<nsIObserverService> observerService = |
|
179 mozilla::services::GetObserverService(); |
|
180 if (observerService) { |
|
181 observerService->NotifyObservers(mSubject, "dom-storage2-changed", mType); |
|
182 } |
|
183 return NS_OK; |
|
184 } |
|
185 |
|
186 } // anonymous namespace |
|
187 |
|
188 void |
|
189 DOMStorage::BroadcastChangeNotification(const nsSubstring& aKey, |
|
190 const nsSubstring& aOldValue, |
|
191 const nsSubstring& aNewValue) |
|
192 { |
|
193 nsCOMPtr<nsIDOMEvent> domEvent; |
|
194 // Note, this DOM event should never reach JS. It is cloned later in |
|
195 // nsGlobalWindow. |
|
196 NS_NewDOMStorageEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr); |
|
197 |
|
198 nsCOMPtr<nsIDOMStorageEvent> event = do_QueryInterface(domEvent); |
|
199 nsresult rv = event->InitStorageEvent(NS_LITERAL_STRING("storage"), |
|
200 false, |
|
201 false, |
|
202 aKey, |
|
203 aOldValue, |
|
204 aNewValue, |
|
205 mDocumentURI, |
|
206 static_cast<nsIDOMStorage*>(this)); |
|
207 if (NS_FAILED(rv)) { |
|
208 return; |
|
209 } |
|
210 |
|
211 nsRefPtr<StorageNotifierRunnable> r = |
|
212 new StorageNotifierRunnable(event, |
|
213 GetType() == LocalStorage |
|
214 ? MOZ_UTF16("localStorage") |
|
215 : MOZ_UTF16("sessionStorage")); |
|
216 NS_DispatchToMainThread(r); |
|
217 } |
|
218 |
|
219 static const uint32_t ASK_BEFORE_ACCEPT = 1; |
|
220 static const uint32_t ACCEPT_SESSION = 2; |
|
221 static const uint32_t BEHAVIOR_REJECT = 2; |
|
222 |
|
223 static const char kPermissionType[] = "cookie"; |
|
224 static const char kStorageEnabled[] = "dom.storage.enabled"; |
|
225 static const char kCookiesBehavior[] = "network.cookie.cookieBehavior"; |
|
226 static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy"; |
|
227 |
|
228 // static, public |
|
229 bool |
|
230 DOMStorage::CanUseStorage(DOMStorage* aStorage) |
|
231 { |
|
232 // This method is responsible for correct setting of mIsSessionOnly. |
|
233 // It doesn't work with mIsPrivate flag at all, since it is checked |
|
234 // regardless mIsSessionOnly flag in DOMStorageCache code. |
|
235 if (aStorage) { |
|
236 aStorage->mIsSessionOnly = false; |
|
237 } |
|
238 |
|
239 if (!mozilla::Preferences::GetBool(kStorageEnabled)) { |
|
240 return false; |
|
241 } |
|
242 |
|
243 // chrome can always use aStorage regardless of permission preferences |
|
244 if (nsContentUtils::IsCallerChrome()) { |
|
245 return true; |
|
246 } |
|
247 |
|
248 nsCOMPtr<nsIPrincipal> subjectPrincipal; |
|
249 nsresult rv = nsContentUtils::GetSecurityManager()-> |
|
250 GetSubjectPrincipal(getter_AddRefs(subjectPrincipal)); |
|
251 NS_ENSURE_SUCCESS(rv, false); |
|
252 |
|
253 // if subjectPrincipal were null we'd have returned after |
|
254 // IsCallerChrome(). |
|
255 |
|
256 nsCOMPtr<nsIPermissionManager> permissionManager = |
|
257 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); |
|
258 if (!permissionManager) { |
|
259 return false; |
|
260 } |
|
261 |
|
262 uint32_t perm; |
|
263 permissionManager->TestPermissionFromPrincipal(subjectPrincipal, |
|
264 kPermissionType, &perm); |
|
265 |
|
266 if (perm == nsIPermissionManager::DENY_ACTION) { |
|
267 return false; |
|
268 } |
|
269 |
|
270 if (perm == nsICookiePermission::ACCESS_SESSION) { |
|
271 if (aStorage) { |
|
272 aStorage->mIsSessionOnly = true; |
|
273 } |
|
274 } else if (perm != nsIPermissionManager::ALLOW_ACTION) { |
|
275 uint32_t cookieBehavior = Preferences::GetUint(kCookiesBehavior); |
|
276 uint32_t lifetimePolicy = Preferences::GetUint(kCookiesLifetimePolicy); |
|
277 |
|
278 // Treat "ask every time" as "reject always". |
|
279 if ((cookieBehavior == BEHAVIOR_REJECT || lifetimePolicy == ASK_BEFORE_ACCEPT)) { |
|
280 return false; |
|
281 } |
|
282 |
|
283 if (lifetimePolicy == ACCEPT_SESSION && aStorage) { |
|
284 aStorage->mIsSessionOnly = true; |
|
285 } |
|
286 } |
|
287 |
|
288 if (aStorage) { |
|
289 return aStorage->CanAccess(subjectPrincipal); |
|
290 } |
|
291 |
|
292 return true; |
|
293 } |
|
294 |
|
295 // nsPIDOMStorage |
|
296 |
|
297 nsPIDOMStorage::StorageType |
|
298 DOMStorage::GetType() const |
|
299 { |
|
300 return mManager->Type(); |
|
301 } |
|
302 |
|
303 nsIPrincipal* |
|
304 DOMStorage::GetPrincipal() |
|
305 { |
|
306 return mPrincipal; |
|
307 } |
|
308 |
|
309 // Defined in DOMStorageManager.cpp |
|
310 extern bool |
|
311 PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal); |
|
312 |
|
313 bool |
|
314 DOMStorage::PrincipalEquals(nsIPrincipal* aPrincipal) |
|
315 { |
|
316 return PrincipalsEqual(mPrincipal, aPrincipal); |
|
317 } |
|
318 |
|
319 bool |
|
320 DOMStorage::CanAccess(nsIPrincipal* aPrincipal) |
|
321 { |
|
322 return !aPrincipal || aPrincipal->Subsumes(mPrincipal); |
|
323 } |
|
324 |
|
325 nsTArray<nsString>* |
|
326 DOMStorage::GetKeys() |
|
327 { |
|
328 if (!CanUseStorage(this)) { |
|
329 return new nsTArray<nsString>(); // return just an empty array |
|
330 } |
|
331 |
|
332 return mCache->GetKeys(this); |
|
333 } |
|
334 |
|
335 } // ::dom |
|
336 } // ::mozilla |