/*
* Copyright (c) 2023 Proton AG
*
* This file is part of ProtonVPN.
*
* ProtonVPN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonVPN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonVPN. If not, see .
*/
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using ProtonVPN.Api.Contracts;
using ProtonVPN.Api.Contracts.Common;
using ProtonVPN.Api.Contracts.HumanVerification;
using ProtonVPN.Api.Deserializers;
namespace ProtonVPN.Api.Handlers;
public class HumanVerificationHandler : HumanVerificationHandlerBase
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private readonly IBaseResponseMessageDeserializer _baseResponseDeserializer;
private readonly IHumanVerifier _humanVerifier;
private readonly bool _isEnabled;
private string _error = string.Empty;
public HumanVerificationHandler(IBaseResponseMessageDeserializer baseResponseDeserializer,
IHumanVerifier humanVerifier,
IHumanVerificationConfig humanVerificationConfig)
{
_baseResponseDeserializer = baseResponseDeserializer;
_humanVerifier = humanVerifier;
_isEnabled = humanVerificationConfig.IsSupported();
}
protected override async Task SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!_isEnabled)
{
return await base.SendAsync(request, cancellationToken);
}
if (IsHumanVerificationInProgress())
{
return FailResponse.HumanVerificationFailureResponse(_error);
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
BaseResponse baseResponse = await _baseResponseDeserializer.DeserializeAsync(response, cancellationToken);
if (baseResponse == null)
{
return response;
}
if (IsVerificationRequired(baseResponse))
{
_error = baseResponse.Error;
HttpResponseMessage result = await HandleHumanVerificationAsync(request, cancellationToken,
baseResponse.Details.HumanVerificationToken);
if (result != null)
{
return result;
}
}
return response;
}
private bool IsVerificationRequired(BaseResponse response)
{
return response.Code == ResponseCodes.HUMAN_VERIFICATION_REQUIRED &&
IsHumanVerificationSupported(response.Details.HumanVerificationMethods);
}
private bool IsHumanVerificationInProgress()
{
return _semaphore.CurrentCount == 0;
}
private bool IsHumanVerificationSupported(IReadOnlyList humanVerificationMethods)
{
return humanVerificationMethods.Contains(HumanVerificationTypes.Captcha);
}
private async Task HandleHumanVerificationAsync(HttpRequestMessage request,
CancellationToken cancellationToken, string hvToken)
{
try
{
await _semaphore.WaitAsync(cancellationToken);
string resolvedToken = await _humanVerifier.VerifyAsync(hvToken);
if (!string.IsNullOrEmpty(resolvedToken))
{
request.Headers.Add("x-pm-human-verification-token-type", "captcha");
request.Headers.Add("x-pm-human-verification-token", resolvedToken);
return await base.SendAsync(request, cancellationToken);
}
return null;
}
finally
{
_semaphore.Release();
}
}
}