Java tutorial
/* * Overchan Android (Meta Imageboard Client) * Copyright (C) 2014-2015 miku-nyan <https://github.com/miku-nyan> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package nya.miku.wishmaster.http.cloudflare; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Date; import java.util.Locale; import nya.miku.wishmaster.api.interfaces.CancellableTask; import nya.miku.wishmaster.common.CompatibilityImpl; import nya.miku.wishmaster.common.Logger; import nya.miku.wishmaster.http.HttpConstants; import nya.miku.wishmaster.http.client.ExtendedHttpClient; import nya.miku.wishmaster.http.recaptcha.Recaptcha; import nya.miku.wishmaster.http.recaptcha.RecaptchaException; import nya.miku.wishmaster.http.streamer.HttpRequestModel; import nya.miku.wishmaster.http.streamer.HttpResponseModel; import nya.miku.wishmaster.http.streamer.HttpStreamer; import nya.miku.wishmaster.lib.WebViewProxy; import org.apache.http.HttpHost; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; import org.apache.http.conn.params.ConnRouteParams; import org.apache.http.cookie.Cookie; import org.apache.http.cookie.SetCookie; import org.apache.http.impl.cookie.BasicClientCookieHC4; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.net.Uri; import android.net.http.SslError; import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.SslErrorHandler; import android.webkit.WebView; import android.webkit.WebViewClient; /** * Cloudflare- * @author miku-nyan * */ /* Google ? ?? ? org.apache.http "deprecated" API 22 (Android 5.1) * ? ? ??? ? ?? apache-hc httpclient 4.3.5.1-android * ?: https://issues.apache.org/jira/browse/HTTPCLIENT-1632 */ @SuppressWarnings("deprecation") public class CloudflareChecker { private static final String TAG = "CloudflareChecker"; /** -? ? */ public static final long TIMEOUT = 35 * 1000; private CloudflareChecker() { } private static CloudflareChecker instance; /** -? */ public static synchronized CloudflareChecker getInstance() { if (instance == null) instance = new CloudflareChecker(); return instance; } /** false, ? (?-) -? ??? */ public boolean isAvaibleAntiDDOS() { return !(processing || InterceptingAntiDDOS.getInstance().isProcessing()); } /** * -? cloudflare * @param exception Cloudflare ? * @param httpClient HTTP * @param task ?? * @param activity ?, ? WebView (webkit) * @return ? cookie null, ? , */ public Cookie checkAntiDDOS(CloudflareException exception, HttpClient httpClient, CancellableTask task, Activity activity) { if (exception.isRecaptcha()) throw new IllegalArgumentException(); HttpHost proxy = null; if (httpClient instanceof ExtendedHttpClient) { proxy = ((ExtendedHttpClient) httpClient).getProxy(); } else if (httpClient != null) { try { proxy = ConnRouteParams.getDefaultProxy(httpClient.getParams()); } catch (Exception e) { /*ignore*/ } } if (proxy != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (httpClient instanceof ExtendedHttpClient) { return InterceptingAntiDDOS.getInstance().check(exception, (ExtendedHttpClient) httpClient, task, activity); } else { throw new IllegalArgumentException( "cannot run anti-DDOS checking with proxy settings; http client is not instance of ExtendedHttpClient"); } } else { return checkAntiDDOS(exception, proxy, task, activity); } } private volatile boolean processing = false; private volatile boolean processing2 = false; private volatile Cookie currentCookie; private volatile WebView webView; private volatile Context webViewContext; private Object lock = new Object(); private Cookie checkAntiDDOS(final CloudflareException exception, final HttpHost proxy, CancellableTask task, final Activity activity) { synchronized (lock) { if (processing) return null; processing = true; } processing2 = true; currentCookie = null; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(activity); CookieManager.getInstance().removeAllCookie(); } else { CompatibilityImpl.clearCookies(CookieManager.getInstance()); } final ViewGroup layout = (ViewGroup) activity.getWindow().getDecorView().getRootView(); final WebViewClient client = new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } @Override public void onPageFinished(WebView webView, String url) { super.onPageFinished(webView, url); Logger.d(TAG, "Got Page: " + url); String value = null; try { String[] cookies = CookieManager.getInstance().getCookie(url).split("[;]"); for (String cookie : cookies) { if ((cookie != null) && (!cookie.trim().equals("")) && (cookie.startsWith(" " + exception.getRequiredCookieName() + "="))) { value = cookie.substring(exception.getRequiredCookieName().length() + 2); } } } catch (NullPointerException e) { Logger.e(TAG, e); } if (value != null) { BasicClientCookieHC4 cf_cookie = new BasicClientCookieHC4(exception.getRequiredCookieName(), value); cf_cookie.setDomain("." + Uri.parse(url).getHost()); cf_cookie.setPath("/"); currentCookie = cf_cookie; Logger.d(TAG, "Cookie found: " + value); processing2 = false; } else { Logger.d(TAG, "Cookie is not found"); } } }; activity.runOnUiThread(new Runnable() { @SuppressLint("SetJavaScriptEnabled") @Override public void run() { webView = new WebView(activity); webView.setVisibility(View.GONE); layout.addView(webView); webView.setWebViewClient(client); webView.getSettings().setUserAgentString(HttpConstants.USER_AGENT_STRING); webView.getSettings().setJavaScriptEnabled(true); webViewContext = webView.getContext(); if (proxy != null) WebViewProxy.setProxy(webViewContext, proxy.getHostName(), proxy.getPort()); webView.loadUrl(exception.getCheckUrl()); } }); long startTime = System.currentTimeMillis(); while (processing2) { long time = System.currentTimeMillis() - startTime; if ((task != null && task.isCancelled()) || time > TIMEOUT) { processing2 = false; } } activity.runOnUiThread(new Runnable() { @Override public void run() { try { layout.removeView(webView); webView.stopLoading(); webView.clearCache(true); webView.destroy(); webView = null; } finally { if (proxy != null) WebViewProxy.setProxy(webViewContext, null, 0); processing = false; } } }); return currentCookie; } /** * ? cloudflare * @param exception Cloudflare ? * @param httpClient HTTP * @param task ?? * @return ? */ public Recaptcha getRecaptcha(CloudflareException exception, HttpClient httpClient, CancellableTask task) throws RecaptchaException { if (!exception.isRecaptcha()) throw new IllegalArgumentException(); String scheme = exception.getCheckCaptchaUrlFormat().startsWith("https") ? "https" : "http"; return Recaptcha.obtain(exception.getRecaptchaPublicKey(), task, httpClient, scheme); } /** * cloudflare, cookie * @param exception Cloudflare ? * @param httpClient HTTP * @param task ?? * @param challenge challenge * @param recaptchaAnswer * @return ? cookie null, ? */ public Cookie checkRecaptcha(CloudflareException exception, ExtendedHttpClient httpClient, CancellableTask task, String challenge, String recaptchaAnswer) { if (!exception.isRecaptcha()) throw new IllegalArgumentException("wrong type of CloudflareException"); try { recaptchaAnswer = URLEncoder.encode(recaptchaAnswer, "UTF-8"); } catch (UnsupportedEncodingException e) { Logger.e(TAG, e); } String url = String.format(Locale.US, exception.getCheckCaptchaUrlFormat(), challenge, recaptchaAnswer); HttpResponseModel responseModel = null; try { HttpRequestModel rqModel = HttpRequestModel.builder().setGET().setNoRedirect(false).build(); CookieStore cookieStore = httpClient.getCookieStore(); removeCookie(cookieStore, exception.getRequiredCookieName()); responseModel = HttpStreamer.getInstance().getFromUrl(url, rqModel, httpClient, null, task); for (int i = 0; i < 3 && responseModel.statusCode == 400; ++i) { Logger.d(TAG, "HTTP 400"); responseModel.release(); responseModel = HttpStreamer.getInstance().getFromUrl(url, rqModel, httpClient, null, task); } for (Cookie cookie : cookieStore.getCookies()) { if (isClearanceCookie(cookie, url, exception.getRequiredCookieName())) { Logger.d(TAG, "Cookie found: " + cookie.getValue()); return cookie; } } Logger.d(TAG, "Cookie is not found"); } catch (Exception e) { Logger.e(TAG, e); } finally { if (responseModel != null) { responseModel.release(); } } return null; } static boolean isClearanceCookie(Cookie cookie, String url, String requiredCookieName) { try { String cookieName = cookie.getName(); String cookieDomain = cookie.getDomain(); if (!cookieDomain.startsWith(".")) { cookieDomain = "." + cookieDomain; } String urlCookie = "." + Uri.parse(url).getHost(); if (cookieName.equals(requiredCookieName) && cookieDomain.equalsIgnoreCase(urlCookie)) { return true; } } catch (Exception e) { Logger.e(TAG, e); } return false; } static void removeCookie(CookieStore store, String name) { boolean flag = false; for (Cookie cookie : store.getCookies()) { if (cookie.getName().equals(name)) { if (cookie instanceof SetCookie) { flag = true; ((SetCookie) cookie).setExpiryDate(new Date(0)); } else { Logger.e(TAG, "cannot remove cookie (object does not implement SetCookie): " + cookie.toString()); } } } if (flag) store.clearExpired(new Date()); } }