Example #1
0
        /// <summary>
        ///     Create a Threat List Update Request Model.
        /// </summary>
        /// <param name="this">
        ///     A <see cref="ThreatListUpdateRequest" />.
        /// </param>
        /// <returns>
        ///     A <see cref="ThreatListUpdateRequestModel" /> if <paramref name="this" /> is not a null reference. A
        ///     null reference otherwise.
        /// </returns>
        internal static ThreatListUpdateRequestModel AsThreatListUpdateRequestModel(this ThreatListUpdateRequest @this)
        {
            ThreatListUpdateRequestModel threatListUpdateRequestModel = null;

            if (@this != null)
            {
                threatListUpdateRequestModel = new ThreatListUpdateRequestModel();
                threatListUpdateRequestModel.ClientMetadata = @this.ClientMetadata.AsClientMetadataModel();
                threatListUpdateRequestModel.Queries        = @this.Queries.Select(m => m.AsThreatListUpdateQueryModel());
            }

            return(threatListUpdateRequestModel);
        }
        /// <summary>
        ///     Get Threat List Updates Asynchronously.
        /// </summary>
        /// <param name="request">
        ///     A <see cref="ThreatListUpdateRequest" />.
        /// </param>
        /// <param name="cancellationToken">
        ///     A cancellation token to cancel the asynchronous operation with.
        /// </param>
        /// <returns>
        ///     A <see cref="ThreatListUpdateResponse" />.
        /// </returns>
        /// <exception cref="Gee.External.Browsing.Clients.BrowsingClientException">
        ///     Thrown if an error communicating with the Google Safe Browsing API occurs.
        /// </exception>
        /// <exception cref="System.ArgumentNullException">
        ///     Thrown if <paramref name="request" /> is a null reference.
        /// </exception>
        /// <exception cref="System.ObjectDisposedException">
        ///     Thrown if the object is disposed.
        /// </exception>
        /// <exception cref="System.OperationCanceledException">
        ///     Thrown if the asynchronous operation is cancelled.
        /// </exception>
        /// <exception cref="System.TimeoutException">
        ///     Thrown if communication with the Google Safe Browsing API times out.
        /// </exception>
        public Task <ThreatListUpdateResponse> GetThreatListUpdatesAsync(ThreatListUpdateRequest request, CancellationToken cancellationToken)
        {
            this.ThrowIfDisposed();
            Guard.ThrowIf(nameof(request), request).Null();

            var getThreatListUpdatesTask = GetThreatListUpdatesAsync(request, cancellationToken);

            return(getThreatListUpdatesTask);

            // <summary>
            //      Get Threat List Updates Asynchronously.
            // </summary>
            async Task <ThreatListUpdateResponse> GetThreatListUpdatesAsync(ThreatListUpdateRequest cRequest, CancellationToken cCancellationToken)
            {
                try {
                    // ...
                    //
                    // Throws an exception if the operation fails.
                    var cRequestModel  = cRequest.AsThreatListUpdateRequestModel();
                    var cResponseModel = await HttpBrowsingClient.GetThreatListUpdatesUri
                                         .PostJsonAsync(cRequestModel, cCancellationToken)
                                         .ReceiveJson <ThreatListUpdateResponseModel>()
                                         .ConfigureAwait(false);

                    var cResponse = cResponseModel.AsThreatListUpdateResponse(cRequest);
                    return(cResponse);
                }
                catch (FlurlHttpTimeoutException cEx) {
                    const string cDetailMessage = "An HTTP request to the Google Safe Browsing API timed out.";
                    throw new TimeoutException(cDetailMessage, cEx);
                }
                catch (FlurlHttpException cEx) {
                    var          httpStatusCode = cEx.Call.HttpStatus ?? HttpStatusCode.BadRequest;
                    const string cDetailMessage = "An HTTP request to the Google Safe Browsing API failed.";
                    throw new BrowsingClientException(cDetailMessage, httpStatusCode, cEx);
                }
            }
        }
Example #3
0
        /// <summary>
        ///     Create a Threat List Update Response.
        /// </summary>
        /// <param name="this">
        ///     A <see cref="ThreatListUpdateResponseModel" />.
        /// </param>
        /// <param name="request">
        ///     The <see cref="ThreatListUpdateRequest" /> made to the Google Safe Browsing API for which the
        ///     <see cref="ThreatListUpdateResponse" /> has been returned.
        /// </param>
        /// <returns>
        ///     A <see cref="ThreatListUpdateResponse" /> if <paramref name="this" /> is not a null reference. A null
        ///     reference otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        ///     Thrown if <paramref name="request" /> is a null reference.
        /// </exception>
        internal static ThreatListUpdateResponse AsThreatListUpdateResponse(this ThreatListUpdateResponseModel @this, ThreatListUpdateRequest request)
        {
            ThreatListUpdateResponse threatListUpdateResponse = null;

            if (@this != null)
            {
                // ...
                //
                // Throws an exception if the operation fails.
                var threatListUpdateResponseBuilder = ThreatListUpdateResponse.Build();
                threatListUpdateResponseBuilder.SetRequest(request);

                SetResults(@this, threatListUpdateResponseBuilder, request);
                threatListUpdateResponse = threatListUpdateResponseBuilder.Build();
            }

            return(threatListUpdateResponse);

            // <summary>
            //      Create Wait to Date.
            // </summary>
            DateTime?CreateWaitToDate(ThreatListUpdateResponseModel cThis)
            {
                DateTime?cWaitToDate = null;

                if (cThis.WaitDuration != null)
                {
                    var cWaitDuration         = cThis.WaitDuration.Substring(0, cThis.WaitDuration.Length - 1);
                    var cIsWaitDurationParsed = double.TryParse(cWaitDuration, out var cWaitDurationDouble);
                    if (cIsWaitDurationParsed)
                    {
                        cWaitToDate = DateTime.UtcNow.AddSeconds(cWaitDurationDouble);
                    }
                }

                return(cWaitToDate);
            }

            // <summary>
            //      Set Results.
            // </summary>
            void SetResults(ThreatListUpdateResponseModel cThis, ThreatListUpdateResponseBuilder cBuilder, ThreatListUpdateRequest cRequest)
            {
                if (cThis.Results != null)
                {
                    var cThreatListRetrieveDate = DateTime.UtcNow;
                    var cThreatListWaitToDate   = CreateWaitToDate(cThis);
                    foreach (var cResultModel in cThis.Results)
                    {
                        var cResult = cResultModel.AsThreatListUpdateResult(cRequest, cThreatListRetrieveDate, cThreatListWaitToDate);
                        cBuilder.AddResult(cResult);
                    }
                }
            }
        }
        private async Task SynchronizeDatabaseAsync()
        {
            var cancellationToken = this._synchronizationTaskCancellationTokenSource.Token;

            while (!cancellationToken.IsCancellationRequested)
            {
                var getThreatListUpdatesTask = GetThreatListUpdatesAsync(this);
                var(threatListUpdateResponse, delayToDate) = await getThreatListUpdatesTask.ConfigureAwait(false);

                if (threatListUpdateResponse != null)
                {
                    foreach (var threatListUpdateResult in threatListUpdateResponse.Results)
                    {
                        var threatListWaitToDate = threatListUpdateResult.RetrievedThreatList.WaitToDate;
                        if (threatListWaitToDate != null && threatListWaitToDate < delayToDate)
                        {
                            delayToDate = threatListWaitToDate.Value;
                        }

                        var synchronizeThreatListTask = SynchronizeThreatListAsync(this, threatListUpdateResult);
                        await synchronizeThreatListTask.ConfigureAwait(false);
                    }
                }

                var delayToTask = DelayToAsync(this, delayToDate);
                await delayToTask.ConfigureAwait(false);
            }

            // <summary>
            //      Delay to a Date Asynchronously.
            // </summary>
            async Task DelayToAsync(BrowsingDatabaseManager @this, DateTime cDate)
            {
                try {
                    var cCancellationToken = @this._synchronizationTaskCancellationTokenSource.Token;
                    var cDelayToTask       = TaskExtension.DelayTo(cDate, cCancellationToken);
                    await cDelayToTask.ConfigureAwait(false);
                }
                catch (OperationCanceledException) {
                    // ...
                    //
                    // We don't care if the cancellation token is cancelled.
                }
            }

            // <summary>
            //      Get Threat List Updates Asynchronously.
            // </summary>
            async Task <(ThreatListUpdateResponse, DateTime)> GetThreatListUpdatesAsync(BrowsingDatabaseManager @this)
            {
                var cDelayToDate = DateTime.UtcNow.AddMinutes(30);

                try {
                    IEnumerable <ThreatListDescriptor> cThreatListDescriptors = @this._updateConstraints.Keys;
                    if (@this._updateConstraints.Count == 0)
                    {
                        // ...
                        //
                        // If the database synchronizer is not restricted to specific threat lists, we retrieve all
                        // available threat lists from the Google Safe Browsing API. We don't cache the result in case
                        // new threat lists are made available between synchronization iterations.
                        //
                        // Throws an exception if the operation fails.
                        var cGetThreatListDescriptorsTask = @this._client.GetThreatListDescriptorsAsync();
                        cThreatListDescriptors = await cGetThreatListDescriptorsTask.ConfigureAwait(false);
                    }

                    // ...
                    //
                    // Retrieve the threat lists from the database. Throws an exception if the operation fails.
                    var cGetThreatListsTask = @this._database.GetThreatListsAsync(cThreatListDescriptors);
                    var cThreatLists        = await cGetThreatListsTask.ConfigureAwait(false);

                    ThreatListUpdateRequestBuilder cThreatListUpdateRequestBuilder = null;
                    foreach (var cThreatList in cThreatLists)
                    {
                        if (cThreatList.Expired)
                        {
                            cThreatListUpdateRequestBuilder = cThreatListUpdateRequestBuilder ?? ThreatListUpdateRequest.Build();
                            var cThreatListDescriptor = cThreatList.Descriptor;
                            var cThreatListState      = cThreatList.State;
                            @this._updateConstraints.TryGetValue(cThreatListDescriptor, out var cThreatListUpdateConstraints);
                            cThreatListUpdateRequestBuilder.AddQuery(b => {
                                b.SetThreatListDescriptor(cThreatListDescriptor);
                                b.SetThreatListState(cThreatListState);
                                b.SetUpdateConstraints(cThreatListUpdateConstraints);
                                return(b.Build());
                            });
                        }
                        else
                        {
                            if (cThreatList.WaitToDate != null && cThreatList.WaitToDate < cDelayToDate)
                            {
                                // ...
                                //
                                // If the current threat list's wait to date is earlier than the previous threat
                                // list's wait to date, take the current the threat list's wait to date instead.
                                cDelayToDate = cThreatList.WaitToDate.Value;
                            }
                        }
                    }

                    ThreatListUpdateResponse cThreatListUpdateResponse = null;
                    if (cThreatListUpdateRequestBuilder != null)
                    {
                        // ...
                        //
                        // Throws an exception if the operation fails.
                        var cThreatListUpdateRequest  = cThreatListUpdateRequestBuilder.Build();
                        var cGetThreatListUpdatesTask = @this._client.GetThreatListUpdatesAsync(cThreatListUpdateRequest);
                        cThreatListUpdateResponse = await cGetThreatListUpdatesTask.ConfigureAwait(false);
                    }

                    return(cThreatListUpdateResponse, cDelayToDate);
                }
                catch {
                    return(null, cDelayToDate);
                }
            }

            // <summary>
            //      Synchronize Threat List Asynchronously.
            // </summary>
            async Task SynchronizeThreatListAsync(BrowsingDatabaseManager @this, ThreatListUpdateResult cThreatListUpdateResult)
            {
                var cSynchronizationStartDate = DateTime.UtcNow;
                var cThreatList = cThreatListUpdateResult.RetrievedThreatList;

                try {
                    var cSha256HashPrefixes = cThreatListUpdateResult.ThreatsToAdd;
                    if (cThreatListUpdateResult.IsFullUpdate)
                    {
                        // ...
                        //
                        // Throws an exception if the operation fails.
                        var cModifyThreatListTask = @this._database.StoreThreatListAsync(cThreatList, cSha256HashPrefixes);
                        await cModifyThreatListTask.ConfigureAwait(false);
                    }
                    else if (cThreatListUpdateResult.IsPartialUpdate)
                    {
                        // ...
                        //
                        // Throws an exception if the operation fails.
                        var cIndices = cThreatListUpdateResult.ThreatsToRemove;
                        var cModifyThreatListTask = @this._database.ModifyThreatListAsync(cThreatList, cSha256HashPrefixes, cIndices);
                        await cModifyThreatListTask.ConfigureAwait(false);
                    }

                    // ...
                    //
                    // Throws an exception if the operation fails.
                    var cComputeThreatListChecksumTask = @this._database.ComputeThreatListChecksumAsync(cThreatList.Descriptor);
                    var cThreatListChecksum            = await cComputeThreatListChecksumTask.ConfigureAwait(false);

                    if (cThreatListChecksum != cThreatListUpdateResult.RetrievedThreatListChecksum)
                    {
                        // ...
                        //
                        // Throws an exception if the operation fails.
                        cThreatList = ThreatList.CreateInvalid(cThreatList.Descriptor);
                        var cUpdateConstraints        = cThreatListUpdateResult.Query.UpdateConstraints;
                        var cGetThreatListUpdatesTask = @this._client.GetThreatListUpdatesAsync(cThreatList, cUpdateConstraints);
                        var cThreatListUpdateRequest  = await cGetThreatListUpdatesTask.ConfigureAwait(false);

                        cThreatListUpdateResult = cThreatListUpdateRequest.Results.First();
                        cSha256HashPrefixes     = cThreatListUpdateResult.ThreatsToAdd;
                        if (cThreatListUpdateResult.IsFullUpdate)
                        {
                            // ...
                            //
                            // Throws an exception if the operation fails.
                            cThreatList = cThreatListUpdateResult.RetrievedThreatList;
                            var cModifyThreatListTask = @this._database.StoreThreatListAsync(cThreatList, cSha256HashPrefixes);
                            await cModifyThreatListTask.ConfigureAwait(false);
                        }
                    }

                    // ...
                    //
                    // Invoke threat list synchronization completed event.
                    var cSynchronizationCompletionDate = DateTime.UtcNow;
                    @this.OnThreatListSynchronizationCompleted(new ThreatListSynchronizationCompletedEventArgs(
                                                                   cThreatList,
                                                                   cSynchronizationStartDate,
                                                                   cSynchronizationCompletionDate
                                                                   ));
                }
                catch (Exception cEx) {
                    // ...
                    //
                    // Invoke threat list synchronization failed event.
                    var cSynchronizationFailureDate = DateTime.UtcNow;
                    @this.OnThreatListSynchronizationFailed(new ThreatListSynchronizationFailedEventArgs(
                                                                cThreatList,
                                                                cSynchronizationStartDate,
                                                                cSynchronizationFailureDate,
                                                                cEx
                                                                ));
                }
            }
        }
        /// <summary>
        ///     Create a Threat List Update Result.
        /// </summary>
        /// <param name="this">
        ///     A <see cref="ThreatListUpdateResultModel" />.
        /// </param>
        /// <param name="request">
        ///     The <see cref="ThreatListUpdateRequest" /> made to the Google Safe Browsing API for which the
        ///     <see cref="ThreatListUpdateResult" /> has been returned.
        /// </param>
        /// <param name="threatListRetrieveDate">
        ///     The date, in Coordinated Universal Time (UTC), the <see cref="ThreatList" /> was retrieved from the
        ///     Google Safe Browsing API. If the date is not expressed in UTC, it is converted to it.
        /// </param>
        /// <param name="threatListWaitToDate">
        ///     The date, in Coordinated Universal Time (UTC), a client must wait to before retrieving the
        ///     <see cref="ThreatList" /> from the Google Safe Browsing API again. If the date is not expressed in UTC,
        ///     it is converted to it. A null reference indicates a client does not need to wait.
        /// </param>
        /// <returns>
        ///     A <see cref="ThreatListUpdateResult" /> if <paramref name="this" /> is not a null reference. A null
        ///     reference otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        ///     Thrown if <paramref name="request" /> is a null reference.
        /// </exception>
        internal static ThreatListUpdateResult AsThreatListUpdateResult(this ThreatListUpdateResultModel @this, ThreatListUpdateRequest request, DateTime threatListRetrieveDate, DateTime?threatListWaitToDate)
        {
            ThreatListUpdateResult threatListUpdateResult = null;

            if (@this != null)
            {
                Guard.ThrowIf(nameof(request), request).Null();

                var threatListUpdateResultBuilder = ThreatListUpdateResult.Build();
                SetThreatsToAdd(@this, threatListUpdateResultBuilder);
                SetThreatsToRemove(@this, threatListUpdateResultBuilder);
                SetRetrievedThreatList(@this, threatListUpdateResultBuilder, threatListRetrieveDate, threatListWaitToDate);
                SetRetrievedThreatListChecksum(@this, threatListUpdateResultBuilder);
                SetUpdateType(@this, threatListUpdateResultBuilder);

                // ...
                //
                // We need to set the query after the retrieved threat list is set.
                SetQuery(threatListUpdateResultBuilder, request);
                threatListUpdateResult = threatListUpdateResultBuilder.Build();
            }

            return(threatListUpdateResult);

            // <summary>
            //      Set Query.
            // </summary>
            void SetQuery(ThreatListUpdateResultBuilder cBuilder, ThreatListUpdateRequest cRequest)
            {
                foreach (var cQuery in cRequest.Queries)
                {
                    var cIsMatchedQuery = cQuery.ThreatListDescriptor.Equals(cBuilder.RetrievedThreatList.Descriptor);
                    if (cIsMatchedQuery)
                    {
                        cBuilder.SetQuery(cQuery);
                        break;
                    }
                }
            }

            // <summary>
            //      Set Retrieved Threat List.
            // </summary>
            void SetRetrievedThreatList(ThreatListUpdateResultModel cModel, ThreatListUpdateResultBuilder cBuilder, DateTime cThreatListRetrieveDate, DateTime?cThreatListWaitToDate)
            {
                var cPlatformType    = cModel.PlatformType.AsPlatformType();
                var cState           = cModel.ThreatListState.Base64Decode().HexadecimalEncode();
                var cThreatEntryType = cModel.ThreatEntryType.AsThreatEntryType();
                var cThreatType      = cModel.ThreatType.AsThreatType();

                cBuilder.SetRetrievedThreatList(b => {
                    b.SetDescriptor(cThreatType, cPlatformType, cThreatEntryType);
                    b.SetRetrieveDate(cThreatListRetrieveDate);
                    b.SetState(cState);
                    b.SetWaitToDate(cThreatListWaitToDate);
                    return(b.Build());
                });
            }

            // <summary>
            //      Set Retrieved Threat List's Checksum.
            // </summary>
            void SetRetrievedThreatListChecksum(ThreatListUpdateResultModel cModel, ThreatListUpdateResultBuilder cBuilder)
            {
                var cRetrievedThreatListChecksum = cModel.Checksum.Sha256Hash.Base64Decode().HexadecimalEncode();

                cBuilder.SetRetrievedThreatListChecksum(cRetrievedThreatListChecksum);
            }

            // <summary>
            //      Set Threats to Add.
            // </summary>
            void SetThreatsToAdd(ThreatListUpdateResultModel cModel, ThreatListUpdateResultBuilder cBuilder)
            {
                if (cModel.ThreatsToAdd != null)
                {
                    foreach (var cThreatToAdd in cModel.ThreatsToAdd)
                    {
                        var cCompressionType = cThreatToAdd.CompressionType.AsCompressionType();
                        if (cCompressionType == CompressionType.Uncompressed)
                        {
                            // ...
                            //
                            // It's faster to 1) Base64 decode the SHA256 hashes, 2) hexadecimal encode them all, and
                            // 3) split them into subsets rather then 1) Base64 decode the SHA256 hashes and 2) encode
                            // them individually.
                            var cSha256Hashes = cThreatToAdd.UncompressedSha256HashPrefixes
                                                .Sha256HashPrefixes
                                                .Base64Decode()
                                                .HexadecimalEncode();

                            // ...
                            //
                            // 1 byte is equal to 2 hexadecimal characters.
                            var cSha256HashPrefixSize = cThreatToAdd.UncompressedSha256HashPrefixes.Sha256HashPrefixSize * 2;
                            for (var cI = 0; cI < cSha256Hashes.Length; cI += cSha256HashPrefixSize)
                            {
                                var cSha256Hash = cSha256Hashes.Substring(cI, cSha256HashPrefixSize);
                                cBuilder.AddThreatToAdd(cSha256Hash);
                            }
                        }
                    }
                }
            }

            // <summary>
            //      Set Threats to Remove.
            // </summary>
            void SetThreatsToRemove(ThreatListUpdateResultModel cModel, ThreatListUpdateResultBuilder cBuilder)
            {
                if (cModel.ThreatsToRemove != null)
                {
                    foreach (var cThreatToRemove in cModel.ThreatsToRemove)
                    {
                        var cCompressionType = cThreatToRemove.CompressionType.AsCompressionType();
                        if (cCompressionType == CompressionType.Uncompressed)
                        {
                            var cIndices = cThreatToRemove.UncompressedIndices.Indices;
                            foreach (var cIndex in cIndices)
                            {
                                cBuilder.AddThreatToRemove(cIndex);
                            }
                        }
                    }
                }
            }

            // <summary>
            //      Set Update Type.
            // </summary>
            void SetUpdateType(ThreatListUpdateResultModel cModel, ThreatListUpdateResultBuilder cBuilder)
            {
                var cUpdateType = cModel.UpdateType.AsThreatListUpdateType();

                cBuilder.SetUpdateType(cUpdateType);
            }
        }