diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7684bff --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup .NET + uses: actions/setup-dotnet@v6 + + - name: Build + run: dotnet build src/FlareSolverrSharp --configuration Release diff --git a/README.md b/README.md index 98dc7c3..0ec8d35 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Full-Featured library: `PM> Install-Package FlareSolverr` ## Dependencies -- [.NET Standard 1.3](https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.3.md) +- [.NET Standard 1.3](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-1-3) / [.NET Standard 2.0]([https://github.com/dotnet/standard/blob/master/docs/versions/netstandard2.0.md](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)) You need a running [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) service. diff --git a/src/FlareSolverrSharp/ClearanceHandler.cs b/src/FlareSolverrSharp/ClearanceHandler.cs index f1c6870..42e3863 100644 --- a/src/FlareSolverrSharp/ClearanceHandler.cs +++ b/src/FlareSolverrSharp/ClearanceHandler.cs @@ -14,7 +14,7 @@ namespace FlareSolverrSharp { /// - /// A HTTP handler that transparently manages CloudFlare's protection bypass. + /// An HTTP handler that transparently manages CloudFlare's protection bypass. /// public class ClearanceHandler : DelegatingHandler { @@ -57,7 +57,9 @@ public ClearanceHandler(string flareSolverrApiUrl) // Validate URI if (!string.IsNullOrWhiteSpace(flareSolverrApiUrl) && !Uri.IsWellFormedUriString(flareSolverrApiUrl, UriKind.Absolute)) + { throw new FlareSolverrException("FlareSolverr URL is malformed: " + flareSolverrApiUrl); + } _flareSolverrApiUrl = flareSolverrApiUrl; @@ -99,11 +101,28 @@ protected override async Task SendAsync(HttpRequestMessage if (ChallengeDetector.IsClearanceRequired(response)) { if (_flareSolverr == null) + { throw new FlareSolverrException("Challenge detected but FlareSolverr is not configured"); + } // Resolve the challenge using FlareSolverr API var flareSolverrResponse = await _flareSolverr.Solve(request); + if (flareSolverrResponse == null) + { + throw new FlareSolverrException("Empty response returned by FlareSolverr"); + } + + if (flareSolverrResponse.Solution == null) + { + throw new FlareSolverrException("Empty solution returned by FlareSolverr"); + } + + if (flareSolverrResponse.Solution.Cookies == null || flareSolverrResponse.Solution.Cookies.Length == 0) + { + throw new FlareSolverrException("Empty cookies returned by FlareSolverr"); + } + // Save the FlareSolverr User-Agent for the following requests var flareSolverUserAgent = flareSolverrResponse.Solution.UserAgent; if (flareSolverUserAgent != null && !flareSolverUserAgent.Equals(request.Headers.UserAgent.ToString())) @@ -120,7 +139,9 @@ protected override async Task SendAsync(HttpRequestMessage // Detect if there is a challenge in the response if (ChallengeDetector.IsClearanceRequired(response)) + { throw new FlareSolverrException("The cookies provided by FlareSolverr are not valid"); + } // Add the "Set-Cookie" header in the response with the cookies provided by FlareSolverr InjectSetCookieHeader(response, flareSolverrResponse); @@ -142,15 +163,18 @@ private void SetUserAgentHeader(HttpRequestMessage request) private void InjectCookies(HttpRequestMessage request, FlareSolverrResponse flareSolverrResponse) { // use only Cloudflare and DDoS-GUARD cookies - var flareCookies = flareSolverrResponse.Solution.Cookies - .Where(cookie => IsCloudflareCookie(cookie.Name)) - .ToList(); + var flareCookies = flareSolverrResponse.Solution + .Cookies + .Where(cookie => IsCloudflareCookie(cookie.Name)) + .ToList(); // not using cookies, just add flaresolverr cookies to the header request if (!HttpClientHandler.UseCookies) { foreach (var rCookie in flareCookies) + { request.Headers.Add(HttpHeaders.Cookie, rCookie.ToHeaderValue()); + } return; } @@ -158,21 +182,29 @@ private void InjectCookies(HttpRequestMessage request, FlareSolverrResponse flar var currentCookies = HttpClientHandler.CookieContainer.GetCookies(request.RequestUri); // remove previous FlareSolverr cookies - foreach (var cookie in flareCookies.Select(flareCookie => currentCookies[flareCookie.Name]).Where(cookie => cookie != null)) + foreach (var cookie in flareCookies.Select(flareCookie => currentCookies[flareCookie.Name]).Where(cookie => cookie != null)) + { cookie.Expired = true; + } // add FlareSolverr cookies to CookieContainer foreach (var rCookie in flareCookies) + { HttpClientHandler.CookieContainer.Add(request.RequestUri, rCookie.ToCookieObj()); + } // check if there is too many cookies, we may need to remove some - if (HttpClientHandler.CookieContainer.PerDomainCapacity >= currentCookies.Count) + if (HttpClientHandler.CookieContainer.PerDomainCapacity >= currentCookies.Count) + { return; + } // check if indeed we have too many cookies var validCookiesCount = currentCookies.Cast().Count(cookie => !cookie.Expired); - if (HttpClientHandler.CookieContainer.PerDomainCapacity >= validCookiesCount) - return; + if (HttpClientHandler.CookieContainer.PerDomainCapacity >= validCookiesCount) + { + return; + } // if there is a too many cookies, we have to make space // maybe is better to raise an exception? @@ -180,11 +212,15 @@ private void InjectCookies(HttpRequestMessage request, FlareSolverrResponse flar foreach (Cookie cookie in currentCookies) { - if (cookieExcess == 0) - break; + if (cookieExcess == 0) + { + break; + } - if (cookie.Expired || IsCloudflareCookie(cookie.Name)) - continue; + if (cookie.Expired || IsCloudflareCookie(cookie.Name)) + { + continue; + } cookie.Expired = true; cookieExcess -= 1; @@ -195,7 +231,9 @@ private static void InjectSetCookieHeader(HttpResponseMessage response, FlareSol { // inject set-cookie headers in the response foreach (var rCookie in flareSolverrResponse.Solution.Cookies.Where(cookie => IsCloudflareCookie(cookie.Name))) + { response.Headers.Add(HttpHeaders.SetCookie, rCookie.ToHeaderValue()); + } } private static bool IsCloudflareCookie(string cookieName) => @@ -204,7 +242,9 @@ private static bool IsCloudflareCookie(string cookieName) => protected override void Dispose(bool disposing) { if (disposing) + { _client.Dispose(); + } base.Dispose(disposing); } diff --git a/src/FlareSolverrSharp/FlareSolverrSharp.csproj b/src/FlareSolverrSharp/FlareSolverrSharp.csproj index d34f48e..7920d60 100644 --- a/src/FlareSolverrSharp/FlareSolverrSharp.csproj +++ b/src/FlareSolverrSharp/FlareSolverrSharp.csproj @@ -1,7 +1,7 @@  - netstandard1.3 + netstandard1.3;netstandard2.0 FlareSolverrSharp FlareSolverrSharp 3.0.8