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 "LocalStorageManager.h"
8 : #include "StorageUtils.h"
9 :
10 : #include "mozIStorageBindingParamsArray.h"
11 : #include "mozIStorageBindingParams.h"
12 : #include "mozIStorageValueArray.h"
13 : #include "mozIStorageFunction.h"
14 : #include "mozilla/BasePrincipal.h"
15 : #include "nsVariant.h"
16 : #include "mozilla/Services.h"
17 : #include "mozilla/Tokenizer.h"
18 :
19 : // Current version of the database schema
20 : #define CURRENT_SCHEMA_VERSION 2
21 :
22 : namespace mozilla {
23 : namespace dom {
24 :
25 : using namespace StorageUtils;
26 :
27 : namespace {
28 :
29 0 : class nsReverseStringSQLFunction final : public mozIStorageFunction
30 : {
31 0 : ~nsReverseStringSQLFunction() {}
32 :
33 : NS_DECL_ISUPPORTS
34 : NS_DECL_MOZISTORAGEFUNCTION
35 : };
36 :
37 0 : NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
38 :
39 : NS_IMETHODIMP
40 0 : nsReverseStringSQLFunction::OnFunctionCall(
41 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
42 : {
43 : nsresult rv;
44 :
45 0 : nsAutoCString stringToReverse;
46 0 : rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
47 0 : NS_ENSURE_SUCCESS(rv, rv);
48 :
49 0 : nsAutoCString result;
50 0 : ReverseString(stringToReverse, result);
51 :
52 0 : RefPtr<nsVariant> outVar(new nsVariant());
53 0 : rv = outVar->SetAsAUTF8String(result);
54 0 : NS_ENSURE_SUCCESS(rv, rv);
55 :
56 0 : outVar.forget(aResult);
57 0 : return NS_OK;
58 : }
59 :
60 : // "scope" to "origin attributes suffix" and "origin key" convertor
61 :
62 0 : class ExtractOriginData : protected mozilla::Tokenizer
63 : {
64 : public:
65 0 : ExtractOriginData(const nsACString& scope, nsACString& suffix,
66 : nsACString& origin)
67 0 : : mozilla::Tokenizer(scope)
68 : {
69 : using mozilla::OriginAttributes;
70 :
71 : // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
72 : // we don't find it, the scope is our new origin key and suffix
73 : // is empty.
74 0 : suffix.Truncate();
75 0 : origin.Assign(scope);
76 :
77 : // Bail out if it isn't appId.
78 : uint32_t appId;
79 0 : if (!ReadInteger(&appId)) {
80 0 : return;
81 : }
82 :
83 : // Should be followed by a colon.
84 0 : if (!CheckChar(':')) {
85 0 : return;
86 : }
87 :
88 : // Bail out if it isn't 'isolatedBrowserFlag'.
89 0 : nsDependentCSubstring isolatedBrowserFlag;
90 0 : if (!ReadWord(isolatedBrowserFlag)) {
91 0 : return;
92 : }
93 :
94 0 : bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
95 0 : bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
96 0 : if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
97 0 : return;
98 : }
99 :
100 : // Should be followed by a colon.
101 0 : if (!CheckChar(':')) {
102 0 : return;
103 : }
104 :
105 : // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
106 : // from it and take the rest as the origin key.
107 :
108 : // If the profile went through schema 1 -> schema 0 -> schema 1 switching
109 : // we may have stored the full attributes origin suffix when there were
110 : // more than just appId and inIsolatedMozBrowser set on storage principal's
111 : // OriginAttributes.
112 : //
113 : // To preserve full uniqueness we store this suffix to the scope key.
114 : // Schema 0 code will just ignore it while keeping the scoping unique.
115 : //
116 : // The whole scope string is in one of the following forms (when we are
117 : // here):
118 : //
119 : // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
120 : // "1001:f:gro.allizom.rxd.:https:443"
121 : // |
122 : // +- the parser cursor position.
123 : //
124 : // If there is '^', the full origin attributes suffix follows. We search
125 : // for ':' since it is the delimiter used in the scope string and is never
126 : // contained in the origin attributes suffix. Remaining string after
127 : // the comma is the reversed-domain+schema+port tuple.
128 0 : Record();
129 0 : if (CheckChar('^')) {
130 0 : Token t;
131 0 : while (Next(t)) {
132 0 : if (t.Equals(Token::Char(':'))) {
133 0 : Claim(suffix);
134 0 : break;
135 : }
136 : }
137 : } else {
138 0 : OriginAttributes attrs(appId, inIsolatedMozBrowser);
139 0 : attrs.CreateSuffix(suffix);
140 : }
141 :
142 : // Consume the rest of the input as "origin".
143 0 : origin.Assign(Substring(mCursor, mEnd));
144 : }
145 : };
146 :
147 : class GetOriginParticular final : public mozIStorageFunction
148 : {
149 : public:
150 : enum EParticular {
151 : ORIGIN_ATTRIBUTES_SUFFIX,
152 : ORIGIN_KEY
153 : };
154 :
155 0 : explicit GetOriginParticular(EParticular aParticular)
156 0 : : mParticular(aParticular) {}
157 :
158 : private:
159 : GetOriginParticular() = delete;
160 0 : ~GetOriginParticular() {}
161 :
162 : EParticular mParticular;
163 :
164 : NS_DECL_ISUPPORTS
165 : NS_DECL_MOZISTORAGEFUNCTION
166 : };
167 :
168 0 : NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
169 :
170 : NS_IMETHODIMP
171 0 : GetOriginParticular::OnFunctionCall(
172 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
173 : {
174 : nsresult rv;
175 :
176 0 : nsAutoCString scope;
177 0 : rv = aFunctionArguments->GetUTF8String(0, scope);
178 0 : NS_ENSURE_SUCCESS(rv, rv);
179 :
180 0 : nsAutoCString suffix, origin;
181 0 : ExtractOriginData(scope, suffix, origin);
182 :
183 0 : nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
184 :
185 0 : switch (mParticular) {
186 : case EParticular::ORIGIN_ATTRIBUTES_SUFFIX:
187 0 : rv = outVar->SetAsAUTF8String(suffix);
188 0 : break;
189 : case EParticular::ORIGIN_KEY:
190 0 : rv = outVar->SetAsAUTF8String(origin);
191 0 : break;
192 : }
193 :
194 0 : NS_ENSURE_SUCCESS(rv, rv);
195 :
196 0 : outVar.forget(aResult);
197 0 : return NS_OK;
198 : }
199 :
200 : class StripOriginAddonId final : public mozIStorageFunction
201 : {
202 : public:
203 0 : explicit StripOriginAddonId() {}
204 :
205 : private:
206 0 : ~StripOriginAddonId() {}
207 :
208 : NS_DECL_ISUPPORTS
209 : NS_DECL_MOZISTORAGEFUNCTION
210 : };
211 :
212 0 : NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
213 :
214 : NS_IMETHODIMP
215 0 : StripOriginAddonId::OnFunctionCall(
216 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
217 : {
218 : nsresult rv;
219 :
220 0 : nsAutoCString suffix;
221 0 : rv = aFunctionArguments->GetUTF8String(0, suffix);
222 0 : NS_ENSURE_SUCCESS(rv, rv);
223 :
224 : // Deserialize and re-serialize to automatically drop any obsolete origin
225 : // attributes.
226 0 : OriginAttributes oa;
227 0 : bool ok = oa.PopulateFromSuffix(suffix);
228 0 : NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
229 :
230 0 : nsAutoCString newSuffix;
231 0 : oa.CreateSuffix(newSuffix);
232 :
233 0 : nsCOMPtr<nsIWritableVariant> outVar = new nsVariant();
234 0 : rv = outVar->SetAsAUTF8String(newSuffix);
235 0 : NS_ENSURE_SUCCESS(rv, rv);
236 :
237 0 : outVar.forget(aResult);
238 0 : return NS_OK;
239 : }
240 :
241 : } // namespace
242 :
243 : namespace StorageDBUpdater {
244 :
245 1 : nsresult CreateSchema1Tables(mozIStorageConnection *aWorkerConnection)
246 : {
247 : nsresult rv;
248 :
249 3 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
250 : "CREATE TABLE IF NOT EXISTS webappsstore2 ("
251 : "originAttributes TEXT, "
252 : "originKey TEXT, "
253 : "scope TEXT, " // Only for schema0 downgrade compatibility
254 : "key TEXT, "
255 3 : "value TEXT)"));
256 1 : NS_ENSURE_SUCCESS(rv, rv);
257 :
258 3 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
259 : "CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
260 3 : " ON webappsstore2(originAttributes, originKey, key)"));
261 1 : NS_ENSURE_SUCCESS(rv, rv);
262 :
263 1 : return NS_OK;
264 : }
265 :
266 1 : nsresult Update(mozIStorageConnection *aWorkerConnection)
267 : {
268 : nsresult rv;
269 :
270 2 : mozStorageTransaction transaction(aWorkerConnection, false);
271 :
272 1 : bool doVacuum = false;
273 :
274 : int32_t schemaVer;
275 1 : rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
276 1 : NS_ENSURE_SUCCESS(rv, rv);
277 :
278 : // downgrade (v0) -> upgrade (v1+) specific code
279 1 : if (schemaVer >= 1) {
280 : bool schema0IndexExists;
281 3 : rv = aWorkerConnection->IndexExists(NS_LITERAL_CSTRING("scope_key_index"),
282 3 : &schema0IndexExists);
283 1 : NS_ENSURE_SUCCESS(rv, rv);
284 :
285 1 : if (schema0IndexExists) {
286 : // If this index exists, the database (already updated to schema >1)
287 : // has been run again on schema 0 code. That recreated that index
288 : // and might store some new rows while updating only the 'scope' column.
289 : // For such added rows we must fill the new 'origin*' columns correctly
290 : // otherwise there would be a data loss. The safest way to do it is to
291 : // simply run the whole update to schema 1 again.
292 0 : schemaVer = 0;
293 : }
294 : }
295 :
296 1 : switch (schemaVer) {
297 : case 0: {
298 : bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
299 :
300 0 : rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore2"),
301 0 : &webappsstore2Exists);
302 0 : NS_ENSURE_SUCCESS(rv, rv);
303 0 : rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
304 0 : &webappsstoreExists);
305 0 : NS_ENSURE_SUCCESS(rv, rv);
306 0 : rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
307 0 : &moz_webappsstoreExists);
308 0 : NS_ENSURE_SUCCESS(rv, rv);
309 :
310 0 : if (!webappsstore2Exists && !webappsstoreExists &&
311 0 : !moz_webappsstoreExists) {
312 : // The database is empty, this is the first start. Just create the schema
313 : // table and break to the next version to update to, i.e. bypass update
314 : // from the old version.
315 :
316 0 : rv = CreateSchema1Tables(aWorkerConnection);
317 0 : NS_ENSURE_SUCCESS(rv, rv);
318 :
319 0 : rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
320 0 : NS_ENSURE_SUCCESS(rv, rv);
321 :
322 0 : break;
323 : }
324 :
325 0 : doVacuum = true;
326 :
327 : // Ensure Gecko 1.9.1 storage table
328 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
329 : "CREATE TABLE IF NOT EXISTS webappsstore2 ("
330 : "scope TEXT, "
331 : "key TEXT, "
332 : "value TEXT, "
333 : "secure INTEGER, "
334 0 : "owner TEXT)"));
335 0 : NS_ENSURE_SUCCESS(rv, rv);
336 :
337 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
338 : "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
339 0 : " ON webappsstore2(scope, key)"));
340 0 : NS_ENSURE_SUCCESS(rv, rv);
341 :
342 0 : nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
343 0 : NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
344 :
345 0 : rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"),
346 0 : 1, function1);
347 0 : NS_ENSURE_SUCCESS(rv, rv);
348 :
349 : // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
350 : // to actual webappsstore2 table and drop the obsolete table. First process
351 : // this newer table upgrade to priority potential duplicates from older
352 : // storage table.
353 0 : if (webappsstoreExists) {
354 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
355 0 : NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
356 : "webappsstore2(scope, key, value, secure, owner) "
357 : "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
358 0 : "FROM webappsstore"));
359 0 : NS_ENSURE_SUCCESS(rv, rv);
360 :
361 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
362 0 : NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
363 0 : NS_ENSURE_SUCCESS(rv, rv);
364 : }
365 :
366 : // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
367 : // to actual webappsstore2 table and drop the obsolete table. Potential
368 : // duplicates will be ignored.
369 0 : if (moz_webappsstoreExists) {
370 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
371 0 : NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
372 : "webappsstore2(scope, key, value, secure, owner) "
373 : "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
374 0 : "FROM moz_webappsstore"));
375 0 : NS_ENSURE_SUCCESS(rv, rv);
376 :
377 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
378 0 : NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
379 0 : NS_ENSURE_SUCCESS(rv, rv);
380 : }
381 :
382 0 : aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("REVERSESTRING"));
383 :
384 : // Update the scoping to match the new implememntation: split to oa suffix
385 : // and origin key First rename the old table, we want to remove some columns
386 : // no longer needed, but even before that drop all indexes from it (CREATE
387 : // IF NOT EXISTS for index on the new table would falsely find the index!)
388 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
389 0 : "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
390 0 : NS_ENSURE_SUCCESS(rv, rv);
391 :
392 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
393 0 : "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
394 0 : NS_ENSURE_SUCCESS(rv, rv);
395 :
396 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
397 0 : "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
398 0 : NS_ENSURE_SUCCESS(rv, rv);
399 :
400 : nsCOMPtr<mozIStorageFunction> oaSuffixFunc(
401 0 : new GetOriginParticular(GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
402 0 : rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"),
403 0 : 1, oaSuffixFunc);
404 0 : NS_ENSURE_SUCCESS(rv, rv);
405 :
406 : nsCOMPtr<mozIStorageFunction> originKeyFunc(
407 0 : new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
408 0 : rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"),
409 0 : 1, originKeyFunc);
410 0 : NS_ENSURE_SUCCESS(rv, rv);
411 :
412 : // Here we ensure this schema tables when we are updating.
413 0 : rv = CreateSchema1Tables(aWorkerConnection);
414 0 : NS_ENSURE_SUCCESS(rv, rv);
415 :
416 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
417 : "INSERT OR IGNORE INTO "
418 : "webappsstore2 (originAttributes, originKey, scope, key, value) "
419 : "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, value "
420 0 : "FROM webappsstore2_old"));
421 0 : NS_ENSURE_SUCCESS(rv, rv);
422 :
423 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
424 0 : NS_LITERAL_CSTRING("DROP TABLE webappsstore2_old"));
425 0 : NS_ENSURE_SUCCESS(rv, rv);
426 :
427 0 : aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"));
428 0 : aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"));
429 :
430 0 : rv = aWorkerConnection->SetSchemaVersion(1);
431 0 : NS_ENSURE_SUCCESS(rv, rv);
432 :
433 : MOZ_FALLTHROUGH;
434 : }
435 : case 1: {
436 : nsCOMPtr<mozIStorageFunction> oaStripAddonId(
437 0 : new StripOriginAddonId());
438 0 : rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("STRIP_ADDON_ID"), 1, oaStripAddonId);
439 0 : NS_ENSURE_SUCCESS(rv, rv);
440 :
441 0 : rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
442 : "UPDATE webappsstore2 "
443 : "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
444 0 : "WHERE originAttributes LIKE '^%'"));
445 0 : NS_ENSURE_SUCCESS(rv, rv);
446 :
447 0 : aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("STRIP_ADDON_ID"));
448 :
449 0 : rv = aWorkerConnection->SetSchemaVersion(2);
450 0 : NS_ENSURE_SUCCESS(rv, rv);
451 :
452 : MOZ_FALLTHROUGH;
453 : }
454 : case CURRENT_SCHEMA_VERSION:
455 : // Ensure the tables and indexes are up. This is mostly a no-op
456 : // in common scenarios.
457 1 : rv = CreateSchema1Tables(aWorkerConnection);
458 1 : NS_ENSURE_SUCCESS(rv, rv);
459 :
460 : // Nothing more to do here, this is the current schema version
461 1 : break;
462 :
463 : default:
464 0 : MOZ_ASSERT(false);
465 : break;
466 : } // switch
467 :
468 1 : rv = transaction.Commit();
469 1 : NS_ENSURE_SUCCESS(rv, rv);
470 :
471 1 : if (doVacuum) {
472 : // In some cases this can make the disk file of the database significantly
473 : // smaller. VACUUM cannot be executed inside a transaction.
474 0 : rv = aWorkerConnection->ExecuteSimpleSQL(
475 0 : NS_LITERAL_CSTRING("VACUUM"));
476 0 : NS_ENSURE_SUCCESS(rv, rv);
477 : }
478 :
479 1 : return NS_OK;
480 : }
481 :
482 : } // namespace StorageDBUpdater
483 : } // namespace dom
484 : } // namespace mozilla
|