switch to nsIJSON for JSON parsing and output. add a deepCopy function instead of using eval(uneval()). make *sure* to read and write UTF-8 to files. bump version

This commit is contained in:
Dan Mills 2008-03-05 00:00:56 -08:00
parent 9b83d920fa
commit 019a0c8e6c
7 changed files with 181 additions and 130 deletions

View File

@ -41,7 +41,7 @@ const EXPORTED_SYMBOLS = ['WEAVE_VERSION', 'STORAGE_FORMAT_VERSION',
'PERMS_FILE', 'PERMS_PASSFILE', 'PERMS_DIRECTORY',
'ONE_BYTE', 'ONE_KILOBYTE', 'ONE_MEGABYTE'];
const WEAVE_VERSION = "0.1.18";
const WEAVE_VERSION = "0.1.19";
const STORAGE_FORMAT_VERSION = 2;
const PREFS_BRANCH = "extensions.weave.";

View File

@ -129,13 +129,13 @@ WeaveCrypto.prototype = {
_opensslPBE: function Crypto__openssl(op, algorithm, input, password) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.write(input, input.length);
inputFOS.writeString(input);
inputFOS.close();
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.write(password, password.length);
passFOS.writeString(password);
passFOS.close();
try {
@ -205,7 +205,7 @@ WeaveCrypto.prototype = {
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.write(password, password.length);
passFOS.writeString(password);
passFOS.close();
try {
@ -237,12 +237,12 @@ WeaveCrypto.prototype = {
_opensslRSAEncrypt: function Crypto__opensslRSAEncrypt(input, pubkey) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.write(input, input.length);
inputFOS.writeString(input);
inputFOS.close();
let keyFile = Utils.getTmp("key");
let [keyFOS] = Utils.open(keyFile, ">");
keyFOS.write(pubkey, pubkey.length);
keyFOS.writeString(pubkey);
keyFOS.close();
let outputFile = Utils.getTmp("output");
@ -264,12 +264,12 @@ WeaveCrypto.prototype = {
_opensslRSADecrypt: function Crypto__opensslRSADecrypt(input, privkey, password) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.write(input, input.length);
inputFOS.writeString(input);
inputFOS.close();
let keyFile = Utils.getTmp("key");
let [keyFOS] = Utils.open(keyFile, ">");
keyFOS.write(privkey, privkey.length);
keyFOS.writeString(privkey);
keyFOS.close();
let outputFile = Utils.getTmp("output");
@ -279,7 +279,7 @@ WeaveCrypto.prototype = {
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.write(password, password.length);
passFOS.writeString(password);
passFOS.close();
try {

View File

@ -114,7 +114,7 @@ DAVCollection.prototype = {
this._authProvider._authFailed = false;
request.channel.notificationCallbacks = this._authProvider;
request.send(data);
let event = yield;
ret = event.target;

View File

@ -79,6 +79,14 @@ Engine.prototype = {
return this.__os;
},
__json: null,
get _json() {
if (!this.__json)
this.__json = Cc["@mozilla.org/dom/json;1"].
createInstance(Ci.nsIJSON);
return this.__json;
},
// _core, and _store need to be overridden in subclasses
__core: null,
get _core() {
@ -104,7 +112,7 @@ Engine.prototype = {
this.__snapshot = value;
},
_init: function BmkEngine__init(davCollection, cryptoId) {
_init: function Engine__init(davCollection, cryptoId) {
this._dav = davCollection;
this._cryptoId = cryptoId;
this._log = Log4Moz.Service.getLogger("Service." + this.logName);
@ -112,13 +120,16 @@ Engine.prototype = {
this._snapshot.load();
},
_checkStatus: function BmkEngine__checkStatus(code, msg, ok404) {
if (code >= 200 && code < 300)
return;
if (ok404 && code == 404)
return;
this._log.error(msg + " Error code: " + code);
throw 'checkStatus failed';
_serializeCommands: function Engine__serializeCommands(commands) {
let json = this._json.encode(commands);
//json = json.replace(/ {action/g, "\n {action");
return json;
},
_serializeConflicts: function Engine__serializeConflicts(conflicts) {
let json = this._json.encode(conflicts);
//json = json.replace(/ {action/g, "\n {action");
return json;
},
_resetServer: function Engine__resetServer(onComplete) {
@ -149,12 +160,12 @@ Engine.prototype = {
this._dav.unlock.async(this._dav, cont);
let unlocked = yield;
this._checkStatus(statusResp.status,
"Could not delete status file.", true);
this._checkStatus(snapshotResp.status,
"Could not delete snapshot file.", true);
this._checkStatus(deltasResp.status,
"Could not delete deltas file.", true);
Utils.checkStatus(statusResp.status,
"Could not delete status file.", [[200,300],404]);
Utils.checkStatus(snapshotResp.status,
"Could not delete snapshot file.", [[200,300],404]);
Utils.checkStatus(deltasResp.status,
"Could not delete deltas file.", [[200,300],404]);
this._log.debug("Server files deleted");
done = true;
@ -255,7 +266,7 @@ Engine.prototype = {
// Before we get started, make sure we have a remote directory to play in
this._dav.MKCOL(this.serverPrefix, cont);
let ret = yield;
this._checkStatus(ret.status, "Could not create remote folder.");
Utils.checkStatus(ret.status, "Could not create remote folder.");
// 1) Fetch server deltas
this._getServerData.async(this, cont);
@ -279,9 +290,9 @@ Engine.prototype = {
this._core.detectUpdates(cont, this._snapshot.data, localJson.data);
let localUpdates = yield;
this._log.debug("local json:\n" + localJson.serialize());
this._log.debug("Local updates: " + serializeCommands(localUpdates));
this._log.debug("Server updates: " + serializeCommands(server.updates));
this._log.trace("local json:\n" + localJson.serialize());
this._log.trace("Local updates: " + this._serializeCommands(localUpdates));
this._log.trace("Server updates: " + this._serializeCommands(server.updates));
if (server.updates.length == 0 && localUpdates.length == 0) {
this._snapshot.version = server.maxVersion;
@ -305,10 +316,10 @@ Engine.prototype = {
this._log.info("Predicted changes for server: " + serverChanges.length);
this._log.info("Client conflicts: " + clientConflicts.length);
this._log.info("Server conflicts: " + serverConflicts.length);
this._log.debug("Changes for client: " + serializeCommands(clientChanges));
this._log.debug("Predicted changes for server: " + serializeCommands(serverChanges));
this._log.debug("Client conflicts: " + serializeConflicts(clientConflicts));
this._log.debug("Server conflicts: " + serializeConflicts(serverConflicts));
this._log.trace("Changes for client: " + this._serializeCommands(clientChanges));
this._log.trace("Predicted changes for server: " + this._serializeCommands(serverChanges));
this._log.trace("Client conflicts: " + this._serializeConflicts(clientConflicts));
this._log.trace("Server conflicts: " + this._serializeConflicts(serverConflicts));
if (!(clientChanges.length || serverChanges.length ||
clientConflicts.length || serverConflicts.length)) {
@ -324,7 +335,7 @@ Engine.prototype = {
this._log.warn("Conflicts found! Discarding server changes");
}
let savedSnap = eval(uneval(this._snapshot.data));
let savedSnap = Utils.deepCopy(this._snapshot.data);
let savedVersion = this._snapshot.version;
let newSnapshot;
@ -346,9 +357,9 @@ Engine.prototype = {
this._log.warn("Commands did not apply correctly");
this._log.debug("Diff from snapshot+commands -> " +
"new snapshot after commands:\n" +
serializeCommands(diff));
this._serializeCommands(diff));
// FIXME: do we really want to revert the snapshot here?
this._snapshot.data = eval(uneval(savedSnap));
this._snapshot.data = Utils.deepCopy(savedSnap);
this._snapshot.version = savedVersion;
}
this._snapshot.save();
@ -372,7 +383,7 @@ Engine.prototype = {
this._log.info("Actual changes for server: " + serverDelta.length);
this._log.debug("Actual changes for server: " +
serializeCommands(serverDelta));
this._serializeCommands(serverDelta));
if (serverDelta.length) {
this._log.info("Uploading changes to server");
@ -391,7 +402,7 @@ Engine.prototype = {
} else {
Crypto.PBEencrypt.async(Crypto, cont,
serializeCommands(server.deltas),
this._serializeCommands(server.deltas),
this._cryptoId);
let data = yield;
this._dav.PUT(this.deltasFile, data, cont);
@ -402,13 +413,14 @@ Engine.prototype = {
c++;
this._dav.PUT(this.statusFile,
uneval({GUID: this._snapshot.GUID,
formatVersion: STORAGE_FORMAT_VERSION,
snapVersion: server.snapVersion,
maxVersion: this._snapshot.version,
snapEncryption: server.snapEncryption,
deltasEncryption: Crypto.defaultAlgorithm,
itemCount: c}), cont);
this._json.encode(
{GUID: this._snapshot.GUID,
formatVersion: STORAGE_FORMAT_VERSION,
snapVersion: server.snapVersion,
maxVersion: this._snapshot.version,
snapEncryption: server.snapEncryption,
deltasEncryption: Crypto.defaultAlgorithm,
itemCount: c}), cont);
let statusPut = yield;
if (deltasPut.status >= 200 && deltasPut.status < 300 &&
@ -488,7 +500,7 @@ Engine.prototype = {
case 200: {
this._log.info("Got status file from server");
let status = eval(resp.responseText);
let status = this._json.decode(resp.responseText);
let deltas, allDeltas;
let snap = new SnapshotStore();
@ -523,56 +535,56 @@ Engine.prototype = {
this._log.info("Downloading server snapshot");
this._dav.GET(this.snapshotFile, cont);
resp = yield;
this._checkStatus(resp.status, "Could not download snapshot.");
Utils.checkStatus(resp.status, "Could not download snapshot.");
Crypto.PBEdecrypt.async(Crypto, cont,
resp.responseText,
this._cryptoId,
status.snapEncryption);
let data = yield;
snap.data = eval(data);
snap.data = this._json.decode(data);
this._log.info("Downloading server deltas");
this._dav.GET(this.deltasFile, cont);
resp = yield;
this._checkStatus(resp.status, "Could not download deltas.");
Utils.checkStatus(resp.status, "Could not download deltas.");
Crypto.PBEdecrypt.async(Crypto, cont,
resp.responseText,
this._cryptoId,
status.deltasEncryption);
data = yield;
allDeltas = eval(data);
deltas = eval(data);
allDeltas = this._json.decode(data);
deltas = this._json.decode(data);
} else if (this._snapshot.version >= status.snapVersion &&
this._snapshot.version < status.maxVersion) {
snap.data = eval(uneval(this._snapshot.data));
snap.data = Utils.deepCopy(this._snapshot.data);
this._log.info("Downloading server deltas");
this._dav.GET(this.deltasFile, cont);
resp = yield;
this._checkStatus(resp.status, "Could not download deltas.");
Utils.checkStatus(resp.status, "Could not download deltas.");
Crypto.PBEdecrypt.async(Crypto, cont,
resp.responseText,
this._cryptoId,
status.deltasEncryption);
let data = yield;
allDeltas = eval(data);
allDeltas = this._json.decode(data);
deltas = allDeltas.slice(this._snapshot.version - status.snapVersion);
} else if (this._snapshot.version == status.maxVersion) {
snap.data = eval(uneval(this._snapshot.data));
snap.data = Utils.deepCopy(this._snapshot.data);
// FIXME: could optimize this case by caching deltas file
this._log.info("Downloading server deltas");
this._dav.GET(this.deltasFile, cont);
resp = yield;
this._checkStatus(resp.status, "Could not download deltas.");
Utils.checkStatus(resp.status, "Could not download deltas.");
Crypto.PBEdecrypt.async(Crypto, cont,
resp.responseText,
this._cryptoId,
status.deltasEncryption);
let data = yield;
allDeltas = eval(data);
allDeltas = this._json.decode(data);
deltas = [];
} else { // this._snapshot.version > status.maxVersion
@ -617,7 +629,7 @@ Engine.prototype = {
ret.snapVersion = this._snapshot.version;
ret.snapEncryption = Crypto.defaultAlgorithm;
ret.deltasEncryption = Crypto.defaultAlgorithm;
ret.snapshot = eval(uneval(this._snapshot.data));
ret.snapshot = Utils.deepCopy(this._snapshot.data);
ret.deltas = [];
ret.updates = [];
} break;
@ -651,26 +663,27 @@ Engine.prototype = {
let data = yield;
this._dav.PUT(this.snapshotFile, data, cont);
resp = yield;
this._checkStatus(resp.status, "Could not upload snapshot.");
Utils.checkStatus(resp.status, "Could not upload snapshot.");
this._dav.PUT(this.deltasFile, uneval([]), cont);
this._dav.PUT(this.deltasFile, "[]", cont);
resp = yield;
this._checkStatus(resp.status, "Could not upload deltas.");
Utils.checkStatus(resp.status, "Could not upload deltas.");
let c = 0;
for (GUID in this._snapshot.data)
c++;
this._dav.PUT(this.statusFile,
uneval({GUID: this._snapshot.GUID,
formatVersion: STORAGE_FORMAT_VERSION,
snapVersion: this._snapshot.version,
maxVersion: this._snapshot.version,
snapEncryption: Crypto.defaultAlgorithm,
deltasEncryption: "none",
itemCount: c}), cont);
this._json.encode(
{GUID: this._snapshot.GUID,
formatVersion: STORAGE_FORMAT_VERSION,
snapVersion: this._snapshot.version,
maxVersion: this._snapshot.version,
snapEncryption: Crypto.defaultAlgorithm,
deltasEncryption: "none",
itemCount: c}), cont);
resp = yield;
this._checkStatus(resp.status, "Could not upload status file.");
Utils.checkStatus(resp.status, "Could not upload status file.");
this._log.info("Full upload to server successful");
ret = true;
@ -746,15 +759,3 @@ HistoryEngine.prototype = {
}
};
HistoryEngine.prototype.__proto__ = new Engine();
serializeCommands: function serializeCommands(commands) {
let json = uneval(commands);
json = json.replace(/ {action/g, "\n {action");
return json;
}
serializeConflicts: function serializeConflicts(conflicts) {
let json = uneval(conflicts);
json = json.replace(/ {action/g, "\n {action");
return json;
}

View File

@ -58,6 +58,14 @@ function Store() {
Store.prototype = {
_logName: "Store",
__json: null,
get _json() {
if (!this.__json)
this.__json = Cc["@mozilla.org/dom/json;1"].
createInstance(Ci.nsIJSON);
return this.__json;
},
_init: function Store__init() {
this._log = Log4Moz.Service.getLogger("Service." + this._logName);
},
@ -65,7 +73,7 @@ Store.prototype = {
applyCommands: function Store_applyCommands(commandList) {
for (var i = 0; i < commandList.length; i++) {
var command = commandList[i];
this._log.debug("Processing command: " + uneval(command));
this._log.debug("Processing command: " + this._json.encode(command));
switch (command["action"]) {
case "create":
this._createCommand(command);
@ -143,7 +151,7 @@ SnapshotStore.prototype = {
},
_createCommand: function SStore__createCommand(command) {
this._data[command.GUID] = eval(uneval(command.data));
this._data[command.GUID] = Utils.deepCopy(command.data);
},
_removeCommand: function SStore__removeCommand(command) {
@ -178,27 +186,18 @@ SnapshotStore.prototype = {
file.QueryInterface(Ci.nsILocalFile);
file.append("weave");
if (!file.exists())
file.create(file.DIRECTORY_TYPE, PERMS_DIRECTORY);
file.append("snapshots");
if (!file.exists())
file.create(file.DIRECTORY_TYPE, PERMS_DIRECTORY);
file.append(this.filename);
if (!file.exists())
file.create(file.NORMAL_FILE_TYPE, PERMS_FILE);
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
fos.init(file, flags, PERMS_FILE, 0);
let out = {version: this.version,
GUID: this.GUID,
snapshot: this.data};
out = uneval(out);
fos.write(out, out.length);
out = this._json.encode(out);
let [fos] = Utils.open(file, ">");
fos.writeString(out);
fos.close();
},
@ -211,19 +210,10 @@ SnapshotStore.prototype = {
if (!file.exists())
return;
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fis.init(file, MODE_RDONLY, PERMS_FILE, 0);
fis.QueryInterface(Ci.nsILineInputStream);
let json = "";
while (fis.available()) {
let ret = {};
fis.readLine(ret);
json += ret.value;
}
fis.close();
json = eval(json);
let [is] = Utils.open(file, "<");
let json = Utils.readStream(is);
is.close();
json = this._json.decode(json);
if (json && 'snapshot' in json && 'version' in json && 'GUID' in json) {
this._log.info("Read saved snapshot from disk");
@ -234,7 +224,7 @@ SnapshotStore.prototype = {
},
serialize: function SStore_serialize() {
let json = uneval(this.data);
let json = this._json.encode(this.data);
json = json.replace(/:{type/g, ":\n\t{type");
json = json.replace(/}, /g, "},\n ");
json = json.replace(/, parentGUID/g, ",\n\t parentGUID");

View File

@ -388,7 +388,8 @@ BookmarksSyncCore.prototype = {
return true;
return false;
default:
this._log.error("commandLike: Unknown item type: " + uneval(a));
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
this._log.error("commandLike: Unknown item type: " + json.encode(a));
return false;
}
}

View File

@ -57,25 +57,79 @@ let Utils = {
if (!a || !b)
return false;
// if neither is an object, just use ==
if (typeof(a) != "object" && typeof(b) != "object")
return a == b;
// check if only one of them is an object
if (typeof(a) != "object" || typeof(b) != "object")
return false;
for (let key in a) {
if (typeof(a[key]) == "object") {
if (!typeof(b[key]) == "object")
if (a instanceof Array)
dump("a is an array\n");
if (b instanceof Array)
dump("b is an array\n");
if (a instanceof Array && b instanceof Array) {
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (!Utils.deepEquals(a[i], b[i]))
return false;
}
} else {
// check if only one of them is an array
if (a instanceof Array || b instanceof Array)
return false;
for (let key in a) {
if (!Utils.deepEquals(a[key], b[key]))
return false;
} else {
if (a[key] != b[key])
return false;
}
}
return true;
},
deepCopy: function Weave_deepCopy(thing) {
if (typeof(thing) != "object" || thing == null)
return thing;
let ret;
if (thing instanceof Array) {
dump("making a cipy of an array!\n\n");
ret = [];
for (let i = 0; i < thing.length; i++)
ret.push(Utils.deepCopy(thing[i]));
} else {
ret = {};
for (let key in thing)
ret[key] = Utils.deepCopy(thing[key]);
}
return ret;
},
checkStatus: function Weave_checkStatus(code, msg, ranges) {
if (!ranges)
ranges = [[200,300]];
for (let i = 0; i < ranges.length; i++) {
rng = ranges[i];
if (typeof(rng) == "object" && code >= rng[0] && code < rng[1])
return;
else if (typeof(rng) == "integer" && code == rng)
return;
}
let log = Log4Moz.Service.getLogger("Service.Util");
log.error(msg + " Error code: " + code);
throw 'checkStatus failed';
},
makeURI: function Weave_makeURI(URIString) {
if (URIString === null || URIString == "")
return null;
@ -224,21 +278,28 @@ let Utils = {
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fis.init(file, MODE_RDONLY, perms, 0);
stream = Cc["@mozilla.org/scriptableinputstream;1"].
createInstance(Ci.nsIScriptableInputStream);
stream.init(fis);
stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
stream.init(fis, "UTF-8", 4096,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
} break;
case ">": {
stream = Cc["@mozilla.org/network/file-output-stream;1"].
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0);
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0);
stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
stream.init(fos, "UTF-8", 4096, 0x0000);
} break;
case ">>": {
stream = Cc["@mozilla.org/network/file-output-stream;1"].
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0);
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0);
stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
stream.init(fos, "UTF-8", 4096, 0x0000);
} break;
default:
@ -248,13 +309,11 @@ let Utils = {
return [stream, file];
},
// assumes an nsIScriptableInputStream
// assumes an nsIConverterInputStream
readStream: function Weave_readStream(is) {
let ret = "";
let chunk = is.read(4096);
while (chunk.length > 0) {
ret += chunk;
chunk = is.read(4096);
let ret = "", str = {};
while (is.readString(4096, str) != 0) {
ret += str.value;
}
return ret;
},