diff --git a/accessible/src/msaa/nsTextAccessibleWrap.cpp b/accessible/src/msaa/nsTextAccessibleWrap.cpp index 9fec3b6fcad5..43d4d25a91c9 100644 --- a/accessible/src/msaa/nsTextAccessibleWrap.cpp +++ b/accessible/src/msaa/nsTextAccessibleWrap.cpp @@ -43,13 +43,15 @@ #include "nsCoreUtils.h" #include "nsDocAccessible.h" -#include "nsIFontMetrics.h" +#include "nsIThebesFontMetrics.h" #include "nsIFrame.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsIRenderingContext.h" #include "nsIComponentManager.h" +#include "gfxFont.h" + //////////////////////////////////////////////////////////////////////////////// // nsTextAccessibleWrap Accessible //////////////////////////////////////////////////////////////////////////////// @@ -253,48 +255,30 @@ STDMETHODIMP nsTextAccessibleWrap::get_fontFamily( __try { *aFontFamily = NULL; - nsIFrame *frame = GetFrame(); - nsCOMPtr presShell = GetPresShell(); - if (!frame || !presShell || !presShell->GetPresContext()) { - return E_FAIL; - } - - nsCOMPtr rc = presShell->GetReferenceRenderingContext(); - if (!rc) { - return E_FAIL; - } - - const nsStyleFont *font = frame->GetStyleFont(); - - const nsStyleVisibility *visibility = frame->GetStyleVisibility(); - - if (NS_FAILED(rc->SetFont(font->mFont, visibility->mLanguage, - presShell->GetPresContext()->GetUserFontSet()))) { - return E_FAIL; - } - - nsCOMPtr deviceContext; - rc->GetDeviceContext(*getter_AddRefs(deviceContext)); - if (!deviceContext) { + nsIFrame* frame = GetFrame(); + if (!frame) { return E_FAIL; } nsCOMPtr fm; - rc->GetFontMetrics(*getter_AddRefs(fm)); - if (!fm) { - return E_FAIL; - } + frame->PresContext()->DeviceContext()-> + GetMetricsFor(frame->GetStyleFont()->mFont, + frame->GetStyleVisibility()->mLanguage, + frame->PresContext()->GetUserFontSet(), + *getter_AddRefs(fm)); - nsAutoString fontFamily; - deviceContext->FirstExistingFont(fm->Font(), fontFamily); - if (fontFamily.IsEmpty()) + nsCOMPtr tfm = do_QueryInterface(fm); + const nsString& name = tfm->GetThebesFontGroup()->GetFontAt(0)->GetName(); + + if (name.IsEmpty()) return S_FALSE; - *aFontFamily = ::SysAllocStringLen(fontFamily.get(), fontFamily.Length()); + *aFontFamily = ::SysAllocStringLen(name.get(), name.Length()); if (!*aFontFamily) return E_OUTOFMEMORY; -} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } +} __except(FilterA11yExceptions(::GetExceptionCode(), + GetExceptionInformation())) { } return S_OK; } diff --git a/content/base/src/CSPUtils.jsm b/content/base/src/CSPUtils.jsm index 07c5f4d36766..6db4ee651bac 100644 --- a/content/base/src/CSPUtils.jsm +++ b/content/base/src/CSPUtils.jsm @@ -163,6 +163,7 @@ CSPPolicyURIListener.prototype = { else { // problem fetching policy so fail closed this._csp.refinePolicy("allow 'none'", this._docURI, this._docRequest); + this._csp.refinePolicy("default-src 'none'", this._docURI, this._docRequest); } // resume the parent document request this._docRequest.resume(); @@ -187,7 +188,7 @@ function CSPRep() { } CSPRep.SRC_DIRECTIVES = { - ALLOW: "allow", + DEFAULT_SRC: "default-src", SCRIPT_SRC: "script-src", STYLE_SRC: "style-src", MEDIA_SRC: "media-src", @@ -205,6 +206,7 @@ CSPRep.URI_DIRECTIVES = { }; CSPRep.OPTIONS_DIRECTIVE = "options"; +CSPRep.ALLOW_DIRECTIVE = "allow"; /** * Factory to create a new CSPRep, parsed from a string. @@ -258,6 +260,17 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { continue directive; } + // ALLOW DIRECTIVE ////////////////////////////////////////////////// + // parse "allow" as equivalent to "default-src", at least until the spec + // stabilizes, at which time we can stop parsing "allow" + if (dirname === CSPRep.ALLOW_DIRECTIVE) { + var dv = CSPSourceList.fromString(dirvalue, self, true); + if (dv) { + aCSPR._directives[SD.DEFAULT_SRC] = dv; + continue directive; + } + } + // SOURCE DIRECTIVES //////////////////////////////////////////////// for each(var sdi in SD) { if (dirname === sdi) { @@ -338,13 +351,13 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { // POLICY_URI can only be alone if (aCSPR._directives.length > 0 || dirs.length > 1) { CSPError("policy-uri directive can only appear alone"); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } // if we were called without a reference to the parent document request // we won't be able to suspend it while we fetch the policy -> fail closed if (!docRequest || !csp) { CSPError("The policy-uri cannot be fetched without a parent request and a CSP."); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } var uri = ''; @@ -352,22 +365,22 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { uri = gIoService.newURI(dirvalue, null, selfUri); } catch(e) { CSPError("could not parse URI in policy URI: " + dirvalue); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } // Verify that policy URI comes from the same origin if (selfUri) { if (selfUri.host !== uri.host){ CSPError("can't fetch policy uri from non-matching hostname: " + uri.host); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } if (selfUri.port !== uri.port){ CSPError("can't fetch policy uri from non-matching port: " + uri.port); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } if (selfUri.scheme !== uri.scheme){ CSPError("can't fetch policy uri from non-matching scheme: " + uri.scheme); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } } @@ -384,12 +397,12 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { // resume the document request and apply most restrictive policy docRequest.resume(); CSPError("Error fetching policy-uri: " + e); - return CSPRep.fromString("allow 'none'"); + return CSPRep.fromString("default-src 'none'"); } // return a fully-open policy to be intersected with the contents of the // policy-uri when it returns - return CSPRep.fromString("allow *"); + return CSPRep.fromString("default-src *"); } // UNIDENTIFIED DIRECTIVE ///////////////////////////////////////////// @@ -397,11 +410,11 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { } // end directive: loop - // if makeExplicit fails for any reason, default to allow 'none'. This - // includes the case where "allow" is not present. + // if makeExplicit fails for any reason, default to default-src 'none'. This + // includes the case where "default-src" is not present. if (aCSPR.makeExplicit()) return aCSPR; - return CSPRep.fromString("allow 'none'", self); + return CSPRep.fromString("default-src 'none'", self); }; CSPRep.prototype = { @@ -534,22 +547,22 @@ CSPRep.prototype = { makeExplicit: function cspsd_makeExplicit() { var SD = CSPRep.SRC_DIRECTIVES; - var allowDir = this._directives[SD.ALLOW]; - if (!allowDir) { - CSPWarning("'allow' directive required but not present. Reverting to \"allow 'none'\""); + var defaultSrcDir = this._directives[SD.DEFAULT_SRC]; + if (!defaultSrcDir) { + CSPWarning("'allow' or 'default-src' directive required but not present. Reverting to \"default-src 'none'\""); return false; } for (var dir in SD) { var dirv = SD[dir]; - if (dirv === SD.ALLOW) continue; + if (dirv === SD.DEFAULT_SRC) continue; if (!this._directives[dirv]) { // implicit directive, make explicit. // All but frame-ancestors directive inherit from 'allow' (bug 555068) if (dirv === SD.FRAME_ANCESTORS) this._directives[dirv] = CSPSourceList.fromString("*"); else - this._directives[dirv] = allowDir.clone(); + this._directives[dirv] = defaultSrcDir.clone(); this._directives[dirv]._isImplicit = true; } } diff --git a/content/base/src/contentSecurityPolicy.js b/content/base/src/contentSecurityPolicy.js index f755bd9bd65d..2c45e79c11dc 100644 --- a/content/base/src/contentSecurityPolicy.js +++ b/content/base/src/contentSecurityPolicy.js @@ -62,7 +62,7 @@ function ContentSecurityPolicy() { CSPdebug("CSP CREATED"); this._isInitialized = false; this._reportOnlyMode = false; - this._policy = CSPRep.fromString("allow *"); + this._policy = CSPRep.fromString("default-src *"); // default options "wide open" since this policy will be intersected soon this._policy._allowInlineScripts = true; @@ -71,7 +71,7 @@ function ContentSecurityPolicy() { this._requestHeaders = []; this._request = ""; this._docRequest = null; - CSPdebug("CSP POLICY INITED TO 'allow *'"); + CSPdebug("CSP POLICY INITED TO 'default-src *'"); } /* @@ -85,7 +85,7 @@ function ContentSecurityPolicy() { csp._MAPPINGS=[]; /* default, catch-all case */ - csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd.ALLOW; + csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd.DEFAULT_SRC; /* self */ csp._MAPPINGS[cp.TYPE_DOCUMENT] = null; @@ -106,9 +106,9 @@ function ContentSecurityPolicy() { /* These must go through the catch-all */ - csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd.ALLOW; - csp._MAPPINGS[cp.TYPE_PING] = cspr_sd.ALLOW; - csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd.ALLOW; + csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd.DEFAULT_SRC; + csp._MAPPINGS[cp.TYPE_PING] = cspr_sd.DEFAULT_SRC; + csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd.DEFAULT_SRC; } ContentSecurityPolicy.prototype = { @@ -386,7 +386,7 @@ ContentSecurityPolicy.prototype = { // report the frame-ancestor violation let directive = this._policy._directives[cspContext]; let violatedPolicy = (directive._isImplicit - ? 'allow' : 'frame-ancestors ') + ? 'default-src' : 'frame-ancestors ') + directive.toString(); this._asyncReportViolation(ancestors[i], violatedPolicy); @@ -441,7 +441,7 @@ ContentSecurityPolicy.prototype = { try { let directive = this._policy._directives[cspContext]; let violatedPolicy = (directive._isImplicit - ? 'allow' : cspContext) + ? 'default-src' : cspContext) + ' ' + directive.toString(); this._asyncReportViolation(aContentLocation, violatedPolicy); } catch(e) { diff --git a/content/base/test/file_CSP_main.html^headers^ b/content/base/test/file_CSP_main.html^headers^ index 6016e5846e0a..5374efd3e17b 100644 --- a/content/base/test/file_CSP_main.html^headers^ +++ b/content/base/test/file_CSP_main.html^headers^ @@ -1 +1 @@ -X-Content-Security-Policy: allow 'self' +X-Content-Security-Policy: default-src 'self' diff --git a/content/base/test/test_bug548193.html b/content/base/test/test_bug548193.html index d2520f15aed8..75d096d583f0 100644 --- a/content/base/test/test_bug548193.html +++ b/content/base/test/test_bug548193.html @@ -91,7 +91,7 @@ window.checkResults = function(reportObj) { "http://example.org/tests/content/base/test/file_CSP.sjs?testid=img_bad&type=img/png", "Incorrect blocked uri"); // correct violated-directive - is(cspReport["violated-directive"], "allow http://mochi.test:8888", + is(cspReport["violated-directive"], "default-src http://mochi.test:8888", "Incorrect violated directive"); // not practical to test request-headers as header names and values will // change with the trunk diff --git a/content/base/test/unit/test_csputils.js b/content/base/test/unit/test_csputils.js index 7ad327ba8012..19b2a9ed3200 100644 --- a/content/base/test/unit/test_csputils.js +++ b/content/base/test/unit/test_csputils.js @@ -73,11 +73,11 @@ function do_check_has_key(foo, key, stack) { stack = Components.stack.caller; var keys = []; - for(let k in keys) { keys.push(k); } + for (let k in foo) { keys.push(k); } var text = key + " in [" + keys.join(",") + "]"; - for(var x in foo) { - if(x == key) { + for (var x in foo) { + if (x == key) { //succeed ++_passedChecks; dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + @@ -359,17 +359,17 @@ test( var cspr; var cspr_allowval; + var SD = CSPRep.SRC_DIRECTIVES; // check default policy "allow *" cspr = CSPRep.fromString("allow *", "http://self.com:80"); - //"ALLOW directive is missing when specified in fromString" - do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW); + // "DEFAULT_SRC directive is missing when specified in fromString" + do_check_has_key(cspr._directives, SD.DEFAULT_SRC); // ... and check that the other directives were auto-filled with the - // ALLOW one. - var SD = CSPRep.SRC_DIRECTIVES; - cspr_allowval = cspr._directives[SD.ALLOW]; - for(var d in CSPRep.SRC_DIRECTIVES) { + // DEFAULT_SRC one. + cspr_allowval = cspr._directives[SD.DEFAULT_SRC]; + for(var d in SD) { //"Missing key " + d do_check_has_key(cspr._directives, SD[d]); //"Implicit directive " + d + " has non-allow value." @@ -378,6 +378,37 @@ test( }); +test( + function test_CSPRep_defaultSrc() { + var cspr, cspr_default_val, cspr_allow; + var SD = CSPRep.SRC_DIRECTIVES; + + // apply policy of "default-src *" (e.g. "allow *") + cspr = CSPRep.fromString("default-src *", "http://self.com:80"); + // "DEFAULT_SRC directive is missing when specified in fromString" + do_check_has_key(cspr._directives, SD.DEFAULT_SRC); + + // check that the other directives were auto-filled with the + // DEFAULT_SRC one. + cspr_default_val = cspr._directives[SD.DEFAULT_SRC]; + for (var d in SD) { + do_check_has_key(cspr._directives, SD[d]); + // "Implicit directive " + d + " has non-default-src value." + do_check_eq(cspr._directives[SD[d]].toString(), cspr_default_val.toString()); + } + + // check that |allow *| and |default-src *| are parsed equivalently and + // result in the same set of explicit policy directives + cspr = CSPRep.fromString("default-src *", "http://self.com:80"); + cspr_allow = CSPRep.fromString("allow *", "http://self.com:80"); + + for (var d in SD) { + do_check_equivalent(cspr._directives[SD[d]], + cspr_allow._directives[SD[d]]); + } + }); + + test( function test_CSPRep_fromString_oneDir() { diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 8e76f95de21e..94031e6b1a73 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1055,6 +1055,36 @@ HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey) DROP_DEAD(); } +// The next four _should_ be implemented, but we need to figure out how +// to transfer the data from the chrome process first. + +NS_IMETHODIMP +HttpChannelChild::GetRemoteAddress(nsACString & _result) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +HttpChannelChild::GetRemotePort(PRInt32 * _result) +{ + NS_ENSURE_ARG_POINTER(_result); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +HttpChannelChild::GetLocalAddress(nsACString & _result) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +HttpChannelChild::GetLocalPort(PRInt32 * _result) +{ + NS_ENSURE_ARG_POINTER(_result); + return NS_ERROR_NOT_AVAILABLE; +} + + //----------------------------------------------------------------------------- // HttpChannelChild::nsICacheInfoChannel //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index 5abd038a42a6..8c61d9bcf004 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -109,6 +109,10 @@ public: PRBool aMerge); // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey); + NS_IMETHOD GetLocalAddress(nsACString& addr); + NS_IMETHOD GetLocalPort(PRInt32* port); + NS_IMETHOD GetRemoteAddress(nsACString& addr); + NS_IMETHOD GetRemotePort(PRInt32* port); // nsISupportsPriority NS_IMETHOD SetPriority(PRInt32 value); // nsIResumableChannel diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index a965e7e67ef2..4e2e9c37b940 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -45,6 +45,7 @@ class nsAHttpSegmentReader; class nsAHttpSegmentWriter; class nsIInterfaceRequestor; class nsIEventTarget; +class nsITransport; class nsHttpRequestHead; //---------------------------------------------------------------------------- @@ -62,13 +63,14 @@ public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; - // called by the connection to get security callbacks to set on the + // called by the connection to get security callbacks to set on the // socket transport. virtual void GetSecurityCallbacks(nsIInterfaceRequestor **, nsIEventTarget **) = 0; // called to report socket status (see nsITransportEventSink) - virtual void OnTransportStatus(nsresult status, PRUint64 progress) = 0; + virtual void OnTransportStatus(nsITransport* transport, + nsresult status, PRUint64 progress) = 0; // called to check the transaction status. virtual PRBool IsDone() = 0; @@ -99,7 +101,8 @@ public: void SetConnection(nsAHttpConnection *); \ void GetSecurityCallbacks(nsIInterfaceRequestor **, \ nsIEventTarget **); \ - void OnTransportStatus(nsresult status, PRUint64 progress); \ + void OnTransportStatus(nsITransport* transport, \ + nsresult status, PRUint64 progress); \ PRBool IsDone(); \ nsresult Status(); \ PRUint32 Available(); \ diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 2153fa073a9e..645f156329bb 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -134,13 +134,16 @@ nsHttpChannel::nsHttpChannel() , mRequestTimeInitialized(PR_FALSE) { LOG(("Creating nsHttpChannel [this=%p]\n", this)); + // Subfields of unions cannot be targeted in an initializer list + mSelfAddr.raw.family = PR_AF_UNSPEC; + mPeerAddr.raw.family = PR_AF_UNSPEC; } nsHttpChannel::~nsHttpChannel() { LOG(("Destroying nsHttpChannel [this=%p]\n", this)); - if (mAuthProvider) + if (mAuthProvider) mAuthProvider->Disconnect(NS_ERROR_ABORT); } @@ -150,7 +153,7 @@ nsHttpChannel::Init(nsIURI *uri, nsProxyInfo *proxyInfo) { nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo); - if (NS_FAILED(rv)) + if (NS_FAILED(rv)) return rv; LOG(("nsHttpChannel::Init [this=%p]\n", this)); @@ -158,7 +161,7 @@ nsHttpChannel::Init(nsIURI *uri, mAuthProvider = do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1", &rv); - if (NS_FAILED(rv)) + if (NS_FAILED(rv)) return rv; rv = mAuthProvider->Init(this); @@ -3711,6 +3714,66 @@ nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey) return NS_OK; } +NS_IMETHODIMP +nsHttpChannel::GetRemoteAddress(nsACString & _result) +{ + if (mPeerAddr.raw.family == PR_AF_UNSPEC) + return NS_ERROR_NOT_AVAILABLE; + + _result.SetCapacity(64); + PR_NetAddrToString(&mPeerAddr, _result.BeginWriting(), 64); + _result.SetLength(strlen(_result.BeginReading())); + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetRemotePort(PRInt32 * _result) +{ + NS_ENSURE_ARG_POINTER(_result); + + if (mPeerAddr.raw.family == PR_AF_INET) { + *_result = (PRInt32)PR_ntohs(mPeerAddr.inet.port); + } + else if (mPeerAddr.raw.family == PR_AF_INET6) { + *_result = (PRInt32)PR_ntohs(mPeerAddr.ipv6.port); + } + else + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetLocalAddress(nsACString & _result) +{ + if (mSelfAddr.raw.family == PR_AF_UNSPEC) + return NS_ERROR_NOT_AVAILABLE; + + _result.SetCapacity(64); + PR_NetAddrToString(&mSelfAddr, _result.BeginWriting(), 64); + _result.SetLength(strlen(_result.BeginReading())); + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetLocalPort(PRInt32 * _result) +{ + NS_ENSURE_ARG_POINTER(_result); + + if (mSelfAddr.raw.family == PR_AF_INET) { + *_result = (PRInt32)PR_ntohs(mSelfAddr.inet.port); + } + else if (mSelfAddr.raw.family == PR_AF_INET6) { + *_result = (PRInt32)PR_ntohs(mSelfAddr.ipv6.port); + } + else + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel::nsISupportsPriority //----------------------------------------------------------------------------- @@ -4153,6 +4216,16 @@ nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status, if (!mProgressSink) GetCallback(mProgressSink); + if (status == nsISocketTransport::STATUS_CONNECTED_TO || + status == nsISocketTransport::STATUS_WAITING_FOR) { + nsCOMPtr socketTransport = + do_QueryInterface(trans); + if (socketTransport) { + socketTransport->GetSelfAddr(&mSelfAddr); + socketTransport->GetPeerAddr(&mPeerAddr); + } + } + // block socket status event after Cancel or OnStopRequest has been called. if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) { LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n", diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index fcf013bfe0b7..b1682fbb4ca2 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -137,6 +137,10 @@ public: NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext); // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey); + NS_IMETHOD GetLocalAddress(nsACString& addr); + NS_IMETHOD GetLocalPort(PRInt32* port); + NS_IMETHOD GetRemoteAddress(nsACString& addr); + NS_IMETHOD GetRemotePort(PRInt32* port); // nsISupportsPriority NS_IMETHOD SetPriority(PRInt32 value); // nsIResumableChannel @@ -347,6 +351,9 @@ private: // the cache entry's expiration time. Otherwise, it is not(see bug 567360). PRUint32 mRequestTimeInitialized : 1; + PRNetAddr mSelfAddr; + PRNetAddr mPeerAddr; + nsTArray mRedirectFuncStack; nsCOMPtr mHasher; diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 1e0da4c4fcf7..3769d7193073 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -615,7 +615,8 @@ nsHttpConnection::OnSocketWritable() // here to reflect the fact that we are waiting. this message will be // trumped (overwritten) if the server responds quickly. // - mTransaction->OnTransportStatus(nsISocketTransport::STATUS_WAITING_FOR, + mTransaction->OnTransportStatus(mSocketTransport, + nsISocketTransport::STATUS_WAITING_FOR, LL_ZERO); rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading @@ -811,7 +812,7 @@ nsHttpConnection::OnTransportStatus(nsITransport *trans, PRUint64 progressMax) { if (mTransaction) - mTransaction->OnTransportStatus(status, progress); + mTransaction->OnTransportStatus(trans, status, progress); return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 13f63f5392dd..773b4ddd7920 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -1491,7 +1491,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans, PRUint64 progressMax) { if (mTransaction) - mTransaction->OnTransportStatus(status, progress); + mTransaction->OnTransportStatus(trans, status, progress); return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index 9ea0408554b7..2961ee88b79e 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -367,7 +367,8 @@ nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result, } void -nsHttpPipeline::OnTransportStatus(nsresult status, PRUint64 progress) +nsHttpPipeline::OnTransportStatus(nsITransport* transport, + nsresult status, PRUint64 progress) { LOG(("nsHttpPipeline::OnStatus [this=%x status=%x progress=%llu]\n", this, status, progress)); @@ -377,10 +378,10 @@ nsHttpPipeline::OnTransportStatus(nsresult status, PRUint64 progress) nsAHttpTransaction *trans; switch (status) { case NS_NET_STATUS_RECEIVING_FROM: - // forward this only to the transaction currently recieving data + // forward this only to the transaction currently recieving data trans = Response(0); if (trans) - trans->OnTransportStatus(status, progress); + trans->OnTransportStatus(transport, status, progress); break; default: // forward other notifications to all transactions @@ -388,7 +389,7 @@ nsHttpPipeline::OnTransportStatus(nsresult status, PRUint64 progress) for (i=0; iOnTransportStatus(status, progress); + trans->OnTransportStatus(transport, status, progress); } break; } diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 6f0260bc3ee9..d9be23b609e5 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -351,14 +351,15 @@ nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb, } void -nsHttpTransaction::OnTransportStatus(nsresult status, PRUint64 progress) +nsHttpTransaction::OnTransportStatus(nsITransport* transport, + nsresult status, PRUint64 progress) { LOG(("nsHttpTransaction::OnSocketStatus [this=%x status=%x progress=%llu]\n", this, status, progress)); if (!mTransportSink) return; - + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // Need to do this before the STATUS_RECEIVING_FROM check below, to make @@ -410,7 +411,7 @@ nsHttpTransaction::OnTransportStatus(nsresult status, PRUint64 progress) progressMax = 0; } - mTransportSink->OnTransportStatus(nsnull, status, progress, progressMax); + mTransportSink->OnTransportStatus(transport, status, progress, progressMax); } PRBool diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 915c41133a11..4e39d17fa693 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -46,12 +46,12 @@ class nsCString; interface nsIURI; interface nsIProxyInfo; -/** - * Dumping ground for http. This interface will never be frozen. If you are - * using any feature exposed by this interface, be aware that this interface +/** + * Dumping ground for http. This interface will never be frozen. If you are + * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(44e35ead-6656-4f9e-b51d-ebaf1bee6e2e)] +[scriptable, uuid(12eb906a-71fe-4b79-b33a-6fe9ab57ea38)] interface nsIHttpChannelInternal : nsISupports { /** @@ -91,17 +91,58 @@ interface nsIHttpChannelInternal : nsISupports attribute boolean forceAllowThirdPartyCookie; /** - * Returns true iff the channel has been canceled. + * True iff the channel has been canceled. */ readonly attribute boolean canceled; /** - * Lets externalhandler tell the channel it is open on behalf of a download + * External handlers may set this to true to notify the channel + * that it is open on behalf of a download. */ attribute boolean channelIsForDownload; /** - * Transfer chain of redirected cache-keys + * The local IP address to which this channel is bound, in the + * format produced by PR_NetAddrToString. May be IPv4 or IPv6. + * Note: in the presence of NAT, this may not be the same as the + * address that the remote host thinks it's talking to. + * + * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's + * endpoints are not yet determined, or in any case when + * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207. + */ + readonly attribute AUTF8String localAddress; + + /** + * The local port number to which this channel is bound. + * + * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's + * endpoints are not yet determined, or in any case when + * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207. + */ + readonly attribute PRInt32 localPort; + + /** + * The IP address of the remote host that this channel is + * connected to, in the format produced by PR_NetAddrToString. + * + * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's + * endpoints are not yet determined, or in any case when + * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207. + */ + readonly attribute AUTF8String remoteAddress; + + /** + * The remote port number that this channel is connected to. + * + * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's + * endpoints are not yet determined, or in any case when + * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207. + */ + readonly attribute PRInt32 remotePort; + + /** + * Transfer chain of redirected cache-keys. */ [noscript] void setCacheKeysRedirectChain(in StringArray cacheKeys); }; diff --git a/netwerk/test/unit/test_traceable_channel.js b/netwerk/test/unit/test_traceable_channel.js index 7286404028f4..726ebdd4401f 100644 --- a/netwerk/test/unit/test_traceable_channel.js +++ b/netwerk/test/unit/test_traceable_channel.js @@ -20,6 +20,13 @@ TracingListener.prototype = { gotOnStartRequest = true; + request.QueryInterface(Components.interfaces.nsIHttpChannelInternal); + do_check_eq(request.localAddress, "127.0.0.1"); + do_check_eq(request.localPort > 0, true); + do_check_neq(request.localPort, 4444); + do_check_eq(request.remoteAddress, "127.0.0.1"); + do_check_eq(request.remotePort, 4444); + // Make sure listener can't be replaced after OnStartRequest was called. request.QueryInterface(Components.interfaces.nsITraceableChannel); try { diff --git a/services/crypto/tests/unit/head_helpers.js b/services/crypto/tests/unit/head_helpers.js index 611554892004..98649ebfded9 100644 --- a/services/crypto/tests/unit/head_helpers.js +++ b/services/crypto/tests/unit/head_helpers.js @@ -50,9 +50,16 @@ registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}" } -// Provide resource://services-crypto if it isn't already available -let weaveService = Cc["@mozilla.org/weave/service;1"].getService(); -weaveService.wrappedJSObject.addResourceAlias(); +// Register resource alias. Normally done in SyncComponents.manifest. +function addResourceAlias() { + Cu.import("resource://gre/modules/Services.jsm"); + const resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + let uri = Services.io.newURI("resource:///modules/services-crypto/", + null, null); + resProt.setSubstitution("services-crypto", uri); +} +addResourceAlias(); /** * Print some debug message to the console. All arguments will be printed, diff --git a/services/sync/SyncComponents.manifest b/services/sync/SyncComponents.manifest index 641ddfe0df68..0d75db3fbb97 100644 --- a/services/sync/SyncComponents.manifest +++ b/services/sync/SyncComponents.manifest @@ -6,3 +6,6 @@ component {d28f8a0b-95da-48f4-b712-caf37097be41} Weave.js contract @mozilla.org/network/protocol/about;1?what=sync-log {d28f8a0b-95da-48f4-b712-caf37097be41} component {a08ee179-df50-48e0-9c87-79e4dd5caeb1} Weave.js contract @mozilla.org/network/protocol/about;1?what=sync-log.1 {a08ee179-df50-48e0-9c87-79e4dd5caeb1} +# Register resource aliases +resource services-sync resource:///modules/services-sync/ +resource services-crypto resource:///modules/services-crypto/ diff --git a/services/sync/Weave.js b/services/sync/Weave.js index b3e04d5825dd..42382a90d133 100644 --- a/services/sync/Weave.js +++ b/services/sync/Weave.js @@ -58,8 +58,6 @@ WeaveService.prototype = { break; case "final-ui-startup": - this.addResourceAlias(); - // Force Weave service to load if it hasn't triggered from overlays this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); this.timer.initWithCallback({ @@ -71,25 +69,6 @@ WeaveService.prototype = { }, 10000, Ci.nsITimer.TYPE_ONE_SHOT); break; } - }, - - addResourceAlias: function() { - let ioService = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService); - let resProt = ioService.getProtocolHandler("resource") - .QueryInterface(Ci.nsIResProtocolHandler); - - // Only create alias if resource://services-sync doesn't already exist. - if (!resProt.hasSubstitution("services-sync")) { - let uri = ioService.newURI("resource:///modules/services-sync/", - null, null); - resProt.setSubstitution("services-sync", uri); - } - if (!resProt.hasSubstitution("services-crypto")) { - let uri = ioService.newURI("resource:///modules/services-crypto/", - null, null); - resProt.setSubstitution("services-crypto", uri); - } } }; diff --git a/services/sync/locales/en-US/errors.properties b/services/sync/locales/en-US/errors.properties index 8763a295d6c5..b722480e9aad 100644 --- a/services/sync/locales/en-US/errors.properties +++ b/services/sync/locales/en-US/errors.properties @@ -1,7 +1,5 @@ error.login.reason.network = Failed to connect to the server error.login.reason.synckey = Wrong Sync Key -# error.login.reason.password is deprecated. -error.login.reason.password = Incorrect username or password error.login.reason.account = Incorrect account name or password error.login.reason.no_password= No saved password to use error.login.reason.no_synckey = No saved Sync Key to use diff --git a/services/sync/modules/constants.js b/services/sync/modules/constants.js index e098db633b9a..bac691541aba 100644 --- a/services/sync/modules/constants.js +++ b/services/sync/modules/constants.js @@ -43,6 +43,11 @@ WEAVE_CHANNEL: "@weave_channel@", WEAVE_VERSION: "@weave_version@", WEAVE_ID: "@weave_id@", +// Sync Server API version that the client supports. +SYNC_API_VERSION: "1.1", +USER_API_VERSION: "1.0", +MISC_API_VERSION: "1.0", + // Version of the data format this client supports. The data format describes // how records are packaged; this is separate from the Server API version and // the per-engine cleartext formats. @@ -88,10 +93,16 @@ HMAC_EVENT_INTERVAL: 600000, // How long to wait between sync attempts if the Master Password is locked. MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000, // 15 minutes -// 50 is hardcoded here because of URL length restrictions. -// (GUIDs can be up to 64 chars long) +// Separate from the ID fetch batch size to allow tuning for mobile. MOBILE_BATCH_SIZE: 50, +// 50 is hardcoded here because of URL length restrictions. +// (GUIDs can be up to 64 chars long.) +// Individual engines can set different values for their limit if their +// identifiers are shorter. +DEFAULT_GUID_FETCH_BATCH_SIZE: 50, +DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE: 50, + // Default batch size for applying incoming records. DEFAULT_STORE_BATCH_SIZE: 1, HISTORY_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE diff --git a/services/sync/modules/engines.js b/services/sync/modules/engines.js index e75951493e01..d1e632d318ae 100644 --- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -416,14 +416,33 @@ function SyncEngine(name) { Engine.call(this, name || "SyncEngine"); this.loadToFetch(); } + +// Enumeration to define approaches to handling bad records. +// Attached to the constructor to allow use as a kind of static enumeration. +SyncEngine.kRecoveryStrategy = { + ignore: "ignore", + retry: "retry", + error: "error" +}; + SyncEngine.prototype = { __proto__: Engine.prototype, _recordObj: CryptoWrapper, version: 1, + + // How many records to pull in a single sync. This is primarily to avoid very + // long first syncs against profiles with many history records. downloadLimit: null, + + // How many records to pull at one time when specifying IDs. This is to avoid + // URI length limitations. + guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE, + mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE, + + // How many records to process in a single batch. applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE, - get storageURL() Svc.Prefs.get("clusterURL") + Svc.Prefs.get("storageAPI") + + get storageURL() Svc.Prefs.get("clusterURL") + SYNC_API_VERSION + "/" + ID.get("WeaveID").username + "/storage/", get engineURL() this.storageURL + this.name, @@ -589,7 +608,9 @@ SyncEngine.prototype = { // Figure out how many total items to fetch this sync; do less on mobile. let batchSize = Infinity; let newitems = new Collection(this.engineURL, this._recordObj); - if (Svc.Prefs.get("client.type") == "mobile") { + let isMobile = (Svc.Prefs.get("client.type") == "mobile"); + + if (isMobile) { batchSize = MOBILE_BATCH_SIZE; } newitems.newer = this.lastSync; @@ -640,14 +661,37 @@ SyncEngine.prototype = { try { try { item.decrypt(); - } catch (ex if (Utils.isHMACMismatch(ex) && - self.handleHMACMismatch(item))) { - // Let's try handling it. - // If the callback returns true, try decrypting again, because - // we've got new keys. - self._log.info("Trying decrypt again..."); - item.decrypt(); - } + } catch (ex if Utils.isHMACMismatch(ex)) { + let strategy = self.handleHMACMismatch(item, true); + if (strategy == SyncEngine.kRecoveryStrategy.retry) { + // You only get one retry. + try { + // Try decrypting again, typically because we've got new keys. + self._log.info("Trying decrypt again..."); + item.decrypt(); + strategy = null; + } catch (ex if Utils.isHMACMismatch(ex)) { + strategy = self.handleHMACMismatch(item, false); + } + } + + switch (strategy) { + case null: + // Retry succeeded! No further handling. + break; + case SyncEngine.kRecoveryStrategy.retry: + self._log.debug("Ignoring second retry suggestion."); + // Fall through to error case. + case SyncEngine.kRecoveryStrategy.error: + self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex)); + failed.push(item.id); + return; + case SyncEngine.kRecoveryStrategy.ignore: + self._log.debug("Ignoring record " + item.id + + " with bad HMAC: already handled."); + return; + } + } } catch (ex) { self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex)); failed.push(item.id); @@ -718,7 +762,12 @@ SyncEngine.prototype = { this.lastSync = this.lastModified; } - // Mobile: process any backlog of GUIDs + // Process any backlog of GUIDs. + // At this point we impose an upper limit on the number of items to fetch + // in a single request, even for desktop, to avoid hitting URI limits. + batchSize = isMobile ? this.mobileGUIDFetchBatchSize : + this.guidFetchBatchSize; + while (fetchBatch.length) { // Reuse the original query, but get rid of the restricting params // and batch remaining records. @@ -1007,8 +1056,32 @@ SyncEngine.prototype = { new Resource(this.engineURL).delete(); this._resetClient(); }, - - handleHMACMismatch: function handleHMACMismatch(item) { - return Weave.Service.handleHMACEvent(); + + removeClientData: function removeClientData() { + // Implement this method in engines that store client specific data + // on the server. + }, + + /* + * Decide on (and partially effect) an error-handling strategy. + * + * Asks the Service to respond to an HMAC error, which might result in keys + * being downloaded. That call returns true if an action which might allow a + * retry to occur. + * + * If `mayRetry` is truthy, and the Service suggests a retry, + * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns + * kRecoveryStrategy.error. + * + * Subclasses of SyncEngine can override this method to allow for different + * behavior -- e.g., to delete and ignore erroneous entries. + * + * All return values will be part of the kRecoveryStrategy enumeration. + */ + handleHMACMismatch: function handleHMACMismatch(item, mayRetry) { + // By default we either try again, or bail out noisily. + return (Weave.Service.handleHMACEvent() && mayRetry) ? + SyncEngine.kRecoveryStrategy.retry : + SyncEngine.kRecoveryStrategy.error; } }; diff --git a/services/sync/modules/engines/bookmarks.js b/services/sync/modules/engines/bookmarks.js index ad5bdf5dea3e..e0b372f90efb 100644 --- a/services/sync/modules/engines/bookmarks.js +++ b/services/sync/modules/engines/bookmarks.js @@ -58,20 +58,14 @@ const MOBILEROOT_ANNO = "mobile/bookmarksRoot"; const MOBILE_ANNO = "MobileBookmarks"; const EXCLUDEBACKUP_ANNO = "places/excludeFromBackup"; const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark"; -const GUID_ANNO = "sync/guid"; const PARENT_ANNO = "sync/parent"; const ANNOS_TO_TRACK = [DESCRIPTION_ANNO, SIDEBAR_ANNO, STATICTITLE_ANNO, - FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO]; + FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO]; const SERVICE_NOT_SUPPORTED = "Service not supported on this platform"; const FOLDER_SORTINDEX = 1000000; -try { - Cu.import("resource://gre/modules/PlacesUtils.jsm"); -} -catch(ex) { - Cu.import("resource://gre/modules/utils.js"); -} +Cu.import("resource://gre/modules/PlacesUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/record.js"); @@ -993,31 +987,12 @@ BookmarksStore.prototype = { } }, - __childGUIDsStm: null, get _childGUIDsStm() { - if (this.__childGUIDsStm) { - return this.__childGUIDsStm; - } - - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "SELECT id AS item_id, guid " + - "FROM moz_bookmarks " + - "WHERE parent = :parent " + - "ORDER BY position"); - } else { - stmt = this._getStmt( - "SELECT b.id AS item_id, " + - "(SELECT id FROM moz_anno_attributes WHERE name = '" + GUID_ANNO + "') AS name_id," + - "a.content AS guid " + - "FROM moz_bookmarks b " + - "LEFT JOIN moz_items_annos a ON a.item_id = b.id " + - "AND a.anno_attribute_id = name_id " + - "WHERE b.parent = :parent " + - "ORDER BY b.position"); - } - return this.__childGUIDsStm = stmt; + return this._getStmt( + "SELECT id AS item_id, guid " + + "FROM moz_bookmarks " + + "WHERE parent = :parent " + + "ORDER BY position"); }, _childGUIDsCols: ["item_id", "guid"], @@ -1145,24 +1120,8 @@ BookmarksStore.prototype = { return this._stmts[query]; this._log.trace("Creating SQL statement: " + query); - return this._stmts[query] = Utils.createStatement(this._hsvc.DBConnection, - query); - }, - - __haveGUIDColumn: null, - get _haveGUIDColumn() { - if (this.__haveGUIDColumn !== null) { - return this.__haveGUIDColumn; - } - let stmt; - try { - stmt = this._hsvc.DBConnection.createStatement( - "SELECT guid FROM moz_places"); - stmt.finalize(); - return this.__haveGUIDColumn = true; - } catch(ex) { - return this.__haveGUIDColumn = false; - } + return this._stmts[query] = this._hsvc.DBConnection + .createAsyncStatement(query); }, get _frecencyStm() { @@ -1174,54 +1133,11 @@ BookmarksStore.prototype = { }, _frecencyCols: ["frecency"], - get _addGUIDAnnotationNameStm() { - let stmt = this._getStmt( - "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)"); - stmt.params.anno_name = GUID_ANNO; - return stmt; - }, - - get _checkGUIDItemAnnotationStm() { - // Gecko <2.0 only - let stmt = this._getStmt( - "SELECT b.id AS item_id, " + - "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS name_id, " + - "a.id AS anno_id, a.dateAdded AS anno_date " + - "FROM moz_bookmarks b " + - "LEFT JOIN moz_items_annos a ON a.item_id = b.id " + - "AND a.anno_attribute_id = name_id " + - "WHERE b.id = :item_id"); - stmt.params.anno_name = GUID_ANNO; - return stmt; - }, - _checkGUIDItemAnnotationCols: ["item_id", "name_id", "anno_id", "anno_date"], - - get _addItemAnnotationStm() { - return this._getStmt( - "INSERT OR REPLACE INTO moz_items_annos " + - "(id, item_id, anno_attribute_id, mime_type, content, flags, " + - "expiration, type, dateAdded, lastModified) " + - "VALUES (:id, :item_id, :name_id, :mime_type, :content, :flags, " + - ":expiration, :type, :date_added, :last_modified)"); - }, - - __setGUIDStm: null, get _setGUIDStm() { - if (this.__setGUIDStm !== null) { - return this.__setGUIDStm; - } - - // Obtains a statement to set the guid iff the guid column exists. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "UPDATE moz_bookmarks " + - "SET guid = :guid " + - "WHERE id = :item_id"); - } else { - stmt = false; - } - return this.__setGUIDStm = stmt; + return this._getStmt( + "UPDATE moz_bookmarks " + + "SET guid = :guid " + + "WHERE id = :item_id"); }, // Some helper functions to handle GUIDs @@ -1229,73 +1145,18 @@ BookmarksStore.prototype = { if (!guid) guid = Utils.makeGUID(); - // If we can, set the GUID on moz_bookmarks and do not do any other work. - let (stmt = this._setGUIDStm) { - if (stmt) { - stmt.params.guid = guid; - stmt.params.item_id = id; - Utils.queryAsync(stmt); - return guid; - } - } - - // Ensure annotation name exists - Utils.queryAsync(this._addGUIDAnnotationNameStm); - - let stmt = this._checkGUIDItemAnnotationStm; + let stmt = this._setGUIDStm; + stmt.params.guid = guid; stmt.params.item_id = id; - let result = Utils.queryAsync(stmt, this._checkGUIDItemAnnotationCols)[0]; - if (!result) { - this._log.warn("Couldn't annotate bookmark id " + id); - return guid; - } - - stmt = this._addItemAnnotationStm; - if (result.anno_id) { - stmt.params.id = result.anno_id; - stmt.params.date_added = result.anno_date; - } else { - stmt.params.id = null; - stmt.params.date_added = Date.now() * 1000; - } - stmt.params.item_id = result.item_id; - stmt.params.name_id = result.name_id; - stmt.params.content = guid; - stmt.params.flags = 0; - stmt.params.expiration = Ci.nsIAnnotationService.EXPIRE_NEVER; - stmt.params.type = Ci.nsIAnnotationService.TYPE_STRING; - stmt.params.last_modified = Date.now() * 1000; Utils.queryAsync(stmt); - return guid; }, - __guidForIdStm: null, get _guidForIdStm() { - if (this.__guidForIdStm) { - return this.__guidForIdStm; - } - - // Try to first read from moz_bookmarks. Creating the statement will - // fail, however, if the guid column does not exist. We fallback to just - // reading the annotation table in this case. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "SELECT guid " + - "FROM moz_bookmarks " + - "WHERE id = :item_id"); - } else { - stmt = this._getStmt( - "SELECT a.content AS guid " + - "FROM moz_items_annos a " + - "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " + - "JOIN moz_bookmarks b ON b.id = a.item_id " + - "WHERE n.name = '" + GUID_ANNO + "' " + - "AND b.id = :item_id"); - } - - return this.__guidForIdStm = stmt; + return this._getStmt( + "SELECT guid " + + "FROM moz_bookmarks " + + "WHERE id = :item_id"); }, _guidForIdCols: ["guid"], @@ -1316,37 +1177,11 @@ BookmarksStore.prototype = { return this._setGUID(id); }, - __idForGUIDStm: null, get _idForGUIDStm() { - if (this.__idForGUIDStm) { - return this.__idForGUIDStm; - } - - - // Try to first read from moz_bookmarks. Creating the statement will - // fail, however, if the guid column does not exist. We fallback to just - // reading the annotation table in this case. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "SELECT id AS item_id " + - "FROM moz_bookmarks " + - "WHERE guid = :guid"); - } else { - // Order results by lastModified so we can preserve the ID of the oldest bookmark. - // Copying a record preserves its dateAdded, and only modifying the - // bookmark alters its lastModified, so we also order by its item_id -- - // lowest wins ties. Of course, Places can still screw us by reassigning IDs... - stmt = this._getStmt( - "SELECT a.item_id AS item_id " + - "FROM moz_items_annos a " + - "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " + - "WHERE n.name = '" + GUID_ANNO + "' " + - "AND a.content = :guid " + - "ORDER BY a.lastModified, a.item_id"); - } - - return this.__idForGUIDStm = stmt; + return this._getStmt( + "SELECT id AS item_id " + + "FROM moz_bookmarks " + + "WHERE guid = :guid"); }, _idForGUIDCols: ["item_id"], @@ -1370,22 +1205,6 @@ BookmarksStore.prototype = { if (!result) return -1; - if (!this._haveGUIDColumn) { - try { - // Assign new GUIDs to any that came later. - for (let i = 1; i < results.length; ++i) { - let surplus = results[i]; - this._log.debug("Assigning new GUID to copied row " + surplus.item_id); - this._setGUID(surplus.item_id); - } - } catch (ex) { - // Just skip it and carry on. This shouldn't happen, but if it does we - // don't want to fail hard. - this._log.debug("Got exception assigning new GUIDs: " + - Utils.exceptionStr(ex)); - } - } - return result.item_id; }, @@ -1589,34 +1408,44 @@ BookmarksTracker.prototype = { * Folder of the item being changed */ _ignore: function BMT__ignore(itemId, folder) { - // Ignore unconditionally if the engine tells us to + // Ignore unconditionally if the engine tells us to. if (this.ignoreAll) return true; - // Ensure that the mobile bookmarks query is correct in the UI - this._ensureMobileQuery(); + // Get the folder id if we weren't given one. + if (folder == null) { + try { + folder = this._bms.getFolderIdForItem(itemId); + } catch (ex) { + this._log.debug("getFolderIdForItem(" + itemId + + ") threw; calling _ensureMobileQuery."); + // I'm guessing that gFIFI can throw, and perhaps that's why + // _ensureMobileQuery is here at all. Try not to call it. + this._ensureMobileQuery(); + folder = this._bms.getFolderIdForItem(itemId); + } + } - // Make sure to remove items that have the exclude annotation + // Ignore livemark children. + if (this._ls.isLivemark(folder)) + return true; + + // Ignore changes to tags (folders under the tags folder). + let tags = kSpecialIds.tags; + if (folder == tags) + return true; + + // Ignore tag items (the actual instance of a tag for a bookmark). + if (this._bms.getFolderIdForItem(folder) == tags) + return true; + + // Make sure to remove items that have the exclude annotation. if (Svc.Annos.itemHasAnnotation(itemId, EXCLUDEBACKUP_ANNO)) { this.removeChangedID(this._GUIDForId(itemId)); return true; } - // Get the folder id if we weren't given one - if (folder == null) - folder = this._bms.getFolderIdForItem(itemId); - - let tags = kSpecialIds.tags; - // Ignore changes to tags (folders under the tags folder) - if (folder == tags) - return true; - - // Ignore tag items (the actual instance of a tag for a bookmark) - if (this._bms.getFolderIdForItem(folder) == tags) - return true; - - // Ignore livemark children - return this._ls.isLivemark(folder); + return false; }, onItemAdded: function BMT_onEndUpdateBatch(itemId, folder, index) { @@ -1655,7 +1484,7 @@ BookmarksTracker.prototype = { let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile); let title = Str.sync.get("mobile.label"); - // Don't add OR do remove the mobile bookmarks if there's nothing + // Don't add OR remove the mobile bookmarks if there's nothing. if (Svc.Bookmark.getIdForItemAt(kSpecialIds.mobile, 0) == -1) { if (mobile.length != 0) Svc.Bookmark.removeItem(mobile[0]); @@ -1675,28 +1504,26 @@ BookmarksTracker.prototype = { this.ignoreAll = false; }, + // This method is oddly structured, but the idea is to return as quickly as + // possible -- this handler gets called *every time* a bookmark changes, for + // *each change*. That's particularly bad when a bunch of livemarks are + // updated. onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value) { - if (this._ignore(itemId)) + // Quicker checks first. + if (this.ignoreAll) return; - // Allocate a new GUID if necessary. - // We only want to do it if there's a dupe, so use idForGUID to achieve that. - if (isAnno && (property == GUID_ANNO)) { - this._log.trace("onItemChanged for " + GUID_ANNO + - ": probably needs a new one."); - this._idForGUID(this._GUIDForId(itemId)); - this._addId(itemId); - return; - } - - // ignore annotations except for the ones that we sync - if (isAnno && ANNOS_TO_TRACK.indexOf(property) == -1) + if (isAnno && (ANNOS_TO_TRACK.indexOf(property) == -1)) + // Ignore annotations except for the ones that we sync. return; - // Ignore favicon changes to avoid unnecessary churn + // Ignore favicon changes to avoid unnecessary churn. if (property == "favicon") return; + if (this._ignore(itemId)) + return; + this._log.trace("onItemChanged: " + itemId + (", " + property + (isAnno? " (anno)" : "")) + (value ? (" = \"" + value + "\"") : "")); diff --git a/services/sync/modules/engines/clients.js b/services/sync/modules/engines/clients.js index fb817292f057..0027d0f0994e 100644 --- a/services/sync/modules/engines/clients.js +++ b/services/sync/modules/engines/clients.js @@ -45,6 +45,7 @@ Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/ext/StringBundle.js"); Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); const CLIENTS_TTL = 1814400; // 21 days @@ -193,19 +194,26 @@ ClientEngine.prototype = { SyncEngine.prototype._resetClient.call(this); this._store.wipe(); }, - + + removeClientData: function removeClientData() { + let res = new Resource(this.engineURL + "/" + this.localID); + res.delete(); + }, + // Override the default behavior to delete bad records from the server. - handleHMACMismatch: function handleHMACMismatch(item) { + handleHMACMismatch: function handleHMACMismatch(item, mayRetry) { this._log.debug("Handling HMAC mismatch for " + item.id); - if (SyncEngine.prototype.handleHMACMismatch.call(this, item)) - return true; + + let base = SyncEngine.prototype.handleHMACMismatch.call(this, item, mayRetry); + if (base != SyncEngine.kRecoveryStrategy.error) + return base; // It's a bad client record. Save it to be deleted at the end of the sync. this._log.debug("Bad client record detected. Scheduling for deletion."); this._deleteId(item.id); - // Don't try again. - return false; + // Neither try again nor error; we're going to delete it. + return SyncEngine.kRecoveryStrategy.ignore; } }; diff --git a/services/sync/modules/engines/forms.js b/services/sync/modules/engines/forms.js index 4048eabb9405..c8a5e9c8c300 100644 --- a/services/sync/modules/engines/forms.js +++ b/services/sync/modules/engines/forms.js @@ -66,7 +66,7 @@ let FormWrapper = { getAllEntries: function getAllEntries() { // Sort by (lastUsed - minLast) / (maxLast - minLast) * timesUsed / maxTimes - let query = this.createStatement( + let query = Svc.Form.DBConnection.createAsyncStatement( "SELECT fieldname name, value FROM moz_formhistory " + "ORDER BY 1.0 * (lastUsed - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) / " + "((SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed DESC LIMIT 1) - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) * " + @@ -76,7 +76,7 @@ let FormWrapper = { }, getEntry: function getEntry(guid) { - let query = this.createStatement( + let query = Svc.Form.DBConnection.createAsyncStatement( "SELECT fieldname name, value FROM moz_formhistory WHERE guid = :guid"); query.params.guid = guid; return Utils.queryAsync(query, ["name", "value"])[0]; @@ -84,7 +84,7 @@ let FormWrapper = { getGUID: function getGUID(name, value) { // Query for the provided entry - let getQuery = this.createStatement( + let getQuery = Svc.Form.DBConnection.createAsyncStatement( "SELECT guid FROM moz_formhistory " + "WHERE fieldname = :name AND value = :value"); getQuery.params.name = name; @@ -106,7 +106,7 @@ let FormWrapper = { return item.guid; // We need to create a guid for this entry - let setQuery = this.createStatement( + let setQuery = Svc.Form.DBConnection.createAsyncStatement( "UPDATE moz_formhistory SET guid = :guid " + "WHERE fieldname = :name AND value = :value"); let guid = Utils.makeGUID(); @@ -119,37 +119,20 @@ let FormWrapper = { }, hasGUID: function hasGUID(guid) { - let query = this.createStatement( + let query = Svc.Form.DBConnection.createAsyncStatement( "SELECT guid FROM moz_formhistory WHERE guid = :guid LIMIT 1"); query.params.guid = guid; return Utils.queryAsync(query, ["guid"]).length == 1; }, replaceGUID: function replaceGUID(oldGUID, newGUID) { - let query = this.createStatement( + let query = Svc.Form.DBConnection.createAsyncStatement( "UPDATE moz_formhistory SET guid = :newGUID WHERE guid = :oldGUID"); query.params.oldGUID = oldGUID; query.params.newGUID = newGUID; Utils.queryAsync(query); - }, - - createStatement: function createStatement(query) { - try { - // Just return the statement right away if it's okay - return Utils.createStatement(Svc.Form.DBConnection, query); - } - catch(ex) { - // Assume guid column must not exist yet, so add it with an index - Svc.Form.DBConnection.executeSimpleSQL( - "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT"); - Svc.Form.DBConnection.executeSimpleSQL( - "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index " + - "ON moz_formhistory (guid)"); - - // Try creating the query now that the column exists - return Utils.createStatement(Svc.Form.DBConnection, query); - } } + }; function FormEngine() { @@ -231,7 +214,7 @@ FormStore.prototype = { }, update: function FormStore_update(record) { - this._log.warn("Ignoring form record update request!"); + this._log.trace("Ignoring form record update request!"); }, wipe: function FormStore_wipe() { @@ -283,7 +266,6 @@ FormTracker.prototype = { this._enabled = false; } break; - // Firefox 4.0 case "satchel-storage-changed": if (data == "addEntry" || data == "before-removeEntry") { subject = subject.QueryInterface(Ci.nsIArray); @@ -294,32 +276,9 @@ FormTracker.prototype = { this.trackEntry(name, value); } break; - // Firefox 3.5/3.6 - case "form-notifier": - this.onFormNotifier(data); - break; } }, - // Firefox 3.5/3.6 - onFormNotifier: function onFormNotifier(data) { - let name, value; - - // Figure out if it's a function that we care about tracking - let formCall = JSON.parse(data); - let func = formCall.func; - if ((func == "addEntry" && formCall.type == "after") || - (func == "removeEntry" && formCall.type == "before")) - [name, value] = formCall.args; - - // Skip if there's nothing of interest - if (name == null || value == null) - return; - - this._log.trace("Logging form action: " + [func, name, value]); - this.trackEntry(name, value); - }, - notify: function FormTracker_notify(formElement, aWindow, actionURI) { if (this.ignoreAll) return; diff --git a/services/sync/modules/engines/history.js b/services/sync/modules/engines/history.js index c41f2ac16d03..2be6f995011b 100644 --- a/services/sync/modules/engines/history.js +++ b/services/sync/modules/engines/history.js @@ -44,7 +44,6 @@ const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; -const GUID_ANNO = "sync/guid"; const HISTORY_TTL = 5184000; // 60 days const TOPIC_UPDATEPLACES_COMPLETE = "places-updatePlaces-complete"; @@ -78,9 +77,6 @@ HistoryEngine.prototype = { downloadLimit: MAX_HISTORY_DOWNLOAD, applyIncomingBatchSize: HISTORY_STORE_BATCH_SIZE, - // For Gecko <2.0 - _sync: Utils.batchSync("History", SyncEngine), - _findDupe: function _findDupe(item) { return this._store.GUIDForUri(item.histUri); } @@ -113,110 +109,28 @@ HistoryStore.prototype = { __asyncHistory: null, get _asyncHistory() { - if (!this.__asyncHistory && "mozIAsyncHistory" in Components.interfaces) { + if (!this.__asyncHistory) { this.__asyncHistory = Cc["@mozilla.org/browser/history;1"] .getService(Ci.mozIAsyncHistory); } return this.__asyncHistory; }, - get _db() { - return this._hsvc.DBConnection; - }, - _stmts: {}, _getStmt: function(query) { if (query in this._stmts) return this._stmts[query]; this._log.trace("Creating SQL statement: " + query); - return this._stmts[query] = Utils.createStatement(this._db, query); + return this._stmts[query] = this._hsvc.DBConnection + .createAsyncStatement(query); }, - get _haveTempTablesStm() { - return this._getStmt( - "SELECT name FROM sqlite_temp_master " + - "WHERE name IN ('moz_places_temp', 'moz_historyvisits_temp')"); - }, - _haveTempTablesCols: ["name"], - - __haveTempTables: null, - get _haveTempTables() { - if (this.__haveTempTables === null) { - this.__haveTempTables = !!Utils.queryAsync( - this._haveTempTablesStm, this._haveTempTablesCols).length; - } - return this.__haveTempTables; - }, - - __haveGUIDColumn: null, - get _haveGUIDColumn() { - if (this.__haveGUIDColumn !== null) { - return this.__haveGUIDColumn; - } - let stmt; - try { - stmt = this._db.createStatement("SELECT guid FROM moz_places"); - stmt.finalize(); - return this.__haveGUIDColumn = true; - } catch(ex) { - return this.__haveGUIDColumn = false; - } - }, - - get _addGUIDAnnotationNameStm() { - // Gecko <2.0 only - let stmt = this._getStmt( - "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)"); - stmt.params.anno_name = GUID_ANNO; - return stmt; - }, - - get _checkGUIDPageAnnotationStm() { - // Gecko <2.0 only - let stmt = this._getStmt( - "SELECT h.id AS place_id, " + - "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS name_id, " + - "a.id AS anno_id, a.dateAdded AS anno_date " + - "FROM (SELECT id FROM moz_places_temp WHERE url = :page_url " + - "UNION " + - "SELECT id FROM moz_places WHERE url = :page_url) AS h " + - "LEFT JOIN moz_annos a ON a.place_id = h.id " + - "AND a.anno_attribute_id = name_id"); - stmt.params.anno_name = GUID_ANNO; - return stmt; - }, - _checkGUIDPageAnnotationCols: ["place_id", "name_id", "anno_id", - "anno_date"], - - get _addPageAnnotationStm() { - // Gecko <2.0 only - return this._getStmt( - "INSERT OR REPLACE INTO moz_annos " + - "(id, place_id, anno_attribute_id, mime_type, content, flags, " + - "expiration, type, dateAdded, lastModified) " + - "VALUES (:id, :place_id, :name_id, :mime_type, :content, :flags, " + - ":expiration, :type, :date_added, :last_modified)"); - }, - - __setGUIDStm: null, get _setGUIDStm() { - if (this.__setGUIDStm !== null) { - return this.__setGUIDStm; - } - - // Obtains a statement to set the guid iff the guid column exists. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "UPDATE moz_places " + - "SET guid = :guid " + - "WHERE url = :page_url"); - } else { - stmt = false; - } - - return this.__setGUIDStm = stmt; + return this._getStmt( + "UPDATE moz_places " + + "SET guid = :guid " + + "WHERE url = :page_url"); }, // Some helper functions to handle GUIDs @@ -226,77 +140,18 @@ HistoryStore.prototype = { if (!guid) guid = Utils.makeGUID(); - // If we can, set the GUID on moz_places and do not do any other work. - let (stmt = this._setGUIDStm) { - if (stmt) { - stmt.params.guid = guid; - stmt.params.page_url = uri; - Utils.queryAsync(stmt); - return guid; - } - } - - // Ensure annotation name exists - Utils.queryAsync(this._addGUIDAnnotationNameStm); - - let stmt = this._checkGUIDPageAnnotationStm; + let stmt = this._setGUIDStm; + stmt.params.guid = guid; stmt.params.page_url = uri; - let result = Utils.queryAsync(stmt, this._checkGUIDPageAnnotationCols)[0]; - if (!result) { - let log = Log4Moz.repository.getLogger("Engine.History"); - log.warn("Couldn't annotate URI " + uri); - return guid; - } - - stmt = this._addPageAnnotationStm; - if (result.anno_id) { - stmt.params.id = result.anno_id; - stmt.params.date_added = result.anno_date; - } else { - stmt.params.id = null; - stmt.params.date_added = Date.now() * 1000; - } - stmt.params.place_id = result.place_id; - stmt.params.name_id = result.name_id; - stmt.params.content = guid; - stmt.params.flags = 0; - stmt.params.expiration = Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY; - stmt.params.type = Ci.nsIAnnotationService.TYPE_STRING; - stmt.params.last_modified = Date.now() * 1000; Utils.queryAsync(stmt); - return guid; }, - __guidStm: null, get _guidStm() { - if (this.__guidStm) { - return this.__guidStm; - } - - // Try to first read from moz_places. Creating the statement will throw - // if the column doesn't exist, though so fallback to just reading from - // the annotation table. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "SELECT guid " + - "FROM moz_places " + - "WHERE url = :page_url"); - } else { - stmt = this._getStmt( - "SELECT a.content AS guid " + - "FROM moz_annos a " + - "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " + - "JOIN ( " + - "SELECT id FROM moz_places_temp WHERE url = :page_url " + - "UNION " + - "SELECT id FROM moz_places WHERE url = :page_url " + - ") AS h ON h.id = a.place_id " + - "WHERE n.name = '" + GUID_ANNO + "'"); - } - - return this.__guidStmt = stmt; + return this._getStmt( + "SELECT guid " + + "FROM moz_places " + + "WHERE url = :page_url"); }, _guidCols: ["guid"], @@ -315,21 +170,6 @@ HistoryStore.prototype = { }, get _visitStm() { - // Gecko <2.0 - if (this._haveTempTables) { - let where = - "WHERE place_id = IFNULL( " + - "(SELECT id FROM moz_places_temp WHERE url = :url), " + - "(SELECT id FROM moz_places WHERE url = :url) " + - ") "; - return this._getStmt( - "SELECT visit_type type, visit_date date " + - "FROM moz_historyvisits_temp " + where + "UNION " + - "SELECT visit_type type, visit_date date " + - "FROM moz_historyvisits " + where + - "ORDER BY date DESC LIMIT 10 "); - } - // Gecko 2.0 return this._getStmt( "SELECT visit_type type, visit_date date " + "FROM moz_historyvisits " + @@ -338,53 +178,15 @@ HistoryStore.prototype = { }, _visitCols: ["date", "type"], - __urlStmt: null, get _urlStm() { - if (this.__urlStmt) { - return this.__urlStmt; - } - - // Try to first read from moz_places. Creating the statement will throw - // if the column doesn't exist, though so fallback to just reading from - // the annotation table. - let stmt; - if (this._haveGUIDColumn) { - stmt = this._getStmt( - "SELECT url, title, frecency " + - "FROM moz_places " + - "WHERE guid = :guid"); - } else { - let where = - "WHERE id = (" + - "SELECT place_id " + - "FROM moz_annos " + - "WHERE content = :guid AND anno_attribute_id = (" + - "SELECT id " + - "FROM moz_anno_attributes " + - "WHERE name = '" + GUID_ANNO + "')) "; - stmt = this._getStmt( - "SELECT url, title, frecency FROM moz_places_temp " + where + - "UNION ALL " + - "SELECT url, title, frecency FROM moz_places " + where + "LIMIT 1"); - } - - return this.__urlStmt = stmt; + return this._getStmt( + "SELECT url, title, frecency " + + "FROM moz_places " + + "WHERE guid = :guid"); }, _urlCols: ["url", "title", "frecency"], get _allUrlStm() { - // Gecko <2.0 - if (this._haveTempTables) - return this._getStmt( - "SELECT url, frecency FROM moz_places_temp " + - "WHERE last_visit_date > :cutoff_date " + - "UNION " + - "SELECT url, frecency FROM moz_places " + - "WHERE last_visit_date > :cutoff_date " + - "ORDER BY 2 DESC " + - "LIMIT :max_results"); - - // Gecko 2.0 return this._getStmt( "SELECT url " + "FROM moz_places " + @@ -425,12 +227,6 @@ HistoryStore.prototype = { }, applyIncomingBatch: function applyIncomingBatch(records) { - // Gecko <2.0 - if (!this._asyncHistory) { - return Store.prototype.applyIncomingBatch.call(this, records); - } - - // Gecko 2.0 let failed = []; // Convert incoming records to mozIPlaceInfo objects. Some records can be @@ -529,9 +325,8 @@ HistoryStore.prototype = { + visit.date); throw "Visit has no date!"; } - // TRANSITION_FRAMED_LINK = TRANSITION_DOWNLOAD + 1 is new in Gecko 2.0 if (!visit.type || !(visit.type >= Svc.History.TRANSITION_LINK && - visit.type <= Svc.History.TRANSITION_DOWNLOAD + 1)) { + visit.type <= Svc.History.TRANSITION_FRAMED_LINK)) { this._log.warn("Encountered record with invalid visit type: " + visit.type); throw "Invalid visit type!"; @@ -563,12 +358,6 @@ HistoryStore.prototype = { return true; }, - create: function HistStore_create(record) { - // Add the url and set the GUID - this.update(record); - this.setGUID(record.histUri, record.id); - }, - remove: function HistStore_remove(record) { let page = this._findURLByGUID(record.id); if (page == null) { @@ -581,29 +370,6 @@ HistoryStore.prototype = { this._log.trace("Removed page: " + [record.id, page.url, page.title]); }, - update: function HistStore_update(record) { - this._log.trace(" -> processing history entry: " + record.histUri); - - if (!this._recordToPlaceInfo(record)) { - return; - } - - for each (let {visitDate, transitionType} in record.visits) { - Svc.History.addVisit(record.uri, visitDate, null, transitionType, - transitionType == 5 || transitionType == 6, 0); - } - - if (record.title) { - try { - this._hsvc.setPageTitle(record.uri, record.title); - } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { - // There's no entry for the given URI, either because it's a - // URI that Places ignores (e.g. javascript:) or there were no - // visits. We can just ignore those cases. - } - } - }, - itemExists: function HistStore_itemExists(id) { if (this._findURLByGUID(id)) return true; diff --git a/services/sync/modules/engines/passwords.js b/services/sync/modules/engines/passwords.js index 2b1aee3d482c..7b9b0d07299a 100644 --- a/services/sync/modules/engines/passwords.js +++ b/services/sync/modules/engines/passwords.js @@ -114,13 +114,8 @@ function PasswordStore(name) { "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); Utils.lazy2(this, "DBConnection", function() { - try { - return Svc.Login.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.mozIStorageConnection); - } catch (ex if (ex.result == Cr.NS_ERROR_NO_INTERFACE)) { - // Gecko <2.0 *sadface* - return null; - } + return Svc.Login.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.mozIStorageConnection); }); } PasswordStore.prototype = { diff --git a/services/sync/modules/engines/prefs.js b/services/sync/modules/engines/prefs.js index 4a7047654612..82286bdb1484 100644 --- a/services/sync/modules/engines/prefs.js +++ b/services/sync/modules/engines/prefs.js @@ -47,6 +47,7 @@ Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/ext/Preferences.js"); +Cu.import("resource://gre/modules/LightweightThemeManager.jsm"); const PREFS_GUID = Utils.encodeBase64url(Svc.AppInfo.ID); @@ -138,20 +139,9 @@ PrefStore.prototype = { }, _setAllPrefs: function PrefStore__setAllPrefs(values) { - // cache - let ltmExists = true; - let ltm = {}; - let enabledBefore = false; let enabledPref = "lightweightThemes.isThemeSelected"; - let prevTheme = ""; - try { - Cu.import("resource://gre/modules/LightweightThemeManager.jsm", ltm); - ltm = ltm.LightweightThemeManager; - enabledBefore = this._prefs.get(enabledPref, false); - prevTheme = ltm.currentTheme; - } catch(ex) { - ltmExists = false; - } // LightweightThemeManager only exists in Firefox 3.6+ + let enabledBefore = this._prefs.get(enabledPref, false); + let prevTheme = LightweightThemeManager.currentTheme; for (let [pref, value] in Iterator(values)) { if (!this._isSynced(pref)) @@ -171,14 +161,12 @@ PrefStore.prototype = { } // Notify the lightweight theme manager of all the new values - if (ltmExists) { - let enabledNow = this._prefs.get(enabledPref, false); - if (enabledBefore && !enabledNow) - ltm.currentTheme = null; - else if (enabledNow && ltm.usedThemes[0] != prevTheme) { - ltm.currentTheme = null; - ltm.currentTheme = ltm.usedThemes[0]; - } + let enabledNow = this._prefs.get(enabledPref, false); + if (enabledBefore && !enabledNow) { + LightweightThemeManager.currentTheme = null; + } else if (enabledNow && LightweightThemeManager.usedThemes[0] != prevTheme) { + LightweightThemeManager.currentTheme = null; + LightweightThemeManager.currentTheme = ltm.usedThemes[0]; } }, diff --git a/services/sync/modules/engines/tabs.js b/services/sync/modules/engines/tabs.js index be3d3f684fdd..0b2e55dc4d79 100644 --- a/services/sync/modules/engines/tabs.js +++ b/services/sync/modules/engines/tabs.js @@ -48,6 +48,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/engines/clients.js"); Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/ext/Preferences.js"); @@ -106,6 +107,10 @@ TabEngine.prototype = { this._tracker.modified = true; }, + removeClientData: function removeClientData() { + new Resource(this.engineURL + "/" + Clients.localID).delete(); + }, + /* The intent is not to show tabs in the menu if they're already * open locally. There are a couple ways to interpret this: for * instance, we could do it by removing a tab from the list when diff --git a/services/sync/modules/resource.js b/services/sync/modules/resource.js index 3d0566251d92..812261b9128c 100644 --- a/services/sync/modules/resource.js +++ b/services/sync/modules/resource.js @@ -21,6 +21,7 @@ * Dan Mills * Anant Narayanan * Philipp von Weitershausen + * Richard Newman * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -70,7 +71,7 @@ function BrokenBasicAuthenticator(identity) { } BrokenBasicAuthenticator.prototype = { onRequest: function BasicAuth_onRequest(headers) { - headers['Authorization'] = 'Basic ' + + headers['authorization'] = 'Basic ' + btoa(this._id.username + ':' + this._id.password); return headers; } @@ -81,7 +82,7 @@ function BasicAuthenticator(identity) { } BasicAuthenticator.prototype = { onRequest: function onRequest(headers) { - headers['Authorization'] = 'Basic ' + + headers['authorization'] = 'Basic ' + btoa(this._id.username + ':' + this._id.passwordUTF8); return headers; } @@ -127,7 +128,7 @@ AuthMgr.prototype = { * function callback(error, result) {...} * * 'error' will be null on successful requests. Likewise, result will not be - * passes (=undefined) when an error occurs. Note that this is independent of + * passed (=undefined) when an error occurs. Note that this is independent of * the status of the HTTP response. */ function AsyncResource(uri) { @@ -141,7 +142,21 @@ function AsyncResource(uri) { AsyncResource.prototype = { _logName: "Net.Resource", - // Wait 5 minutes before killing a request + // The string to use as the base User-Agent in Sync requests. + // These strings will look something like + // + // Firefox/4.0 FxSync/1.8.0.20100101.mobile + // + // or + // + // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop + // + _userAgent: + Svc.AppInfo.name + "/" + Svc.AppInfo.version + // Product. + " FxSync/" + WEAVE_VERSION + "." + // Sync. + Svc.AppInfo.appBuildID + ".", // Build. + + // Wait 5 minutes before killing a request. ABORT_TIMEOUT: 300000, // ** {{{ Resource.authenticator }}} ** @@ -171,12 +186,8 @@ AsyncResource.prototype = { set headers(value) { this._headers = value; }, - setHeader: function Res_setHeader() { - if (arguments.length % 2) - throw "setHeader only accepts arguments in multiples of 2"; - for (let i = 0; i < arguments.length; i += 2) { - this._headers[arguments[i].toLowerCase()] = arguments[i + 1]; - } + setHeader: function Res_setHeader(header, value) { + this._headers[header.toLowerCase()] = value; }, // ** {{{ Resource.uri }}} ** @@ -227,10 +238,16 @@ AsyncResource.prototype = { // Setup a callback to handle bad HTTPS certificates. channel.notificationCallbacks = new BadCertListener(); - // Avoid calling the authorizer more than once + // Compose a UA string fragment from the various available identifiers. + if (Svc.Prefs.get("sendVersionInfo", true)) { + let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop"); + channel.setRequestHeader("user-agent", ua, false); + } + + // Avoid calling the authorizer more than once. let headers = this.headers; for (let key in headers) { - if (key == 'Authorization') + if (key == 'authorization') this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); else this._log.trace("HTTP Header " + key + ": " + headers[key]); @@ -242,14 +259,14 @@ AsyncResource.prototype = { _onProgress: function Res__onProgress(channel) {}, _doRequest: function _doRequest(action, data, callback) { + this._log.trace("In _doRequest."); this._callback = callback; let channel = this._channel = this._createRequest(); if ("undefined" != typeof(data)) this._data = data; - // PUT and POST are trreated differently because - // they have payload data. + // PUT and POST are treated differently because they have payload data. if ("PUT" == action || "POST" == action) { // Convert non-string bodies into JSON if (this._data.constructor.toString() != String) @@ -278,6 +295,8 @@ AsyncResource.prototype = { }, _onComplete: function _onComplete(error, data) { + this._log.trace("In _onComplete. Error is " + error + "."); + if (error) { this._callback(error); return; @@ -287,29 +306,54 @@ AsyncResource.prototype = { let channel = this._channel; let action = channel.requestMethod; - // Set some default values in-case there's no response header - let headers = {}; + this._log.trace("Channel: " + channel); + this._log.trace("Action: " + action); + + // Process status and success first. This way a problem with headers + // doesn't fail to include accurate status information. let status = 0; let success = false; + try { - // Read out the response headers if available + status = channel.responseStatus; + success = channel.requestSucceeded; // HTTP status. + + this._log.trace("Status: " + status); + this._log.trace("Success: " + success); + + // Log the status of the request. + let mesg = [action, success ? "success" : "fail", status, + channel.URI.spec].join(" "); + this._log.debug("mesg: " + mesg); + + if (mesg.length > 200) + mesg = mesg.substr(0, 200) + "…"; + this._log.debug(mesg); + + // Additionally give the full response body when Trace logging. + if (this._log.level <= Log4Moz.Level.Trace) + this._log.trace(action + " body: " + data); + + } catch(ex) { + // Got a response, but an exception occurred during processing. + // This shouldn't occur. + this._log.warn("Caught unexpected exception " + Utils.exceptionStr(ex) + + " in _onComplete."); + this._log.debug(Utils.stackTrace(ex)); + } + + // Process headers. They can be empty, or the call can otherwise fail, so + // put this in its own try block. + let headers = {}; + try { + this._log.trace("Processing response headers."); + + // Read out the response headers if available. channel.visitResponseHeaders({ visitHeader: function visitHeader(header, value) { headers[header.toLowerCase()] = value; } }); - status = channel.responseStatus; - success = channel.requestSucceeded; - - // Log the status of the request - let mesg = [action, success ? "success" : "fail", status, - channel.URI.spec].join(" "); - if (mesg.length > 200) - mesg = mesg.substr(0, 200) + "…"; - this._log.debug(mesg); - // Additionally give the full response body when Trace logging - if (this._log.level <= Log4Moz.Level.Trace) - this._log.trace(action + " body: " + data); // This is a server-side safety valve to allow slowing down // clients without hurting performance. @@ -320,23 +364,26 @@ AsyncResource.prototype = { if (success && headers["x-weave-quota-remaining"]) Observers.notify("weave:service:quota:remaining", parseInt(headers["x-weave-quota-remaining"], 10)); - } - // Got a response but no header; must be cached (use default values) - catch(ex) { - this._log.debug(action + " cached: " + status); + } catch (ex) { + this._log.debug("Caught exception " + Utils.exceptionStr(ex) + + " visiting headers in _onComplete."); + this._log.debug(Utils.stackTrace(ex)); } - let ret = new String(data); - ret.headers = headers; - ret.status = status; + let ret = new String(data); + ret.status = status; ret.success = success; + ret.headers = headers; - // Make a lazy getter to convert the json response into an object + // Make a lazy getter to convert the json response into an object. + // Note that this can cause a parse error to be thrown far away from the + // actual fetch, so be warned! Utils.lazy2(ret, "obj", function() JSON.parse(ret)); - // Notify if we get a 401 to maybe try again with a new uri + // Notify if we get a 401 to maybe try again with a new URI. + // TODO: more retry logic. if (status == 401) { - // Create an object to allow observers to decide if we should try again + // Create an object to allow observers to decide if we should try again. let subject = { newUri: "", resource: this, @@ -344,7 +391,7 @@ AsyncResource.prototype = { } Observers.notify("weave:resource:status:401", subject); - // Do the same type of request but with the new uri + // Do the same type of request but with the new URI. if (subject.newUri != "") { this.uri = subject.newUri; this._doRequest(action, this._data, this._callback); @@ -481,36 +528,46 @@ function ChannelListener(onComplete, onProgress, logger, timeout) { ChannelListener.prototype = { onStartRequest: function Channel_onStartRequest(channel) { + this._log.trace("onStartRequest called for channel " + channel + "."); channel.QueryInterface(Ci.nsIHttpChannel); - // Save the latest server timestamp when possible + // Save the latest server timestamp when possible. try { Resource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0; } catch(ex) {} - this._log.trace(channel.requestMethod + " " + channel.URI.spec); + this._log.trace("onStartRequest: " + channel.requestMethod + " " + + channel.URI.spec); this._data = ''; this.delayAbort(); }, onStopRequest: function Channel_onStopRequest(channel, context, status) { - // Clear the abort timer now that the channel is done + // Clear the abort timer now that the channel is done. this.abortTimer.clear(); + let success = Components.isSuccessCode(status); + this._log.trace("Channel for " + channel.requestMethod + " " + + channel.URI.spec + ": isSuccessCode(" + status + ")? " + + success); + if (this._data == '') this._data = null; // Throw the failure code and stop execution. Use Components.Exception() // instead of Error() so the exception is QI-able and can be passed across // XPCOM borders while preserving the status code. - if (!Components.isSuccessCode(status)) { + if (!success) { let message = Components.Exception("", status).name; let error = Components.Exception(message, status); this._onComplete(error); return; } + this._log.trace("Channel: flags = " + channel.loadFlags + + ", URI = " + channel.URI.spec + + ", HTTP success? " + channel.requestSucceeded); this._onComplete(null, this._data); }, diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index 529fb5150b02..0cd2d865e007 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -154,7 +154,7 @@ WeaveSvc.prototype = { let misc = Svc.Prefs.get("miscURL"); if (misc.indexOf(":") == -1) misc = this.serverURL + misc; - return misc + "1.0/"; + return misc + MISC_API_VERSION + "/"; }, get userAPI() { @@ -162,7 +162,7 @@ WeaveSvc.prototype = { let user = Svc.Prefs.get("userURL"); if (user.indexOf(":") == -1) user = this.serverURL + user; - return user + "1.0/"; + return user + USER_API_VERSION + "/"; }, get pwResetURL() { @@ -237,7 +237,7 @@ WeaveSvc.prototype = { if (this.clusterURL == "" || this.username == "") return; - let storageAPI = this.clusterURL + Svc.Prefs.get("storageAPI") + "/"; + let storageAPI = this.clusterURL + SYNC_API_VERSION + "/"; this.userBaseURL = storageAPI + this.username + "/"; this._log.debug("Caching URLs under storage user base: " + this.userBaseURL); @@ -1014,6 +1014,16 @@ WeaveSvc.prototype = { }))(), startOver: function() { + // Clear client-specific data from the server, including disabled engines. + for each (let engine in [Clients].concat(Engines.getAll())) { + try { + engine.removeClientData(); + } catch(ex) { + this._log.warn("Deleting client data for " + engine.name + " failed:" + + Utils.exceptionStr(ex)); + } + } + // Set a username error so the status message shows "set up..." Status.login = LOGIN_FAILED_NO_USERNAME; this.logout(); @@ -1184,12 +1194,6 @@ WeaveSvc.prototype = { checkAccount: function checkAccount(account) { let username = this._usernameFromAccount(account); - return this.checkUsername(username); - }, - - // Backwards compat with the Firefox UI. Fold into checkAccount() once - // bug 595066 has landed. - checkUsername: function checkUsername(username) { let url = this.userAPI + username; let res = new Resource(url); res.authenticator = new NoOpAuthenticator(); @@ -1211,18 +1215,9 @@ WeaveSvc.prototype = { return this._errorStr(data); }, - createAccount: function createAccount() { - // Backwards compat with the Firefox UI. Change to signature to - // (email, password, captchaChallenge, captchaResponse) once - // bug 595066 has landed. - let username, email, password, captchaChallenge, captchaResponse; - if (arguments.length == 4) { - [email, password, captchaChallenge, captchaResponse] = arguments; - username = this._usernameFromAccount(email); - } else { - [username, password, email, captchaChallenge, captchaResponse] = arguments; - } - + createAccount: function createAccount(email, password, + captchaChallenge, captchaResponse) { + let username = this._usernameFromAccount(email); let payload = JSON.stringify({ "password": Utils.encodeUTF8(password), "email": email, diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js index 4c2fedbc6e97..505951a1d3ed 100644 --- a/services/sync/modules/util.js +++ b/services/sync/modules/util.js @@ -47,15 +47,7 @@ Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/ext/Preferences.js"); Cu.import("resource://services-sync/ext/StringBundle.js"); Cu.import("resource://services-sync/log4moz.js"); - -let NetUtil; -try { - let ns = {}; - Cu.import("resource://gre/modules/NetUtil.jsm", ns); - NetUtil = ns.NetUtil; -} catch (ex) { - // Firefox 3.5 :( -} +Cu.import("resource://gre/modules/NetUtil.jsm"); // Constants for makeSyncCallback, waitForSyncCallback const CB_READY = {}; @@ -220,15 +212,6 @@ let Utils = { } }, - createStatement: function createStatement(db, query) { - // Gecko 2.0 - if (db.createAsyncStatement) - return db.createAsyncStatement(query); - - // Gecko <2.0 - return db.createStatement(query); - }, - // Prototype for mozIStorageCallback, used in queryAsync below. // This allows us to define the handle* functions just once rather // than on every queryAsync invocation. @@ -1069,21 +1052,6 @@ let Utils = { return; } - // Gecko < 2.0 - if (!NetUtil || !NetUtil.newChannel) { - let json; - try { - let [is] = Utils.open(file, "<"); - json = JSON.parse(Utils.readStream(is)); - is.close(); - } catch (ex) { - if (that._log) - that._log.debug("Failed to load json: " + Utils.exceptionStr(ex)); - } - callback.call(that, json); - return; - } - let channel = NetUtil.newChannel(file); channel.contentType = "application/json"; @@ -1127,21 +1095,10 @@ let Utils = { let json = typeof obj == "function" ? obj.call(that) : obj; let out = JSON.stringify(json); - // Firefox 3.5 - if (!NetUtil) { - let [fos] = Utils.open(file, ">"); - fos.writeString(out); - fos.close(); - if (typeof callback == "function") { - callback.call(that); - } - return; - } - let fos = Cc["@mozilla.org/network/safe-file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, - fos.DEFER_OPEN || 0); + fos.DEFER_OPEN); let is = this._utf8Converter.convertToInputStream(out); NetUtil.asyncCopy(is, fos, function (result) { if (typeof callback == "function") { @@ -1190,59 +1147,6 @@ let Utils = { return thisObj[name] = timer; }, - // Gecko <2.0 - open: function open(pathOrFile, mode, perms) { - let stream, file; - - if (pathOrFile instanceof Ci.nsIFile) { - file = pathOrFile; - } else { - file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); - dump("PATH IS" + pathOrFile + "\n"); - file.initWithPath(pathOrFile); - } - - if (!perms) - perms = PERMS_FILE; - - switch(mode) { - case "<": { - if (!file.exists()) - throw "Cannot open file for reading, file does not exist"; - let fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - fis.init(file, MODE_RDONLY, perms, 0); - 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 ">": { - let fos = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - 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 ">>": { - let fos = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - 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: - throw "Illegal mode to open(): " + mode; - } - - return [stream, file]; - }, - getIcon: function(iconUri, defaultIcon) { try { let iconURI = Utils.makeURI(iconUri); @@ -1263,16 +1167,6 @@ let Utils = { return Str.errors.get("error.reason.unknown"); }, - // Gecko <2.0 - // assumes an nsIConverterInputStream - readStream: function Weave_readStream(is) { - let ret = "", str = {}; - while (is.readString(4096, str) != 0) { - ret += str.value; - } - return ret; - }, - encodeUTF8: function(str) { try { str = this._utf8Converter.ConvertFromUnicode(str); @@ -1672,15 +1566,9 @@ this.__defineGetter__("_sessionCID", function() { Svc.__defineGetter__("Crypto", function() { let cryptoSvc; - try { - let ns = {}; - Cu.import("resource://services-crypto/WeaveCrypto.js", ns); - cryptoSvc = new ns.WeaveCrypto(); - } catch (ex) { - // Fallback to binary WeaveCrypto - cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]. - getService(Ci.IWeaveCrypto); - } + let ns = {}; + Cu.import("resource://services-crypto/WeaveCrypto.js", ns); + cryptoSvc = new ns.WeaveCrypto(); delete Svc.Crypto; return Svc.Crypto = cryptoSvc; }); diff --git a/services/sync/services-sync.js b/services/sync/services-sync.js index a91540b10796..a2f19adcdb0a 100644 --- a/services/sync/services-sync.js +++ b/services/sync/services-sync.js @@ -1,5 +1,4 @@ pref("services.sync.serverURL", "https://auth.services.mozilla.com/"); -pref("services.sync.storageAPI", "1.0"); pref("services.sync.userURL", "user/"); pref("services.sync.miscURL", "misc/"); pref("services.sync.termsURL", "https://services.mozilla.com/tos/"); @@ -9,6 +8,7 @@ pref("services.sync.syncKeyHelpURL", "https://services.mozilla.com/help/synckey" pref("services.sync.lastversion", "firstrun"); pref("services.sync.autoconnect", true); +pref("services.sync.sendVersionInfo", true); pref("services.sync.engine.bookmarks", true); pref("services.sync.engine.history", true); diff --git a/services/sync/tests/unit/head_appinfo.js.in b/services/sync/tests/unit/head_appinfo.js.in index b6202b9065bc..6353726228aa 100644 --- a/services/sync/tests/unit/head_appinfo.js.in +++ b/services/sync/tests/unit/head_appinfo.js.in @@ -66,9 +66,18 @@ registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}" } -// Provide resource://services-sync if it isn't already available -let weaveService = Cc["@mozilla.org/weave/service;1"].getService(); -weaveService.wrappedJSObject.addResourceAlias(); +// Register resource aliases. Normally done in SyncComponents.manifest. +function addResourceAlias() { + Cu.import("resource://gre/modules/Services.jsm"); + const resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + let uri; + uri = Services.io.newURI("resource:///modules/services-sync/", null, null); + resProt.setSubstitution("services-sync", uri); + uri = Services.io.newURI("resource:///modules/services-crypto/", null, null); + resProt.setSubstitution("services-crypto", uri); +} +addResourceAlias(); // Some tests hang on OSX debug builds. See bug 604565. diff --git a/services/sync/tests/unit/head_http_server.js b/services/sync/tests/unit/head_http_server.js index e6844465750c..9152679db315 100644 --- a/services/sync/tests/unit/head_http_server.js +++ b/services/sync/tests/unit/head_http_server.js @@ -267,7 +267,7 @@ ServerCollection.prototype = { * Test setup helpers. */ function sync_httpd_setup(handlers) { - handlers["/1.0/foo/storage/meta/global"] + handlers["/1.1/foo/storage/meta/global"] = (new ServerWBO('global', {})).handler(); return httpd_setup(handlers); } diff --git a/services/sync/tests/unit/test_bookmark_engine.js b/services/sync/tests/unit/test_bookmark_engine.js index 05a22ccb1a02..10fa599c78e0 100644 --- a/services/sync/tests/unit/test_bookmark_engine.js +++ b/services/sync/tests/unit/test_bookmark_engine.js @@ -5,12 +5,7 @@ Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/service.js"); -try { - Cu.import("resource://gre/modules/PlacesUtils.jsm"); -} -catch(ex) { - Cu.import("resource://gre/modules/utils.js"); -} +Cu.import("resource://gre/modules/PlacesUtils.jsm"); Engines.register(BookmarksEngine); @@ -71,8 +66,8 @@ function test_processIncoming_error_orderChildren() { {engines: {bookmarks: {version: engine.version, syncID: engine.syncID}}}); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/bookmarks": collection.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/bookmarks": collection.handler() }); try { @@ -153,8 +148,8 @@ function test_restorePromptsReupload() { {engines: {bookmarks: {version: engine.version, syncID: engine.syncID}}}); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/bookmarks": collection.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/bookmarks": collection.handler() }); Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup... @@ -328,8 +323,8 @@ function test_mismatched_types() { syncID: engine.syncID}}}); _("GUID: " + store.GUIDForId(6, true)); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/bookmarks": collection.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/bookmarks": collection.handler() }); try { diff --git a/services/sync/tests/unit/test_bookmark_livemarks.js b/services/sync/tests/unit/test_bookmark_livemarks.js index 150be7179468..39c677c053d3 100644 --- a/services/sync/tests/unit/test_bookmark_livemarks.js +++ b/services/sync/tests/unit/test_bookmark_livemarks.js @@ -5,12 +5,7 @@ Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/service.js"); -try { - Cu.import("resource://gre/modules/PlacesUtils.jsm"); -} -catch(ex) { - Cu.import("resource://gre/modules/utils.js"); -} +Cu.import("resource://gre/modules/PlacesUtils.jsm"); const DESCRIPTION_ANNO = "bookmarkProperties/description"; diff --git a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js index f0e71b2fd5f2..aec5a917b442 100644 --- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js +++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js @@ -5,12 +5,7 @@ Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/service.js"); -try { - Cu.import("resource://gre/modules/PlacesUtils.jsm"); -} -catch(ex) { - Cu.import("resource://gre/modules/utils.js"); -} +Cu.import("resource://gre/modules/PlacesUtils.jsm"); const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark"; var IOService = Cc["@mozilla.org/network/io-service;1"] @@ -115,8 +110,8 @@ function test_annotation_uploaded() { {engines: {bookmarks: {version: engine.version, syncID: engine.syncID}}}); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/bookmarks": collection.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/bookmarks": collection.handler() }); try { @@ -204,8 +199,8 @@ function test_smart_bookmarks_duped() { {engines: {bookmarks: {version: engine.version, syncID: engine.syncID}}}); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/bookmarks": collection.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/bookmarks": collection.handler() }); try { diff --git a/services/sync/tests/unit/test_bookmark_store.js b/services/sync/tests/unit/test_bookmark_store.js index a50dbda47492..25b7838151c6 100644 --- a/services/sync/tests/unit/test_bookmark_store.js +++ b/services/sync/tests/unit/test_bookmark_store.js @@ -352,64 +352,6 @@ function test_reparentOrphans() { } } -// Copying a bookmark in the UI also copies its annotations, including the GUID -// annotation in 3.x. -function test_copying_avoid_duplicate_guids() { - if (store._haveGUIDColumn) { - _("No GUID annotation handling functionality; returning without testing."); - return; - } - - try { - _("Ensure the record isn't present yet."); - let ids = Svc.Bookmark.getBookmarkIdsForURI(fxuri, {}); - do_check_eq(ids.length, 0); - - _("Let's create a new record."); - let fxrecord = new Bookmark("bookmarks", "get-firefox1"); - fxrecord.bmkUri = fxuri.spec; - fxrecord.description = "Firefox is awesome."; - fxrecord.title = "Get Firefox!"; - fxrecord.tags = ["firefox", "awesome", "browser"]; - fxrecord.keyword = "awesome"; - fxrecord.loadInSidebar = false; - fxrecord.parentName = "Bookmarks Toolbar"; - fxrecord.parentid = "toolbar"; - store.applyIncoming(fxrecord); - - _("Verify it has been created correctly."); - let id = store.idForGUID(fxrecord.id); - do_check_eq(store.GUIDForId(id), fxrecord.id); - do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri)); - do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"), - fxrecord.description); - - _("Copy the record as happens in the UI: with the same GUID."); - let parentID = Svc.Bookmark.getFolderIdForItem(id); - let copy = Svc.Bookmark.insertBookmark(parentID, fxuri, -1, fxrecord.title); - Svc.Bookmark.setItemLastModified(copy, Svc.Bookmark.getItemLastModified(id)); - Svc.Bookmark.setItemDateAdded(copy, Svc.Bookmark.getItemDateAdded(id)); - store._setGUID(copy, fxrecord.id); - - do_check_eq(store.GUIDForId(copy), store.GUIDForId(id)); - - _("Calling idForGUID fixes things."); - _("GUID before: " + store.GUIDForId(copy)); - do_check_eq(store.idForGUID(fxrecord.id), id); // Oldest wins. - _("GUID now: " + store.GUIDForId(copy)); - do_check_neq(store.GUIDForId(copy), store.GUIDForId(id)); - - _("Verify that the anno itself has changed."); - do_check_neq(Svc.Annos.getItemAnnotation(copy, "sync/guid"), fxrecord.id); - do_check_eq(Svc.Annos.getItemAnnotation(copy, "sync/guid"), - store.GUIDForId(copy)); - - } finally { - _("Clean up."); - store.wipe(); - } -} - function run_test() { initTestLogging('Trace'); test_bookmark_create(); @@ -422,5 +364,4 @@ function run_test() { test_move_order(); test_orphan(); test_reparentOrphans(); - test_copying_avoid_duplicate_guids(); } diff --git a/services/sync/tests/unit/test_bookmark_tracker.js b/services/sync/tests/unit/test_bookmark_tracker.js index c14032b8c776..63aad33633d1 100644 --- a/services/sync/tests/unit/test_bookmark_tracker.js +++ b/services/sync/tests/unit/test_bookmark_tracker.js @@ -1,15 +1,7 @@ +Cu.import("resource://services-sync/engines/bookmarks.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/util.js"); -try { - Cu.import("resource://gre/modules/PlacesUtils.jsm"); -} -catch(ex) { - Cu.import("resource://gre/modules/utils.js"); -} - -// Grab a backstage pass so we can fetch the anno constant. Evil but useful. -let bsp = Cu.import("resource://services-sync/engines/bookmarks.js"); -const SYNC_GUID_ANNO = bsp.GUID_ANNO; // Not the same as Places' GUID_ANNO! +Cu.import("resource://gre/modules/PlacesUtils.jsm"); Engines.register(BookmarksEngine); let engine = Engines.get("bookmarks"); @@ -248,55 +240,33 @@ function test_tracking() { } } -function test_guid_stripping() { - // Gecko <2.0 - if (store._haveGUIDColumn) { - _("We have a GUID column; not testing anno GUID fixing."); - return; - } +function test_onItemChanged() { + // Anno that's in ANNOS_TO_TRACK. + const GENERATOR_ANNO = "microsummary/generatorURI"; _("Verify we've got an empty tracker to work with."); let tracker = engine._tracker; - let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder, - "Test Folder", - Svc.Bookmark.DEFAULT_INDEX); - function createBmk() { - return Svc.Bookmark.insertBookmark(folder, - Utils.makeURI("http://getfirefox.com"), - Svc.Bookmark.DEFAULT_INDEX, - "Get Firefox!"); - } - do_check_eq([id for (id in tracker.changedIDs)].length, 0); try { - _("Directly testing GUID stripping."); + Svc.Obs.notify("weave:engine:stop-tracking"); + let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder, + "Parent", + Svc.Bookmark.DEFAULT_INDEX); + _("Track changes to annos."); + let b = Svc.Bookmark.insertBookmark(folder, + Utils.makeURI("http://getfirefox.com"), + Svc.Bookmark.DEFAULT_INDEX, + "Get Firefox!"); + let bGUID = engine._store.GUIDForId(b); + _("New item is " + b); + _("GUID: " + bGUID); Svc.Obs.notify("weave:engine:start-tracking"); - tracker.ignoreAll = false; - let suspect = createBmk(); - let victim = createBmk(); + Svc.Annos.setItemAnnotation(b, GENERATOR_ANNO, "http://foo.bar/", 0, + Svc.Annos.EXPIRE_NEVER); + do_check_true(tracker.changedIDs[bGUID] > 0); - _("Suspect: " + suspect + ", victim: " + victim); - - let suspectGUID = store.GUIDForId(suspect); - let victimGUID = store.GUIDForId(victim); - _("Set the GUID on one entry to be the same as another."); - do_check_neq(suspectGUID, victimGUID); - Svc.Annos.setItemAnnotation(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim), - 0, Svc.Annos.EXPIRE_NEVER); - - _("Tracker changed it to something else."); - let newGUID = store.GUIDForId(suspect); - do_check_neq(newGUID, victimGUID); - do_check_neq(newGUID, suspectGUID); - - _("Victim GUID remains unchanged."); - do_check_eq(victimGUID, store.GUIDForId(victim)); - - _("New GUID is in the tracker."); - _(JSON.stringify(tracker.changedIDs)); - do_check_neq(tracker.changedIDs[newGUID], null); } finally { _("Clean up."); store.wipe(); @@ -312,8 +282,8 @@ function run_test() { Log4Moz.repository.getLogger("Store.Bookmarks").level = Log4Moz.Level.Trace; Log4Moz.repository.getLogger("Tracker.Bookmarks").level = Log4Moz.Level.Trace; + test_onItemChanged(); test_copying_places(); test_tracking(); - test_guid_stripping(); } diff --git a/services/sync/tests/unit/test_clients_engine.js b/services/sync/tests/unit/test_clients_engine.js index 4f2f237d37f5..a3e539eeebfe 100644 --- a/services/sync/tests/unit/test_clients_engine.js +++ b/services/sync/tests/unit/test_clients_engine.js @@ -2,6 +2,7 @@ Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); +Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/engines/clients.js"); Cu.import("resource://services-sync/service.js"); @@ -33,10 +34,10 @@ function test_bad_hmac() { } let handlers = { - "/1.0/foo/info/collections": collectionsHelper.handler, - "/1.0/foo/storage/meta/global": upd("meta", global.handler()), - "/1.0/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()), - "/1.0/foo/storage/clients": trackDeletedHandler("crypto", clientsColl.handler()) + "/1.1/foo/info/collections": collectionsHelper.handler, + "/1.1/foo/storage/meta/global": upd("meta", global.handler()), + "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/foo/storage/clients": trackDeletedHandler("crypto", clientsColl.handler()) }; let server = httpd_setup(handlers); @@ -76,6 +77,61 @@ function test_bad_hmac() { do_check_eq(1, clientsColl.count()); _("Records now: " + clientsColl.get({})); + _("Now change our keys but don't upload them. " + + "That means we get an HMAC error but redownload keys."); + Service.lastHMACEvent = 0; + Clients.localID = Utils.makeGUID(); + Clients.resetClient(); + CollectionKeys.generateNewKeys(); + deleted = false; + do_check_eq(1, clientsColl.count()); + Clients.sync(); + + _("Old record was not deleted, new one uploaded."); + do_check_false(deleted); + do_check_eq(2, clientsColl.count()); + _("Records now: " + clientsColl.get({})); + + _("Now try the scenario where our keys are wrong *and* there's a bad record."); + // Clean up and start fresh. + clientsColl.wbos = {}; + Service.lastHMACEvent = 0; + Clients.localID = Utils.makeGUID(); + Clients.resetClient(); + deleted = false; + do_check_eq(0, clientsColl.count()); + + // Create and upload keys. + CollectionKeys.generateNewKeys(); + serverKeys = CollectionKeys.asWBO("crypto", "keys"); + serverKeys.encrypt(Weave.Service.syncKeyBundle); + do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success); + + // Sync once to upload a record. + Clients.sync(); + do_check_eq(1, clientsColl.count()); + + // Generate and upload new keys, so the old client record is wrong. + CollectionKeys.generateNewKeys(); + serverKeys = CollectionKeys.asWBO("crypto", "keys"); + serverKeys.encrypt(Weave.Service.syncKeyBundle); + do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success); + + // Create a new client record and new keys. Now our keys are wrong, as well + // as the object on the server. We'll download the new keys and also delete + // the bad client record. + Clients.localID = Utils.makeGUID(); + Clients.resetClient(); + CollectionKeys.generateNewKeys(); + let oldKey = CollectionKeys.keyForCollection(); + + do_check_false(deleted); + Clients.sync(); + do_check_true(deleted); + do_check_eq(1, clientsColl.count()); + let newKey = CollectionKeys.keyForCollection(); + do_check_false(oldKey.equals(newKey)); + } finally { server.stop(do_test_finished); Svc.Prefs.resetBranch(""); @@ -101,7 +157,6 @@ function test_sync() { _("Ensure that Clients engine uploads a new client record once a week."); Svc.Prefs.set("clusterURL", "http://localhost:8080/"); Svc.Prefs.set("username", "foo"); - new SyncTestingInfrastructure(); CollectionKeys.generateNewKeys(); @@ -111,9 +166,12 @@ function test_sync() { let coll = new ServerCollection(); let clientwbo = coll.wbos[Clients.localID] = new ServerWBO(Clients.localID); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler(), - "/1.0/foo/storage/clients": coll.handler() + "/1.1/foo/storage/meta/global": global.handler(), + "/1.1/foo/storage/clients": coll.handler() }); + server.registerPathHandler( + "/1.1/foo/storage/clients/" + Clients.localID, clientwbo.handler()); + do_test_pending(); try { @@ -133,10 +191,13 @@ function test_sync() { do_check_true(!!clientwbo.payload); do_check_true(Clients.lastRecordUpload > lastweek); + _("Remove client record."); + Clients.removeClientData(); + do_check_eq(clientwbo.payload, undefined); + _("Time travel one day back, no record uploaded."); Clients.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH; let yesterday = Clients.lastRecordUpload; - clientwbo.payload = undefined; Clients.sync(); do_check_eq(clientwbo.payload, undefined); do_check_eq(Clients.lastRecordUpload, yesterday); @@ -152,7 +213,7 @@ function test_sync() { function run_test() { initTestLogging("Trace"); Log4Moz.repository.getLogger("Engine.Clients").level = Log4Moz.Level.Trace; - test_bad_hmac(); // Needs to run first: doesn't use fake service! + test_bad_hmac(); test_properties(); test_sync(); } diff --git a/services/sync/tests/unit/test_clients_escape.js b/services/sync/tests/unit/test_clients_escape.js index d52d3e1007e8..8bc160f792ac 100644 --- a/services/sync/tests/unit/test_clients_escape.js +++ b/services/sync/tests/unit/test_clients_escape.js @@ -8,7 +8,7 @@ function run_test() { _("Set up test fixtures."); ID.set('WeaveID', new Identity('Some Identity', 'foo')); Svc.Prefs.set("clusterURL", "http://fakebase/"); - let baseUri = "http://fakebase/1.0/foo/storage/"; + let baseUri = "http://fakebase/1.1/foo/storage/"; let pubUri = baseUri + "keys/pubkey"; let privUri = baseUri + "keys/privkey"; diff --git a/services/sync/tests/unit/test_corrupt_keys.js b/services/sync/tests/unit/test_corrupt_keys.js index c8474fcc7a95..35a8df0b86cc 100644 --- a/services/sync/tests/unit/test_corrupt_keys.js +++ b/services/sync/tests/unit/test_corrupt_keys.js @@ -36,22 +36,22 @@ function test_locally_changed_keys() { do_test_pending(); let server = httpd_setup({ // Special. - "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()), - "/1.0/johndoe/info/collections": collectionsHelper.handler, - "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), // Track modified times. - "/1.0/johndoe/storage/clients": upd("clients", clients.handler()), - "/1.0/johndoe/storage/clients/foobar": upd("clients", new ServerWBO("clients").handler()), - "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/clients/foobar": upd("clients", new ServerWBO("clients").handler()), + "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), // Just so we don't get 404s in the logs. - "/1.0/johndoe/storage/bookmarks": new ServerCollection().handler(), - "/1.0/johndoe/storage/forms": new ServerCollection().handler(), - "/1.0/johndoe/storage/passwords": new ServerCollection().handler(), - "/1.0/johndoe/storage/prefs": new ServerCollection().handler(), + "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(), + "/1.1/johndoe/storage/forms": new ServerCollection().handler(), + "/1.1/johndoe/storage/passwords": new ServerCollection().handler(), + "/1.1/johndoe/storage/prefs": new ServerCollection().handler(), - "/1.0/johndoe/storage/history": upd("history", history.handler()), + "/1.1/johndoe/storage/history": upd("history", history.handler()), }); try { @@ -144,7 +144,7 @@ function test_locally_changed_keys() { wbo.modified = modified; history.wbos[id] = wbo; server.registerPathHandler( - "/1.0/johndoe/storage/history/record-no--" + i, + "/1.1/johndoe/storage/history/record-no--" + i, upd("history", wbo.handler())); } @@ -204,7 +204,7 @@ function test_locally_changed_keys() { wbo.modified = modified; history.wbos[id] = wbo; server.registerPathHandler( - "/1.0/johndoe/storage/history/record-no--" + i, + "/1.1/johndoe/storage/history/record-no--" + i, upd("history", wbo.handler())); } collections.history = Date.now()/1000; diff --git a/services/sync/tests/unit/test_history_engine.js b/services/sync/tests/unit/test_history_engine.js index d856ccf0d6b3..fa267b979109 100644 --- a/services/sync/tests/unit/test_history_engine.js +++ b/services/sync/tests/unit/test_history_engine.js @@ -51,7 +51,7 @@ function test_processIncoming_mobile_history_batched() { } let server = sync_httpd_setup({ - "/1.0/foo/storage/history": collection.handler() + "/1.1/foo/storage/history": collection.handler() }); do_test_pending(); diff --git a/services/sync/tests/unit/test_history_store.js b/services/sync/tests/unit/test_history_store.js index d26407526c2b..489b4a4510d9 100644 --- a/services/sync/tests/unit/test_history_store.js +++ b/services/sync/tests/unit/test_history_store.js @@ -160,11 +160,10 @@ function run_test() { }, function (next) { _("Make sure we handle invalid URLs in places databases gracefully."); - let table = store._haveTempTables ? "moz_places_temp" : "moz_places"; - let query = "INSERT INTO " + table + " " + let query = "INSERT INTO moz_places " + "(url, title, rev_host, visit_count, last_visit_date) " + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"; - let stmt = Utils.createStatement(Svc.History.DBConnection, query); + let stmt = Svc.History.DBConnection.createAsyncStatement(query); let result = Utils.queryAsync(stmt); do_check_eq([id for (id in store.getAllIDs())].length, 4); diff --git a/services/sync/tests/unit/test_places_guid_downgrade.js b/services/sync/tests/unit/test_places_guid_downgrade.js index 66efda65e3d4..dd0e5f76ba85 100644 --- a/services/sync/tests/unit/test_places_guid_downgrade.js +++ b/services/sync/tests/unit/test_places_guid_downgrade.js @@ -95,8 +95,7 @@ function test_history_guids() { dump("tbguid: " + tbguid + "\n"); _("History: Verify GUIDs are added to the guid column."); - let stmt = Utils.createStatement( - Svc.History.DBConnection, + let stmt = Svc.History.DBConnection.createAsyncStatement( "SELECT id FROM moz_places WHERE guid = :guid"); stmt.params.guid = fxguid; @@ -108,8 +107,7 @@ function test_history_guids() { do_check_eq(result.length, 1); _("History: Verify GUIDs weren't added to annotations."); - stmt = Utils.createStatement( - Svc.History.DBConnection, + stmt = Svc.History.DBConnection.createAsyncStatement( "SELECT a.content AS guid FROM moz_annos a WHERE guid = :guid"); stmt.params.guid = fxguid; @@ -136,8 +134,7 @@ function test_bookmark_guids() { let tbguid = store.GUIDForId(tbid); _("Bookmarks: Verify GUIDs are added to the guid column."); - let stmt = Utils.createStatement( - Svc.History.DBConnection, + let stmt = Svc.History.DBConnection.createAsyncStatement( "SELECT id FROM moz_bookmarks WHERE guid = :guid"); stmt.params.guid = fxguid; @@ -151,8 +148,7 @@ function test_bookmark_guids() { do_check_eq(result[0].id, tbid); _("Bookmarks: Verify GUIDs weren't added to annotations."); - stmt = Utils.createStatement( - Svc.History.DBConnection, + stmt = Svc.History.DBConnection.createAsyncStatement( "SELECT a.content AS guid FROM moz_items_annos a WHERE guid = :guid"); stmt.params.guid = fxguid; diff --git a/services/sync/tests/unit/test_resource.js b/services/sync/tests/unit/test_resource.js index 1bb5fe283226..8c617fb9869f 100644 --- a/services/sync/tests/unit/test_resource.js +++ b/services/sync/tests/unit/test_resource.js @@ -221,6 +221,23 @@ function run_test() { let did401 = false; Observers.add("weave:resource:status:401", function() did401 = true); + _("Test that the BasicAuthenticator doesn't screw up header case."); + let res1 = new Resource("http://localhost:8080/foo"); + res1.setHeader("Authorization", "Basic foobar"); + res1.authenticator = new NoOpAuthenticator(); + do_check_eq(res1._headers["authorization"], "Basic foobar"); + do_check_eq(res1.headers["authorization"], "Basic foobar"); + let id = new Identity("secret", "guest", "guest"); + res1.authenticator = new BasicAuthenticator(id); + + // In other words... it correctly overwrites our downcased version + // when accessed through .headers. + do_check_eq(res1._headers["authorization"], "Basic foobar"); + do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); + do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); + do_check_true(!res1._headers["Authorization"]); + do_check_true(!res1.headers["Authorization"]); + _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); let res2 = new Resource("http://localhost:8080/protected"); content = res2.get(); @@ -346,8 +363,8 @@ function run_test() { do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"})); _("setHeader(): setting multiple headers, overwriting existing header"); - res9.setHeader('X-WHAT-is-Weave', 'more awesomer', - 'X-Another-Header', 'hello world'); + res9.setHeader('X-WHAT-is-Weave', 'more awesomer'); + res9.setHeader('X-Another-Header', 'hello world'); do_check_eq(res9.headers['x-what-is-weave'], 'more awesomer'); do_check_eq(res9.headers['x-another-header'], 'hello world'); content = res9.get(); diff --git a/services/sync/tests/unit/test_resource_async.js b/services/sync/tests/unit/test_resource_async.js index da57273b843e..94236a2a65ed 100644 --- a/services/sync/tests/unit/test_resource_async.js +++ b/services/sync/tests/unit/test_resource_async.js @@ -206,6 +206,26 @@ function run_test() { next(); })); + }, function (next) { + + _("Test that the BasicAuthenticator doesn't screw up header case."); + let res1 = new AsyncResource("http://localhost:8080/foo"); + res1.setHeader("Authorization", "Basic foobar"); + res1.authenticator = new NoOpAuthenticator(); + do_check_eq(res1._headers["authorization"], "Basic foobar"); + do_check_eq(res1.headers["authorization"], "Basic foobar"); + let id = new Identity("secret", "guest", "guest"); + res1.authenticator = new BasicAuthenticator(id); + + // In other words... it correctly overwrites our downcased version + // when accessed through .headers. + do_check_eq(res1._headers["authorization"], "Basic foobar"); + do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); + do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); + do_check_true(!res1._headers["Authorization"]); + do_check_true(!res1.headers["Authorization"]); + next(); + }, function (next) { _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); @@ -458,8 +478,8 @@ function run_test() { _("setHeader(): setting multiple headers, overwriting existing header"); do_test_pending(); - res_headers.setHeader('X-WHAT-is-Weave', 'more awesomer', - 'X-Another-Header', 'hello world'); + res_headers.setHeader('X-WHAT-is-Weave', 'more awesomer'); + res_headers.setHeader('X-Another-Header', 'hello world'); do_check_eq(res_headers.headers['x-what-is-weave'], 'more awesomer'); do_check_eq(res_headers.headers['x-another-header'], 'hello world'); res_headers.get(ensureThrows(function (error, content) { diff --git a/services/sync/tests/unit/test_resource_ua.js b/services/sync/tests/unit/test_resource_ua.js new file mode 100644 index 000000000000..cf2c286cdef0 --- /dev/null +++ b/services/sync/tests/unit/test_resource_ua.js @@ -0,0 +1,91 @@ +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/resource.js"); +Cu.import("resource://services-sync/service.js"); + +function test_resource_user_agent() { + let meta_global = new ServerWBO('global'); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let collections = collectionsHelper.collections; + + let ua; + function uaHandler(f) { + return function(request, response) { + ua = request.getHeader("User-Agent"); + return f(request, response); + }; + } + + do_test_pending(); + let server = httpd_setup({ + "/1.1/johndoe/info/collections": uaHandler(collectionsHelper.handler), + "/1.1/johndoe/storage/meta/global": uaHandler(meta_global.handler()), + }); + + Weave.Service.serverURL = "http://localhost:8080/"; + Weave.Service.clusterURL = "http://localhost:8080/"; + Weave.Service.username = "johndoe"; + Weave.Service.password = "ilovejane"; + + let expectedUA = Svc.AppInfo.name + "/" + Svc.AppInfo.version + + " FxSync/" + WEAVE_VERSION + "." + Svc.AppInfo.appBuildID; + + function test_fetchInfo(next) { + _("Testing _fetchInfo."); + Weave.Service._fetchInfo(); + _("User-Agent: " + ua); + do_check_eq(ua, expectedUA + ".desktop"); + ua = ""; + next(); + } + + function test_desktop_post(next) { + _("Testing direct Resource POST."); + let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global"); + r.post("foo=bar", function (error, content) { + _("User-Agent: " + ua); + do_check_eq(ua, expectedUA + ".desktop"); + ua = ""; + next(); + }); + } + + function test_desktop_get(next) { + _("Testing async."); + Svc.Prefs.set("client.type", "desktop"); + let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global"); + r.get(function(error, content) { + _("User-Agent: " + ua); + do_check_eq(ua, expectedUA + ".desktop"); + ua = ""; + next(); + }); + } + + function test_mobile_get(next) { + _("Testing mobile."); + Svc.Prefs.set("client.type", "mobile"); + let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global"); + r.get(function (error, content) { + _("User-Agent: " + ua); + do_check_eq(ua, expectedUA + ".mobile"); + ua = ""; + next(); + }); + } + + Utils.asyncChain( + test_fetchInfo, + test_desktop_post, + test_desktop_get, + test_mobile_get, + function (next) { + server.stop(next); + }, + do_test_finished)(); +} + +function run_test() { + test_resource_user_agent(); +} diff --git a/services/sync/tests/unit/test_service_attributes.js b/services/sync/tests/unit/test_service_attributes.js index 1bf66634d093..e3e08e2bd9e0 100644 --- a/services/sync/tests/unit/test_service_attributes.js +++ b/services/sync/tests/unit/test_service_attributes.js @@ -81,13 +81,13 @@ function test_urls() { Service.clusterURL = "http://weave.cluster/"; do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/"); - do_check_eq(Service.userBaseURL, "http://weave.cluster/1.0/johndoe/"); + do_check_eq(Service.userBaseURL, "http://weave.cluster/1.1/johndoe/"); do_check_eq(Service.infoURL, - "http://weave.cluster/1.0/johndoe/info/collections"); + "http://weave.cluster/1.1/johndoe/info/collections"); do_check_eq(Service.storageURL, - "http://weave.cluster/1.0/johndoe/storage/"); + "http://weave.cluster/1.1/johndoe/storage/"); do_check_eq(Service.metaURL, - "http://weave.cluster/1.0/johndoe/storage/meta/global"); + "http://weave.cluster/1.1/johndoe/storage/meta/global"); _("The 'miscURL' and 'userURL' attributes can be relative to 'serverURL' or absolute."); Svc.Prefs.set("miscURL", "relative/misc/"); diff --git a/services/sync/tests/unit/test_service_checkAccount.js b/services/sync/tests/unit/test_service_checkAccount.js index 0e8b3b8ecca5..f35e66c90663 100644 --- a/services/sync/tests/unit/test_service_checkAccount.js +++ b/services/sync/tests/unit/test_service_checkAccount.js @@ -15,7 +15,7 @@ function run_test() { Service.serverURL = "http://localhost:8080/"; _("A 404 will be recorded as 'generic-server-error'"); - do_check_eq(Service.checkUsername("jimdoe"), "generic-server-error"); + do_check_eq(Service.checkAccount("jimdoe"), "generic-server-error"); _("Account that's available."); do_check_eq(Service.checkAccount("john@doe.com"), "available"); @@ -23,13 +23,11 @@ function run_test() { _("Account that's not available."); do_check_eq(Service.checkAccount("jane@doe.com"), "notAvailable"); - // Backwards compat with the Firefox UI. Remove once bug 595066 has landed. + _("Username fallback: Account that's not available."); + do_check_eq(Service.checkAccount("johndoe"), "notAvailable"); - _("Account that's not available."); - do_check_eq(Service.checkUsername("johndoe"), "notAvailable"); - - _("Account that's available."); - do_check_eq(Service.checkUsername("janedoe"), "available"); + _("Username fallback: Account that's available."); + do_check_eq(Service.checkAccount("janedoe"), "available"); } finally { Svc.Prefs.resetBranch(""); diff --git a/services/sync/tests/unit/test_service_createAccount.js b/services/sync/tests/unit/test_service_createAccount.js index 29c6e41bf04b..b8cee9095e71 100644 --- a/services/sync/tests/unit/test_service_createAccount.js +++ b/services/sync/tests/unit/test_service_createAccount.js @@ -23,10 +23,7 @@ function run_test() { // jane@doe.com "/user/1.0/vuuf3eqgloxpxmzph27f5a6ve7gzlrms": send(400, "Bad Request", "2"), // jim@doe.com - "/user/1.0/vz6fhecgw5t3sgx3a4cektoiokyczkqd": send(500, "Server Error", "Server Error"), - "/user/1.0/johndoe": send(200, "OK", "0"), - "/user/1.0/janedoe": send(400, "Bad Request", "2"), - "/user/1.0/jimdoe": send(500, "Server Error", "Server Error") + "/user/1.0/vz6fhecgw5t3sgx3a4cektoiokyczkqd": send(500, "Server Error", "Server Error") }); try { Service.serverURL = "http://localhost:8080/"; @@ -65,24 +62,6 @@ function run_test() { "challenge", "response"); do_check_eq(secretHeader, "my-server-secret"); - - // Backwards compat with the Firefox UI. Remove once bug 595066 has landed. - - _("Create an old-style account."); - res = Service.createAccount("johndoe", "mysecretpw", "john@doe.com", - "challenge", "response"); - do_check_eq(res, null); - - _("Invalid captcha or other user-friendly error."); - res = Service.createAccount("janedoe", "anothersecretpw", "jane@doe.com", - "challenge", "response"); - do_check_eq(res, "invalid-captcha"); - - _("Generic server error."); - res = Service.createAccount("jimdoe", "preciousss", "jim@doe.com", - "challenge", "response"); - do_check_eq(res, "generic-server-error"); - } finally { Svc.Prefs.resetBranch(""); server.stop(do_test_finished); diff --git a/services/sync/tests/unit/test_service_detect_upgrade.js b/services/sync/tests/unit/test_service_detect_upgrade.js index e6f3bce8a7a5..71f6c712cbac 100644 --- a/services/sync/tests/unit/test_service_detect_upgrade.js +++ b/services/sync/tests/unit/test_service_detect_upgrade.js @@ -24,20 +24,20 @@ function v4_upgrade(next) { let keysWBO = new ServerWBO("keys"); let server = httpd_setup({ // Special. - "/1.0/johndoe/info/collections": collectionsHelper.handler, - "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), - "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), // Track modified times. - "/1.0/johndoe/storage/clients": upd("clients", clients.handler()), - "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), // Just so we don't get 404s in the logs. - "/1.0/johndoe/storage/bookmarks": new ServerCollection().handler(), - "/1.0/johndoe/storage/forms": new ServerCollection().handler(), - "/1.0/johndoe/storage/history": new ServerCollection().handler(), - "/1.0/johndoe/storage/passwords": new ServerCollection().handler(), - "/1.0/johndoe/storage/prefs": new ServerCollection().handler() + "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(), + "/1.1/johndoe/storage/forms": new ServerCollection().handler(), + "/1.1/johndoe/storage/history": new ServerCollection().handler(), + "/1.1/johndoe/storage/passwords": new ServerCollection().handler(), + "/1.1/johndoe/storage/prefs": new ServerCollection().handler() }); try { @@ -203,14 +203,14 @@ function v5_upgrade(next) { let server = httpd_setup({ // Special. - "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()), - "/1.0/johndoe/info/collections": collectionsHelper.handler, - "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), - "/1.0/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()), + "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()), // Track modified times. - "/1.0/johndoe/storage/clients": upd("clients", clients.handler()), - "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), }); try { diff --git a/services/sync/tests/unit/test_service_login.js b/services/sync/tests/unit/test_service_login.js index d4eae29584d0..c3fe70b442dd 100644 --- a/services/sync/tests/unit/test_service_login.js +++ b/services/sync/tests/unit/test_service_login.js @@ -38,16 +38,16 @@ function run_test() { do_test_pending(); let server = httpd_setup({ - "/1.0/johndoe/info/collections": login_handler, - "/1.0/janedoe/info/collections": login_handler, + "/1.1/johndoe/info/collections": login_handler, + "/1.1/janedoe/info/collections": login_handler, // We need these handlers because we test login, and login // is where keys are generated or fetched. // TODO: have Jane fetch her keys, not generate them... - "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(), - "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(), - "/1.0/janedoe/storage/crypto/keys": new ServerWBO().handler(), - "/1.0/janedoe/storage/meta/global": new ServerWBO().handler() + "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(), + "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(), + "/1.1/janedoe/storage/crypto/keys": new ServerWBO().handler(), + "/1.1/janedoe/storage/meta/global": new ServerWBO().handler() }); try { diff --git a/services/sync/tests/unit/test_service_passwordUTF8.js b/services/sync/tests/unit/test_service_passwordUTF8.js index e045447665bc..a9ef0a033394 100644 --- a/services/sync/tests/unit/test_service_passwordUTF8.js +++ b/services/sync/tests/unit/test_service_passwordUTF8.js @@ -55,9 +55,9 @@ function run_test() { do_test_pending(); let server = httpd_setup({ - "/1.0/johndoe/info/collections": info_collections, - "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(), - "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(), + "/1.1/johndoe/info/collections": info_collections, + "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(), + "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(), "/user/1.0/johndoe/password": change_password }); diff --git a/services/sync/tests/unit/test_service_quota.js b/services/sync/tests/unit/test_service_quota.js index 9165a83f8e94..763ee4ef8aad 100644 --- a/services/sync/tests/unit/test_service_quota.js +++ b/services/sync/tests/unit/test_service_quota.js @@ -9,10 +9,10 @@ function run_test() { do_test_pending(); let server = httpd_setup({ - "/1.0/johndoe/info/collection_usage": httpd_handler(200, "OK", JSON.stringify(collection_usage)), - "/1.0/johndoe/info/quota": httpd_handler(200, "OK", JSON.stringify(quota)), - "/1.0/janedoe/info/collection_usage": httpd_handler(200, "OK", "gargabe"), - "/1.0/janedoe/info/quota": httpd_handler(200, "OK", "more garbage") + "/1.1/johndoe/info/collection_usage": httpd_handler(200, "OK", JSON.stringify(collection_usage)), + "/1.1/johndoe/info/quota": httpd_handler(200, "OK", JSON.stringify(quota)), + "/1.1/janedoe/info/collection_usage": httpd_handler(200, "OK", "gargabe"), + "/1.1/janedoe/info/quota": httpd_handler(200, "OK", "more garbage") }); try { diff --git a/services/sync/tests/unit/test_service_startOver.js b/services/sync/tests/unit/test_service_startOver.js new file mode 100644 index 000000000000..55d865ebda69 --- /dev/null +++ b/services/sync/tests/unit/test_service_startOver.js @@ -0,0 +1,30 @@ +Cu.import("resource://services-sync/engines.js"); +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/util.js"); + +function BlaEngine() { + SyncEngine.call(this, "Bla"); +} +BlaEngine.prototype = { + __proto__: SyncEngine.prototype, + + removed: false, + removeClientData: function() { + this.removed = true; + } + +}; +Engines.register(BlaEngine); + + +function test_removeClientData() { + let engine = Engines.get("bla"); + do_check_false(engine.removed); + Service.startOver(); + do_check_true(engine.removed); +} + + +function run_test() { + test_removeClientData(); +} diff --git a/services/sync/tests/unit/test_service_sync_401.js b/services/sync/tests/unit/test_service_sync_401.js index 5eb3eea1ebd1..e50169961374 100644 --- a/services/sync/tests/unit/test_service_sync_401.js +++ b/services/sync/tests/unit/test_service_sync_401.js @@ -20,9 +20,9 @@ function run_test() { do_test_pending(); let server = httpd_setup({ - "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(), - "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(), - "/1.0/johndoe/info/collections": login_handler + "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(), + "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(), + "/1.1/johndoe/info/collections": login_handler }); const GLOBAL_SCORE = 42; diff --git a/services/sync/tests/unit/test_service_sync_checkServerError.js b/services/sync/tests/unit/test_service_sync_checkServerError.js index ddba9abd3029..5b8cb738d38d 100644 --- a/services/sync/tests/unit/test_service_sync_checkServerError.js +++ b/services/sync/tests/unit/test_service_sync_checkServerError.js @@ -38,10 +38,10 @@ function sync_httpd_setup() { engines: engines}); let handlers = { - "/1.0/johndoe/info/collections": collectionsHelper.handler, - "/1.0/johndoe/storage/meta/global": upd("meta", globalWBO.handler()), - "/1.0/johndoe/storage/clients": upd("clients", clientsColl.handler()), - "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()) + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/meta/global": upd("meta", globalWBO.handler()), + "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()) } return httpd_setup(handlers); } @@ -58,7 +58,7 @@ function generateAndUploadKeys() { CollectionKeys.generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); serverKeys.encrypt(Weave.Service.syncKeyBundle); - return serverKeys.upload("http://localhost:8080/1.0/johndoe/storage/crypto/keys").success; + return serverKeys.upload("http://localhost:8080/1.1/johndoe/storage/crypto/keys").success; } function test_backoff500(next) { diff --git a/services/sync/tests/unit/test_service_sync_remoteSetup.js b/services/sync/tests/unit/test_service_sync_remoteSetup.js index 5e3b013b0d89..aca0f14bd619 100644 --- a/services/sync/tests/unit/test_service_sync_remoteSetup.js +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -33,12 +33,12 @@ function run_test() { let metaColl = new ServerCollection({global: meta_global}); do_test_pending(); let server = httpd_setup({ - "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), - "/1.0/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()), - "/1.0/johndoe/storage/clients": upd("clients", clients.handler()), - "/1.0/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)), - "/1.0/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), - "/1.0/johndoe/info/collections": collectionsHelper.handler + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()), + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)), + "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), + "/1.1/johndoe/info/collections": collectionsHelper.handler }); try { diff --git a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js index a35577393649..42e5fa517611 100644 --- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js +++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js @@ -48,14 +48,14 @@ let upd = collectionsHelper.with_updated_collection; function sync_httpd_setup(handlers) { - handlers["/1.0/johndoe/info/collections"] = collectionsHelper.handler; + handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler; let cr = new ServerWBO("keys"); - handlers["/1.0/johndoe/storage/crypto/keys"] = + handlers["/1.1/johndoe/storage/crypto/keys"] = upd("crypto", cr.handler()); let cl = new ServerCollection(); - handlers["/1.0/johndoe/storage/clients"] = + handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); return httpd_setup(handlers); @@ -75,8 +75,8 @@ function test_newAccount() { _("Test: New account does not disable locally enabled engines."); let engine = Engines.get("steam"); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(), - "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler() + "/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler() }); do_test_pending(); setUp(); @@ -107,8 +107,8 @@ function test_enabledLocally() { storageVersion: STORAGE_VERSION, engines: {}}); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler() }); do_test_pending(); setUp(); @@ -144,8 +144,8 @@ function test_disabledLocally() { }); let steamCollection = new ServerWBO("steam", PAYLOAD); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": steamCollection.handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": steamCollection.handler() }); do_test_pending(); setUp(); @@ -186,8 +186,8 @@ function test_enabledRemotely() { version: engine.version}} }); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler() }); do_test_pending(); setUp(); @@ -219,10 +219,10 @@ function test_disabledRemotelyTwoClients() { storageVersion: STORAGE_VERSION, engines: {}}); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": + "/1.1/johndoe/storage/meta/global": upd("meta", metaWBO.handler()), - "/1.0/johndoe/storage/steam": + "/1.1/johndoe/storage/steam": upd("steam", new ServerWBO("steam", {}).handler()) }); do_test_pending(); @@ -265,8 +265,8 @@ function test_disabledRemotely() { storageVersion: STORAGE_VERSION, engines: {}}); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler() }); do_test_pending(); setUp(); @@ -299,9 +299,9 @@ function test_dependentEnginesEnabledLocally() { storageVersion: STORAGE_VERSION, engines: {}}); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), - "/1.0/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), + "/1.1/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler() }); do_test_pending(); setUp(); @@ -344,9 +344,9 @@ function test_dependentEnginesDisabledLocally() { let steamCollection = new ServerWBO("steam", PAYLOAD); let stirlingCollection = new ServerWBO("stirling", PAYLOAD); let server = sync_httpd_setup({ - "/1.0/johndoe/storage/meta/global": metaWBO.handler(), - "/1.0/johndoe/storage/steam": steamCollection.handler(), - "/1.0/johndoe/storage/stirling": stirlingCollection.handler() + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": steamCollection.handler(), + "/1.1/johndoe/storage/stirling": stirlingCollection.handler() }); do_test_pending(); setUp(); diff --git a/services/sync/tests/unit/test_service_verifyLogin.js b/services/sync/tests/unit/test_service_verifyLogin.js index 7b12bb6724c2..e3e8bb5634ed 100644 --- a/services/sync/tests/unit/test_service_verifyLogin.js +++ b/services/sync/tests/unit/test_service_verifyLogin.js @@ -34,10 +34,10 @@ function run_test() { do_test_pending(); let server = httpd_setup({ - "/api/1.0/johndoe/info/collections": login_handler, - "/api/1.0/janedoe/info/collections": service_unavailable, - "/api/1.0/johndoe/storage/meta/global": new ServerWBO().handler(), - "/api/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(), + "/api/1.1/johndoe/info/collections": login_handler, + "/api/1.1/janedoe/info/collections": service_unavailable, + "/api/1.1/johndoe/storage/meta/global": new ServerWBO().handler(), + "/api/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(), "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "http://localhost:8080/api/") }); diff --git a/services/sync/tests/unit/test_service_wipeServer.js b/services/sync/tests/unit/test_service_wipeServer.js index f5023e852580..92d2f093face 100644 --- a/services/sync/tests/unit/test_service_wipeServer.js +++ b/services/sync/tests/unit/test_service_wipeServer.js @@ -44,9 +44,9 @@ function test_withCollectionList_fail() { let diesel_coll = new FakeCollection(); let server = httpd_setup({ - "/1.0/johndoe/storage/steam": steam_coll.handler(), - "/1.0/johndoe/storage/petrol": serviceUnavailable, - "/1.0/johndoe/storage/diesel": diesel_coll.handler() + "/1.1/johndoe/storage/steam": steam_coll.handler(), + "/1.1/johndoe/storage/petrol": serviceUnavailable, + "/1.1/johndoe/storage/diesel": diesel_coll.handler() }); do_test_pending(); @@ -99,10 +99,10 @@ function test_wipeServer_leaves_collections() { } let server = httpd_setup({ - "/1.0/johndoe/storage/steam": steam_coll.handler(), - "/1.0/johndoe/storage/diesel": diesel_coll.handler(), - "/1.0/johndoe/storage/keys": keys_coll.handler(), - "/1.0/johndoe/info/collections": info_collections + "/1.1/johndoe/storage/steam": steam_coll.handler(), + "/1.1/johndoe/storage/diesel": diesel_coll.handler(), + "/1.1/johndoe/storage/keys": keys_coll.handler(), + "/1.1/johndoe/info/collections": info_collections }); do_test_pending(); diff --git a/services/sync/tests/unit/test_syncengine.js b/services/sync/tests/unit/test_syncengine.js index 956ab2e54dd1..bf91493934aa 100644 --- a/services/sync/tests/unit/test_syncengine.js +++ b/services/sync/tests/unit/test_syncengine.js @@ -12,9 +12,9 @@ function test_url_attributes() { Svc.Prefs.set("clusterURL", "https://cluster/"); let engine = makeSteamEngine(); try { - do_check_eq(engine.storageURL, "https://cluster/1.0/foo/storage/"); - do_check_eq(engine.engineURL, "https://cluster/1.0/foo/storage/steam"); - do_check_eq(engine.metaURL, "https://cluster/1.0/foo/storage/meta/global"); + do_check_eq(engine.storageURL, "https://cluster/1.1/foo/storage/"); + do_check_eq(engine.engineURL, "https://cluster/1.1/foo/storage/steam"); + do_check_eq(engine.metaURL, "https://cluster/1.1/foo/storage/meta/global"); } finally { Svc.Prefs.resetBranch(""); } @@ -129,7 +129,7 @@ function test_wipeServer() { const PAYLOAD = 42; let steamCollection = new ServerWBO("steam", PAYLOAD); let server = httpd_setup({ - "/1.0/foo/storage/steam": steamCollection.handler() + "/1.1/foo/storage/steam": steamCollection.handler() }); do_test_pending(); diff --git a/services/sync/tests/unit/test_syncengine_sync.js b/services/sync/tests/unit/test_syncengine_sync.js index 79327e2a5822..ca0419bf73e9 100644 --- a/services/sync/tests/unit/test_syncengine_sync.js +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -129,7 +129,7 @@ function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() { denomination: "Flying Scotsman"})); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -176,7 +176,7 @@ function test_syncStartup_serverHasNewerVersion() { Svc.Prefs.set("username", "foo"); let global = new ServerWBO('global', {engines: {steam: {version: 23456}}}); let server = httpd_setup({ - "/1.0/foo/storage/meta/global": global.handler() + "/1.1/foo/storage/meta/global": global.handler() }); do_test_pending(); @@ -215,7 +215,7 @@ function test_syncStartup_syncIDMismatchResetsClient() { let global = new ServerWBO('global', {engines: {steam: {version: engine.version, syncID: 'foobar'}}}); - server.registerPathHandler("/1.0/foo/storage/meta/global", global.handler()); + server.registerPathHandler("/1.1/foo/storage/meta/global", global.handler()); try { @@ -250,7 +250,7 @@ function test_processIncoming_emptyServer() { let collection = new ServerCollection(); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -293,9 +293,9 @@ function test_processIncoming_createFromServer() { denomination: "Pathological Case"})); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler(), - "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(), - "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler() + "/1.1/foo/storage/steam": collection.handler(), + "/1.1/foo/storage/steam/flying": collection.wbos.flying.handler(), + "/1.1/foo/storage/steam/scotsman": collection.wbos.scotsman.handler() }); do_test_pending(); @@ -387,7 +387,7 @@ function test_processIncoming_reconcile() { deleted: true})); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -483,7 +483,7 @@ function test_processIncoming_mobile_batchSize() { } let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -563,7 +563,7 @@ function test_processIncoming_store_toFetch() { meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -631,7 +631,7 @@ function test_processIncoming_resume_toFetch() { meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -688,7 +688,7 @@ function test_processIncoming_applyIncomingBatchSize_smaller() { meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -746,7 +746,7 @@ function test_processIncoming_applyIncomingBatchSize_multiple() { meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -777,10 +777,6 @@ function test_processIncoming_failed_records() { Svc.Prefs.set("clusterURL", "http://localhost:8080/"); Svc.Prefs.set("username", "foo"); - // Pretend to be a mobile client so we can test failed record handling - // while batching GETs. - Svc.Prefs.set("client.type", "mobile"); - // Let's create three and a bit batches worth of server side records. let collection = new ServerCollection(); const NUMBER_OF_RECORDS = MOBILE_BATCH_SIZE * 3 + 5; @@ -824,8 +820,20 @@ function test_processIncoming_failed_records() { let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL)); meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; + + // Keep track of requests made of a collection. + let count = 0; + let uris = []; + function recording_handler(collection) { + let h = collection.handler(); + return function(req, res) { + ++count; + uris.push(req.path + "?" + req.queryString); + return h(req, res); + }; + } let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": recording_handler(collection) }); do_test_pending(); @@ -863,6 +871,33 @@ function test_processIncoming_failed_records() { // Ensure the observer was notified do_check_eq(observerData, engine.name); do_check_eq(observerSubject.failed, BOGUS_RECORDS.length); + + // Testing batching of failed item fetches. + // Try to sync again. Ensure that we split the request into chunks to avoid + // URI length limitations. + function batchDownload(batchSize) { + count = 0; + uris = []; + engine.guidFetchBatchSize = batchSize; + engine._processIncoming(); + _("Tried again. Requests: " + count + "; URIs: " + JSON.stringify(uris)); + return count; + } + + // There are 8 bad records, so this needs 3 fetches. + _("Test batching with ID batch size 3, normal mobile batch size."); + do_check_eq(batchDownload(3), 3); + + // Now see with a more realistic limit. + _("Test batching with sufficient ID batch size."); + do_check_eq(batchDownload(BOGUS_RECORDS.length), 1); + + // If we're on mobile, that limit is used by default. + _("Test batching with tiny mobile batch size."); + Svc.Prefs.set("client.type", "mobile"); + engine.mobileGUIDFetchBatchSize = 2; + do_check_eq(batchDownload(BOGUS_RECORDS.length), 4); + } finally { server.stop(do_test_finished); Svc.Prefs.resetBranch(""); @@ -910,7 +945,7 @@ function test_processIncoming_decrypt_failed() { meta_global.payload.engines = {steam: {version: engine.version, syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -961,9 +996,9 @@ function test_uploadOutgoing_toEmptyServer() { collection.wbos.scotsman = new ServerWBO('scotsman'); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler(), - "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(), - "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler() + "/1.1/foo/storage/steam": collection.handler(), + "/1.1/foo/storage/steam/flying": collection.wbos.flying.handler(), + "/1.1/foo/storage/steam/scotsman": collection.wbos.scotsman.handler() }); do_test_pending(); CollectionKeys.generateNewKeys(); @@ -1023,7 +1058,7 @@ function test_uploadOutgoing_failed() { collection.wbos.flying = new ServerWBO('flying'); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -1107,7 +1142,7 @@ function test_uploadOutgoing_MAX_UPLOAD_RECORDS() { syncID: engine.syncID}}; let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -1165,7 +1200,7 @@ function test_syncFinish_deleteByIds() { denomination: "Rekonstruktionslokomotive"})); let server = httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -1219,7 +1254,7 @@ function test_syncFinish_deleteLotsInBatches() { } let server = httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -1274,7 +1309,7 @@ function test_sync_partialUpload() { let collection = new ServerCollection(); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); CollectionKeys.generateNewKeys(); @@ -1356,7 +1391,7 @@ function test_canDecrypt_noCryptoKeys() { denomination: "LNER Class A3 4472"})); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); @@ -1387,7 +1422,7 @@ function test_canDecrypt_true() { denomination: "LNER Class A3 4472"})); let server = sync_httpd_setup({ - "/1.0/foo/storage/steam": collection.handler() + "/1.1/foo/storage/steam": collection.handler() }); do_test_pending(); diff --git a/services/sync/tests/unit/test_utils_file.js b/services/sync/tests/unit/test_utils_file.js deleted file mode 100644 index dbfdbea834c0..000000000000 --- a/services/sync/tests/unit/test_utils_file.js +++ /dev/null @@ -1,135 +0,0 @@ -_("Test file-related utility functions"); - -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://services-sync/constants.js"); - -function run_test() { - // Disabled due to Windows failures (bug 599193) - //_test_getTmp(); - //_test_open(); -} - -function _test_getTmp() { - // as the setup phase remove the tmp directory from the - // filesystem - let svc = Cc["@mozilla.org/file/directory_service;1"] - .getService(Ci.nsIProperties); - let tmp = svc.get("ProfD", Ci.nsIFile); - tmp.QueryInterface(Ci.nsILocalFile); - tmp.append("weave"); - tmp.append("tmp"); - if (tmp.exists()) - tmp.remove(true); - - // call getTmp with no argument. A ref to the tmp - // dir is returned - _("getTmp with no argument"); - let tmpDir = Utils.getTmp(); - do_check_true(tmpDir instanceof Ci.nsILocalFile); - do_check_true(tmpDir.isDirectory()); - do_check_true(tmpDir.exists()); - do_check_eq(tmpDir.leafName, "tmp"); - - // call getTmp with a string. A ref to the file - // named with this string and included in the - // tmp dir is returned - _("getTmp with a string"); - let tmpFile = Utils.getTmp("name"); - do_check_true(tmpFile instanceof Ci.nsILocalFile); - do_check_true(!tmpFile.exists()); // getTmp doesn't create the file! - do_check_eq(tmpFile.leafName, "name"); - do_check_true(tmpDir.contains(tmpFile, false)); -} - -function _test_open() { - - // we rely on Utils.getTmp to get a temporary file and - // test Utils.open on that file, that's ok Util.getTmp - // is also tested (test_getTmp) - function createFile() { - let f = Utils.getTmp("_test_"); - f.create(f.NORMAL_FILE_TYPE, PERMS_FILE); - return f; - } - - // we should probably test more things here, for example - // we should test that we cannot write to a stream that - // is created as a result of opening a file for reading - - let s, f; - - _("Open for reading, providing a file"); - let f1 = createFile(); - [s, f] = Utils.open(f1, "<"); - do_check_eq(f.path, f1.path); - do_check_true(s instanceof Ci.nsIConverterInputStream); - f1.remove(false); - - _("Open for reading, providing a file name"); - let f2 = createFile(); - let path2 = f2.path; - [s, f] = Utils.open(path2, "<"); - do_check_eq(f.path, path2); - do_check_true(s instanceof Ci.nsIConverterInputStream); - f2.remove(false); - - _("Open for writing with truncate mode, providing a file"); - let f3 = createFile(); - [s, f] = Utils.open(f3, ">"); - do_check_eq(f.path, f3.path); - do_check_true(s instanceof Ci.nsIConverterOutputStream); - f3.remove(false); - - _("Open for writing with truncate mode, providing a file name"); - let f4 = createFile(); - let path4 = f4.path; - [s, f] = Utils.open(path4, ">"); - do_check_eq(f.path, path4); - do_check_true(s instanceof Ci.nsIConverterOutputStream); - f4.remove(false); - - _("Open for writing with append mode, providing a file"); - let f5 = createFile(); - [s, f] = Utils.open(f5, ">>"); - do_check_eq(f.path, f5.path); - do_check_true(s instanceof Ci.nsIConverterOutputStream); - f5.remove(false); - - _("Open for writing with append mode, providing a file name"); - let f6 = createFile(); - let path6 = f6.path; - [s, f] = Utils.open(path6, ">>"); - do_check_eq(f.path, path6); - do_check_true(s instanceof Ci.nsIConverterOutputStream); - f6.remove(false); - - _("Open with illegal mode"); - let f7 = createFile(); - let except7; - try { - Utils.open(f7, "?!"); - } catch(e) { - except7 = e; - } - do_check_true(!!except7); - f7.remove(false); - - _("Open non-existing file for reading"); - let f8 = createFile(); - let path8 = f8.path; - f8.remove(false); - let except8; - try { - Utils.open(path8, "<"); - } catch(e) { - except8 = e; - } - do_check_true(!!except8); - - _("Open for reading, provide permissions"); - let f9 = createFile(); - [s, f] = Utils.open(f9, "<", 0644); - do_check_eq(f.path, f9.path); - do_check_true(s instanceof Ci.nsIConverterInputStream); - f9.remove(false); -} diff --git a/services/sync/tests/unit/test_utils_getErrorString.js b/services/sync/tests/unit/test_utils_getErrorString.js index 4dbc5d4229ac..4b8497dfa190 100644 --- a/services/sync/tests/unit/test_utils_getErrorString.js +++ b/services/sync/tests/unit/test_utils_getErrorString.js @@ -6,7 +6,7 @@ function run_test() { // we just test whether the returned string includes the // string "unknown", should be good enough - str = Utils.getErrorString("error.login.reason.password"); + str = Utils.getErrorString("error.login.reason.account"); do_check_true(str.match(/unknown/i) == null); str = Utils.getErrorString("foobar"); diff --git a/services/sync/tests/unit/test_utils_json.js b/services/sync/tests/unit/test_utils_json.js index a9427ae8aa54..dd890d0db097 100644 --- a/services/sync/tests/unit/test_utils_json.js +++ b/services/sync/tests/unit/test_utils_json.js @@ -1,5 +1,6 @@ _("Make sure json saves and loads from disk"); Cu.import("resource://services-sync/util.js"); +Cu.import("resource://services-sync/constants.js"); function run_test() { do_test_pending(); @@ -68,9 +69,15 @@ function run_test() { // Write a file with some invalid JSON let file = Utils.getProfileFile({ autoCreate: true, path: "weave/log.json" }); - let [fos] = Utils.open(file, ">"); - fos.writeString("invalid json!"); - fos.close(); + let fos = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, + fos.DEFER_OPEN); + let stream = Cc["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Ci.nsIConverterOutputStream); + stream.init(fos, "UTF-8", 4096, 0x0000); + stream.writeString("invalid json!"); + stream.close(); let trace, debug; Utils.jsonLoad("log", diff --git a/services/sync/version.txt b/services/sync/version.txt index d3bdbdf1fdae..27f9cd322bb9 100644 --- a/services/sync/version.txt +++ b/services/sync/version.txt @@ -1 +1 @@ -1.7 +1.8.0