diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 27ddf7bcb909..0f9eb90600f7 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -391,6 +391,7 @@ @BINPATH@/components/nsSafebrowsingApplication.manifest @BINPATH@/components/nsSafebrowsingApplication.js @BINPATH@/components/nsURLClassifier.manifest +@BINPATH@/components/nsUrlClassifierHashCompleter.js @BINPATH@/components/nsUrlClassifierListManager.js @BINPATH@/components/nsUrlClassifierLib.js @BINPATH@/components/url-classifier.xpt diff --git a/toolkit/components/build/nsToolkitCompsCID.h b/toolkit/components/build/nsToolkitCompsCID.h index 1d4e1699c946..f86d6e4f38b6 100644 --- a/toolkit/components/build/nsToolkitCompsCID.h +++ b/toolkit/components/build/nsToolkitCompsCID.h @@ -175,11 +175,6 @@ #define NS_URLCLASSIFIERUTILS_CID \ { 0xb7b2ccec, 0x7912, 0x4ea6, { 0xa5, 0x48, 0xb0, 0x38, 0x44, 0x70, 0x04, 0xbd} } -// {786e0a0e-e035-4600-8ee0-365a63a80b80} -#define NS_URLCLASSIFIERHASHCOMPLETER_CID \ -{ 0x786e0a0e, 0xe035, 0x4600, \ - { 0x8e, 0xe0, 0x36, 0x5a, 0x63, 0xa8, 0x0b, 0x80 } } - // {10f2f5f0-f103-4901-980f-ba11bd70d60d} #define NS_SCRIPTABLEUNESCAPEHTML_CID \ { 0x10f2f5f0, 0xf103, 0x4901, { 0x98, 0x0f, 0xba, 0x11, 0xbd, 0x70, 0xd6, 0x0d} } diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index 0aaf99a7fd16..29d88d42b795 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -63,7 +63,6 @@ #include "nsUrlClassifierDBService.h" #include "nsUrlClassifierStreamUpdater.h" #include "nsUrlClassifierUtils.h" -#include "nsUrlClassifierHashCompleter.h" #endif #ifdef MOZ_FEEDS @@ -101,7 +100,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind) #ifdef MOZ_URL_CLASSIFIER NS_GENERIC_FACTORY_CONSTRUCTOR(nsUrlClassifierStreamUpdater) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUrlClassifierUtils, Init) -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUrlClassifierHashCompleter, Init) static nsresult nsUrlClassifierDBServiceConstructor(nsISupports *aOuter, REFNSIID aIID, @@ -151,7 +149,6 @@ NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSERVICE_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID); NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID); -NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERHASHCOMPLETER_CID); #endif #ifdef MOZ_FEEDS NS_DEFINE_NAMED_CID(NS_SCRIPTABLEUNESCAPEHTML_CID); @@ -182,7 +179,6 @@ static const mozilla::Module::CIDEntry kToolkitCIDs[] = { { &kNS_URLCLASSIFIERDBSERVICE_CID, false, NULL, nsUrlClassifierDBServiceConstructor }, { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, NULL, nsUrlClassifierStreamUpdaterConstructor }, { &kNS_URLCLASSIFIERUTILS_CID, false, NULL, nsUrlClassifierUtilsConstructor }, - { &kNS_URLCLASSIFIERHASHCOMPLETER_CID, false, NULL, nsUrlClassifierHashCompleterConstructor }, #endif #ifdef MOZ_FEEDS { &kNS_SCRIPTABLEUNESCAPEHTML_CID, false, NULL, nsScriptableUnescapeHTMLConstructor }, @@ -215,7 +211,6 @@ static const mozilla::Module::ContractIDEntry kToolkitContracts[] = { { NS_URICLASSIFIERSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID }, { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID }, { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID }, - { NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID, &kNS_URLCLASSIFIERHASHCOMPLETER_CID }, #endif #ifdef MOZ_FEEDS { NS_SCRIPTABLEUNESCAPEHTML_CONTRACTID, &kNS_SCRIPTABLEUNESCAPEHTML_CID }, diff --git a/toolkit/components/url-classifier/Makefile.in b/toolkit/components/url-classifier/Makefile.in index a8e8ce2c463b..52aab01d2547 100644 --- a/toolkit/components/url-classifier/Makefile.in +++ b/toolkit/components/url-classifier/Makefile.in @@ -61,7 +61,6 @@ CPPSRCS = \ nsUrlClassifierDBService.cpp \ nsUrlClassifierStreamUpdater.cpp \ nsUrlClassifierUtils.cpp \ - nsUrlClassifierHashCompleter.cpp \ $(NULL) LOCAL_INCLUDES = \ @@ -69,7 +68,10 @@ LOCAL_INCLUDES = \ $(SQLITE_CFLAGS) \ $(NULL) -EXTRA_COMPONENTS = nsURLClassifier.manifest +EXTRA_COMPONENTS = \ + nsUrlClassifierHashCompleter.js \ + nsURLClassifier.manifest \ + $(NULL) # Same as JS components that are run through the pre-processor. EXTRA_PP_COMPONENTS = \ diff --git a/toolkit/components/url-classifier/nsURLClassifier.manifest b/toolkit/components/url-classifier/nsURLClassifier.manifest index d3e3df4064d5..f035dea80953 100644 --- a/toolkit/components/url-classifier/nsURLClassifier.manifest +++ b/toolkit/components/url-classifier/nsURLClassifier.manifest @@ -2,3 +2,5 @@ component {26a4a019-2827-4a89-a85c-5931a678823a} nsUrlClassifierLib.js contract @mozilla.org/url-classifier/jslib;1 {26a4a019-2827-4a89-a85c-5931a678823a} component {ca168834-cc00-48f9-b83c-fd018e58cae3} nsUrlClassifierListManager.js contract @mozilla.org/url-classifier/listmanager;1 {ca168834-cc00-48f9-b83c-fd018e58cae3} +component {9111de73-9322-4bfc-8b65-2b727f3e6ec8} nsUrlClassifierHashCompleter.js +contract @mozilla.org/url-classifier/hashcompleter;1 {9111de73-9322-4bfc-8b65-2b727f3e6ec8} diff --git a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.cpp b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.cpp deleted file mode 100644 index 725ea0396e13..000000000000 --- a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.cpp +++ /dev/null @@ -1,722 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Mozilla Corporation. - * Portions created by the Initial Developer are Copyright (C) 2008 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Camp - * - * 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 - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsUrlClassifierHashCompleter.h" -#include "nsIChannel.h" -#include "nsICryptoHMAC.h" -#include "nsIHttpChannel.h" -#include "nsIKeyModule.h" -#include "nsIObserverService.h" -#include "nsIUploadChannel.h" -#include "nsNetUtil.h" -#include "nsStreamUtils.h" -#include "nsStringStream.h" -#include "nsServiceManagerUtils.h" -#include "nsThreadUtils.h" -#include "nsUrlClassifierDBService.h" -#include "nsUrlClassifierUtils.h" -#include "prlog.h" -#include "prprf.h" - -// NSPR_LOG_MODULES=UrlClassifierHashCompleter:5 -#if defined(PR_LOGGING) -static const PRLogModuleInfo *gUrlClassifierHashCompleterLog = nsnull; -#define LOG(args) PR_LOG(gUrlClassifierHashCompleterLog, PR_LOG_DEBUG, args) -#define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierHashCompleterLog, 4) -#else -#define LOG(args) -#define LOG_ENABLED() (PR_FALSE) -#endif - -// Back off from making server requests if we have at least -// gBackoffErrors errors -static const PRUint32 gBackoffErrors = 2; -// .. within gBackoffTime seconds -static const PRUint32 gBackoffTime = 5 * 60; -// ... and back off gBackoffInterval seconds, doubling seach time -static const PRUint32 gBackoffInterval = 30 * 60; -// ... up to a maximum of gBackoffMax. -static const PRUint32 gBackoffMax = 8 * 60 * 60; - -NS_IMPL_ISUPPORTS3(nsUrlClassifierHashCompleterRequest, - nsIRequestObserver, - nsIStreamListener, - nsIObserver) - -nsresult -nsUrlClassifierHashCompleterRequest::Begin() -{ - LOG(("nsUrlClassifierHashCompleterRequest::Begin [%p]", this)); - - if (PR_IntervalNow() < mCompleter->GetNextRequestTime()) { - NS_WARNING("Gethash server backed off, failing gethash request."); - NotifyFailure(NS_ERROR_ABORT); - return NS_ERROR_ABORT; - } - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) - observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); - - nsresult rv = OpenChannel(); - if (NS_FAILED(rv)) { - NotifyFailure(rv); - return rv; - } - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleterRequest::Add(const nsACString& partialHash, - nsIUrlClassifierHashCompleterCallback *c) -{ - LOG(("nsUrlClassifierHashCompleterRequest::Add [%p]", this)); - Request *request = mRequests.AppendElement(); - if (!request) - return NS_ERROR_OUT_OF_MEMORY; - - request->partialHash = partialHash; - request->callback = c; - - return NS_OK; -} - - -nsresult -nsUrlClassifierHashCompleterRequest::OpenChannel() -{ - LOG(("nsUrlClassifierHashCompleterRequest::OpenChannel [%p]", this)); - nsresult rv; - - PRUint32 loadFlags = nsIChannel::INHIBIT_CACHING | - nsIChannel::LOAD_BYPASS_CACHE; - rv = NS_NewChannel(getter_AddRefs(mChannel), mURI, nsnull, nsnull, nsnull, - loadFlags); - NS_ENSURE_SUCCESS(rv, rv); - - nsCAutoString requestBody; - rv = BuildRequest(requestBody); - NS_ENSURE_SUCCESS(rv, rv); - - rv = AddRequestBody(requestBody); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mChannel->AsyncOpen(this, nsnull); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleterRequest::BuildRequest(nsCAutoString &aRequestBody) -{ - LOG(("nsUrlClassifierHashCompleterRequest::BuildRequest [%p]", this)); - - nsCAutoString body; - for (PRUint32 i = 0; i < mRequests.Length(); i++) { - Request &request = mRequests[i]; - body.Append(request.partialHash); - } - - aRequestBody.AppendInt(PARTIAL_LENGTH); - aRequestBody.Append(':'); - aRequestBody.AppendInt(body.Length()); - aRequestBody.Append('\n'); - aRequestBody.Append(body); - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleterRequest::AddRequestBody(const nsACString &aRequestBody) -{ - LOG(("nsUrlClassifierHashCompleterRequest::AddRequestBody [%p]", this)); - - nsresult rv; - nsCOMPtr strStream = - do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = strStream->SetData(aRequestBody.BeginReading(), - aRequestBody.Length()); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr uploadChannel = do_QueryInterface(mChannel, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = uploadChannel->SetUploadStream(strStream, - NS_LITERAL_CSTRING("text/plain"), - -1); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST")); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -void -nsUrlClassifierHashCompleterRequest::RescheduleItems() -{ - // This request has failed in a way that we expect might succeed if - // we try again. Schedule the individual hashes for another attempt. - for (PRUint32 i = 0; i < mRequests.Length(); i++) { - Request &request = mRequests[i]; - nsresult rv = mCompleter->Complete(request.partialHash, request.callback); - if (NS_FAILED(rv)) { - // We couldn't reschedule the request - the best we can do here is - // tell it that we failed to complete the request. - request.callback->CompletionFinished(rv); - } - } - - mRescheduled = PR_TRUE; -} - -/** - * Reads the MAC from the response and checks it against the - * locally-computed MAC. - */ -nsresult -nsUrlClassifierHashCompleterRequest::HandleMAC(nsACString::const_iterator& begin, - const nsACString::const_iterator& end) -{ - mVerified = PR_FALSE; - - // First line should be either the MAC or a k:pleaserekey request. - nsACString::const_iterator iter = begin; - if (!FindCharInReadable('\n', iter, end)) { - return NS_ERROR_FAILURE; - } - - nsCAutoString serverMAC(Substring(begin, iter++)); - begin = iter; - - if (serverMAC.EqualsLiteral("e:pleaserekey")) { - LOG(("Rekey requested")); - - // Reschedule our items to be requested again. - RescheduleItems(); - - // Let the hash completer know that we need a new key. - return mCompleter->RekeyRequested(); - } - - nsUrlClassifierUtils::UnUrlsafeBase64(serverMAC); - - nsresult rv; - - nsCOMPtr keyObjectFactory( - do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr keyObject; - rv = keyObjectFactory->KeyFromString(nsIKeyObject::HMAC, mClientKey, - getter_AddRefs(keyObject)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr hmac = - do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = hmac->Init(nsICryptoHMAC::SHA1, keyObject); - NS_ENSURE_SUCCESS(rv, rv); - - const nsCSubstring &remaining = Substring(begin, end); - rv = hmac->Update(reinterpret_cast(remaining.BeginReading()), - remaining.Length()); - NS_ENSURE_SUCCESS(rv, rv); - - nsCAutoString clientMAC; - rv = hmac->Finish(PR_TRUE, clientMAC); - NS_ENSURE_SUCCESS(rv, rv); - - if (clientMAC != serverMAC) { - NS_WARNING("Invalid MAC in gethash response."); - return NS_ERROR_FAILURE; - } - - mVerified = PR_TRUE; - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleterRequest::HandleItem(const nsACString& item, - const nsACString& tableName, - PRUint32 chunkId) -{ - // If this item matches any of the requested partial hashes, add them - // to the response. - for (PRUint32 i = 0; i < mRequests.Length(); i++) { - Request &request = mRequests[i]; - if (StringBeginsWith(item, request.partialHash)) { - Response *response = request.responses.AppendElement(); - if (!response) - return NS_ERROR_OUT_OF_MEMORY; - response->completeHash = item; - response->tableName = tableName; - response->chunkId = chunkId; - } - } - - return NS_OK; -} - -/** - * Reads one table of results from the response. Leaves begin pointing at the - * next table. - */ -nsresult -nsUrlClassifierHashCompleterRequest::HandleTable(nsACString::const_iterator& begin, - const nsACString::const_iterator& end) -{ - nsACString::const_iterator iter; - iter = begin; - if (!FindCharInReadable(':', iter, end)) { - // No table line. - NS_WARNING("Received badly-formatted gethash response."); - return NS_ERROR_FAILURE; - } - - const nsCSubstring& tableName = Substring(begin, iter); - iter++; - begin = iter; - - if (!FindCharInReadable('\n', iter, end)) { - // Unterminated header line. - NS_WARNING("Received badly-formatted gethash response."); - return NS_ERROR_FAILURE; - } - - const nsCSubstring& remaining = Substring(begin, iter); - iter++; - begin = iter; - - PRUint32 chunkId; - PRInt32 size; - if (PR_sscanf(PromiseFlatCString(remaining).get(), - "%u:%d", &chunkId, &size) != 2) { - NS_WARNING("Received badly-formatted gethash response."); - return NS_ERROR_FAILURE; - } - - if (size % COMPLETE_LENGTH != 0) { - NS_WARNING("Unexpected gethash response length"); - return NS_ERROR_FAILURE; - } - - // begin now refers to the hash data. - - if (begin.size_forward() < size) { - NS_WARNING("Response does not match the expected response length."); - return NS_ERROR_FAILURE; - } - - for (PRInt32 i = 0; i < (size / COMPLETE_LENGTH); i++) { - // Read the complete hash. - iter.advance(COMPLETE_LENGTH); - - nsresult rv = HandleItem(Substring(begin, iter), tableName, chunkId); - NS_ENSURE_SUCCESS(rv, rv); - - begin = iter; - } - - // begin now points at the end of the hash data. - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleterRequest::HandleResponse() -{ - if (mResponse.IsEmpty()) { - // Empty response, we're done. - return NS_OK; - } - - nsCString::const_iterator begin, end; - mResponse.BeginReading(begin); - mResponse.EndReading(end); - - nsresult rv; - - // If we have a client key, we're expecting a MAC. - if (!mClientKey.IsEmpty()) { - rv = HandleMAC(begin, end); - NS_ENSURE_SUCCESS(rv, rv); - - if (mRescheduled) { - // We were rescheduled due to a k:pleaserekey request from the - // server. Don't bother reading the rest of the response. - return NS_OK; - } - } - - while (begin != end) { - rv = HandleTable(begin, end); - NS_ENSURE_SUCCESS(rv, rv); - } - - return NS_OK; -} - -void -nsUrlClassifierHashCompleterRequest::NotifySuccess() -{ - LOG(("nsUrlClassifierHashCompleterRequest::NotifySuccess [%p]", this)); - - for (PRUint32 i = 0; i < mRequests.Length(); i++) { - Request &request = mRequests[i]; - - for (PRUint32 j = 0; j < request.responses.Length(); j++) { - Response &response = request.responses[j]; - request.callback->Completion(response.completeHash, - response.tableName, - response.chunkId, - mVerified); - } - - request.callback->CompletionFinished(NS_OK); - } -} - -void -nsUrlClassifierHashCompleterRequest::NotifyFailure(nsresult status) -{ - LOG(("nsUrlClassifierHashCompleterRequest::NotifyFailure [%p]", this)); - - for (PRUint32 i = 0; i < mRequests.Length(); i++) { - Request &request = mRequests[i]; - request.callback->CompletionFinished(status); - } -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleterRequest::OnStartRequest(nsIRequest *request, - nsISupports *context) -{ - LOG(("nsUrlClassifierHashCompleter::OnStartRequest [%p]", this)); - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleterRequest::OnDataAvailable(nsIRequest *request, - nsISupports *context, - nsIInputStream *stream, - PRUint32 sourceOffset, - PRUint32 length) -{ - LOG(("nsUrlClassifierHashCompleter::OnDataAvailable [%p]", this)); - - if (mShuttingDown) - return NS_ERROR_ABORT; - - nsCAutoString piece; - nsresult rv = NS_ConsumeStream(stream, length, piece); - NS_ENSURE_SUCCESS(rv, rv); - - mResponse.Append(piece); - - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleterRequest::OnStopRequest(nsIRequest *request, - nsISupports *context, - nsresult status) -{ - LOG(("nsUrlClassifierHashCompleter::OnStopRequest [%p, status=%d]", - this, status)); - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) - observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); - - if (mShuttingDown) - return NS_ERROR_ABORT; - - if (NS_SUCCEEDED(status)) { - nsCOMPtr channel = do_QueryInterface(request); - if (channel) { - PRBool success; - status = channel->GetRequestSucceeded(&success); - if (NS_SUCCEEDED(status) && !success) { - status = NS_ERROR_ABORT; - } - } - } - - mCompleter->NoteServerResponse(NS_SUCCEEDED(status)); - - if (NS_SUCCEEDED(status)) - status = HandleResponse(); - - // If we were rescheduled, don't bother notifying success or failure. - if (!mRescheduled) { - if (NS_SUCCEEDED(status)) - NotifySuccess(); - else - NotifyFailure(status); - } - - mChannel = nsnull; - - return NS_OK; -} - - -NS_IMETHODIMP -nsUrlClassifierHashCompleterRequest::Observe(nsISupports *subject, - const char *topic, - const PRUnichar *data) -{ - if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { - mShuttingDown = PR_TRUE; - if (mChannel) - mChannel->Cancel(NS_ERROR_ABORT); - } - - return NS_OK; -} - -NS_IMPL_ISUPPORTS4(nsUrlClassifierHashCompleter, - nsIUrlClassifierHashCompleter, - nsIRunnable, - nsIObserver, - nsISupportsWeakReference) - -nsresult -nsUrlClassifierHashCompleter::Init() -{ -#if defined(PR_LOGGING) - if (!gUrlClassifierHashCompleterLog) - gUrlClassifierHashCompleterLog = PR_NewLogModule("UrlClassifierHashCompleter"); -#endif - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (observerService) - observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE); - - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::Complete(const nsACString &partialHash, - nsIUrlClassifierHashCompleterCallback *c) -{ - LOG(("nsUrlClassifierHashCompleter::Complete [%p]", this)); - - if (mShuttingDown) - return NS_ERROR_NOT_INITIALIZED; - - // We batch all of the requested completions in a single request until the - // next time we reach the main loop. - if (!mRequest) { - mRequest = new nsUrlClassifierHashCompleterRequest(this); - if (!mRequest) { - return NS_ERROR_OUT_OF_MEMORY; - } - - // If we don't have a gethash url yet, don't bother scheduling - // the request until we have one. - if (!mGethashUrl.IsEmpty()) { - // Schedule ourselves to start this request on the main loop. - NS_DispatchToCurrentThread(this); - } - } - - return mRequest->Add(partialHash, c); -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::SetGethashUrl(const nsACString &url) -{ - mGethashUrl = url; - - if (mRequest) { - // Schedule any pending request. - NS_DispatchToCurrentThread(this); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::GetGethashUrl(nsACString &url) -{ - url = mGethashUrl; - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::SetKeys(const nsACString &clientKey, - const nsACString &wrappedKey) -{ - LOG(("nsUrlClassifierHashCompleter::SetKeys [%p]", this)); - - NS_ASSERTION(clientKey.IsEmpty() == wrappedKey.IsEmpty(), - "Must either have both a client key and a wrapped key or neither."); - - if (clientKey.IsEmpty()) { - mClientKey.Truncate(); - mWrappedKey.Truncate(); - return NS_OK; - } - - nsresult rv = nsUrlClassifierUtils::DecodeClientKey(clientKey, mClientKey); - NS_ENSURE_SUCCESS(rv, rv); - mWrappedKey = wrappedKey; - - return NS_OK; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::Run() -{ - LOG(("nsUrlClassifierHashCompleter::Run [%p]\n", this)); - - if (mShuttingDown) { - mRequest = nsnull; - return NS_ERROR_NOT_INITIALIZED; - } - - if (!mRequest) - return NS_OK; - - NS_ASSERTION(!mGethashUrl.IsEmpty(), - "Request dispatched without a gethash url specified."); - - nsCOMPtr uri; - nsresult rv; - if (mClientKey.IsEmpty()) { - rv = NS_NewURI(getter_AddRefs(uri), mGethashUrl); - NS_ENSURE_SUCCESS(rv, rv); - } else { - mRequest->SetClientKey(mClientKey); - - nsCAutoString requestURL(mGethashUrl); - requestURL.Append("&wrkey="); - requestURL.Append(mWrappedKey); - rv = NS_NewURI(getter_AddRefs(uri), requestURL); - NS_ENSURE_SUCCESS(rv, rv); - } - - mRequest->SetURI(uri); - - // Dispatch the http request. - rv = mRequest->Begin(); - mRequest = nsnull; - return rv; -} - -NS_IMETHODIMP -nsUrlClassifierHashCompleter::Observe(nsISupports *subject, const char *topic, - const PRUnichar *data) -{ - if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { - mShuttingDown = PR_TRUE; - } - - return NS_OK; -} - -nsresult -nsUrlClassifierHashCompleter::RekeyRequested() -{ - // Our keys are no longer valid. - SetKeys(EmptyCString(), EmptyCString()); - - // Notify the key manager that we need a new key. Until we get a - // new key, gethash requests will be unauthenticated (and therefore - // uncacheable). - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - if (!observerService) - return NS_ERROR_FAILURE; - - return observerService->NotifyObservers(static_cast(this), - "url-classifier-rekey-requested", - nsnull); -} - -void -nsUrlClassifierHashCompleter::NoteServerResponse(PRBool success) -{ - LOG(("nsUrlClassifierHashCompleter::NoteServerResponse [%p, %d]", - this, success)); - - if (success) { - mBackoff = PR_FALSE; - mNextRequestTime = 0; - mBackoffTime = 0; - return; - } - - PRIntervalTime now = PR_IntervalNow(); - - // Record the error time. - mErrorTimes.AppendElement(now); - if (mErrorTimes.Length() > gBackoffErrors) { - mErrorTimes.RemoveElementAt(0); - } - - if (mBackoff) { - mBackoffTime *= 2; - LOG(("Doubled backoff time to %d seconds", mBackoffTime)); - } else if (mErrorTimes.Length() == gBackoffErrors && - PR_IntervalToSeconds(now - mErrorTimes[0]) <= gBackoffTime) { - mBackoff = PR_TRUE; - mBackoffTime = gBackoffInterval; - LOG(("Starting backoff, backoff time is %d seconds", mBackoffTime)); - } - - if (mBackoff) { - mBackoffTime = NS_MIN(mBackoffTime, gBackoffMax); - LOG(("Using %d for backoff time", mBackoffTime)); - mNextRequestTime = now + PR_SecondsToInterval(mBackoffTime); - } -} diff --git a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.h b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.h deleted file mode 100644 index 32ec6095b884..000000000000 --- a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.h +++ /dev/null @@ -1,161 +0,0 @@ -//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Mozilla Corporation. - * Portions created by the Initial Developer are Copyright (C) 2008 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dave Camp - * - * 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 - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef nsUrlClassifierHashCompleter_h_ -#define nsUrlClassifierHashCompleter_h_ - -#include "nsIUrlClassifierHashCompleter.h" - -#include "nsAutoPtr.h" -#include "nsCOMPtr.h" -#include "nsIChannel.h" -#include "nsIObserver.h" -#include "nsIRunnable.h" -#include "nsIStreamListener.h" -#include "nsIURI.h" -#include "nsTArray.h" -#include "nsString.h" -#include "nsWeakReference.h" - -class nsUrlClassifierHashCompleter; - -class nsUrlClassifierHashCompleterRequest : public nsIStreamListener - , public nsIObserver -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSISTREAMLISTENER - NS_DECL_NSIOBSERVER - - nsUrlClassifierHashCompleterRequest(nsUrlClassifierHashCompleter *completer) - : mShuttingDown(PR_FALSE) - , mCompleter(completer) - , mVerified(PR_FALSE) - , mRescheduled(PR_FALSE) - { } - ~nsUrlClassifierHashCompleterRequest() { } - - nsresult Begin(); - nsresult Add(const nsACString &partialHash, - nsIUrlClassifierHashCompleterCallback *c); - - void SetURI(nsIURI *uri) { mURI = uri; } - void SetClientKey(const nsACString &clientKey) { mClientKey = clientKey; } - -private: - nsresult OpenChannel(); - nsresult BuildRequest(nsCAutoString &request); - nsresult AddRequestBody(const nsACString &requestBody); - void RescheduleItems(); - nsresult HandleMAC(nsACString::const_iterator &begin, - const nsACString::const_iterator &end); - nsresult HandleItem(const nsACString &item, - const nsACString &table, - PRUint32 chunkId); - nsresult HandleTable(nsACString::const_iterator &begin, - const nsACString::const_iterator &end); - nsresult HandleResponse(); - void NotifySuccess(); - void NotifyFailure(nsresult status); - - PRBool mShuttingDown; - nsRefPtr mCompleter; - nsCOMPtr mURI; - nsCString mClientKey; - nsCOMPtr mChannel; - nsCString mResponse; - PRBool mVerified; - PRBool mRescheduled; - - struct Response { - nsCString completeHash; - nsCString tableName; - PRUint32 chunkId; - }; - - struct Request { - nsCString partialHash; - nsTArray responses; - nsCOMPtr callback; - }; - - nsTArray mRequests; -}; - -class nsUrlClassifierHashCompleter : public nsIUrlClassifierHashCompleter - , public nsIRunnable - , public nsIObserver - , public nsSupportsWeakReference -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIURLCLASSIFIERHASHCOMPLETER - NS_DECL_NSIRUNNABLE - NS_DECL_NSIOBSERVER - - nsUrlClassifierHashCompleter() - : mBackoff(PR_FALSE) - , mBackoffTime(0) - , mNextRequestTime(0) - , mShuttingDown(PR_FALSE) - {} - ~nsUrlClassifierHashCompleter() {} - - nsresult Init(); - - nsresult RekeyRequested(); - - void NoteServerResponse(PRBool success); - PRIntervalTime GetNextRequestTime() { return mNextRequestTime; } - -private: - nsRefPtr mRequest; - nsCString mGethashUrl; - nsCString mClientKey; - nsCString mWrappedKey; - - nsTArray mErrorTimes; - PRBool mBackoff; - PRUint32 mBackoffTime; - PRIntervalTime mNextRequestTime; - - PRBool mShuttingDown; -}; - -#endif // nsUrlClassifierHashCompleter_h_ diff --git a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js new file mode 100644 index 000000000000..5ef6c9a6527b --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js @@ -0,0 +1,612 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is a hash completer for the url classifier that works with + * the Google Safe Browsing API. + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Camp (Original C++ implementation) + * Mehdi Mulani (JS rewrite) + * + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +// COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h, +// they correspond to the length, in bytes, of a hash prefix and the total +// hash. +const COMPLETE_LENGTH = 32; +const PARTIAL_LENGTH = 4; + +// These backoff related constants are taken from v2 of the Google Safe Browsing +// API. +// BACKOFF_ERRORS: the number of errors incurred until we start to back off. +// BACKOFF_INTERVAL: the initial time, in seconds, to wait once we start backing +// off. +// BACKOFF_MAX: as the backoff time doubles after each failure, this is a +// ceiling on the time to wait, in seconds. +// BACKOFF_TIME: length of the interval of time, in seconds, during which errors +// are taken into account. + +const BACKOFF_ERRORS = 2; +const BACKOFF_INTERVAL = 30 * 60; +const BACKOFF_MAX = 8 * 60 * 60; +const BACKOFF_TIME = 5 * 60; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +let keyFactory = Cc["@mozilla.org/security/keyobjectfactory;1"] + .getService(Ci.nsIKeyObjectFactory); + +function HashCompleter() { + // This is a HashCompleterRequest and is used by multiple calls to |complete| + // in succession to avoid unnecessarily creating requests. Once it has been + // started, this is set to null again. + this._currentRequest = null; + + // Key used in the HMAC process by the client to verify the request has not + // been tampered with. It is stored as a binary blob. + this._clientKey = ""; + // Key used in the HMAC process and sent remotely to the server in the URL + // of the request. It is stored as a base64 string. + this._wrappedKey = ""; + + // Whether we have been informed of a shutdown by the xpcom-shutdown event. + this._shuttingDown = false; + + // All of these backoff properties are different per completer as the DB + // service keeps one completer per table. + // + // _backoff tells us whether we are "backing off" from making requests. + // It is set in |noteServerResponse| and set after a number of failures. + this._backoff = false; + // _backoffTime tells us how long we should wait (in seconds) before making + // another request. + this._backoffTime = 0; + // _nextRequestTime is the earliest time at which we are allowed to make + // another request by the backoff policy. It is measured in milliseconds. + this._nextRequestTime = 0; + // A list of the times at which a failed request was made, recorded in + // |noteServerResponse|. Sorted by oldest to newest and its length is clamped + // by BACKOFF_ERRORS. + this._errorTimes = []; + + Services.obs.addObserver(this, "xpcom-shutdown", true); +} +HashCompleter.prototype = { + classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIUrlClassifierHashCompleter, + Ci.nsIRunnable, + Ci.nsIObserver, + Ci.nsISupportsWeakReference, + Ci.nsISupports]), + + // This is mainly how the HashCompleter interacts with other components. + // Even though it only takes one partial hash and callback, subsequent + // calls are made into the same HTTP request by using a thread dispatch. + complete: function HC_complete(aPartialHash, aCallback) { + if (!this._currentRequest) { + this._currentRequest = new HashCompleterRequest(this); + + // It's possible for getHashUrl to not be set, usually at start-up. + if (this._getHashUrl) { + Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL); + } + } + + this._currentRequest.add(aPartialHash, aCallback); + }, + + // This is called when either the getHashUrl has been set or after a few calls + // to |complete|. It starts off the HTTP request by making a |begin| call + // to the HashCompleterRequest. + run: function HC_run() { + if (this._shuttingDown) { + this._currentRequest = null; + throw Cr.NS_ERROR_NOT_INITIALIZED; + } + + if (!this._currentRequest) { + return; + } + + if (!this._getHashUrl) { + throw Cr.NS_ERROR_NOT_INITIALIZED; + } + + let url = this._getHashUrl; + if (this._clientKey) { + this._currentRequest.clientKey = this._clientKey; + url += "&wrkey=" + this._wrappedKey; + } + + let uri = Services.io.newURI(url, null, null); + this._currentRequest.setURI(uri); + + // If |begin| fails, we should get rid of our request. + try { + this._currentRequest.begin(); + } + finally { + this._currentRequest = null; + } + }, + + // When a rekey has been requested, we can only clear our keys and make + // unauthenticated requests. + // The HashCompleter does not handle the rekeying but instead sends a + // notification to have listeners do the work. + rekeyRequested: function HC_rekeyRequested() { + this.setKeys("", ""); + + Services.obs.notifyObservers(this, "url-classifier-rekey-requested", null); + }, + + // setKeys expects clientKey and wrappedKey to be url safe, base64 strings. + // When called with an empty client string, setKeys resets both the client + // key and wrapped key. + setKeys: function HC_setKeys(aClientKey, aWrappedKey) { + if (aClientKey == "") { + this._clientKey = ""; + this._wrappedKey = ""; + return; + } + + // The decoding of clientKey was originally done by using + // nsUrlClassifierUtils::DecodeClientKey. + this._clientKey = atob(unUrlsafeBase64(aClientKey)); + this._wrappedKey = aWrappedKey; + }, + + get gethashUrl() { + return this._getHashUrl; + }, + // Because we hold off on making a request until we have a valid getHashUrl, + // we kick off the process here. + set gethashUrl(aNewUrl) { + this._getHashUrl = aNewUrl; + + if (this._currentRequest) { + Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL); + } + }, + + // This handles all the logic about setting a back off time based on + // server responses. It should only be called once in the life time of a + // request. + // The logic behind backoffs is documented in the Google Safe Browsing API, + // the general idea is that a completer should go into backoff mode after + // BACKOFF_ERRORS errors in the last BACKOFF_TIME seconds. From there, + // we do not make a request for BACKOFF_INTERVAL seconds and for every failed + // request after that we double how long to wait, to a maximum of BACKOFF_MAX. + // Note that even in the case of a successful response we still keep a history + // of past errors. + noteServerResponse: function HC_noteServerResponse(aSuccess) { + if (aSuccess) { + this._backoff = false; + this._nextRequestTime = 0; + this._backoffTime = 0; + return; + } + + let now = Date.now(); + + // We only alter the size of |_errorTimes| here, so we can guarantee that + // its length is at most BACKOFF_ERRORS. + this._errorTimes.push(now); + if (this._errorTimes.length > BACKOFF_ERRORS) { + this._errorTimes.shift(); + } + + if (this._backoff) { + this._backoffTime *= 2; + } else if (this._errorTimes.length == BACKOFF_ERRORS && + ((now - this._errorTimes[0]) / 1000) <= BACKOFF_TIME) { + this._backoff = true; + this._backoffTime = BACKOFF_INTERVAL; + } + + if (this._backoff) { + this._backoffTime = Math.min(this._backoffTime, BACKOFF_MAX); + this._nextRequestTime = now + (this._backoffTime * 1000); + } + }, + + // This is not included on the interface but is used to communicate to the + // HashCompleterRequest. It returns a time in milliseconds. + get nextRequestTime() { + return this._nextRequestTime; + }, + + observe: function HC_observe(aSubject, aTopic, aData) { + if (aTopic == "xpcom-shutdown") { + this._shuttingDown = true; + } + }, +}; + +function HashCompleterRequest(aCompleter) { + // HashCompleter object that created this HashCompleterRequest. + this._completer = aCompleter; + // The internal set of hashes and callbacks that this request corresponds to. + this._requests = []; + // URI to query for hash completions. Largely comes from the + // browser.safebrowsing.provider.#.gethashURL pref. + this._uri = null; + // nsIChannel that the hash completion query is transmitted over. + this._channel = null; + // Response body of hash completion. Created in onDataAvailable. + this._response = ""; + // Client key when HMAC is used. + this._clientKey = ""; + // Request was rescheduled, possibly due to a "e:pleaserekey" request from + // the server. + this._rescheduled = false; + // Whether the request was encrypted. This is also used as the |trusted| + // parameter to the nsIUrlClassifierHashCompleterCallback. + this._verified = false; + // Whether we have been informed of a shutdown by the xpcom-shutdown event. + this._shuttingDown = false; +} +HashCompleterRequest.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, + Ci.nsIStreamListener, + Ci.nsIObserver, + Ci.nsISupports]), + + // This is called by the HashCompleter to add a hash and callback to the + // HashCompleterRequest. It must be called before calling |begin|. + add: function HCR_add(aPartialHash, aCallback) { + this._requests.push({ + partialHash: aPartialHash, + callback: aCallback, + responses: [], + }); + }, + + // This initiates the HTTP request. It can fail due to backoff timings and + // will notify all callbacks as necessary. + begin: function HCR_begin() { + if (Date.now() < this._completer.nextRequestTime) { + this.notifyFailure(Cr.NS_ERROR_ABORT); + return; + } + + Services.obs.addObserver(this, "xpcom-shutdown", false); + + try { + this.openChannel(); + } + catch (err) { + this.notifyFailure(err); + throw err; + } + }, + + setURI: function HCR_setURI(aURI) { + this._uri = aURI; + }, + + // Creates an nsIChannel for the request and fills the body. + openChannel: function HCR_openChannel() { + let loadFlags = Ci.nsIChannel.INHIBIT_CACHING | + Ci.nsIChannel.LOAD_BYPASS_CACHE; + + let channel = Services.io.newChannelFromURI(this._uri); + channel.loadFlags = loadFlags; + + this._channel = channel; + + let body = this.buildRequest(); + this.addRequestBody(body); + + channel.asyncOpen(this, null); + }, + + // Returns a string for the request body based on the contents of + // this._requests. + buildRequest: function HCR_buildRequest() { + // Sometimes duplicate entries are sent to HashCompleter but we do not need + // to propagate these to the server. (bug 633644) + let prefixes = []; + + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + if (prefixes.indexOf(request.partialHash) == -1) { + prefixes.push(request.partialHash); + } + } + + let body; + body = PARTIAL_LENGTH + ":" + (PARTIAL_LENGTH * prefixes.length) + + "\n" + prefixes.join(""); + + return body; + }, + + // Sets the request body of this._channel. + addRequestBody: function HCR_addRequestBody(aBody) { + let inputStream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); + + inputStream.setData(aBody, aBody.length); + + let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel); + uploadChannel.setUploadStream(inputStream, "text/plain", -1); + + let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel); + httpChannel.requestMethod = "POST"; + }, + + // Parses the response body and eventually adds items to the |responses| array + // for elements of |this._requests|. + handleResponse: function HCR_handleResponse() { + if (this._response == "") { + return; + } + + let start = 0; + if (this._clientKey) { + start = this.handleMAC(start); + + if (this._rescheduled) { + return; + } + } + + let length = this._response.length; + while (start != length) + start = this.handleTable(start); + }, + + // This parses and confirms that the MAC in the response matches the expected + // value. This throws an error if the MAC does not match or otherwise, returns + // the index after the MAC header. + handleMAC: function HCR_handleMAC(aStart) { + this._verified = false; + + let body = this._response.substring(aStart); + + // We have to deal with the index of the new line character instead of + // splitting the string as there could be new line characters in the data + // parts. + let newlineIndex = body.indexOf("\n"); + if (newlineIndex == -1) { + throw errorWithStack(); + } + + let serverMAC = body.substring(0, newlineIndex); + if (serverMAC == "e:pleaserekey") { + this.rescheduleItems(); + + this._completer.rekeyRequested(); + return this._response.length; + } + + serverMAC = unUrlsafeBase64(serverMAC); + + let keyObject = keyFactory.keyFromString(Ci.nsIKeyObject.HMAC, + this._clientKey); + + let data = body.substring(newlineIndex + 1).split("") + .map(function(x) x.charCodeAt(0)); + + let hmac = Cc["@mozilla.org/security/hmac;1"] + .createInstance(Ci.nsICryptoHMAC); + hmac.init(Ci.nsICryptoHMAC.SHA1, keyObject); + hmac.update(data, data.length); + let clientMAC = hmac.finish(true); + + if (clientMAC != serverMAC) { + throw errorWithStack(); + } + + this._verified = true; + + return aStart + newlineIndex + 1; + }, + + // This parses a table entry in the response body and calls |handleItem| + // for complete hash in the table entry. Like |handleMAC|, it returns the + // index in |_response| right after the table it parsed. + handleTable: function HCR_handleTable(aStart) { + let body = this._response.substring(aStart); + + // Like in handleMAC, we deal with new line indexes as there could be + // new line characters in the data parts. + let newlineIndex = body.indexOf("\n"); + if (newlineIndex == -1) { + throw errorWithStack(); + } + let header = body.substring(0, newlineIndex); + let entries = header.split(":"); + if (entries.length != 3) { + throw errorWithStack(); + } + + let list = entries[0]; + let addChunk = parseInt(entries[1]); + let dataLength = parseInt(entries[2]); + + if (dataLength % COMPLETE_LENGTH != 0 || + dataLength == 0 || + dataLength > body.length - (newlineIndex + 1)) { + throw errorWithStack(); + } + + let data = body.substr(newlineIndex + 1, dataLength); + for (let i = 0; i < (dataLength / COMPLETE_LENGTH); i++) { + this.handleItem(data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH), list, + addChunk); + } + + return aStart + newlineIndex + 1 + dataLength; + }, + + // This adds a complete hash to any entry in |this._requests| that matches + // the hash. + handleItem: function HCR_handleItem(aData, aTableName, aChunkId) { + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + if (aData.substring(0,4) == request.partialHash) { + request.responses.push({ + completeHash: aData, + tableName: aTableName, + chunkId: aChunkId, + }); + } + } + }, + + // notifySuccess and notifyFailure are used to alert the callbacks with + // results. notifySuccess makes |completion| and |completionFinished| calls + // while notifyFailure only makes a |completionFinished| call with the error + // code. + notifySuccess: function HCR_notifySuccess() { + for (let i = 0; i < this._requests.length; i++) { + let request = this._requests[i]; + for (let j = 0; j < request.responses.length; j++) { + let response = request.responses[j]; + request.callback.completion(response.completeHash, response.tableName, + response.chunkId, this._verified); + } + + request.callback.completionFinished(Cr.NS_OK); + } + }, + notifyFailure: function HCR_notifyFailure(aStatus) { + for (let i = 0; i < this._requests; i++) { + let request = this._requests[i]; + request.callback.completionFinished(aStatus); + } + }, + + // rescheduleItems is called after a "e:pleaserekey" response. It is meant + // to be called after |rekeyRequested| has been called as it re-calls + // the HashCompleter with |complete| for all the items on this request. + rescheduleItems: function HCR_rescheduleItems() { + for (let i = 0; i < this._requests[i]; i++) { + let request = this._requests[i]; + try { + this._completer.complete(request.partialHash, request.callback); + } + catch (err) { + request.callback.completionFinished(err); + } + } + + this._rescheduled = true; + }, + + onDataAvailable: function HCR_onDataAvailable(aRequest, aContext, + aInputStream, aOffset, aCount) { + let sis = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + sis.init(aInputStream); + this._response += sis.readBytes(aCount); + }, + + onStartRequest: function HCR_onStartRequest(aRequest, aContext) { + // At this point no data is available for us and we have no reason to + // terminate the connection, so we do nothing until |onStopRequest|. + }, + + onStopRequest: function HCR_onStopRequest(aRequest, aContext, aStatusCode) { + Services.obs.removeObserver(this, "xpcom-shutdown"); + + if (this._shuttingDown) { + throw Cr.NS_ERROR_ABORT; + } + + if (Components.isSuccessCode(aStatusCode)) { + let channel = aRequest.QueryInterface(Ci.nsIHttpChannel); + let success = channel.requestSucceeded; + if (!success) { + aStatusCode = Cr.NS_ERROR_ABORT; + } + } + + let success = Components.isSuccessCode(aStatusCode); + this._completer.noteServerResponse(success); + + if (success) { + try { + this.handleResponse(); + } + catch (err) { + dump(err.stack); + aStatusCode = err.value; + success = false; + } + } + + if (!this._rescheduled) { + if (success) { + this.notifySuccess(); + } else { + this.notifyFailure(aStatusCode); + } + } + }, + + set clientKey(aVal) { + this._clientKey = aVal; + }, + + observe: function HCR_observe(aSubject, aTopic, aData) { + if (aTopic != "xpcom-shutdown") { + return; + } + + this._shuttingDown = true; + if (this._channel) { + this._channel.cancel(Cr.NS_ERROR_ABORT); + } + }, +}; + +// Converts a URL safe base64 string to a normal base64 string. Will not change +// normal base64 strings. This is modelled after the same function in +// nsUrlClassifierUtils.h. +function unUrlsafeBase64(aStr) { + return !aStr ? "" : aStr.replace(/-/g, "+") + .replace(/_/g, "/"); +} + +function errorWithStack() { + let err = new Error(); + err.value = Cr.NS_ERROR_FAILURE; + return err; +} + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([HashCompleter]);