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
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.
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"
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)
63 #define LOG_ENABLED() (PR_FALSE)
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
,
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");
95 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, PR_FALSE
);
97 nsresult rv
= OpenChannel();
107 nsUrlClassifierHashCompleterRequest::Add(const nsACString
& partialHash
,
108 nsIUrlClassifierHashCompleterCallback
*c
)
110 LOG(("nsUrlClassifierHashCompleterRequest::Add [%p]", this));
111 Request
*request
= mRequests
.AppendElement();
113 return NS_ERROR_OUT_OF_MEMORY
;
115 request
->partialHash
= partialHash
;
116 request
->callback
= c
;
123 nsUrlClassifierHashCompleterRequest::OpenChannel()
125 LOG(("nsUrlClassifierHashCompleterRequest::OpenChannel [%p]", this));
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
);
145 nsUrlClassifierHashCompleterRequest::BuildRequest(nsCAutoString
&aRequestBody
)
147 LOG(("nsUrlClassifierHashCompleterRequest::BuildRequest [%p]", this));
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
);
165 nsUrlClassifierHashCompleterRequest::AddRequestBody(const nsACString
&aRequestBody
)
167 LOG(("nsUrlClassifierHashCompleterRequest::AddRequestBody [%p]", this));
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"),
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
);
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
);
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.
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
++));
232 if (serverMAC
.EqualsLiteral("e:pleaserekey")) {
233 LOG(("Rekey requested"));
235 // Reschedule our items to be requested again.
238 // Let the hash completer know that we need a new key.
239 return mCompleter
->RekeyRequested();
242 nsUrlClassifierUtils::UnUrlsafeBase64(serverMAC
);
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()),
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
;
282 nsUrlClassifierHashCompleterRequest::HandleItem(const nsACString
& item
,
283 const nsACString
& tableName
,
286 // If this item matches any of the requested partial hashes, add them
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();
293 return NS_ERROR_OUT_OF_MEMORY
;
294 response
->completeHash
= item
;
295 response
->tableName
= tableName
;
296 response
->chunkId
= chunkId
;
304 * Reads one table of results from the response. Leaves begin pointing at the
308 nsUrlClassifierHashCompleterRequest::HandleTable(nsACString::const_iterator
& begin
,
309 const nsACString::const_iterator
& end
)
311 nsACString::const_iterator iter
;
313 if (!FindCharInReadable(':', iter
, end
)) {
315 NS_WARNING("Received badly-formatted gethash response.");
316 return NS_ERROR_FAILURE
;
319 const nsCSubstring
& tableName
= Substring(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
);
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
);
363 // begin now points at the end of the hash data.
369 nsUrlClassifierHashCompleterRequest::HandleResponse()
371 if (mResponse
.IsEmpty()) {
372 // Empty response, we're done.
376 nsCString::const_iterator begin
, end
;
377 mResponse
.BeginReading(begin
);
378 mResponse
.EndReading(end
);
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
);
388 // We were rescheduled due to a k:pleaserekey request from the
389 // server. Don't bother reading the rest of the response.
394 while (begin
!= end
) {
395 rv
= HandleTable(begin
, end
);
396 NS_ENSURE_SUCCESS(rv
, rv
);
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
,
418 request
.callback
->CompletionFinished(NS_OK
);
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
);
434 nsUrlClassifierHashCompleterRequest::OnStartRequest(nsIRequest
*request
,
435 nsISupports
*context
)
437 LOG(("nsUrlClassifierHashCompleter::OnStartRequest [%p]", this));
442 nsUrlClassifierHashCompleterRequest::OnDataAvailable(nsIRequest
*request
,
443 nsISupports
*context
,
444 nsIInputStream
*stream
,
445 PRUint32 sourceOffset
,
448 LOG(("nsUrlClassifierHashCompleter::OnDataAvailable [%p]", this));
451 return NS_ERROR_ABORT
;
454 nsresult rv
= NS_ConsumeStream(stream
, length
, piece
);
455 NS_ENSURE_SUCCESS(rv
, rv
);
457 mResponse
.Append(piece
);
463 nsUrlClassifierHashCompleterRequest::OnStopRequest(nsIRequest
*request
,
464 nsISupports
*context
,
467 LOG(("nsUrlClassifierHashCompleter::OnStopRequest [%p, status=%d]",
470 nsCOMPtr
<nsIObserverService
> observerService
=
471 do_GetService("@mozilla.org/observer-service;1");
473 observerService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
476 return NS_ERROR_ABORT
;
478 if (NS_SUCCEEDED(status
)) {
479 nsCOMPtr
<nsIHttpChannel
> channel
= do_QueryInterface(request
);
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.
496 if (NS_SUCCEEDED(status
))
499 NotifyFailure(status
);
509 nsUrlClassifierHashCompleterRequest::Observe(nsISupports
*subject
,
511 const PRUnichar
*data
)
513 if (!strcmp(topic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
514 mShuttingDown
= PR_TRUE
;
516 mChannel
->Cancel(NS_ERROR_ABORT
);
522 NS_IMPL_ISUPPORTS4(nsUrlClassifierHashCompleter
,
523 nsIUrlClassifierHashCompleter
,
526 nsISupportsWeakReference
)
529 nsUrlClassifierHashCompleter::Init()
531 #if defined(PR_LOGGING)
532 if (!gUrlClassifierHashCompleterLog
)
533 gUrlClassifierHashCompleterLog
= PR_NewLogModule("UrlClassifierHashCompleter");
536 nsCOMPtr
<nsIObserverService
> observerService
=
537 do_GetService("@mozilla.org/observer-service;1");
539 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, PR_TRUE
);
545 nsUrlClassifierHashCompleter::Complete(const nsACString
&partialHash
,
546 nsIUrlClassifierHashCompleterCallback
*c
)
548 LOG(("nsUrlClassifierHashCompleter::Complete [%p]", this));
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.
556 mRequest
= new nsUrlClassifierHashCompleterRequest(this);
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
);
573 nsUrlClassifierHashCompleter::SetGethashUrl(const nsACString
&url
)
578 // Schedule any pending request.
579 NS_DispatchToCurrentThread(this);
586 nsUrlClassifierHashCompleter::GetGethashUrl(nsACString
&url
)
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();
607 nsresult rv
= nsUrlClassifierUtils::DecodeClientKey(clientKey
, mClientKey
);
608 NS_ENSURE_SUCCESS(rv
, rv
);
609 mWrappedKey
= wrappedKey
;
615 nsUrlClassifierHashCompleter::Run()
617 LOG(("nsUrlClassifierHashCompleter::Run [%p]\n", this));
621 return NS_ERROR_NOT_INITIALIZED
;
627 NS_ASSERTION(!mGethashUrl
.IsEmpty(),
628 "Request dispatched without a gethash url specified.");
630 nsCOMPtr
<nsIURI
> uri
;
632 if (mClientKey
.IsEmpty()) {
633 rv
= NS_NewURI(getter_AddRefs(uri
), mGethashUrl
);
634 NS_ENSURE_SUCCESS(rv
, rv
);
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();
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
;
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
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",
681 NS_ENSURE_SUCCESS(rv
, rv
);
687 nsUrlClassifierHashCompleter::NoteServerResponse(PRBool success
)
689 LOG(("nsUrlClassifierHashCompleter::NoteServerResponse [%p, %d]",
694 mNextRequestTime
= 0;
699 PRIntervalTime now
= PR_IntervalNow();
701 // Record the error time.
702 mErrorTimes
.AppendElement(now
);
703 if (mErrorTimes
.Length() > gBackoffErrors
) {
704 mErrorTimes
.RemoveElementAt(0);
709 LOG(("Doubled backoff time to %d seconds", mBackoffTime
));
710 } else if (mErrorTimes
.Length() == gBackoffErrors
&&
711 PR_IntervalToSeconds(now
- mErrorTimes
[0]) <= gBackoffTime
) {
713 mBackoffTime
= gBackoffInterval
;
714 LOG(("Starting backoff, backoff time is %d seconds", mBackoffTime
));
718 mBackoffTime
= PR_MIN(mBackoffTime
, gBackoffMax
);
719 LOG(("Using %d for backoff time", mBackoffTime
));
720 mNextRequestTime
= now
+ PR_SecondsToInterval(mBackoffTime
);