예제 #1
0
        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));
        }
예제 #2
0
        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);
        }
예제 #3
0
        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();
            }
        }
예제 #4
0
        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));
        }
예제 #5
0
        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));
        }
예제 #6
0
        /// <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.");
        }
예제 #7
0
        /// <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);
        }