From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../nsUrlClassifierStreamUpdater.cpp | 881 +++++++++++++++++++++ 1 file changed, 881 insertions(+) create mode 100644 toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp (limited to 'toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp') diff --git a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp new file mode 100644 index 0000000000..641339c3ea --- /dev/null +++ b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp @@ -0,0 +1,881 @@ +//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Components.h" +#include "nsCRT.h" +#include "nsIHttpChannel.h" +#include "nsIObserverService.h" +#include "nsIStringStream.h" +#include "nsIUploadChannel.h" +#include "nsIURI.h" +#include "nsIUrlClassifierDBService.h" +#include "nsIUrlClassifierRemoteSettingsService.h" +#include "nsUrlClassifierUtils.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsUrlClassifierStreamUpdater.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "nsIInterfaceRequestor.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Try.h" +#include "nsContentUtils.h" +#include "nsIURLFormatter.h" +#include "Classifier.h" +#include "UrlClassifierTelemetryUtils.h" +#include "mozilla/StaticPrefs_urlclassifier.h" + +using namespace mozilla::safebrowsing; +using namespace mozilla; + +#define MIN_TIMEOUT_MS (60 * 1000) + +static const char* gQuitApplicationMessage = "quit-application"; + +// Limit the list file size to 32mb +const uint32_t MAX_FILE_SIZE = (32 * 1024 * 1024); + +// Retry delay when we failed to DownloadUpdate() if due to +// DBService busy. +const uint32_t FETCH_NEXT_REQUEST_RETRY_DELAY_MS = 1000; + +#undef LOG + +// MOZ_LOG=UrlClassifierStreamUpdater:5 +static mozilla::LazyLogModule gUrlClassifierStreamUpdaterLog( + "UrlClassifierStreamUpdater"); +#define LOG(args) TrimAndLog args +#define LOG_ENABLED() \ + MOZ_LOG_TEST(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Debug) + +// Calls nsIURLFormatter::TrimSensitiveURLs to remove sensitive +// info from the logging message. +static MOZ_FORMAT_PRINTF(1, 2) void TrimAndLog(const char* aFmt, ...) { + nsString raw; + + va_list ap; + va_start(ap, aFmt); + raw.AppendVprintf(aFmt, ap); + va_end(ap); + + nsCOMPtr urlFormatter = + do_GetService("@mozilla.org/toolkit/URLFormatterService;1"); + + nsString trimmed; + nsresult rv = urlFormatter->TrimSensitiveURLs(raw, trimmed); + if (NS_FAILED(rv)) { + trimmed.Truncate(); + } + + // Use %s so we aren't exposing random strings to printf interpolation. + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Debug, + ("%s", NS_ConvertUTF16toUTF8(trimmed).get())); +} + +// This class does absolutely nothing, except pass requests onto the DBService. + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassiferStreamUpdater implementation +// Handles creating/running the stream listener + +nsUrlClassifierStreamUpdater::nsUrlClassifierStreamUpdater() + : mIsUpdating(false), + mInitialized(false), + mDownloadError(false), + mBeganStream(false), + mChannel(nullptr), + mTelemetryClockStart(0) { + LOG(("nsUrlClassifierStreamUpdater init [this=%p]", this)); +} + +NS_IMPL_ISUPPORTS(nsUrlClassifierStreamUpdater, nsIUrlClassifierStreamUpdater, + nsIUrlClassifierUpdateObserver, nsIRequestObserver, + nsIStreamListener, nsIObserver, nsIInterfaceRequestor, + nsITimerCallback, nsINamed) + +/** + * Clear out the update. + */ +void nsUrlClassifierStreamUpdater::DownloadDone() { + LOG(("nsUrlClassifierStreamUpdater::DownloadDone [this=%p]", this)); + mIsUpdating = false; + + mPendingUpdates.Clear(); + mDownloadError = false; + mCurrentRequest = nullptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassifierStreamUpdater implementation + +nsresult nsUrlClassifierStreamUpdater::FetchUpdate( + nsIURI* aUpdateUrl, const nsACString& aRequestPayload, bool aIsPostRequest, + const nsACString& aStreamTable) { + mBeganStream = false; + nsresult rv; + // moz-sbrs is a customed scheme used by Safe Browsing. When the scheme is + // present in the update url, we'll fetch the data from + // UrlClassifierRemoteSettingsService. + if (aUpdateUrl->SchemeIs("moz-sbrs")) { +#ifdef DEBUG + LOG(("Fetching update %s from RemoteSettings", aRequestPayload.Data())); +#endif + nsCOMPtr rsService = + do_GetService("@mozilla.org/url-classifier/list-service;1"); + if (!rsService) { + return NS_ERROR_FAILURE; + } + + rv = rsService->FetchList(aRequestPayload, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { +#ifdef DEBUG + LOG(("Fetching update %s from %s", aRequestPayload.Data(), + aUpdateUrl->GetSpecOrDefault().get())); +#endif + uint32_t loadFlags = nsIChannel::INHIBIT_CACHING | + nsIChannel::LOAD_BYPASS_CACHE | + nsIChannel::LOAD_BYPASS_URL_CLASSIFIER; + + // SafeBrowsing update request should never be classified to make sure + // we can recover from a bad SafeBrowsing database. + rv = NS_NewChannel(getter_AddRefs(mChannel), aUpdateUrl, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // nsICookieJarSettings + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + this, // aInterfaceRequestor + loadFlags); + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr loadInfo = mChannel->LoadInfo(); + mozilla::OriginAttributes attrs; + attrs.mFirstPartyDomain.AssignLiteral( + NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN); + loadInfo->SetOriginAttributes(attrs); + // allow deprecated HTTP request from SystemPrincipal + loadInfo->SetAllowDeprecatedSystemRequests(true); + + if (!aIsPostRequest) { + // We use POST method to send our request in v2. In v4, the request + // needs to be embedded to the URL and use GET method to send. + // However, from the Chromium source code, a extended HTTP header has + // to be sent along with the request to make the request succeed. + // The following description is from Chromium source code: + // + // "The following header informs the envelope server (which sits in + // front of Google's stubby server) that the received GET request should + // be interpreted as a POST." + // + nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel->SetRequestHeader("X-HTTP-Method-Override"_ns, "POST"_ns, + false); + NS_ENSURE_SUCCESS(rv, rv); + } else if (!aRequestPayload.IsEmpty()) { + rv = AddRequestBody(aRequestPayload); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Set the appropriate content type for file/data URIs, for unit testing + // purposes. + // This is only used for testing and should be deleted. + if (aUpdateUrl->SchemeIs("file") || aUpdateUrl->SchemeIs("data")) { + mChannel->SetContentType("application/vnd.google.safebrowsing-update"_ns); + } else { + // We assume everything else is an HTTP request. + + // Disable keepalive. + nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = httpChannel->SetRequestHeader("Connection"_ns, "close"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make the request. + rv = mChannel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + mTelemetryClockStart = PR_IntervalNow(); + mStreamTable = aStreamTable; + + if (StaticPrefs::urlclassifier_update_response_timeout_ms() > + StaticPrefs::urlclassifier_update_timeout_ms()) { + NS_WARNING( + "Safe Browsing response timeout is greater than the general " + "timeout. Disabling these update timeouts."); + return NS_OK; + } + MOZ_TRY_VAR(mResponseTimeoutTimer, + NS_NewTimerWithCallback( + this, StaticPrefs::urlclassifier_update_response_timeout_ms(), + nsITimer::TYPE_ONE_SHOT)); + + MOZ_TRY_VAR(mTimeoutTimer, + NS_NewTimerWithCallback( + this, StaticPrefs::urlclassifier_update_timeout_ms(), + nsITimer::TYPE_ONE_SHOT)); + + if (StaticPrefs::urlclassifier_update_timeout_ms() < MIN_TIMEOUT_MS) { + LOG(("Download update timeout %d ms (< %d ms) would be too small", + StaticPrefs::urlclassifier_update_timeout_ms(), MIN_TIMEOUT_MS)); + } + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchUpdate( + const nsACString& aUpdateUrl, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aStreamTable) { + LOG(("(pre) Fetching update from %s\n", + PromiseFlatCString(aUpdateUrl).get())); + + nsCString updateUrl(aUpdateUrl); + if (!aIsPostRequest) { + updateUrl.AppendPrintf("&$req=%s", nsCString(aRequestPayload).get()); + } + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), updateUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString urlSpec; + uri->GetAsciiSpec(urlSpec); + + LOG(("(post) Fetching update from %s\n", urlSpec.get())); + + return FetchUpdate(uri, aRequestPayload, aIsPostRequest, aStreamTable); +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::DownloadUpdates( + const nsACString& aRequestTables, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aUpdateUrl, + nsIUrlClassifierCallback* aSuccessCallback, + nsIUrlClassifierCallback* aUpdateErrorCallback, + nsIUrlClassifierCallback* aDownloadErrorCallback, bool* _retval) { + NS_ENSURE_ARG(aSuccessCallback); + NS_ENSURE_ARG(aUpdateErrorCallback); + NS_ENSURE_ARG(aDownloadErrorCallback); + + if (mIsUpdating) { + LOG(("Already updating, queueing update %s from %s", aRequestPayload.Data(), + aUpdateUrl.Data())); + *_retval = false; + UpdateRequest* request = mPendingRequests.AppendElement(fallible); + if (!request) { + return NS_ERROR_OUT_OF_MEMORY; + } + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, request); + return NS_OK; + } + + if (aUpdateUrl.IsEmpty()) { + NS_ERROR("updateUrl not set"); + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv; + + if (!mInitialized) { + // Add an observer for shutdown so we can cancel any pending list + // downloads. quit-application is the same event that the download + // manager listens for and uses to cancel pending downloads. + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + observerService->AddObserver(this, gQuitApplicationMessage, false); + + mDBService = mozilla::components::UrlClassifierDB::Service(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + } + + rv = mDBService->BeginUpdate(this, aRequestTables); + if (rv == NS_ERROR_NOT_AVAILABLE) { + LOG(("Service busy, already updating, queuing update %s from %s", + aRequestPayload.Data(), aUpdateUrl.Data())); + *_retval = false; + UpdateRequest* request = mPendingRequests.AppendElement(fallible); + if (!request) { + return NS_ERROR_OUT_OF_MEMORY; + } + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, request); + + // We cannot guarantee that we will be notified when DBService is done + // processing the current update, so we fire a retry timer on our own. + MOZ_TRY_VAR(mFetchNextRequestTimer, + NS_NewTimerWithCallback(this, FETCH_NEXT_REQUEST_RETRY_DELAY_MS, + nsITimer::TYPE_ONE_SHOT)); + + return NS_OK; + } + + if (NS_FAILED(rv)) { + return rv; + } + + nsUrlClassifierUtils* urlUtil = nsUrlClassifierUtils::GetInstance(); + if (NS_WARN_IF(!urlUtil)) { + return NS_ERROR_FAILURE; + } + + nsTArray tables; + mozilla::safebrowsing::Classifier::SplitTables(aRequestTables, tables); + urlUtil->GetTelemetryProvider(tables.SafeElementAt(0, ""_ns), + mTelemetryProvider); + + mCurrentRequest = MakeUnique(); + BuildUpdateRequest(aRequestTables, aRequestPayload, aIsPostRequest, + aUpdateUrl, aSuccessCallback, aUpdateErrorCallback, + aDownloadErrorCallback, mCurrentRequest.get()); + + mIsUpdating = true; + *_retval = true; + + LOG(("FetchUpdate: %s", mCurrentRequest->mUrl.Data())); + + return FetchUpdate(aUpdateUrl, aRequestPayload, aIsPostRequest, ""_ns); +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIUrlClassifierUpdateObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateUrlRequested(const nsACString& aUrl, + const nsACString& aTable) { + LOG(("Queuing requested update from %s\n", PromiseFlatCString(aUrl).get())); + + PendingUpdate* update = mPendingUpdates.AppendElement(fallible); + if (!update) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allow data: and file: urls for unit testing purposes, otherwise assume http + if (StringBeginsWith(aUrl, "data:"_ns) || + StringBeginsWith(aUrl, "file:"_ns)) { + update->mUrl = aUrl; + } else { + // For unittesting update urls to localhost should use http, not https + // (otherwise the connection will fail silently, since there will be no + // cert available). + if (!StringBeginsWith(aUrl, "localhost"_ns)) { + update->mUrl = "https://"_ns + aUrl; + } else { + update->mUrl = "http://"_ns + aUrl; + } + } + update->mTable = aTable; + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchNext() { + if (mPendingUpdates.Length() == 0) { + return NS_OK; + } + + PendingUpdate& update = mPendingUpdates[0]; + LOG(("Fetching update url: %s\n", update.mUrl.get())); + nsresult rv = + FetchUpdate(update.mUrl, ""_ns, + true, // This method is for v2 and v2 is always a POST. + update.mTable); + if (NS_FAILED(rv)) { + nsAutoCString errorName; + mozilla::GetErrorName(rv, errorName); + LOG(("Error (%s) fetching update url: %s\n", errorName.get(), + update.mUrl.get())); + // We can commit the urls that we've applied so far. This is + // probably a transient server problem, so trigger backoff. + mDownloadError = true; + mDBService->FinishUpdate(); + return rv; + } + + mPendingUpdates.RemoveElementAt(0); + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::FetchNextRequest() { + if (mPendingRequests.Length() == 0) { + LOG(("No more requests, returning")); + return NS_OK; + } + + UpdateRequest request = mPendingRequests[0]; + mPendingRequests.RemoveElementAt(0); + LOG(("Stream updater: fetching next request: %s, %s", request.mTables.get(), + request.mUrl.get())); + bool dummy; + DownloadUpdates(request.mTables, request.mRequestPayload, + request.mIsPostRequest, request.mUrl, + request.mSuccessCallback, request.mUpdateErrorCallback, + request.mDownloadErrorCallback, &dummy); + return NS_OK; +} + +void nsUrlClassifierStreamUpdater::BuildUpdateRequest( + const nsACString& aRequestTables, const nsACString& aRequestPayload, + bool aIsPostRequest, const nsACString& aUpdateUrl, + nsIUrlClassifierCallback* aSuccessCallback, + nsIUrlClassifierCallback* aUpdateErrorCallback, + nsIUrlClassifierCallback* aDownloadErrorCallback, UpdateRequest* aRequest) { + MOZ_ASSERT(aRequest); + + aRequest->mTables = aRequestTables; + aRequest->mRequestPayload = aRequestPayload; + aRequest->mIsPostRequest = aIsPostRequest; + aRequest->mUrl = aUpdateUrl; + aRequest->mSuccessCallback = aSuccessCallback; + aRequest->mUpdateErrorCallback = aUpdateErrorCallback; + aRequest->mDownloadErrorCallback = aDownloadErrorCallback; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::StreamFinished(nsresult status, + uint32_t requestedDelay) { + // We are a service and may not be reset with Init between calls, so reset + // mBeganStream manually. + mBeganStream = false; + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(status, errorName); + LOG(("nsUrlClassifierStreamUpdater::StreamFinished [%s, %d]", + errorName.get(), requestedDelay)); + } + if (NS_FAILED(status) || mPendingUpdates.Length() == 0) { + // We're done. + LOG(("nsUrlClassifierStreamUpdater::Done [this=%p]", this)); + mDBService->FinishUpdate(); + return NS_OK; + } + + // This timer is for fetching indirect updates ("forwards") from any "u:" + // lines that we encountered while processing the server response. It is NOT + // for scheduling the next time we pull the list from the server. That's a + // different timer in listmanager.js (see bug 1110891). + nsresult rv; + rv = NS_NewTimerWithCallback(getter_AddRefs(mFetchIndirectUpdatesTimer), this, + requestedDelay, nsITimer::TYPE_ONE_SHOT); + + if (NS_FAILED(rv)) { + NS_WARNING( + "Unable to initialize timer, fetching next safebrowsing item " + "immediately"); + return FetchNext(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateSuccess(uint32_t requestedTimeout) { + LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess [this=%p]", this)); + if (mPendingUpdates.Length() != 0) { + NS_WARNING("Didn't fetch all safebrowsing update redirects"); + } + + // DownloadDone() clears mSuccessCallback, so we save it off here. + nsCOMPtr successCallback = + mDownloadError ? nullptr : mCurrentRequest->mSuccessCallback.get(); + nsCOMPtr downloadErrorCallback = + mDownloadError ? mCurrentRequest->mDownloadErrorCallback.get() : nullptr; + + DownloadDone(); + + nsAutoCString strTimeout; + strTimeout.AppendInt(requestedTimeout); + if (successCallback) { + LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess callback [this=%p]", + this)); + successCallback->HandleEvent(strTimeout); + } else if (downloadErrorCallback) { + downloadErrorCallback->HandleEvent(mDownloadErrorStatusStr); + mDownloadErrorStatusStr.Truncate(); + LOG(("Notify download error callback in UpdateSuccess [this=%p]", this)); + } + // Now fetch the next request + LOG(("stream updater: calling into fetch next request")); + FetchNextRequest(); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::UpdateError(nsresult result) { + LOG(("nsUrlClassifierStreamUpdater::UpdateError [this=%p]", this)); + + // DownloadDone() clears mUpdateErrorCallback, so we save it off here. + nsCOMPtr errorCallback = + mDownloadError ? nullptr : mCurrentRequest->mUpdateErrorCallback.get(); + nsCOMPtr downloadErrorCallback = + mDownloadError ? mCurrentRequest->mDownloadErrorCallback.get() : nullptr; + DownloadDone(); + + if (errorCallback) { + nsAutoCString strResult; + mozilla::GetErrorName(result, strResult); + errorCallback->HandleEvent(strResult); + } else if (downloadErrorCallback) { + LOG(("Notify download error callback in UpdateError [this=%p]", this)); + downloadErrorCallback->HandleEvent(mDownloadErrorStatusStr); + mDownloadErrorStatusStr.Truncate(); + } + + return NS_OK; +} + +nsresult nsUrlClassifierStreamUpdater::AddRequestBody( + const nsACString& aRequestBody) { + 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, "text/plain"_ns, -1); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel->SetRequestMethod("POST"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListenerObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnStartRequest(nsIRequest* request) { + nsresult rv; + bool downloadError = false; + nsAutoCString strStatus; + nsresult status = NS_OK; + + // Only update if we got http success header + nsCOMPtr httpChannel = do_QueryInterface(request); + if (httpChannel) { + rv = httpChannel->GetStatus(&status); + NS_ENSURE_SUCCESS(rv, rv); + + if (LOG_ENABLED()) { + nsAutoCString errorName, spec; + mozilla::GetErrorName(status, errorName); + nsCOMPtr uri; + rv = httpChannel->GetURI(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + uri->GetAsciiSpec(spec); + } + LOG( + ("nsUrlClassifierStreamUpdater::OnStartRequest " + "(status=%s, uri=%s, this=%p)", + errorName.get(), spec.get(), this)); + } + if (mTelemetryClockStart > 0) { + uint32_t msecs = + PR_IntervalToMilliseconds(PR_IntervalNow() - mTelemetryClockStart); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_SERVER_RESPONSE_TIME, + mTelemetryProvider, msecs); + } + + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + + uint8_t netErrCode = NS_FAILED(status) ? NetworkErrorToBucket(status) : 0; + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_REMOTE_NETWORK_ERROR, + mTelemetryProvider, netErrCode); + + if (NS_FAILED(status)) { + // Assume we're overloading the server and trigger backoff. + downloadError = true; + } else { + bool succeeded = false; + rv = httpChannel->GetRequestSucceeded(&succeeded); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t requestStatus; + rv = httpChannel->GetResponseStatus(&requestStatus); + NS_ENSURE_SUCCESS(rv, rv); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_REMOTE_STATUS2, + mTelemetryProvider, HTTPStatusToBucket(requestStatus)); + if (requestStatus == 400) { + printf_stderr( + "Safe Browsing server returned a 400 during update:" + "request url = %s, payload = %s\n", + mCurrentRequest->mUrl.get(), + mCurrentRequest->mRequestPayload.get()); + } + + LOG(("nsUrlClassifierStreamUpdater::OnStartRequest %s (%d)", + succeeded ? "succeeded" : "failed", requestStatus)); + if (!succeeded) { + // 404 or other error, pass error status back + strStatus.AppendInt(requestStatus); + downloadError = true; + } + } + } + + if (downloadError) { + LOG(("nsUrlClassifierStreamUpdater::Download error [this=%p]", this)); + mDownloadError = true; + mDownloadErrorStatusStr = strStatus; + status = NS_ERROR_ABORT; + } else if (NS_SUCCEEDED(status)) { + MOZ_ASSERT(mCurrentRequest->mDownloadErrorCallback); + mBeganStream = true; + LOG(("nsUrlClassifierStreamUpdater::Beginning stream [this=%p]", this)); + rv = mDBService->BeginStream(mStreamTable); + NS_ENSURE_SUCCESS(rv, rv); + } + + mStreamTable.Truncate(); + + return status; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnDataAvailable(nsIRequest* request, + nsIInputStream* aIStream, + uint64_t aSourceOffset, + uint32_t aLength) { + if (!mDBService) return NS_ERROR_NOT_INITIALIZED; + + LOG(("OnDataAvailable (%d bytes)", aLength)); + + if (aSourceOffset > MAX_FILE_SIZE) { + LOG(( + "OnDataAvailable::Abort because exceeded the maximum file size(%" PRIu64 + ")", + aSourceOffset)); + return NS_ERROR_FILE_TOO_BIG; + } + + nsresult rv; + + // Copy the data into a nsCString + nsCString chunk; + rv = NS_ConsumeStream(aIStream, aLength, chunk); + NS_ENSURE_SUCCESS(rv, rv); + + // LOG(("Chunk (%d): %s\n\n", chunk.Length(), chunk.get())); + rv = mDBService->UpdateStream(chunk); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + if (!mDBService) return NS_ERROR_NOT_INITIALIZED; + + if (LOG_ENABLED()) { + nsAutoCString errorName; + mozilla::GetErrorName(aStatus, errorName); + LOG(("OnStopRequest (status %s, beganStream %s, this=%p)", errorName.get(), + mBeganStream ? "true" : "false", this)); + } + + nsresult rv; + + if (NS_SUCCEEDED(aStatus)) { + // Success, finish this stream and move on to the next. + rv = mDBService->FinishStream(); + } else if (mBeganStream) { + LOG(("OnStopRequest::Canceling update [this=%p]", this)); + // We began this stream and couldn't finish it. We have to cancel the + // update, it's not in a consistent state. + mDownloadError = true; + rv = mDBService->CancelUpdate(); + } else { + LOG(("OnStopRequest::Finishing update [this=%p]", this)); + // The fetch failed, but we didn't start the stream (probably a + // server or connection error). We can commit what we've applied + // so far, and request again later. + rv = mDBService->FinishUpdate(); + } + + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + + // mResponseTimeoutTimer may be cleared in OnStartRequest, so we check + // mTimeoutTimer to see whether the update was has timed out + if (mTimeoutTimer) { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast(eNoTimeout)); + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + + mTelemetryProvider.Truncate(); + mTelemetryClockStart = 0; + mChannel = nullptr; + + // If the fetch failed, return the network status rather than NS_OK, the + // result of finishing a possibly-empty update + if (NS_SUCCEEDED(aStatus)) { + return rv; + } + return aStatus; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIObserver implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (nsCRT::strcmp(aTopic, gQuitApplicationMessage) == 0) { + if (mIsUpdating && mChannel) { + LOG(("Cancel download")); + nsresult rv; + rv = mChannel->Cancel(NS_ERROR_ABORT); + NS_ENSURE_SUCCESS(rv, rv); + mIsUpdating = false; + mChannel = nullptr; + mTelemetryClockStart = 0; + } + if (mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer->Cancel(); + mFetchIndirectUpdatesTimer = nullptr; + } + if (mFetchNextRequestTimer) { + mFetchNextRequestTimer->Cancel(); + mFetchNextRequestTimer = nullptr; + } + if (mResponseTimeoutTimer) { + mResponseTimeoutTimer->Cancel(); + mResponseTimeoutTimer = nullptr; + } + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + } + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIInterfaceRequestor implementation + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::GetInterface(const nsIID& eventSinkIID, + void** _retval) { + return QueryInterface(eventSinkIID, _retval); +} + +/////////////////////////////////////////////////////////////////////////////// +// nsITimerCallback implementation +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::Notify(nsITimer* timer) { + LOG(("nsUrlClassifierStreamUpdater::Notify [%p]", this)); + + if (timer == mFetchNextRequestTimer) { + mFetchNextRequestTimer = nullptr; + FetchNextRequest(); + return NS_OK; + } + + if (timer == mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer = nullptr; + // Start the update process up again. + FetchNext(); + return NS_OK; + } + + bool updateFailed = false; + if (timer == mResponseTimeoutTimer) { + mResponseTimeoutTimer = nullptr; + if (mTimeoutTimer) { + mTimeoutTimer->Cancel(); + mTimeoutTimer = nullptr; + } + mDownloadError = true; // Trigger backoff + updateFailed = true; + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error, + ("Safe Browsing timed out while waiting for the update server to " + "respond.")); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast(eResponseTimeout)); + } + + if (timer == mTimeoutTimer) { + mTimeoutTimer = nullptr; + // No backoff since the connection may just be temporarily slow. + updateFailed = true; + MOZ_LOG(gUrlClassifierStreamUpdaterLog, mozilla::LogLevel::Error, + ("Safe Browsing timed out while waiting for the update server to " + "finish.")); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::URLCLASSIFIER_UPDATE_TIMEOUT, mTelemetryProvider, + static_cast(eDownloadTimeout)); + } + + if (updateFailed) { + // Cancelling the channel will trigger OnStopRequest. + if (mChannel) { + mozilla::Unused << mChannel->Cancel(NS_ERROR_ABORT); + mChannel = nullptr; + } + mTelemetryClockStart = 0; + + if (mFetchIndirectUpdatesTimer) { + mFetchIndirectUpdatesTimer->Cancel(); + mFetchIndirectUpdatesTimer = nullptr; + } + if (mFetchNextRequestTimer) { + mFetchNextRequestTimer->Cancel(); + mFetchNextRequestTimer = nullptr; + } + + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("A timer is fired from nowhere."); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +//// nsINamed + +NS_IMETHODIMP +nsUrlClassifierStreamUpdater::GetName(nsACString& aName) { + aName.AssignLiteral("nsUrlClassifierStreamUpdater"); + return NS_OK; +} -- cgit v1.2.3