private static bool SolveJsChallenge(ref HttpResponse response, HttpRequest request, Uri uri, string retry, DLog log, CancellationToken cancellationToken) { log?.Invoke($"{LogPrefix}Solving JS Challenge for URL: {uri.AbsoluteUri} ..."); response = PassClearance(request, response, uri, log, cancellationToken); return(IsChallengePassed("JS", ref response, request, uri, retry, log)); }
private static bool IsChallengePassed(string tag, ref HttpResponse response, HttpRequest request, Uri uri, string retry, DLog log) { // ReSharper disable once SwitchStatementMissingSomeCases switch (response.StatusCode) { case HttpStatusCode.ServiceUnavailable: case HttpStatusCode.Forbidden: return(false); case HttpStatusCode.Found: // Т.к. ранее использовался ручной режим - нужно обработать редирект, если он есть, чтобы вернуть отфильтрованное тело запроса if (response.HasRedirect) { if (!response.ContainsCookie(uri, CfClearanceCookie)) { return(false); } log?.Invoke($"{LogPrefix}Passed [{tag}]. Trying to get the original response at: {uri.AbsoluteUri} ..."); // Не используем manual т.к. могут быть переадресации bool ignoreProtocolErrors = request.IgnoreProtocolErrors; // Отключаем обработку HTTP ошибок request.IgnoreProtocolErrors = true; request.AddCloudflareHeaders(uri); // заголовки важны для прохождения cloudflare response = request.Get(response.RedirectAddress.AbsoluteUri); request.IgnoreProtocolErrors = ignoreProtocolErrors; #pragma warning disable 618 if (IsCloudflared(response)) #pragma warning restore 618 { log?.Invoke($"{LogPrefix}ERROR [{tag}]. Unable to get he original response at: {uri.AbsoluteUri}"); return(false); } } log?.Invoke($"{LogPrefix}OK [{tag}]. Done: {uri.AbsoluteUri}"); return(true); } log?.Invoke($"{LogPrefix}ERROR [{tag}]. Status code : {response.StatusCode}{retry}."); return(false); }
private static void Delay(int milliseconds, DLog log, CancellationToken cancellationToken) { log?.Invoke($"{LogPrefix}: delay {milliseconds} ms..."); if (cancellationToken == default(CancellationToken)) { Thread.Sleep(milliseconds); } else { cancellationToken.WaitHandle.WaitOne(milliseconds); cancellationToken.ThrowIfCancellationRequested(); } }
private static bool SolveRecaptchaChallenge(ref HttpResponse response, HttpRequest request, Uri uri, string retry, DLog log, CancellationToken cancelToken) { log?.Invoke($"{LogPrefix}Solving Recaptcha Challenge for URL: {uri.AbsoluteUri} ..."); if (request.CaptchaSolver == null) { throw new CloudflareException($"{nameof(HttpRequest.CaptchaSolver)} required"); } string respStr = response.ToString(); string siteKey = respStr.Substring("data-sitekey=\"", "\"") ?? throw new CloudflareException("Value of \"data-sitekey\" not found"); string s = respStr.Substring("name=\"s\" value=\"", "\"") ?? throw new CloudflareException("Value of \"s\" not found"); string rayId = respStr.Substring("data-ray=\"", "\"") ?? throw new CloudflareException("Ray Id not found"); string answer = request.CaptchaSolver.SolveRecaptcha(uri.AbsoluteUri, siteKey, cancelToken); cancelToken.ThrowIfCancellationRequested(); var rp = new RequestParams { ["s"] = s, ["id"] = rayId, ["g-recaptcha-response"] = answer }; string bfChallengeId = respStr.Substring("'bf_challenge_id', '", "'"); if (bfChallengeId != null) { rp.Add(new KeyValuePair <string, string>("bf_challenge_id", bfChallengeId)); rp.Add(new KeyValuePair <string, string>("bf_execution_time", "4")); rp.Add(new KeyValuePair <string, string>("bf_result_hash", string.Empty)); } response = request.ManualGet(new Uri(uri, "/cdn-cgi/l/chk_captcha"), uri, rp); return(IsChallengePassed("ReCaptcha", ref response, request, uri, retry, log)); }
private static HttpResponse PassClearance(HttpRequest request, HttpResponse response, string refererUrl, DLog log, CancellationToken cancellationToken) { // Using Uri for correct port resolving var uri = GetSolutionUri(response); log?.Invoke($"CloudFlare: ждем {Delay} мс..."); if (cancellationToken == default(CancellationToken)) { Thread.Sleep(Delay); } else { cancellationToken.WaitHandle.WaitOne(Delay); cancellationToken.ThrowIfCancellationRequested(); } return(ManualGet(request, uri.AbsoluteUri, refererUrl)); }
/// <summary> /// GET request with bypassing Cloudflare JavaScript challenge. /// </summary> /// <param name="request">Http request</param> /// <param name="uri">Uri Address</param> /// <param name="log">Log action</param> /// <param name="cancellationToken">Cancel protection</param> /// <param name="captchaSolver">Captcha solving provider when Recaptcha required for pass</param> /// <exception cref="HttpException">When HTTP request failed</exception> /// <exception cref="CloudflareException">When unable to bypass Cloudflare</exception> /// <exception cref="CaptchaException">When unable to solve captcha using <see cref="ICaptchaSolver"/> provider.</exception> /// <returns>Returns original HttpResponse</returns> public static HttpResponse GetThroughCloudflare(this HttpRequest request, Uri uri, DLog log = null, CancellationToken cancellationToken = default(CancellationToken), ICaptchaSolver captchaSolver = null) { if (!request.UseCookies) { throw new CloudflareException($"{LogPrefix}Cookies must be enabled. Please set ${nameof(HttpRequest.UseCookies)} to true."); } // User-Agent is required if (string.IsNullOrEmpty(request.UserAgent)) { request.UserAgent = Http.ChromeUserAgent(); } log?.Invoke($"{LogPrefix}Checking availability at: {uri.AbsoluteUri} ..."); for (int i = 0; i < MaxRetries; i++) { string retry = $". Retry {i + 1} / {MaxRetries}."; log?.Invoke($"{LogPrefix}Trying to bypass{retry}"); var response = ManualGet(request, uri); if (!response.IsCloudflared()) { log?.Invoke($"{LogPrefix} OK. Not found at: {uri.AbsoluteUri}"); return(response); } // Remove expired clearance if present var cookies = request.Cookies.GetCookies(uri); foreach (Cookie cookie in cookies) { if (cookie.Name != CfClearanceCookie) { continue; } cookie.Expired = true; break; } if (cancellationToken != default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); } // Bypass depend on challenge type: JS / Recaptcha // if (HasJsChallenge(response)) { SolveJsChallenge(ref response, request, uri, retry, log, cancellationToken); } if (HasRecaptchaChallenge(response)) { SolveRecaptchaChallenge(ref response, request, uri, retry, log, cancellationToken); } if (response.IsCloudflared()) { throw new CloudflareException(HasAccessDeniedError(response) ? "Access denied. Try to use another IP address." : "Unknown challenge type"); } return(response); } throw new CloudflareException(MaxRetries, $"{LogPrefix}ERROR. Rate limit reached."); }
/// <summary> /// GET request with bypassing Cloudflare JavaScript challenge. /// </summary> /// <param name="request">Http request</param> /// <param name="url">Address</param> /// <param name="log">Log action</param> /// <param name="cancellationToken">Cancel protection</param> /// <returns>Returns original HttpResponse</returns> public static HttpResponse GetThroughCloudflare(this HttpRequest request, string url, DLog log = null, CancellationToken cancellationToken = default(CancellationToken)) { if (request.DontTrackCookies) { throw new HttpException("В свойствах HTTP запроса отключена обработка Cookies через свойство DontTrackCookies. Для CloudFlare куки обязательны."); } // User-Agent is required if (string.IsNullOrEmpty(request.UserAgent)) { request.UserAgent = Http.ChromeUserAgent(); } log?.Invoke("Проверяем наличие CloudFlare по адресу: " + url); for (int i = 0; i < MaxRetries; i++) { string retry = $". Попытка {i + 1} из {MaxRetries}."; log?.Invoke("Обхожу CloudFlare" + retry); var response = ManualGet(request, url); if (!response.IsCloudflared()) { log?.Invoke("УСПЕХ: Cloudflare не обнаружен, работаем дальше: " + url); return(response); } // Remove expired clearance if present var cookies = request.Cookies.GetCookies(url); foreach (Cookie cookie in cookies) { if (cookie.Name != CfClearanceCookie) { continue; } cookie.Expired = true; break; } if (cancellationToken != default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); } response = PassClearance(request, response, url, log, cancellationToken); // ReSharper disable once SwitchStatementMissingSomeCases switch (response.StatusCode) { case HttpStatusCode.ServiceUnavailable: case HttpStatusCode.Forbidden: continue; case HttpStatusCode.Found: // Т.к. ранее использовался ручной режим - нужно обработать редирект, если он есть, чтобы вернуть отфильтрованное тело запроса if (response.HasRedirect) { if (!response.ContainsCookie(url, CfClearanceCookie)) { continue; } log?.Invoke($"CloudFlare этап пройден, получаю оригинальную страницу: {url}..."); // Не используем manual т.к. могут быть переадресации bool ignoreProtocolErrors = request.IgnoreProtocolErrors; // Отключаем обработку HTTP ошибок request.IgnoreProtocolErrors = true; request.AddCloudflareHeaders(url); // заголовки важны для прохождения cloudflare response = request.Get(response.RedirectAddress.AbsoluteUri); request.IgnoreProtocolErrors = ignoreProtocolErrors; if (IsCloudflared(response)) { log?.Invoke($"Ошибка CloudFlare: этап пройден, но оригинальная страница не получена: {url}"); continue; } } log?.Invoke($"CloudFlare: успех, оригинальная страница получена: {url}"); return(response); } log?.Invoke($"CloudFlare не смог пройти JS Challenge, причина не ясна. Статус код: {response.StatusCode}" + retry); } throw new CloudflareException(MaxRetries, "Превышен лимит попыток обхода Cloudflare"); }
/// <summary> /// GET request with bypassing Cloudflare JavaScript challenge. /// </summary> /// <param name="uri">Address</param> /// <param name="log">Log action</param> /// <param name="cancellationToken">Cancel protection</param> /// <returns>Returns original HttpResponse</returns> public static HttpResponse GetThroughCloudflare(this HttpRequest request, string url, DLog log = null, #if USE_CAPTCHA_SOLVER DSolveRecaptcha reCaptchaSolver = null, #endif CancellationToken cancellationToken = default(CancellationToken)) { // User-Agent is required if (string.IsNullOrEmpty(request.UserAgent)) { request.UserAgent = Http.ChromeUserAgent(); } log?.Invoke("Проверяем наличие CloudFlare по адресу: " + url); HttpResponse response = null; for (int i = 0; i < MaxRetries; i++) { string retry = $". Попытка {i + 1} из {MaxRetries}."; #region Try catch disabled /*try * {*/ #endregion response = ManualGet(request, url); if (!response.IsCloudflared()) { log?.Invoke("УСПЕХ: Cloudflare не обнаружен, работаем дальше: " + url); return(response); } log?.Invoke("Обхожу CloudFlare" + retry); if (cancellationToken != default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); } response = PassClearance(request, response, url, log, cancellationToken); // JS Challange passed if (response.StatusCode == HttpStatusCode.Found) { // Т.к. ранее использовался ручной режим - нужно обработать редирект, если он есть, чтобы вернуть отфильтрованное тело запроса // TODO: иногда бывает 403 forbidden из-за cloudflare, в чем проблема? if (response.HasRedirect) { response = request.Get(response.RedirectAddress); } break; } #if USE_CAPTCHA_SOLVER if (response.StatusCode == HttpStatusCode.Forbidden && reCaptchaSolver == null) { continue; } #else if (response.StatusCode == HttpStatusCode.Forbidden) // && reCaptchaSolver == null) { continue; } #endif // Not implemented status code if (response.StatusCode != HttpStatusCode.Forbidden) { log?.Invoke( $"CloudFlare не смог пройти JS Challange, причина не ясна. Статус код: {response.StatusCode}" + retry); #if USE_CAPTCHA_SOLVER continue; #endif } #region Captcha solver #if USE_CAPTCHA_SOLVER // IF Forbidden and has recaptcha // // ReCaptcha solve required string strResponse = response.ToString(); // TODO: while for captcha solving const string siteKeyPattern = "data-sitekey=\""; if (!strResponse.Contains(siteKeyPattern)) { string error = "CloudFlare не смог пройти т.к. возращен код Forbidden, но ключ сайта для рекаптчи не найден" + retry; log?.Invoke(error); throw new CloudflareException(error); } if (reCaptchaSolver == null) { throw new CloudflareException("Cloudflare требует решение ReCaptcha, но делегат для её решения не был предоставлен"); } string siteKey = strResponse.Substring(siteKeyPattern, "\""); if (siteKey == string.Empty) { throw new CloudflareException("Cloudflare требует решение ReCaptcha, но ключ сайта не был найден в HTML коде"); } string submitRelativeUrl = strResponse.Substring("id=\"challenge-form\" action=\"", "\""); if (submitRelativeUrl == string.Empty) { throw new CloudflareException("Cloudflare требует решение ReCaptcha, но адрес публикации формы с каптчей не найден в HTML коде"); } // Build uri Form GET action var address = response.Address; var submitUri = new Uri($"{address.Scheme}://{address.Host}:{address.Port}{submitRelativeUrl}"); log?.Invoke("CloudFlare выдал рекаптчу, решаю..."); // Решаем рекаптчу и отправляем форму var rp = new RequestParams { ["g-recaptcha-response"] = reCaptchaSolver(siteKey) }; response = request.Get(submitUri, rp); // После отправки формы кидает на главную, поэтому идем по адресу какой требуется согласно функции if (response.Address.AbsoluteUri != url) { response = request.Get(url); } #endif #endregion } if (response == null) { throw new Exception("Ответ null"); } // Clearance failed. if (response.IsCloudflared()) { throw new CloudflareException(MaxRetries, "Превышен лимит попыток обхода Cloudflare"); } log?.Invoke("CloudFlare успешно пройден"); return(response); }