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