From 99333d3c987ba622de6cbf5187707780c51cb0bf Mon Sep 17 00:00:00 2001 From: Dan Mills Date: Fri, 7 Mar 2008 01:56:36 -0800 Subject: [PATCH] Asynchronous generator helpers rework + PKI work * Async helpers are in a module of their own now * Async routines have simpler semantics now. onComplete handlers are taken care of by the helpers. Exceptions are bubbled up across nested asynchronous generators * Stack traces are automatically logged for unhandled exceptions * Async generators are now allowed to 'bottom out' (StopIteration is ignored) - this is configurable. * RSA key generation fixes * On login we now create an RSA keypair, encrypt the private one with PBE, and upload them to the server * Log files are now limited to 2MB (down from 5) --- services/sync/modules/crypto.js | 175 +++++----- services/sync/modules/dav.js | 460 ++++++++++++-------------- services/sync/modules/engines.js | 501 ++++++++++++++--------------- services/sync/modules/identity.js | 4 + services/sync/modules/log4moz.js | 2 +- services/sync/modules/service.js | 171 +++++----- services/sync/modules/syncCores.js | 29 +- services/sync/modules/util.js | 101 ++---- 8 files changed, 681 insertions(+), 762 deletions(-) diff --git a/services/sync/modules/crypto.js b/services/sync/modules/crypto.js index 5c12df48af79..185e9079cba9 100644 --- a/services/sync/modules/crypto.js +++ b/services/sync/modules/crypto.js @@ -45,8 +45,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://weave/log4moz.js"); Cu.import("resource://weave/constants.js"); Cu.import("resource://weave/util.js"); +Cu.import("resource://weave/async.js"); -Function.prototype.async = Utils.generatorAsync; +Function.prototype.async = Async.sugar; function WeaveCrypto() { this._init(); @@ -210,7 +211,7 @@ WeaveCrypto.prototype = { try { this._openssl("pkcs8", "-in", "privkey.pem", "-out", "enckey.pem", - "-topk8", "-v2", algorithm, "-pass", "file:pass"); + "-topk8", "-v2", algorithm, "-passout", "file:pass"); } catch (e) { throw e; @@ -223,7 +224,7 @@ WeaveCrypto.prototype = { let [cryptedKeyFIS] = Utils.open(cryptedKeyF, "<"); let cryptedKey = Utils.readStream(cryptedKeyFIS); cryptedKeyFIS.close(); - cryptedKey.remove(false); + cryptedKeyF.remove(false); let [pubKeyFIS] = Utils.open(pubKeyF, "<"); let pubKey = Utils.readStream(pubKeyFIS); @@ -234,7 +235,7 @@ WeaveCrypto.prototype = { }, // returns 'input' encrypted with the 'pubkey' public RSA key - _opensslRSAEncrypt: function Crypto__opensslRSAEncrypt(input, pubkey) { + _opensslRSAencrypt: function Crypto__opensslRSAencrypt(input, pubkey) { let inputFile = Utils.getTmp("input"); let [inputFOS] = Utils.open(inputFile, ">"); inputFOS.writeString(input); @@ -261,7 +262,7 @@ WeaveCrypto.prototype = { }, // returns 'input' decrypted with the 'privkey' private RSA key and password - _opensslRSADecrypt: function Crypto__opensslRSADecrypt(input, privkey, password) { + _opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, privkey, password) { let inputFile = Utils.getTmp("input"); let [inputFOS] = Utils.open(inputFile, ">"); inputFOS.writeString(input); @@ -338,131 +339,127 @@ WeaveCrypto.prototype = { // Crypto - PBEencrypt: function Crypto_PBEencrypt(onComplete, data, identity, algorithm) { - let [self, cont] = yield; - let listener = new Utils.EventListener(cont); - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + PBEencrypt: function Crypto_PBEencrypt(data, identity, algorithm) { + let self = yield; let ret; - try { - if (!algorithm) - algorithm = this.defaultAlgorithm; + if (!algorithm) + algorithm = this.defaultAlgorithm; - if (algorithm != "none") - this._log.debug("Encrypting data"); + if (algorithm != "none") + this._log.debug("Encrypting data"); - switch (algorithm) { - case "none": - ret = data; - break; + switch (algorithm) { + case "none": + ret = data; + break; - case "XXXTEA": // Weave 0.1.12.10 and below had this typo - case "XXTEA": { - let gen = this._xxtea.encrypt(data, identity.password); - ret = gen.next(); + case "XXXTEA": // Weave 0.1.12.10 and below had this typo + case "XXTEA": { + let gen = this._xxtea.encrypt(data, identity.password); + ret = gen.next(); + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + try { while (typeof(ret) == "object") { - timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT); + timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT); yield; // Yield to main loop ret = gen.next(); } gen.close(); - } break; - - case "aes-128-cbc": - case "aes-192-cbc": - case "aes-256-cbc": - case "bf-cbc": - case "des-ede3-cbc": - ret = this._opensslPBE("-e", algorithm, data, identity.password); - break; - - default: - throw "Unknown encryption algorithm: " + algorithm; + } finally { + timer = null; } + } break; - if (algorithm != "none") - this._log.debug("Done encrypting data"); + case "aes-128-cbc": + case "aes-192-cbc": + case "aes-256-cbc": + case "bf-cbc": + case "des-ede3-cbc": + ret = this._opensslPBE("-e", algorithm, data, identity.password); + break; - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - timer = null; - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + default: + throw "Unknown encryption algorithm: " + algorithm; } - this._log.warn("generator not properly closed"); + + if (algorithm != "none") + this._log.debug("Done encrypting data"); + + self.done(ret); }, - PBEdecrypt: function Crypto_PBEdecrypt(onComplete, data, identity, algorithm) { - let [self, cont] = yield; - let listener = new Utils.EventListener(cont); - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + PBEdecrypt: function Crypto_PBEdecrypt(data, identity, algorithm) { + let self = yield; let ret; - try { - if (!algorithm) - algorithm = this.defaultAlgorithm; + if (!algorithm) + algorithm = this.defaultAlgorithm; - if (algorithm != "none") - this._log.debug("Decrypting data"); + if (algorithm != "none") + this._log.debug("Decrypting data"); - switch (algorithm) { - case "none": - ret = data; - break; + switch (algorithm) { + case "none": + ret = data; + break; - case "XXXTEA": // Weave 0.1.12.10 and below had this typo - case "XXTEA": { - let gen = this._xxtea.decrypt(data, identity.password); - ret = gen.next(); + case "XXXTEA": // Weave 0.1.12.10 and below had this typo + case "XXTEA": { + let gen = this._xxtea.decrypt(data, identity.password); + ret = gen.next(); + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + try { while (typeof(ret) == "object") { - timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT); + timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT); yield; // Yield to main loop ret = gen.next(); } gen.close(); - } break; - - case "aes-128-cbc": - case "aes-192-cbc": - case "aes-256-cbc": - case "bf-cbc": - case "des-ede3-cbc": - ret = this._opensslPBE("-d", algorithm, data, identity.password); - break; - - default: - throw "Unknown encryption algorithm: " + algorithm; + } finally { + timer = null; } + } break; - if (algorithm != "none") - this._log.debug("Done decrypting data"); + case "aes-128-cbc": + case "aes-192-cbc": + case "aes-256-cbc": + case "bf-cbc": + case "des-ede3-cbc": + ret = this._opensslPBE("-d", algorithm, data, identity.password); + break; - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - timer = null; - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + default: + throw "Unknown encryption algorithm: " + algorithm; } - this._log.warn("generator not properly closed"); + + if (algorithm != "none") + this._log.debug("Done decrypting data"); + + self.done(ret); }, PBEkeygen: function Crypto_PBEkeygen() { - return this._opensslRand(); + let self = yield; + let ret = this._opensslRand(); + self.done(ret); }, RSAkeygen: function Crypto_RSAkeygen(password) { - return this._opensslRSAKeyGen(password); + let self = yield; + let ret = this._opensslRSAKeyGen(password); + self.done(ret); }, RSAencrypt: function Crypto_RSAencrypt(data, key) { - return this._opensslRSAEncrypt(data, key); + let self = yield; + let ret = this._opensslRSAencrypt(data, key); + self.done(ret); }, RSAdecrypt: function Crypto_RSAdecrypt(data, key, password) { - return this._opensslRSADecrypt(data, key, password); + let self = yield; + let ret = this._opensslRSAdecrypt(data, key, password); + self.done(ret); } }; diff --git a/services/sync/modules/dav.js b/services/sync/modules/dav.js index 4d96ed5f19de..89beb84bbe53 100644 --- a/services/sync/modules/dav.js +++ b/services/sync/modules/dav.js @@ -44,8 +44,9 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://weave/log4moz.js"); Cu.import("resource://weave/util.js"); +Cu.import("resource://weave/async.js"); -Function.prototype.async = Utils.generatorAsync; +Function.prototype.async = Async.sugar; /* * DAV object @@ -80,58 +81,50 @@ DAVCollection.prototype = { return this._loggedIn; }, - _makeRequest: function DC__makeRequest(onComplete, op, path, headers, data) { - let [self, cont] = yield; + _makeRequest: function DC__makeRequest(op, path, headers, data) { + let self = yield; let ret; - try { - this._log.debug("Creating " + op + " request for " + this._baseURL + path); + this._log.debug("Creating " + op + " request for " + this._baseURL + path); + + let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); + request = request.QueryInterface(Ci.nsIDOMEventTarget); - let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); - request = request.QueryInterface(Ci.nsIDOMEventTarget); - - request.addEventListener("load", new Utils.EventListener(cont, "load"), false); - request.addEventListener("error", new Utils.EventListener(cont, "error"), false); - request = request.QueryInterface(Ci.nsIXMLHttpRequest); - request.open(op, this._baseURL + path, true); + request.addEventListener("load", new Utils.EventListener(self.cb, "load"), false); + request.addEventListener("error", new Utils.EventListener(self.cb, "error"), false); + request = request.QueryInterface(Ci.nsIXMLHttpRequest); + request.open(op, this._baseURL + path, true); - // Force cache validation - let channel = request.channel; - channel = channel.QueryInterface(Ci.nsIRequest); - let loadFlags = channel.loadFlags; - loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS; - channel.loadFlags = loadFlags; + // Force cache validation + let channel = request.channel; + channel = channel.QueryInterface(Ci.nsIRequest); + let loadFlags = channel.loadFlags; + loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS; + channel.loadFlags = loadFlags; - let key; - for (key in headers) { - if (key == 'Authorization') - this._log.debug("HTTP Header " + key + ": ***** (suppressed)"); - else - this._log.debug("HTTP Header " + key + ": " + headers[key]); - request.setRequestHeader(key, headers[key]); - } - - this._authProvider._authFailed = false; - request.channel.notificationCallbacks = this._authProvider; - - request.send(data); - let event = yield; - ret = event.target; - - if (this._authProvider._authFailed) - this._log.warn("_makeRequest: authentication failed"); - if (ret.status < 200 || ret.status >= 300) - this._log.warn("_makeRequest: got status " + ret.status); - - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + let key; + for (key in headers) { + if (key == 'Authorization') + this._log.debug("HTTP Header " + key + ": ***** (suppressed)"); + else + this._log.debug("HTTP Header " + key + ": " + headers[key]); + request.setRequestHeader(key, headers[key]); } - this._log.warn("generator not properly closed"); + + this._authProvider._authFailed = false; + request.channel.notificationCallbacks = this._authProvider; + + request.send(data); + let event = yield; + ret = event.target; + + if (this._authProvider._authFailed) + this._log.warn("_makeRequest: authentication failed"); + if (ret.status < 200 || ret.status >= 300) + this._log.warn("_makeRequest: got status " + ret.status); + + self.done(ret); }, get _defaultHeaders() { @@ -142,52 +135,52 @@ DAVCollection.prototype = { }, // mkdir -p - _mkcol: function DC__mkcol(path, onComplete) { - let [self, cont] = yield; - let ret; + _mkcol: function DC__mkcol(path) { + let self = yield; + let ok = true; try { let components = path.split('/'); let path2 = ''; - + for (let i = 0; i < components.length; i++) { - - // trailing slashes will cause an empty path component at the end - if (components[i] == '') - break; - - path2 = path2 + components[i]; - - // check if it exists first - this._makeRequest.async(this, cont, "GET", path2 + "/", this._defaultHeaders); - ret = yield; - if (!(ret.status == 404 || ret.status == 500)) { // FIXME: 500 is a services.m.c oddity - this._log.debug("Skipping creation of path " + path2 + - " (got status " + ret.status + ")"); - } else { - this._log.debug("Creating path: " + path2); - let gen = this._makeRequest.async(this, cont, "MKCOL", path2, - this._defaultHeaders); - ret = yield; - - if (ret.status != 201) { - this._log.debug(ret.responseText); - throw 'request failed: ' + ret.status; - } - } - - // add slash *after* the request, trailing slashes cause a 412! - path2 = path2 + "/"; + + // trailing slashes will cause an empty path component at the end + if (components[i] == '') + break; + + path2 = path2 + components[i]; + + // check if it exists first + this._makeRequest.async(this, self.cb, "GET", path2 + "/", this._defaultHeaders); + let ret = yield; + if (!(ret.status == 404 || ret.status == 500)) { // FIXME: 500 is a services.m.c oddity + this._log.debug("Skipping creation of path " + path2 + + " (got status " + ret.status + ")"); + } else { + this._log.debug("Creating path: " + path2); + this._makeRequest.async(this, self.cb, "MKCOL", path2, + this._defaultHeaders); + ret = yield; + + if (ret.status != 201) { + this._log.debug(ret.responseText); + throw 'request failed: ' + ret.status; + } + } + + // add slash *after* the request, trailing slashes cause a 412! + path2 = path2 + "/"; } } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + this._log.error("Could not create directory on server"); + this._log.error("Exception caught: " + (e.message? e.message : e) + + " - " + (e.location? e.location : "")); + ok = false; } - this._log.warn("generator not properly closed"); + + self.done(ok); }, GET: function DC_GET(path, onComplete) { @@ -206,7 +199,7 @@ DAVCollection.prototype = { }, MKCOL: function DC_MKCOL(path, onComplete) { - return this._mkcol.async(this, path, onComplete); + return this._mkcol.async(this, onComplete, path); }, PROPFIND: function DC_PROPFIND(path, data, onComplete) { @@ -233,37 +226,33 @@ DAVCollection.prototype = { // Login / Logout - login: function DC_login(onComplete, username, password) { - let [self, cont] = yield; + login: function DC_login(username, password) { + let self = yield; - try { - if (this._loggedIn) { - this._log.debug("Login requested, but already logged in"); - return; - } - - this._log.info("Logging in"); - - let URI = Utils.makeURI(this._baseURL); - this._auth = "Basic " + btoa(username + ":" + password); - - // Make a call to make sure it's working - this.GET("", cont); - let resp = yield; - - if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300) - return; - - this._loggedIn = true; - - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self, onComplete, this._loggedIn); - yield; // onComplete is responsible for closing the generator + if (this._loggedIn) { + this._log.debug("Login requested, but already logged in"); + self.done(true); + yield; } - this._log.warn("generator not properly closed"); + + this._log.info("Logging in"); + + let URI = Utils.makeURI(this._baseURL); + this._auth = "Basic " + btoa(username + ":" + password); + + // Make a call to make sure it's working + this.GET("", self.cb); + let resp = yield; + + if (this._authProvider._authFailed || + resp.status < 200 || resp.status >= 300) { + self.done(false); + yield; + } + + this._loggedIn = true; + + self.done(true); }, logout: function DC_logout() { @@ -274,170 +263,143 @@ DAVCollection.prototype = { // Locking - _getActiveLock: function DC__getActiveLock(onComplete) { - let [self, cont] = yield; + _getActiveLock: function DC__getActiveLock() { + let self = yield; let ret = null; - try { - this._log.info("Getting active lock token"); - this.PROPFIND("", - "" + - "" + - " " + - "", cont); - let resp = yield; + this._log.info("Getting active lock token"); + this.PROPFIND("", + "" + + "" + + " " + + "", self.cb); + let resp = yield; - if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300) - return; - - let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href'); - let token = tokens.iterateNext(); - ret = token.textContent; - - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - if (ret) - this._log.debug("Found an active lock token"); - else - this._log.debug("No active lock token found"); - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + if (this._authProvider._authFailed || + resp.status < 200 || resp.status >= 300) { + self.done(false); + yield; } - this._log.warn("generator not properly closed"); + + let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href'); + let token = tokens.iterateNext(); + ret = token.textContent; + + if (ret) + this._log.debug("Found an active lock token"); + else + this._log.debug("No active lock token found"); + self.done(ret); }, - lock: function DC_lock(onComplete) { - let [self, cont] = yield; + lock: function DC_lock() { + let self = yield; this._token = null; - try { - this._log.info("Acquiring lock"); + this._log.info("Acquiring lock"); - if (this._token) { - this._log.debug("Lock called, but we already hold a token"); - return; - } - - this.LOCK("", - "\n" + - "\n" + - " \n" + - " \n" + - "", cont); - let resp = yield; - - if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300) - return; - - let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href'); - let token = tokens.iterateNext(); - if (token) - this._token = token.textContent; - - } catch (e){ - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - if (this._token) - this._log.info("Lock acquired"); - else - this._log.warn("Could not acquire lock"); - Utils.generatorDone(this, self, onComplete, this._token); - yield; // onComplete is responsible for closing the generator + if (this._token) { + this._log.debug("Lock called, but we already hold a token"); + self.done(this._token); + yield; } - this._log.warn("generator not properly closed"); + + this.LOCK("", + "\n" + + "\n" + + " \n" + + " \n" + + "", self.cb); + let resp = yield; + + if (this._authProvider._authFailed || + resp.status < 200 || resp.status >= 300) { + self.done(this._token); + yield; + } + + let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href'); + let token = tokens.iterateNext(); + if (token) + this._token = token.textContent; + + if (this._token) + this._log.debug("Lock acquired"); + else + this._log.warn("Could not acquire lock"); + + self.done(this._token); }, - unlock: function DC_unlock(onComplete) { - let [self, cont] = yield; - try { - this._log.info("Releasing lock"); + unlock: function DC_unlock() { + let self = yield; - if (this._token === null) { - this._log.debug("Unlock called, but we don't hold a token right now"); - return; - } + this._log.info("Releasing lock"); - this.UNLOCK("", cont); - let resp = yield; - - if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300) - return; - - this._token = null; - - } catch (e){ - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - if (this._token) { - this._log.info("Could not release lock"); - Utils.generatorDone(this, self, onComplete, false); - } else { - this._log.info("Lock released (or we didn't have one)"); - Utils.generatorDone(this, self, onComplete, true); - } - yield; // onComplete is responsible for closing the generator + if (this._token === null) { + this._log.debug("Unlock called, but we don't hold a token right now"); + self.done(true); + yield; } - this._log.warn("generator not properly closed"); + + this.UNLOCK("", self.cb); + let resp = yield; + + if (this._authProvider._authFailed || + resp.status < 200 || resp.status >= 300) { + self.done(false); + yield; + } + + this._token = null; + + if (this._token) + this._log.info("Could not release lock"); + else + this._log.info("Lock released (or we didn't have one)"); + + self.done(!this._token); }, - forceUnlock: function DC_forceUnlock(onComplete) { - let [self, cont] = yield; + forceUnlock: function DC_forceUnlock() { + let self = yield; let unlocked = true; - try { - this._log.info("Forcibly releasing any server locks"); + this._log.info("Forcibly releasing any server locks"); - this._getActiveLock.async(this, cont); - this._token = yield; + this._getActiveLock.async(this, self.cb); + this._token = yield; - if (!this._token) { - this._log.info("No server lock found"); - return; - } - - this._log.info("Server lock found, unlocking"); - this.unlock.async(this, cont); - unlocked = yield; - - } catch (e){ - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - if (unlocked) - this._log.debug("Lock released"); - else - this._log.debug("No lock released"); - Utils.generatorDone(this, self, onComplete, unlocked); - yield; // onComplete is responsible for closing the generator + if (!this._token) { + this._log.info("No server lock found"); + self.done(true); + yield; } - this._log.warn("generator not properly closed"); + + this._log.info("Server lock found, unlocking"); + this.unlock.async(this, self.cb); + unlocked = yield; + + if (unlocked) + this._log.debug("Lock released"); + else + this._log.debug("No lock released"); + self.done(unlocked); }, - stealLock: function DC_stealLock(onComplete) { - let [self, cont] = yield; + stealLock: function DC_stealLock() { + let self = yield; let stolen = null; - try { - this.forceUnlock.async(this, cont); - let unlocked = yield; + this.forceUnlock.async(this, self.cb); + let unlocked = yield; - if (unlocked) { - this.lock.async(this, cont); - stolen = yield; - } - - } catch (e){ - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self, onComplete, stolen); - yield; // onComplete is responsible for closing the generator + if (unlocked) { + this.lock.async(this, self.cb); + stolen = yield; } - this._log.warn("generator not properly closed"); + + self.done(stolen); } }; diff --git a/services/sync/modules/engines.js b/services/sync/modules/engines.js index a695d0d45753..983d2c40b049 100644 --- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -48,8 +48,9 @@ Cu.import("resource://weave/util.js"); Cu.import("resource://weave/crypto.js"); Cu.import("resource://weave/stores.js"); Cu.import("resource://weave/syncCores.js"); +Cu.import("resource://weave/async.js"); -Function.prototype.async = Utils.generatorAsync; +Function.prototype.async = Async.sugar; let Crypto = new WeaveCrypto(); function Engine(davCollection, cryptoId) { @@ -68,6 +69,7 @@ Engine.prototype = { // These can be overridden in subclasses, but don't need to be (assuming // serverPrefix is not shared with anything else) get statusFile() { return this.serverPrefix + "status.json"; }, + get keysFile() { return this.serverPrefix + "keys.json"; }, get snapshotFile() { return this.serverPrefix + "snapshot.json"; }, get deltasFile() { return this.serverPrefix + "deltas.json"; }, @@ -120,6 +122,29 @@ Engine.prototype = { this._snapshot.load(); }, + _getSymKey: function Engine__getCryptoId(cryptoId) { + let self = yield; + let done = false; + + this._dav.GET(this.keysFile, self.cb); + let keysResp = yield; + Utils.ensureStatus(keysResp.status, + "Could not get keys file.", [[200,300]]); + let keys = keysResp.responseText; + + if (!keys[this._userHash]) { + this._log.error("Keyring does not contain a key for this user"); + return; + } + + //Crypto.RSAdecrypt.async(Crypto, self.cb, + // keys[this._userHash], + + self.done(done); + yield; + this._log.warn("_getSymKey generator not properly closed"); + }, + _serializeCommands: function Engine__serializeCommands(commands) { let json = this._json.encode(commands); //json = json.replace(/ {action/g, "\n {action"); @@ -132,64 +157,52 @@ Engine.prototype = { return json; }, - _resetServer: function Engine__resetServer(onComplete) { - let [self, cont] = yield; + _resetServer: function Engine__resetServer() { + let self = yield; let done = false; try { this._log.debug("Resetting server data"); this._os.notifyObservers(null, this._osPrefix + "reset-server:start", ""); - this._dav.lock.async(this._dav, cont); + this._dav.lock.async(this._dav, self.cb); let locked = yield; - if (locked) - this._log.debug("Lock acquired"); - else { - this._log.warn("Could not acquire lock, aborting server reset"); - return; - } + if (!locked) + throw "Could not acquire lock, aborting server reset"; // try to delete all 3, check status after - this._dav.DELETE(this.statusFile, cont); + this._dav.DELETE(this.statusFile, self.cb); let statusResp = yield; - this._dav.DELETE(this.snapshotFile, cont); + this._dav.DELETE(this.snapshotFile, self.cb); let snapshotResp = yield; - this._dav.DELETE(this.deltasFile, cont); + this._dav.DELETE(this.deltasFile, self.cb); let deltasResp = yield; - this._dav.unlock.async(this._dav, cont); + this._dav.unlock.async(this._dav, self.cb); let unlocked = yield; - 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]); + Utils.ensureStatus(statusResp.status, + "Could not delete status file.", [[200,300],404]); + Utils.ensureStatus(snapshotResp.status, + "Could not delete snapshot file.", [[200,300],404]); + Utils.ensureStatus(deltasResp.status, + "Could not delete deltas file.", [[200,300],404]); this._log.debug("Server files deleted"); done = true; + this._os.notifyObservers(null, this._osPrefix + "reset-server:success", ""); } catch (e) { - if (e != 'checkStatus failed') - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - if (done) { - this._log.debug("Server reset completed successfully"); - this._os.notifyObservers(null, this._osPrefix + "reset-server:success", ""); - } else { - this._log.debug("Server reset failed"); - this._os.notifyObservers(null, this._osPrefix + "reset-server:error", ""); - } - Utils.generatorDone(this, self, onComplete, done) - yield; // onComplete is responsible for closing the generator + this._log.error("Could not delete server files"); + this._os.notifyObservers(null, this._osPrefix + "reset-server:error", ""); + throw e; } - this._log.warn("generator not properly closed"); + + self.done(done) }, - _resetClient: function Engine__resetClient(onComplete) { - let [self, cont] = yield; + _resetClient: function Engine__resetClient() { + let self = yield; let done = false; try { @@ -201,7 +214,7 @@ Engine.prototype = { done = true; } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + throw e; } finally { if (done) { @@ -211,10 +224,8 @@ Engine.prototype = { this._log.debug("Client reset failed"); this._os.notifyObservers(null, this._osPrefix + "reset-client:error", ""); } - Utils.generatorDone(this, self, onComplete, done); - yield; // onComplete is responsible for closing the generator + self.done(done); } - this._log.warn("generator not properly closed"); }, // original @@ -245,15 +256,15 @@ Engine.prototype = { // 3.1) Apply local delta with server changes ("D") // 3.2) Append server delta to the delta file and upload ("C") - _sync: function BmkEngine__sync(onComplete) { - let [self, cont] = yield; + _sync: function BmkEngine__sync() { + let self = yield; let synced = false, locked = null; try { this._log.info("Beginning sync"); this._os.notifyObservers(null, this._osPrefix + "sync:start", ""); - this._dav.lock.async(this._dav, cont); + this._dav.lock.async(this._dav, self.cb); locked = yield; if (locked) @@ -264,12 +275,13 @@ Engine.prototype = { } // Before we get started, make sure we have a remote directory to play in - this._dav.MKCOL(this.serverPrefix, cont); + this._dav.MKCOL(this.serverPrefix, self.cb); let ret = yield; - Utils.checkStatus(ret.status, "Could not create remote folder."); + if (!ret) + throw "Could not create remote folder"; // 1) Fetch server deltas - this._getServerData.async(this, cont); + this._getServerData.async(this, self.cb); let server = yield; this._log.info("Local snapshot version: " + this._snapshot.version); @@ -287,7 +299,7 @@ Engine.prototype = { let localJson = new SnapshotStore(); localJson.data = this._store.wrap(); - this._core.detectUpdates(cont, this._snapshot.data, localJson.data); + this._core.detectUpdates(self.cb, this._snapshot.data, localJson.data); let localUpdates = yield; this._log.trace("local json:\n" + localJson.serialize()); @@ -304,7 +316,7 @@ Engine.prototype = { // 3) Reconcile client/server deltas and generate new deltas for them. this._log.info("Reconciling client/server updates"); - this._core.reconcile(cont, localUpdates, server.updates); + this._core.reconcile(self.cb, localUpdates, server.updates); ret = yield; let clientChanges = ret.propagations[0]; @@ -351,7 +363,7 @@ Engine.prototype = { this._store.applyCommands(clientChanges); newSnapshot = this._store.wrap(); - this._core.detectUpdates(cont, this._snapshot.data, newSnapshot); + this._core.detectUpdates(self.cb, this._snapshot.data, newSnapshot); let diff = yield; if (diff.length != 0) { this._log.warn("Commands did not apply correctly"); @@ -372,7 +384,7 @@ Engine.prototype = { // conflicts, it should be the same as what the resolver returned newSnapshot = this._store.wrap(); - this._core.detectUpdates(cont, server.snapshot, newSnapshot); + this._core.detectUpdates(self.cb, server.snapshot, newSnapshot); let serverDelta = yield; // Log an error if not the same @@ -395,17 +407,17 @@ Engine.prototype = { if (server.formatVersion != STORAGE_FORMAT_VERSION || this._encryptionChanged) { - this._fullUpload.async(this, cont); + this._fullUpload.async(this, self.cb); let status = yield; if (!status) this._log.error("Could not upload files to server"); // eep? } else { - Crypto.PBEencrypt.async(Crypto, cont, + Crypto.PBEencrypt.async(Crypto, self.cb, this._serializeCommands(server.deltas), this._cryptoId); let data = yield; - this._dav.PUT(this.deltasFile, data, cont); + this._dav.PUT(this.deltasFile, data, self.cb); let deltasPut = yield; let c = 0; @@ -420,7 +432,7 @@ Engine.prototype = { maxVersion: this._snapshot.version, snapEncryption: server.snapEncryption, deltasEncryption: Crypto.defaultAlgorithm, - itemCount: c}), cont); + itemCount: c}), self.cb); let statusPut = yield; if (deltasPut.status >= 200 && deltasPut.status < 300 && @@ -439,24 +451,22 @@ Engine.prototype = { synced = true; } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + throw e; } finally { let ok = false; if (locked) { - this._dav.unlock.async(this._dav, cont); + this._dav.unlock.async(this._dav, self.cb); ok = yield; } if (ok && synced) { this._os.notifyObservers(null, this._osPrefix + "sync:success", ""); - Utils.generatorDone(this, self, onComplete, true); + self.done(true); } else { this._os.notifyObservers(null, this._osPrefix + "sync:error", ""); - Utils.generatorDone(this, self, onComplete, false); + self.done(false); } - yield; // onComplete is responsible for closing the generator } - this._log.warn("generator not properly closed"); }, /* Get the deltas/combined updates from the server @@ -483,220 +493,205 @@ Engine.prototype = { * the relevant deltas (from our snapshot version to current), * combined into a single set. */ - _getServerData: function BmkEngine__getServerData(onComplete) { - let [self, cont] = yield; + _getServerData: function BmkEngine__getServerData() { + let self = yield; let ret = {status: -1, formatVersion: null, maxVersion: null, snapVersion: null, snapEncryption: null, deltasEncryption: null, snapshot: null, deltas: null, updates: null}; - try { - this._log.debug("Getting status file from server"); - this._dav.GET(this.statusFile, cont); - let resp = yield; - let status = resp.status; - - switch (status) { - case 200: { - this._log.info("Got status file from server"); - - let status = this._json.decode(resp.responseText); - let deltas, allDeltas; - let snap = new SnapshotStore(); - - // Bail out if the server has a newer format version than we can parse - if (status.formatVersion > STORAGE_FORMAT_VERSION) { - this._log.error("Server uses storage format v" + status.formatVersion + - ", this client understands up to v" + STORAGE_FORMAT_VERSION); - Utils.generatorDone(this, self, onComplete, ret) - return; - } + this._log.debug("Getting status file from server"); + this._dav.GET(this.statusFile, self.cb); + let resp = yield; + let status = resp.status; - if (status.formatVersion == 0) { - ret.snapEncryption = status.snapEncryption = "none"; - ret.deltasEncryption = status.deltasEncryption = "none"; - } - - if (status.GUID != this._snapshot.GUID) { - this._log.info("Remote/local sync GUIDs do not match. " + - "Forcing initial sync."); - this._log.debug("Remote: " + status.GUID); - this._log.debug("Local: " + this._snapshot.GUID); - this._store.resetGUIDs(); - this._snapshot.data = {}; - this._snapshot.version = -1; - this._snapshot.GUID = status.GUID; - } - - if (this._snapshot.version < status.snapVersion) { - if (this._snapshot.version >= 0) - this._log.info("Local snapshot is out of date"); - - this._log.info("Downloading server snapshot"); - this._dav.GET(this.snapshotFile, cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not download snapshot."); - Crypto.PBEdecrypt.async(Crypto, cont, - resp.responseText, - this._cryptoId, - status.snapEncryption); - let data = yield; - snap.data = this._json.decode(data); + switch (status) { + case 200: { + this._log.info("Got status file from server"); - this._log.info("Downloading server deltas"); - this._dav.GET(this.deltasFile, cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not download deltas."); - Crypto.PBEdecrypt.async(Crypto, cont, - resp.responseText, - this._cryptoId, - status.deltasEncryption); - data = yield; - allDeltas = this._json.decode(data); - deltas = this._json.decode(data); - - } else if (this._snapshot.version >= status.snapVersion && - this._snapshot.version < status.maxVersion) { - snap.data = Utils.deepCopy(this._snapshot.data); - - this._log.info("Downloading server deltas"); - this._dav.GET(this.deltasFile, cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not download deltas."); - Crypto.PBEdecrypt.async(Crypto, cont, - resp.responseText, - this._cryptoId, - status.deltasEncryption); - let data = yield; - allDeltas = this._json.decode(data); - deltas = allDeltas.slice(this._snapshot.version - status.snapVersion); - - } else if (this._snapshot.version == status.maxVersion) { - snap.data = Utils.deepCopy(this._snapshot.data); + let status = this._json.decode(resp.responseText); + let deltas, allDeltas; + let snap = new SnapshotStore(); - // FIXME: could optimize this case by caching deltas file - this._log.info("Downloading server deltas"); - this._dav.GET(this.deltasFile, cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not download deltas."); - Crypto.PBEdecrypt.async(Crypto, cont, - resp.responseText, - this._cryptoId, - status.deltasEncryption); - let data = yield; - allDeltas = this._json.decode(data); - deltas = []; - - } else { // this._snapshot.version > status.maxVersion - this._log.error("Server snapshot is older than local snapshot"); - return; - } - - for (var i = 0; i < deltas.length; i++) { - snap.applyCommands(deltas[i]); - } - - ret.status = 0; - ret.formatVersion = status.formatVersion; - ret.maxVersion = status.maxVersion; - ret.snapVersion = status.snapVersion; - ret.snapEncryption = status.snapEncryption; - ret.deltasEncryption = status.deltasEncryption; - ret.snapshot = snap.data; - ret.deltas = allDeltas; - this._core.detectUpdates(cont, this._snapshot.data, snap.data); - ret.updates = yield; - } break; - - case 404: { - this._log.info("Server has no status file, Initial upload to server"); - - this._snapshot.data = this._store.wrap(); - this._snapshot.version = 0; - this._snapshot.GUID = null; // in case there are other snapshots out there - - this._fullUpload.async(this, cont); - let uploadStatus = yield; - if (!uploadStatus) - return; - - this._log.info("Initial upload to server successful"); - this._snapshot.save(); - - ret.status = 0; - ret.formatVersion = STORAGE_FORMAT_VERSION; - ret.maxVersion = this._snapshot.version; - ret.snapVersion = this._snapshot.version; - ret.snapEncryption = Crypto.defaultAlgorithm; - ret.deltasEncryption = Crypto.defaultAlgorithm; - ret.snapshot = Utils.deepCopy(this._snapshot.data); - ret.deltas = []; - ret.updates = []; - } break; - - default: - this._log.error("Could not get status file: unknown HTTP status code " + - status); + // Bail out if the server has a newer format version than we can parse + if (status.formatVersion > STORAGE_FORMAT_VERSION) { + this._log.error("Server uses storage format v" + status.formatVersion + + ", this client understands up to v" + STORAGE_FORMAT_VERSION); break; } - } catch (e) { - if (e != 'checkStatus failed' && - e != 'decrypt failed') - this._log.error("Exception caught: " + (e.message? e.message : e)); + if (status.formatVersion == 0) { + ret.snapEncryption = status.snapEncryption = "none"; + ret.deltasEncryption = status.deltasEncryption = "none"; + } - } finally { - Utils.generatorDone(this, self, onComplete, ret) - yield; // onComplete is responsible for closing the generator + if (status.GUID != this._snapshot.GUID) { + this._log.info("Remote/local sync GUIDs do not match. " + + "Forcing initial sync."); + this._log.debug("Remote: " + status.GUID); + this._log.debug("Local: " + this._snapshot.GUID); + this._store.resetGUIDs(); + this._snapshot.data = {}; + this._snapshot.version = -1; + this._snapshot.GUID = status.GUID; + } + + if (this._snapshot.version < status.snapVersion) { + this._log.trace("Local snapshot version < server snapVersion"); + + if (this._snapshot.version >= 0) + this._log.info("Local snapshot is out of date"); + + this._log.info("Downloading server snapshot"); + this._dav.GET(this.snapshotFile, self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not download snapshot."); + Crypto.PBEdecrypt.async(Crypto, self.cb, + resp.responseText, + this._cryptoId, + status.snapEncryption); + let data = yield; + snap.data = this._json.decode(data); + + this._log.info("Downloading server deltas"); + this._dav.GET(this.deltasFile, self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not download deltas."); + Crypto.PBEdecrypt.async(Crypto, self.cb, + resp.responseText, + this._cryptoId, + status.deltasEncryption); + data = yield; + allDeltas = this._json.decode(data); + deltas = this._json.decode(data); + + } else if (this._snapshot.version >= status.snapVersion && + this._snapshot.version < status.maxVersion) { + this._log.trace("Server snapVersion <= local snapshot version < server maxVersion"); + snap.data = Utils.deepCopy(this._snapshot.data); + + this._log.info("Downloading server deltas"); + this._dav.GET(this.deltasFile, self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not download deltas."); + Crypto.PBEdecrypt.async(Crypto, self.cb, + resp.responseText, + this._cryptoId, + status.deltasEncryption); + let data = yield; + allDeltas = this._json.decode(data); + deltas = allDeltas.slice(this._snapshot.version - status.snapVersion); + + } else if (this._snapshot.version == status.maxVersion) { + this._log.trace("Local snapshot version == server maxVersion"); + 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, self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not download deltas."); + Crypto.PBEdecrypt.async(Crypto, self.cb, + resp.responseText, + this._cryptoId, + status.deltasEncryption); + let data = yield; + allDeltas = this._json.decode(data); + deltas = []; + + } else { // this._snapshot.version > status.maxVersion + this._log.error("Server snapshot is older than local snapshot"); + break; + } + + for (var i = 0; i < deltas.length; i++) { + snap.applyCommands(deltas[i]); + } + + ret.status = 0; + ret.formatVersion = status.formatVersion; + ret.maxVersion = status.maxVersion; + ret.snapVersion = status.snapVersion; + ret.snapEncryption = status.snapEncryption; + ret.deltasEncryption = status.deltasEncryption; + ret.snapshot = snap.data; + ret.deltas = allDeltas; + this._core.detectUpdates(self.cb, this._snapshot.data, snap.data); + ret.updates = yield; + } break; + + case 404: { + this._log.info("Server has no status file, Initial upload to server"); + + this._snapshot.data = this._store.wrap(); + this._snapshot.version = 0; + this._snapshot.GUID = null; // in case there are other snapshots out there + + this._fullUpload.async(this, self.cb); + let uploadStatus = yield; + if (!uploadStatus) + break; + + this._log.info("Initial upload to server successful"); + this._snapshot.save(); + + ret.status = 0; + ret.formatVersion = STORAGE_FORMAT_VERSION; + ret.maxVersion = this._snapshot.version; + ret.snapVersion = this._snapshot.version; + ret.snapEncryption = Crypto.defaultAlgorithm; + ret.deltasEncryption = Crypto.defaultAlgorithm; + ret.snapshot = Utils.deepCopy(this._snapshot.data); + ret.deltas = []; + ret.updates = []; + } break; + + default: + this._log.error("Could not get status file: unknown HTTP status code " + + status); + break; } - this._log.warn("generator not properly closed"); + + self.done(ret) }, - _fullUpload: function Engine__fullUpload(onComplete) { - let [self, cont] = yield; + _fullUpload: function Engine__fullUpload() { + let self = yield; let ret = false; - try { - Crypto.PBEencrypt.async(Crypto, cont, - this._snapshot.serialize(), - this._cryptoId); - let data = yield; - this._dav.PUT(this.snapshotFile, data, cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not upload snapshot."); + let gen = Crypto.PBEencrypt.async(Crypto, self.cb, + this._snapshot.serialize(), + this._cryptoId); + let data = yield; + if (gen.failed) throw "Encryption failed."; + + this._dav.PUT(this.snapshotFile, data, self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not upload snapshot."); - this._dav.PUT(this.deltasFile, "[]", cont); - resp = yield; - Utils.checkStatus(resp.status, "Could not upload deltas."); + this._dav.PUT(this.deltasFile, "[]", self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not upload deltas."); - let c = 0; - for (GUID in this._snapshot.data) - c++; + let c = 0; + for (GUID in this._snapshot.data) + c++; - this._dav.PUT(this.statusFile, - 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; - Utils.checkStatus(resp.status, "Could not upload status file."); + this._dav.PUT(this.statusFile, + 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}), self.cb); + resp = yield; + Utils.ensureStatus(resp.status, "Could not upload status file."); - this._log.info("Full upload to server successful"); - ret = true; - - } catch (e) { - if (e != 'checkStatus failed') - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self, onComplete, ret) - yield; // onComplete is responsible for closing the generator - } - this._log.warn("generator not properly closed"); + this._log.info("Full upload to server successful"); + ret = true; + self.done(ret) }, sync: function Engine_sync(onComplete) { diff --git a/services/sync/modules/identity.js b/services/sync/modules/identity.js index 2cec498dd883..517b19ae4969 100644 --- a/services/sync/modules/identity.js +++ b/services/sync/modules/identity.js @@ -66,6 +66,10 @@ Identity.prototype = { get username() { return this._username; }, set username(value) { this._username = value; }, + _key: null, + get key() { return this._key; }, + set key(value) { this._key = value; }, + _password: null, get password() { if (this._password === null) diff --git a/services/sync/modules/log4moz.js b/services/sync/modules/log4moz.js index 3365d40a557c..3142ff24ca29 100644 --- a/services/sync/modules/log4moz.js +++ b/services/sync/modules/log4moz.js @@ -478,7 +478,7 @@ Log4MozService.prototype = { return new FileAppender(file, formatter); case "rotating": // FIXME: hardcoded constants - return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 5, 0); + return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 2, 0); default: dump("log4moz: unknown appender kind: " + kind); return; diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index 2850ec0b8ddf..1ae68d1d18ae 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -49,9 +49,10 @@ Cu.import("resource://weave/crypto.js"); Cu.import("resource://weave/engines.js"); Cu.import("resource://weave/dav.js"); Cu.import("resource://weave/identity.js"); +Cu.import("resource://weave/async.js"); - -Function.prototype.async = Utils.generatorAsync; +Function.prototype.async = Async.sugar; +let Crypto = new WeaveCrypto(); /* * Service singleton @@ -303,79 +304,103 @@ WeaveSyncService.prototype = { this._os.notifyObservers(null, "weave:service-unlock:success", ""); }, - _login: function WeaveSync__login(onComplete) { - let [self, cont] = yield; - let success = false; + _login: function WeaveSync__login() { + let self = yield; try { this._log.debug("Logging in"); this._os.notifyObservers(null, "weave:service-login:start", ""); - if (!this.username) { - this._log.warn("No username set, login failed"); - return; - } - if (!this.password) { - this._log.warn("No password given or found in password manager"); - return; - } + if (!this.username) + throw "No username set, login failed"; + if (!this.password) + throw "No password given or found in password manager"; let serverURL = this._prefs.getCharPref("serverURL"); this._dav.baseURL = serverURL + "user/" + this.userPath + "/"; this._log.info("Using server URL: " + this._dav.baseURL); - this._dav.login.async(this._dav, cont, this.username, this.password); - success = yield; + this._dav.login.async(this._dav, self.cb, this.username, this.password); + let success = yield; // FIXME: we want to limit this to when we get a 404! if (!success) { this._log.debug("Attempting to create user directory"); this._dav.baseURL = serverURL; - this._dav.MKCOL("user/" + this.userPath, cont); + this._dav.MKCOL("user/" + this.userPath, self.cb); let ret = yield; + if (!ret) + throw "Could not create user directory. Got status: " + ret.status; - if (ret.status == 201) { - this._log.debug("Successfully created user directory. Re-attempting login."); - this._dav.baseURL = serverURL + "user/" + this.userPath + "/"; - this._dav.login.async(this._dav, cont, this.username, this.password); - success = yield; - } else { - this._log.debug("Could not create user directory. Got status: " + ret.status); - } + this._log.debug("Successfully created user directory. Re-attempting login."); + this._dav.baseURL = serverURL + "user/" + this.userPath + "/"; + this._dav.login.async(this._dav, self.cb, this.username, this.password); + success = yield; + if (!success) + throw "Created user directory, but login still failed. Aborting."; } + this._dav.GET("private/privkey", self.cb); + let keyResp = yield; + Utils.ensureStatus(keyResp.status, + "Could not get private key from server", [[200,300],404]); + + if (keyResp.status != 404) { + this._cryptoId.key = keyResp.responseText; + + } else { + // generate a new key + this._log.debug("Generating new RSA key"); + Crypto.RSAkeygen.async(Crypto, self.cb, this._cryptoId.password); + let [privkey, pubkey] = yield; + + this._cryptoId.key = privkey; + + this._dav.MKCOL("private/", self.cb); + ret = yield; + if (!ret) + throw "Could not create private key directory"; + + this._dav.MKCOL("public/", self.cb); + ret = yield; + if (!ret) + throw "Could not create public key directory"; + + this._dav.PUT("private/privkey", privkey, self.cb); + ret = yield; + Utils.ensureStatus(ret.status, "Could not upload private key"); + + this._dav.PUT("public/pubkey", pubkey, self.cb); + ret = yield; + Utils.ensureStatus(ret.status, "Could not upload public key"); + } + + this._passphrase = null; + this._os.notifyObservers(null, "weave:service-login:success", ""); + self.done(true); + } catch (e) { - this._log.error("Exception caught: " + e.message); - - } finally { - this._passphrase = null; - if (success) { - //this._log.debug("Login successful"); // chrome prints this too, hm - this._os.notifyObservers(null, "weave:service-login:success", ""); - } else { - //this._log.debug("Login error"); - this._os.notifyObservers(null, "weave:service-login:error", ""); - } - Utils.generatorDone(this, self, onComplete, success); - yield; // onComplete is responsible for closing the generator + this._log.warn(Async.exceptionStr(self, e)); + this._log.trace(e.trace); + this._os.notifyObservers(null, "weave:service-login:error", ""); + self.done(false); } - this._log.warn("generator not properly closed"); }, - _resetLock: function WeaveSync__resetLock(onComplete) { - let [self, cont] = yield; + _resetLock: function WeaveSync__resetLock() { + let self = yield; let success = false; try { this._log.debug("Resetting server lock"); this._os.notifyObservers(null, "weave:server-lock-reset:start", ""); - this._dav.forceUnlock.async(this._dav, cont); + this._dav.forceUnlock.async(this._dav, self.cb); success = yield; } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + throw e; } finally { if (success) { @@ -385,14 +410,12 @@ WeaveSyncService.prototype = { this._log.debug("Server lock reset failed"); this._os.notifyObservers(null, "weave:server-lock-reset:error", ""); } - Utils.generatorDone(this, self, onComplete, success); - yield; // generatorDone is responsible for closing the generator + self.done(success); } - this._log.warn("generator not properly closed"); }, _sync: function WeaveSync__sync() { - let [self, cont] = yield; + let self = yield; let success = false; try { @@ -402,11 +425,11 @@ WeaveSyncService.prototype = { this._os.notifyObservers(null, "weave:service:sync:start", ""); if (this._prefs.getBoolPref("bookmarks")) { - this._bmkEngine.sync(cont); + this._bmkEngine.sync(self.cb); yield; } if (this._prefs.getBoolPref("history")) { - this._histEngine.sync(cont); + this._histEngine.sync(self.cb); yield; } @@ -414,61 +437,41 @@ WeaveSyncService.prototype = { this._unlock(); } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + throw e; } finally { if (success) this._os.notifyObservers(null, "weave:service:sync:success", ""); else this._os.notifyObservers(null, "weave:service:sync:error", ""); - Utils.generatorDone(this, self); - yield; // generatorDone is responsible for closing the generator + self.done(); } - this._log.warn("generator not properly closed"); }, _resetServer: function WeaveSync__resetServer() { - let [self, cont] = yield; + let self = yield; - try { - if (!this._lock()) - return; + if (!this._lock()) + return; - this._bmkEngine.resetServer(cont); - this._histEngine.resetServer(cont); + this._bmkEngine.resetServer(self.cb); + this._histEngine.resetServer(self.cb); - this._unlock(); - - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self); - yield; // generatorDone is responsible for closing the generator - } - this._log.warn("generator not properly closed"); + this._unlock(); + self.done(); }, _resetClient: function WeaveSync__resetClient() { - let [self, cont] = yield; + let self = yield; - try { - if (!this._lock()) - return; + if (!this._lock()) + return; - this._bmkEngine.resetClient(cont); - this._histEngine.resetClient(cont); + this._bmkEngine.resetClient(self.cb); + this._histEngine.resetClient(self.cb); - this._unlock(); - - } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); - - } finally { - Utils.generatorDone(this, self); - yield; // generatorDone is responsible for closing the generator - } - this._log.warn("generator not properly closed"); + this._unlock(); + self.done(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]), diff --git a/services/sync/modules/syncCores.js b/services/sync/modules/syncCores.js index 87a0415b080e..b3de598f105b 100644 --- a/services/sync/modules/syncCores.js +++ b/services/sync/modules/syncCores.js @@ -45,8 +45,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://weave/log4moz.js"); Cu.import("resource://weave/constants.js"); Cu.import("resource://weave/util.js"); +Cu.import("resource://weave/async.js"); -Function.prototype.async = Utils.generatorAsync; +Function.prototype.async = Async.sugar; /* * SyncCore objects @@ -89,9 +90,9 @@ SyncCore.prototype = { return this._nodeParentsInt(tree[GUID].parentGUID, tree, parents); }, - _detectUpdates: function SC__detectUpdates(onComplete, a, b) { - let [self, cont] = yield; - let listener = new Utils.EventListener(cont); + _detectUpdates: function SC__detectUpdates(a, b) { + let self = yield; + let listener = new Utils.EventListener(self.cb); let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); let cmds = []; @@ -116,6 +117,7 @@ SyncCore.prototype = { depth: parents.length, parents: parents}); } } + for (let GUID in b) { timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT); @@ -141,14 +143,12 @@ SyncCore.prototype = { }); } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + throw e; } finally { timer = null; - Utils.generatorDone(this, self, onComplete, cmds); - yield; // onComplete is responsible for closing the generator + self.done(cmds); } - this._log.warn("generator not properly closed"); }, _commandLike: function SC__commandLike(a, b) { @@ -216,9 +216,9 @@ SyncCore.prototype = { return false; }, - _reconcile: function SC__reconcile(onComplete, listA, listB) { - let [self, cont] = yield; - let listener = new Utils.EventListener(cont); + _reconcile: function SC__reconcile(listA, listB) { + let self = yield; + let listener = new Utils.EventListener(self.cb); let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); let propagations = [[], []]; @@ -297,14 +297,13 @@ SyncCore.prototype = { ret = {propagations: propagations, conflicts: conflicts}; } catch (e) { - this._log.error("Exception caught: " + (e.message? e.message : e)); + this._log.error("Exception caught: " + (e.message? e.message : e) + + " - " + (e.location? e.location : "_reconcile")); } finally { timer = null; - Utils.generatorDone(this, self, onComplete, ret); - yield; // onComplete is responsible for closing the generator + self.done(ret); } - this._log.warn("generator not properly closed"); }, // Public methods diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js index fe8d6682f8ca..309dd98b8e16 100644 --- a/services/sync/modules/util.js +++ b/services/sync/modules/util.js @@ -113,6 +113,23 @@ let Utils = { return ret; }, + exceptionStr: function Weave_exceptionStr(e) { + let message = e.message? e.message : e; + let location = e.location? " (" + e.location + ")" : ""; + return message + location; + }, + + stackTrace: function Weave_stackTrace(stackFrame, str) { + if (stackFrame.caller) + str = Utils.stackTrace(stackFrame.caller, str); + + if (!str) + str = ""; + str += stackFrame + "\n"; + + return str; + }, + checkStatus: function Weave_checkStatus(code, msg, ranges) { if (!ranges) ranges = [[200,300]]; @@ -120,14 +137,19 @@ let Utils = { for (let i = 0; i < ranges.length; i++) { rng = ranges[i]; if (typeof(rng) == "object" && code >= rng[0] && code < rng[1]) - return; + return true; else if (typeof(rng) == "integer" && code == rng) - return; + return true; } let log = Log4Moz.Service.getLogger("Service.Util"); log.error(msg + " Error code: " + code); - throw 'checkStatus failed'; + return false; + }, + + ensureStatus: function Weave_ensureStatus(args) { + if (!Utils.checkStatus.apply(Utils, arguments)) + throw 'checkStatus failed'; }, makeURI: function Weave_makeURI(URIString) { @@ -147,73 +169,6 @@ let Utils = { Ci.nsIDOMXPathResult.ANY_TYPE, null); }, - bind2: function Weave_bind2(object, method) { - return function innerBind() { return method.apply(object, arguments); } - }, - - // Meant to be used like this in code that imports this file: - // - // Function.prototype.async = generatorAsync; - // - // So that you can do: - // - // gen = fooGen.async(...); - // ret = yield; - // - // where fooGen is a generator function, and gen is the running generator. - // ret is whatever the generator 'returns' via generatorDone(). - - generatorAsync: function Weave_generatorAsync(self, extra_args) { - try { - let args = Array.prototype.slice.call(arguments, 1); - let gen = this.apply(self, args); - gen.next(); // must initialize before sending - gen.send([gen, function(data) {Utils.continueGenerator(gen, data);}]); - return gen; - } catch (e) { - if (e instanceof StopIteration) { - dump("async warning: generator stopped unexpectedly"); - return null; - } else { - dump("Exception caught: " + e.message); - } - } - }, - - continueGenerator: function Weave_continueGenerator(generator, data) { - try { generator.send(data); } - catch (e) { - if (e instanceof StopIteration) - dump("continueGenerator warning: generator stopped unexpectedly"); - else - dump("Exception caught: " + e.message); - } - }, - - // generators created using Function.async can't simply call the - // callback with the return value, since that would cause the calling - // function to end up running (after the yield) from inside the - // generator. Instead, generators can call this method which sets up - // a timer to call the callback from a timer (and cleans up the timer - // to avoid leaks). It also closes generators after the timeout, to - // keep things clean. - generatorDone: function Weave_generatorDone(object, generator, callback, retval) { - if (object._timer) - throw "Called generatorDone when there is a timer already set." - - let cb = Utils.bind2(object, function(event) { - generator.close(); - generator = null; - object._timer = null; - if (callback) - callback(retval); - }); - - object._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - object._timer.initWithCallback(new Utils.EventListener(cb), - 0, object._timer.TYPE_ONE_SHOT); - }, - runCmd: function Weave_runCmd() { var binary; var args = []; @@ -318,6 +273,10 @@ let Utils = { return ret; }, + bind2: function Async_bind2(object, method) { + return function innerBind() { return method.apply(object, arguments); } + }, + /* * Event listener object * Used to handle XMLHttpRequest and nsITimer callbacks @@ -341,7 +300,7 @@ Utils.EventListener.prototype = { // nsITimerCallback notify: function EL_notify(timer) { - this._log.trace("Timer fired"); + //this._log.trace("Timer fired"); this._handler(timer); } }