Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
64 changes: 52 additions & 12 deletions src/FlareSolverrSharp/ClearanceHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace FlareSolverrSharp
{
/// <summary>
/// A HTTP handler that transparently manages CloudFlare's protection bypass.
/// An HTTP handler that transparently manages CloudFlare's protection bypass.
/// </summary>
public class ClearanceHandler : DelegatingHandler
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -99,11 +101,28 @@ protected override async Task<HttpResponseMessage> 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()))
Expand All @@ -120,7 +139,9 @@ protected override async Task<HttpResponseMessage> 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);
Expand All @@ -142,49 +163,64 @@ 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;
}

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<Cookie>().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?
var cookieExcess = HttpClientHandler.CookieContainer.PerDomainCapacity - validCookiesCount;

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;
Expand All @@ -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) =>
Expand All @@ -204,7 +242,9 @@ private static bool IsCloudflareCookie(string cookieName) =>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_client.Dispose();
}

base.Dispose(disposing);
}
Expand Down
2 changes: 1 addition & 1 deletion src/FlareSolverrSharp/FlareSolverrSharp.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
<PackageId>FlareSolverrSharp</PackageId>
<RootNamespace>FlareSolverrSharp</RootNamespace>
<Version>3.0.8</Version>
Expand Down
Loading