Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / toolkit / components / url-classifier / src / nsUrlClassifierHashCompleter.cpp
blobb838fe07b9951754c7e3ee44332673c98df2bb3d
1 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2008
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Dave Camp <dcamp@mozilla.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsUrlClassifierHashCompleter.h"
40 #include "nsIChannel.h"
41 #include "nsICryptoHMAC.h"
42 #include "nsIHttpChannel.h"
43 #include "nsIKeyModule.h"
44 #include "nsIObserverService.h"
45 #include "nsIUploadChannel.h"
46 #include "nsNetUtil.h"
47 #include "nsStreamUtils.h"
48 #include "nsStringStream.h"
49 #include "nsServiceManagerUtils.h"
50 #include "nsThreadUtils.h"
51 #include "nsUrlClassifierDBService.h"
52 #include "nsUrlClassifierUtils.h"
53 #include "prlog.h"
54 #include "prprf.h"
56 // NSPR_LOG_MODULES=UrlClassifierHashCompleter:5
57 #if defined(PR_LOGGING)
58 static const PRLogModuleInfo *gUrlClassifierHashCompleterLog = nsnull;
59 #define LOG(args) PR_LOG(gUrlClassifierHashCompleterLog, PR_LOG_DEBUG, args)
60 #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierHashCompleterLog, 4)
61 #else
62 #define LOG(args)
63 #define LOG_ENABLED() (PR_FALSE)
64 #endif
66 // Back off from making server requests if we have at least
67 // gBackoffErrors errors
68 static const PRUint32 gBackoffErrors = 2;
69 // .. within gBackoffTime seconds
70 static const PRUint32 gBackoffTime = 5 * 60;
71 // ... and back off gBackoffInterval seconds, doubling seach time
72 static const PRUint32 gBackoffInterval = 30 * 60;
73 // ... up to a maximum of gBackoffMax.
74 static const PRUint32 gBackoffMax = 8 * 60 * 60;
76 NS_IMPL_ISUPPORTS3(nsUrlClassifierHashCompleterRequest,
77 nsIRequestObserver,
78 nsIStreamListener,
79 nsIObserver)
81 nsresult
82 nsUrlClassifierHashCompleterRequest::Begin()
84 LOG(("nsUrlClassifierHashCompleterRequest::Begin [%p]", this));
86 if (PR_IntervalNow() < mCompleter->GetNextRequestTime()) {
87 NS_WARNING("Gethash server backed off, failing gethash request.");
88 NotifyFailure(NS_ERROR_ABORT);
89 return NS_ERROR_ABORT;
92 nsCOMPtr<nsIObserverService> observerService =
93 do_GetService("@mozilla.org/observer-service;1");
94 if (observerService)
95 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
97 nsresult rv = OpenChannel();
98 if (NS_FAILED(rv)) {
99 NotifyFailure(rv);
100 return rv;
103 return NS_OK;
106 nsresult
107 nsUrlClassifierHashCompleterRequest::Add(const nsACString& partialHash,
108 nsIUrlClassifierHashCompleterCallback *c)
110 LOG(("nsUrlClassifierHashCompleterRequest::Add [%p]", this));
111 Request *request = mRequests.AppendElement();
112 if (!request)
113 return NS_ERROR_OUT_OF_MEMORY;
115 request->partialHash = partialHash;
116 request->callback = c;
118 return NS_OK;
122 nsresult
123 nsUrlClassifierHashCompleterRequest::OpenChannel()
125 LOG(("nsUrlClassifierHashCompleterRequest::OpenChannel [%p]", this));
126 nsresult rv;
128 rv = NS_NewChannel(getter_AddRefs(mChannel), mURI);
129 NS_ENSURE_SUCCESS(rv, rv);
131 nsCAutoString requestBody;
132 rv = BuildRequest(requestBody);
133 NS_ENSURE_SUCCESS(rv, rv);
135 rv = AddRequestBody(requestBody);
136 NS_ENSURE_SUCCESS(rv, rv);
138 rv = mChannel->AsyncOpen(this, nsnull);
139 NS_ENSURE_SUCCESS(rv, rv);
141 return NS_OK;
144 nsresult
145 nsUrlClassifierHashCompleterRequest::BuildRequest(nsCAutoString &aRequestBody)
147 LOG(("nsUrlClassifierHashCompleterRequest::BuildRequest [%p]", this));
149 nsCAutoString body;
150 for (PRUint32 i = 0; i < mRequests.Length(); i++) {
151 Request &request = mRequests[i];
152 body.Append(request.partialHash);
155 aRequestBody.AppendInt(PARTIAL_LENGTH);
156 aRequestBody.Append(':');
157 aRequestBody.AppendInt(body.Length());
158 aRequestBody.Append('\n');
159 aRequestBody.Append(body);
161 return NS_OK;
164 nsresult
165 nsUrlClassifierHashCompleterRequest::AddRequestBody(const nsACString &aRequestBody)
167 LOG(("nsUrlClassifierHashCompleterRequest::AddRequestBody [%p]", this));
169 nsresult rv;
170 nsCOMPtr<nsIStringInputStream> strStream =
171 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
172 NS_ENSURE_SUCCESS(rv, rv);
174 rv = strStream->SetData(aRequestBody.BeginReading(),
175 aRequestBody.Length());
176 NS_ENSURE_SUCCESS(rv, rv);
178 nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(mChannel, &rv);
179 NS_ENSURE_SUCCESS(rv, rv);
181 rv = uploadChannel->SetUploadStream(strStream,
182 NS_LITERAL_CSTRING("text/plain"),
183 -1);
184 NS_ENSURE_SUCCESS(rv, rv);
186 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
187 NS_ENSURE_SUCCESS(rv, rv);
189 rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
190 NS_ENSURE_SUCCESS(rv, rv);
192 return NS_OK;
195 void
196 nsUrlClassifierHashCompleterRequest::RescheduleItems()
198 // This request has failed in a way that we expect might succeed if
199 // we try again. Schedule the individual hashes for another attempt.
200 for (PRUint32 i = 0; i < mRequests.Length(); i++) {
201 Request &request = mRequests[i];
202 nsresult rv = mCompleter->Complete(request.partialHash, request.callback);
203 if (NS_FAILED(rv)) {
204 // We couldn't reschedule the request - the best we can do here is
205 // tell it that we failed to complete the request.
206 request.callback->CompletionFinished(rv);
210 mRescheduled = PR_TRUE;
214 * Reads the MAC from the response and checks it against the
215 * locally-computed MAC.
217 nsresult
218 nsUrlClassifierHashCompleterRequest::HandleMAC(nsACString::const_iterator& begin,
219 const nsACString::const_iterator& end)
221 mVerified = PR_FALSE;
223 // First line should be either the MAC or a k:pleaserekey request.
224 nsACString::const_iterator iter = begin;
225 if (!FindCharInReadable('\n', iter, end)) {
226 return NS_ERROR_FAILURE;
229 nsCAutoString serverMAC(Substring(begin, iter++));
230 begin = iter;
232 if (serverMAC.EqualsLiteral("e:pleaserekey")) {
233 LOG(("Rekey requested"));
235 // Reschedule our items to be requested again.
236 RescheduleItems();
238 // Let the hash completer know that we need a new key.
239 return mCompleter->RekeyRequested();
242 nsUrlClassifierUtils::UnUrlsafeBase64(serverMAC);
244 nsresult rv;
246 nsCOMPtr<nsIKeyObjectFactory> keyObjectFactory(
247 do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv));
248 NS_ENSURE_SUCCESS(rv, rv);
250 nsCOMPtr<nsIKeyObject> keyObject;
251 rv = keyObjectFactory->KeyFromString(nsIKeyObject::HMAC, mClientKey,
252 getter_AddRefs(keyObject));
253 NS_ENSURE_SUCCESS(rv, rv);
255 nsCOMPtr<nsICryptoHMAC> hmac =
256 do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv);
257 NS_ENSURE_SUCCESS(rv, rv);
259 rv = hmac->Init(nsICryptoHMAC::SHA1, keyObject);
260 NS_ENSURE_SUCCESS(rv, rv);
262 const nsCSubstring &remaining = Substring(begin, end);
263 rv = hmac->Update(reinterpret_cast<const PRUint8*>(remaining.BeginReading()),
264 remaining.Length());
265 NS_ENSURE_SUCCESS(rv, rv);
267 nsCAutoString clientMAC;
268 rv = hmac->Finish(PR_TRUE, clientMAC);
269 NS_ENSURE_SUCCESS(rv, rv);
271 if (clientMAC != serverMAC) {
272 NS_WARNING("Invalid MAC in gethash response.");
273 return NS_ERROR_FAILURE;
276 mVerified = PR_TRUE;
278 return NS_OK;
281 nsresult
282 nsUrlClassifierHashCompleterRequest::HandleItem(const nsACString& item,
283 const nsACString& tableName,
284 PRUint32 chunkId)
286 // If this item matches any of the requested partial hashes, add them
287 // to the response.
288 for (PRUint32 i = 0; i < mRequests.Length(); i++) {
289 Request &request = mRequests[i];
290 if (StringBeginsWith(item, request.partialHash)) {
291 Response *response = request.responses.AppendElement();
292 if (!response)
293 return NS_ERROR_OUT_OF_MEMORY;
294 response->completeHash = item;
295 response->tableName = tableName;
296 response->chunkId = chunkId;
300 return NS_OK;
304 * Reads one table of results from the response. Leaves begin pointing at the
305 * next table.
307 nsresult
308 nsUrlClassifierHashCompleterRequest::HandleTable(nsACString::const_iterator& begin,
309 const nsACString::const_iterator& end)
311 nsACString::const_iterator iter;
312 iter = begin;
313 if (!FindCharInReadable(':', iter, end)) {
314 // No table line.
315 NS_WARNING("Received badly-formatted gethash response.");
316 return NS_ERROR_FAILURE;
319 const nsCSubstring& tableName = Substring(begin, iter);
320 iter++;
321 begin = iter;
323 if (!FindCharInReadable('\n', iter, end)) {
324 // Unterminated header line.
325 NS_WARNING("Received badly-formatted gethash response.");
326 return NS_ERROR_FAILURE;
329 const nsCSubstring& remaining = Substring(begin, iter);
330 iter++;
331 begin = iter;
333 PRUint32 chunkId;
334 PRInt32 size;
335 if (PR_sscanf(PromiseFlatCString(remaining).get(),
336 "%u:%d", &chunkId, &size) != 2) {
337 NS_WARNING("Received badly-formatted gethash response.");
338 return NS_ERROR_FAILURE;
341 if (size % COMPLETE_LENGTH != 0) {
342 NS_WARNING("Unexpected gethash response length");
343 return NS_ERROR_FAILURE;
346 // begin now refers to the hash data.
348 if (begin.size_forward() < size) {
349 NS_WARNING("Response does not match the expected response length.");
350 return NS_ERROR_FAILURE;
353 for (PRInt32 i = 0; i < (size / COMPLETE_LENGTH); i++) {
354 // Read the complete hash.
355 iter.advance(COMPLETE_LENGTH);
357 nsresult rv = HandleItem(Substring(begin, iter), tableName, chunkId);
358 NS_ENSURE_SUCCESS(rv, rv);
360 begin = iter;
363 // begin now points at the end of the hash data.
365 return NS_OK;
368 nsresult
369 nsUrlClassifierHashCompleterRequest::HandleResponse()
371 if (mResponse.IsEmpty()) {
372 // Empty response, we're done.
373 return NS_OK;
376 nsCString::const_iterator begin, end;
377 mResponse.BeginReading(begin);
378 mResponse.EndReading(end);
380 nsresult rv;
382 // If we have a client key, we're expecting a MAC.
383 if (!mClientKey.IsEmpty()) {
384 rv = HandleMAC(begin, end);
385 NS_ENSURE_SUCCESS(rv, rv);
387 if (mRescheduled) {
388 // We were rescheduled due to a k:pleaserekey request from the
389 // server. Don't bother reading the rest of the response.
390 return NS_OK;
394 while (begin != end) {
395 rv = HandleTable(begin, end);
396 NS_ENSURE_SUCCESS(rv, rv);
399 return NS_OK;
402 void
403 nsUrlClassifierHashCompleterRequest::NotifySuccess()
405 LOG(("nsUrlClassifierHashCompleterRequest::NotifySuccess [%p]", this));
407 for (PRUint32 i = 0; i < mRequests.Length(); i++) {
408 Request &request = mRequests[i];
410 for (PRUint32 j = 0; j < request.responses.Length(); j++) {
411 Response &response = request.responses[j];
412 request.callback->Completion(response.completeHash,
413 response.tableName,
414 response.chunkId,
415 mVerified);
418 request.callback->CompletionFinished(NS_OK);
422 void
423 nsUrlClassifierHashCompleterRequest::NotifyFailure(nsresult status)
425 LOG(("nsUrlClassifierHashCompleterRequest::NotifyFailure [%p]", this));
427 for (PRUint32 i = 0; i < mRequests.Length(); i++) {
428 Request &request = mRequests[i];
429 request.callback->CompletionFinished(status);
433 NS_IMETHODIMP
434 nsUrlClassifierHashCompleterRequest::OnStartRequest(nsIRequest *request,
435 nsISupports *context)
437 LOG(("nsUrlClassifierHashCompleter::OnStartRequest [%p]", this));
438 return NS_OK;
441 NS_IMETHODIMP
442 nsUrlClassifierHashCompleterRequest::OnDataAvailable(nsIRequest *request,
443 nsISupports *context,
444 nsIInputStream *stream,
445 PRUint32 sourceOffset,
446 PRUint32 length)
448 LOG(("nsUrlClassifierHashCompleter::OnDataAvailable [%p]", this));
450 if (mShuttingDown)
451 return NS_ERROR_ABORT;
453 nsCAutoString piece;
454 nsresult rv = NS_ConsumeStream(stream, length, piece);
455 NS_ENSURE_SUCCESS(rv, rv);
457 mResponse.Append(piece);
459 return NS_OK;
462 NS_IMETHODIMP
463 nsUrlClassifierHashCompleterRequest::OnStopRequest(nsIRequest *request,
464 nsISupports *context,
465 nsresult status)
467 LOG(("nsUrlClassifierHashCompleter::OnStopRequest [%p, status=%d]",
468 this, status));
470 nsCOMPtr<nsIObserverService> observerService =
471 do_GetService("@mozilla.org/observer-service;1");
472 if (observerService)
473 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
475 if (mShuttingDown)
476 return NS_ERROR_ABORT;
478 if (NS_SUCCEEDED(status)) {
479 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(request);
480 if (channel) {
481 PRBool success;
482 status = channel->GetRequestSucceeded(&success);
483 if (NS_SUCCEEDED(status) && !success) {
484 status = NS_ERROR_ABORT;
489 mCompleter->NoteServerResponse(NS_SUCCEEDED(status));
491 if (NS_SUCCEEDED(status))
492 status = HandleResponse();
494 // If we were rescheduled, don't bother notifying success or failure.
495 if (!mRescheduled) {
496 if (NS_SUCCEEDED(status))
497 NotifySuccess();
498 else
499 NotifyFailure(status);
502 mChannel = nsnull;
504 return NS_OK;
508 NS_IMETHODIMP
509 nsUrlClassifierHashCompleterRequest::Observe(nsISupports *subject,
510 const char *topic,
511 const PRUnichar *data)
513 if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
514 mShuttingDown = PR_TRUE;
515 if (mChannel)
516 mChannel->Cancel(NS_ERROR_ABORT);
519 return NS_OK;
522 NS_IMPL_ISUPPORTS4(nsUrlClassifierHashCompleter,
523 nsIUrlClassifierHashCompleter,
524 nsIRunnable,
525 nsIObserver,
526 nsISupportsWeakReference)
528 nsresult
529 nsUrlClassifierHashCompleter::Init()
531 #if defined(PR_LOGGING)
532 if (!gUrlClassifierHashCompleterLog)
533 gUrlClassifierHashCompleterLog = PR_NewLogModule("UrlClassifierHashCompleter");
534 #endif
536 nsCOMPtr<nsIObserverService> observerService =
537 do_GetService("@mozilla.org/observer-service;1");
538 if (observerService)
539 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
541 return NS_OK;
544 NS_IMETHODIMP
545 nsUrlClassifierHashCompleter::Complete(const nsACString &partialHash,
546 nsIUrlClassifierHashCompleterCallback *c)
548 LOG(("nsUrlClassifierHashCompleter::Complete [%p]", this));
550 if (mShuttingDown)
551 return NS_ERROR_NOT_INITIALIZED;
553 // We batch all of the requested completions in a single request until the
554 // next time we reach the main loop.
555 if (!mRequest) {
556 mRequest = new nsUrlClassifierHashCompleterRequest(this);
557 if (!mRequest) {
558 return NS_ERROR_OUT_OF_MEMORY;
561 // If we don't have a gethash url yet, don't bother scheduling
562 // the request until we have one.
563 if (!mGethashUrl.IsEmpty()) {
564 // Schedule ourselves to start this request on the main loop.
565 NS_DispatchToCurrentThread(this);
569 return mRequest->Add(partialHash, c);
572 NS_IMETHODIMP
573 nsUrlClassifierHashCompleter::SetGethashUrl(const nsACString &url)
575 mGethashUrl = url;
577 if (mRequest) {
578 // Schedule any pending request.
579 NS_DispatchToCurrentThread(this);
582 return NS_OK;
585 NS_IMETHODIMP
586 nsUrlClassifierHashCompleter::GetGethashUrl(nsACString &url)
588 url = mGethashUrl;
589 return NS_OK;
592 NS_IMETHODIMP
593 nsUrlClassifierHashCompleter::SetKeys(const nsACString &clientKey,
594 const nsACString &wrappedKey)
596 LOG(("nsUrlClassifierHashCompleter::SetKeys [%p]", this));
598 NS_ASSERTION(clientKey.IsEmpty() == wrappedKey.IsEmpty(),
599 "Must either have both a client key and a wrapped key or neither.");
601 if (clientKey.IsEmpty()) {
602 mClientKey.Truncate();
603 mWrappedKey.Truncate();
604 return NS_OK;
607 nsresult rv = nsUrlClassifierUtils::DecodeClientKey(clientKey, mClientKey);
608 NS_ENSURE_SUCCESS(rv, rv);
609 mWrappedKey = wrappedKey;
611 return NS_OK;
614 NS_IMETHODIMP
615 nsUrlClassifierHashCompleter::Run()
617 LOG(("nsUrlClassifierHashCompleter::Run [%p]\n", this));
619 if (mShuttingDown) {
620 mRequest = nsnull;
621 return NS_ERROR_NOT_INITIALIZED;
624 if (!mRequest)
625 return NS_OK;
627 NS_ASSERTION(!mGethashUrl.IsEmpty(),
628 "Request dispatched without a gethash url specified.");
630 nsCOMPtr<nsIURI> uri;
631 nsresult rv;
632 if (mClientKey.IsEmpty()) {
633 rv = NS_NewURI(getter_AddRefs(uri), mGethashUrl);
634 NS_ENSURE_SUCCESS(rv, rv);
635 } else {
636 mRequest->SetClientKey(mClientKey);
638 nsCAutoString requestURL(mGethashUrl);
639 requestURL.Append("&wrkey=");
640 requestURL.Append(mWrappedKey);
641 rv = NS_NewURI(getter_AddRefs(uri), requestURL);
642 NS_ENSURE_SUCCESS(rv, rv);
645 mRequest->SetURI(uri);
647 // Dispatch the http request.
648 rv = mRequest->Begin();
649 mRequest = nsnull;
650 return rv;
653 NS_IMETHODIMP
654 nsUrlClassifierHashCompleter::Observe(nsISupports *subject, const char *topic,
655 const PRUnichar *data)
657 if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
658 mShuttingDown = PR_TRUE;
661 return NS_OK;
664 nsresult
665 nsUrlClassifierHashCompleter::RekeyRequested()
667 // Our keys are no longer valid.
668 SetKeys(EmptyCString(), EmptyCString());
670 // Notify the key manager that we need a new key. Until we get a
671 // new key, gethash requests will be unauthenticated (and therefore
672 // uncacheable).
673 nsresult rv;
674 nsCOMPtr<nsIObserverService> observerService =
675 do_GetService("@mozilla.org/observer-service;1", &rv);
676 NS_ENSURE_SUCCESS(rv, rv);
678 rv = observerService->NotifyObservers(static_cast<nsIUrlClassifierHashCompleter*>(this),
679 "url-classifier-rekey-requested",
680 nsnull);
681 NS_ENSURE_SUCCESS(rv, rv);
683 return NS_OK;
686 void
687 nsUrlClassifierHashCompleter::NoteServerResponse(PRBool success)
689 LOG(("nsUrlClassifierHashCompleter::NoteServerResponse [%p, %d]",
690 this, success));
692 if (success) {
693 mBackoff = PR_FALSE;
694 mNextRequestTime = 0;
695 mBackoffTime = 0;
696 return;
699 PRIntervalTime now = PR_IntervalNow();
701 // Record the error time.
702 mErrorTimes.AppendElement(now);
703 if (mErrorTimes.Length() > gBackoffErrors) {
704 mErrorTimes.RemoveElementAt(0);
707 if (mBackoff) {
708 mBackoffTime *= 2;
709 LOG(("Doubled backoff time to %d seconds", mBackoffTime));
710 } else if (mErrorTimes.Length() == gBackoffErrors &&
711 PR_IntervalToSeconds(now - mErrorTimes[0]) <= gBackoffTime) {
712 mBackoff = PR_TRUE;
713 mBackoffTime = gBackoffInterval;
714 LOG(("Starting backoff, backoff time is %d seconds", mBackoffTime));
717 if (mBackoff) {
718 mBackoffTime = PR_MIN(mBackoffTime, gBackoffMax);
719 LOG(("Using %d for backoff time", mBackoffTime));
720 mNextRequestTime = now + PR_SecondsToInterval(mBackoffTime);