package adonai.diary_browser; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; import android.webkit.CookieManager; import com.squareup.okhttp.Call; import com.squareup.okhttp.FormEncodingBuilder; import com.squareup.okhttp.Headers; import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.Util; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.CookiePolicy; import java.net.CookieStore; import java.net.HttpCookie; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLException; import adonai.diary_browser.misc.FileUtils; import okio.BufferedSink; import okio.Okio; import okio.Source; @SuppressWarnings("deprecation") public class DiaryHttpClient { public final static String CLOUDFLARE_ANCHOR = "a = document.getElementById('jschl-answer');"; public final static String FIXED_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36"; private final static Pattern PASS_PATTERN = Pattern.compile("name=\"pass\" value=\"(.+?)\""); private final static Pattern CHALLENGE_PATTERN = Pattern.compile("name=\"jschl_vc\" value=\"(\\w+)\""); private final static Pattern OPERATION_PATTERN = Pattern.compile("setTimeout\\(function\\(\\)\\{\\s+(var s,t,o,p,b,r,e,a,k,i,n,g,f.+?\\r?\\n[\\s\\S]+?a\\.value =.+?)\\r?\\n"); private URI currentUrl = URI.create("http://www.diary.ru"); OkHttpClient httpClient = new OkHttpClient(); java.net.CookieManager cookieManager = new java.net.CookieManager(); List<Call> runningRequests = new ArrayList<>(); public DiaryHttpClient() { cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); httpClient.setReadTimeout(30, TimeUnit.SECONDS); httpClient.setCookieHandler(cookieManager); httpClient.interceptors().add(new UserAgentInterceptor()); } public class UserAgentInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest.newBuilder() .removeHeader("User-Agent") .addHeader("User-Agent", FIXED_USER_AGENT) .build(); return chain.proceed(requestWithUserAgent); } } public void abort() { new Thread() { @Override public void run() { for(Call request : runningRequests) { request.cancel(); } runningRequests.clear(); } }.start(); } public CookieStore getCookieStore() { return cookieManager.getCookieStore(); } public boolean hasCookie(@NonNull String name) { for(HttpCookie cookie : cookieManager.getCookieStore().getCookies()) { if(cookie.getName().equals(name)) return true; } return false; } public String postPageToString(@NonNull String url, @NonNull RequestBody data) { return postPageToString(url, data, null); } public String postPageToString(@NonNull String url, @NonNull RequestBody data, @Nullable Headers headers) { URI current = resolve(url); Request.Builder httpPost = new Request.Builder() .url(HttpUrl.get(current)) .post(data); if(headers != null) { httpPost.headers(headers); } Call call = httpClient.newCall(httpPost.build()); runningRequests.add(call); try { Response answer = call.execute(); syncCookiesWithWebViews(); return answer.body().string(); } catch (IOException e) { return null; } } public String postPageToString(@NonNull String url, @NonNull List<Pair<String, String>> nameValuePairs) { URI current = resolve(url); FormEncodingBuilder rb = new FormEncodingBuilder(); for(Pair<String, String> param : nameValuePairs) { try { rb.addEncoded(param.first, URLEncoder.encode(param.second, "windows-1251")); } catch (UnsupportedEncodingException ignored) { } } Request httpPost = new Request.Builder() .url(HttpUrl.get(current)) .post(rb.build()) .build(); Call call = httpClient.newCall(httpPost); runningRequests.add(call); try { Response answer = call.execute(); syncCookiesWithWebViews(); return answer.body().string(); } catch (IOException e) { return null; } } public String postPageToString(@NonNull RequestBody data) { return postPageToString("http://www.diary.ru/diary.php", data); } public String postPageToString(@NonNull List<Pair<String, String>> nameValuePairs) { return postPageToString("http://www.diary.ru/diary.php", nameValuePairs); } /** * Return page as string or null if failed * @param url * @return * @throws IOException */ public String getPageAsString(@NonNull String url) { try { if (url.startsWith("file")) return null; // Не загружать локальные Call call = httpClient.newCall(new Request.Builder().url(HttpUrl.get(resolve(url))).get().build()); runningRequests.add(call); Response answer = call.execute(); syncCookiesWithWebViews(); return answer.body().string(); } catch (IOException e) { return null; } } public byte[] getPageAsByteArray(@NonNull String url) { if (url.startsWith("file")) return null; // Не загружать локальные try { Call call = httpClient.newCall(new Request.Builder().url(HttpUrl.get(resolve(url))).get().build()); runningRequests.add(call); Response answer = call.execute(); syncCookiesWithWebViews(); return answer.body().bytes(); } catch (Exception ignored) { return null; } // stream close / timeout } /** * Manual page processing * Remember to disconnect connection! * * @param url url to fetch * @return connection for manual usage */ public Response getPage(@NonNull URI url) throws IOException { try { Call call = httpClient.newCall(new Request.Builder().url(HttpUrl.get(url)).get().build()); runningRequests.add(call); Response result = call.execute(); syncCookiesWithWebViews(); return result; } catch (SSLException e) { return null; } } /** * Manual page processing * Remember to disconnect connection! * * @param url url to fetch * @return connection for manual usage */ public Response getPage(@NonNull URI url, @NonNull Headers headers) throws IOException { try { Request.Builder builder = new Request.Builder() .url(HttpUrl.get(url)) .headers(headers) .get(); Call call = httpClient.newCall(builder.build()); runningRequests.add(call); Response result = call.execute(); syncCookiesWithWebViews(); return result; } catch (SSLException e) { return null; } } public boolean cloudFlareSolve(@NonNull String responseString) { // initialize Rhino Context rhino = Context.enter(); try { String domain = "www.diary.ru"; getPage(URI.create("http://" + domain)); // CF should wait Thread.sleep(5000); // extract the arithmetic operation Matcher operationSearch = OPERATION_PATTERN.matcher(responseString); Matcher challengeSearch = CHALLENGE_PATTERN.matcher(responseString); Matcher passSearch = PASS_PATTERN.matcher(responseString); if(!operationSearch.find() || !passSearch.find() || !challengeSearch.find()) return false; String rawOperation = operationSearch.group(1); String challengePass = passSearch.group(1); String challenge = challengeSearch.group(1); String operation = rawOperation .replaceAll("a\\.value = (parseInt\\(.+?\\)).+", "$1") .replaceAll("\\s{3,}[a-z](?: = |\\.).+", ""); String js = operation.replace("\n", ""); rhino.setOptimizationLevel(-1); Scriptable scope = rhino.initStandardObjects(); int result = ((Double) rhino.evaluateString(scope, js, "CloudFlare JS Challenge", 1, null)).intValue(); String answer = String.valueOf(result + domain.length()); Headers headers = new Headers.Builder() .add("Referer", "http://www.diary.ru/") .build(); String url = new HttpUrl.Builder() .scheme("http") .host("www.diary.ru") .addPathSegment("cdn-cgi").addPathSegment("l").addPathSegment("chk_jschl") .addEncodedQueryParameter("jschl_vc", URLEncoder.encode(challenge, "windows-1251")) .addEncodedQueryParameter("pass", URLEncoder.encode(challengePass, "windows-1251")) .addEncodedQueryParameter("jschl_answer", URLEncoder.encode(answer, "windows-1251")) .build().toString(); Response response = getPage(URI.create(url), headers); if(response.isSuccessful()) { response.request(); return true; } } catch (Exception e) { return false; } finally { Context.exit(); } return false; } public interface ProgressListener { void transferred(long transferredBytes); } public static class CountingFileRequestBody extends RequestBody { private static final int SEGMENT_SIZE = 2048; // okio.Segment.SIZE private final File file; private final ProgressListener listener; private final MediaType contentType; public CountingFileRequestBody(File file, ProgressListener listener) { this.file = file; this.contentType = MediaType.parse(FileUtils.getMimeType(file)); this.listener = listener; } @Override public long contentLength() { return file.length(); } @Override public MediaType contentType() { return contentType; } @Override public void writeTo(BufferedSink sink) throws IOException { Source source = null; try { source = Okio.source(file); long total = 0; long read; while ((read = source.read(sink.buffer(), SEGMENT_SIZE)) != -1) { total += read; sink.flush(); this.listener.transferred(total); } } finally { Util.closeQuietly(source); } } } private void syncCookiesWithWebViews() { List<HttpCookie> cookies = getCookieStore().getCookies(); CookieManager cookieManager = CookieManager.getInstance(); // to webviews for (HttpCookie cookie : cookies) { String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain(); cookieManager.setCookie("diary.ru", cookieString); } } public void syncCookiesWithClient() { CookieStore store = getCookieStore(); CookieManager cookieManager = CookieManager.getInstance(); String cookies = cookieManager.getCookie("diary.ru"); String[] cookieValues = cookies.split(";"); HttpCookie cookie; URI diary = URI.create("http://diary.ru"); for (String cookieValue : cookieValues) { String[] split = cookieValue.split("="); if (split.length == 2) cookie = new HttpCookie(split[0], split[1]); else cookie = new HttpCookie(split[0], null); cookie.setDomain("diary.ru"); store.add(diary, cookie); } } public void setCurrentUrl(URI url) { currentUrl = url; } public URI resolve(String url) { return currentUrl.resolve(url); } public String getCurrentUrl() { return currentUrl.toString(); } }