Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
/**
6
* Handles serialization of the data and persistence into a file.
7
*
8
* The file is stored in JSON format, without indentation, using UTF-8 encoding.
9
* With indentation applied, the file would look like this:
10
*
11
* {
12
* "logins": [
13
* {
14
* "id": 2,
15
* "hostname": "http://www.example.com",
16
* "httpRealm": null,
17
* "formSubmitURL": "http://www.example.com",
18
* "usernameField": "username_field",
19
* "passwordField": "password_field",
20
* "encryptedUsername": "...",
21
* "encryptedPassword": "...",
22
* "guid": "...",
23
* "encType": 1,
24
* "timeCreated": 1262304000000,
25
* "timeLastUsed": 1262304000000,
26
* "timePasswordChanged": 1262476800000,
27
* "timesUsed": 1
28
* },
29
* {
30
* "id": 4,
31
* (...)
32
* }
33
* ],
34
* "nextId": 10,
35
* "version": 1
36
* }
37
*/
38
39
"use strict";
40
41
const EXPORTED_SYMBOLS = ["LoginStore"];
42
43
// Globals
44
45
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
46
47
ChromeUtils.defineModuleGetter(
48
this,
49
"JSONFile",
51
);
52
53
/**
54
* Current data version assigned by the code that last touched the data.
55
*
56
* This number should be updated only when it is important to understand whether
57
* an old version of the code has touched the data, for example to execute an
58
* update logic. In most cases, this number should not be changed, in
59
* particular when no special one-time update logic is needed.
60
*
61
* For example, this number should NOT be changed when a new optional field is
62
* added to a login entry.
63
*/
64
const kDataVersion = 3;
65
66
// The permission type we store in the permission manager.
67
const PERMISSION_SAVE_LOGINS = "login-saving";
68
69
const MAX_DATE_MS = 8640000000000000;
70
71
// LoginStore
72
73
/**
74
* Inherits from JSONFile and handles serialization of login-related data and
75
* persistence into a file.
76
*
77
* @param aPath
78
* String containing the file path where data should be saved.
79
*/
80
function LoginStore(aPath) {
81
JSONFile.call(this, {
82
path: aPath,
83
dataPostProcessor: this._dataPostProcessor.bind(this),
84
});
85
}
86
87
LoginStore.prototype = Object.create(JSONFile.prototype);
88
LoginStore.prototype.constructor = LoginStore;
89
90
/**
91
* Synchronously work on the data just loaded into memory.
92
*/
93
LoginStore.prototype._dataPostProcessor = function(data) {
94
if (data.nextId === undefined) {
95
data.nextId = 1;
96
}
97
98
// Create any arrays that are not present in the saved file.
99
if (!data.logins) {
100
data.logins = [];
101
}
102
103
if (!data.potentiallyVulnerablePasswords) {
104
data.potentiallyVulnerablePasswords = [];
105
}
106
107
if (!data.dismissedBreachAlertsByLoginGUID) {
108
data.dismissedBreachAlertsByLoginGUID = {};
109
}
110
111
// sanitize dates in logins
112
if (!("version" in data) || data.version < 3) {
113
let dateProperties = ["timeCreated", "timeLastUsed", "timePasswordChanged"];
114
let now = Date.now();
115
function getEarliestDate(login, defaultDate) {
116
let earliestDate = dateProperties.reduce((earliest, pname) => {
117
let ts = login[pname];
118
return !ts ? earliest : Math.min(ts, earliest);
119
}, defaultDate);
120
return earliestDate;
121
}
122
for (let login of data.logins) {
123
for (let pname of dateProperties) {
124
let earliestDate;
125
if (!login[pname] || login[pname] > MAX_DATE_MS) {
126
login[pname] =
127
earliestDate || (earliestDate = getEarliestDate(login, now));
128
}
129
}
130
}
131
}
132
133
// Indicate that the current version of the code has touched the file.
134
data.version = kDataVersion;
135
136
return data;
137
};