From 65905566dd6a191c2ae1a991823e6c20f3896f72 Mon Sep 17 00:00:00 2001 From: Etheirystech Date: Fri, 3 Apr 2026 14:08:55 +0200 Subject: [PATCH 1/3] Fixed: Use FlareSolverr response body instead of re-requesting The second HTTP request after FlareSolverr solves a Cloudflare challenge fails because cf_clearance is validated against the solver's TLS fingerprint, which .NET HttpClient cannot replicate. Use the solved response body directly and fall back to cookie retry only when FlareSolverr returns no body. Also fix error message to report flaresolverrResponse.StatusCode instead of the original response.StatusCode. Closes #2561 --- .../FlareSolverr/FlareSolverr.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs index 5107bf151..2a186f45e 100644 --- a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs +++ b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs @@ -58,7 +58,7 @@ public override HttpResponse PostResponse(HttpResponse response) if (flaresolverrResponse.StatusCode != HttpStatusCode.OK && flaresolverrResponse.StatusCode != HttpStatusCode.InternalServerError) { - throw new FlareSolverrException("HTTP StatusCode not 200 or 500. Status is :" + response.StatusCode); + throw new FlareSolverrException("HTTP StatusCode not 200 or 500. Status is :" + flaresolverrResponse.StatusCode); } var result = JsonConvert.DeserializeObject(flaresolverrResponse.Content); @@ -71,7 +71,32 @@ public override HttpResponse PostResponse(HttpResponse response) InjectCookies(newRequest, result); - //Request again with User-Agent and Cookies from Flaresolverr + // Use FlareSolverr's response body directly when available. + // A second HTTP request with the extracted cookies would get rejected + // because cf_clearance is validated against the TLS fingerprint of the + // client that solved the challenge (FlareSolverr's headless browser), + // which differs from .NET HttpClient's fingerprint. + if (result.Solution.Response.IsNotNullOrWhiteSpace()) + { + var headers = new HttpHeader(); + + // Preserve the Content-Type from FlareSolverr's solution so downstream + // parsers (e.g. JSON indexers) interpret the body correctly. + if (result.Solution.Headers?.ContentType.IsNotNullOrWhiteSpace() == true) + { + headers.ContentType = result.Solution.Headers.ContentType; + } + + return new HttpResponse( + response.Request, + headers, + response.Cookies, + result.Solution.Response, + response.ElapsedTime, + HttpStatusCode.OK); + } + + // Fallback: if FlareSolverr returned no body, retry with cookies (original behavior) var finalResponse = _httpClient.Execute(newRequest); return finalResponse; From 714d7980b487c07365db107c7122fbda6f8a6745 Mon Sep 17 00:00:00 2001 From: Etheirystech Date: Thu, 9 Apr 2026 09:16:01 +0200 Subject: [PATCH 2/3] Update code comment to reflect actual root cause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cf_clearance cookie is not bound to TLS fingerprint — testing confirmed it works from curl (different TLS stack). The real issue is that CF selectively challenges by IP/UA, so FlareSolverr may not receive a challenge and returns no cf_clearance cookie. --- .../IndexerProxies/FlareSolverr/FlareSolverr.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs index 2a186f45e..2fcb1b7bb 100644 --- a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs +++ b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs @@ -72,10 +72,9 @@ public override HttpResponse PostResponse(HttpResponse response) InjectCookies(newRequest, result); // Use FlareSolverr's response body directly when available. - // A second HTTP request with the extracted cookies would get rejected - // because cf_clearance is validated against the TLS fingerprint of the - // client that solved the challenge (FlareSolverr's headless browser), - // which differs from .NET HttpClient's fingerprint. + // When CF selectively challenges by IP/UA, FlareSolverr may not + // receive a challenge and thus returns no cf_clearance cookie. + // A second request without that cookie gets 403'd. if (result.Solution.Response.IsNotNullOrWhiteSpace()) { var headers = new HttpHeader(); From f449ad6682ae06ab5022e47f0ba64f978a15198f Mon Sep 17 00:00:00 2001 From: Etheirystech Date: Thu, 9 Apr 2026 16:36:05 +0200 Subject: [PATCH 3/3] Try cookie-based retry first, fall back to response body Attempt the cookie+UA retry before using FlareSolverr's response body so that when cookies work, subsequent requests can reuse them without going through FlareSolverr. Only fall back to the response body when the retry is still CF-protected. --- .../FlareSolverr/FlareSolverr.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs index 2fcb1b7bb..cc92c4cc9 100644 --- a/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs +++ b/src/NzbDrone.Core/IndexerProxies/FlareSolverr/FlareSolverr.cs @@ -71,16 +71,22 @@ public override HttpResponse PostResponse(HttpResponse response) InjectCookies(newRequest, result); - // Use FlareSolverr's response body directly when available. - // When CF selectively challenges by IP/UA, FlareSolverr may not - // receive a challenge and thus returns no cf_clearance cookie. - // A second request without that cookie gets 403'd. + // Try cookie-based retry first so subsequent requests can + // reuse the cookies without going through FlareSolverr. + var finalResponse = _httpClient.Execute(newRequest); + + if (!CloudFlareDetectionService.IsCloudflareProtected(finalResponse)) + { + return finalResponse; + } + + // Cookie retry was blocked — fall back to FlareSolverr's response body. + // This happens when CF selectively challenges by IP/UA and FlareSolverr + // doesn't receive a challenge, so no cf_clearance cookie is returned. if (result.Solution.Response.IsNotNullOrWhiteSpace()) { var headers = new HttpHeader(); - // Preserve the Content-Type from FlareSolverr's solution so downstream - // parsers (e.g. JSON indexers) interpret the body correctly. if (result.Solution.Headers?.ContentType.IsNotNullOrWhiteSpace() == true) { headers.ContentType = result.Solution.Headers.ContentType; @@ -95,9 +101,6 @@ public override HttpResponse PostResponse(HttpResponse response) HttpStatusCode.OK); } - // Fallback: if FlareSolverr returned no body, retry with cookies (original behavior) - var finalResponse = _httpClient.Execute(newRequest); - return finalResponse; }