package com.germainz.crappalinks; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.Toast; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.ConnectException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.UnknownHostException; import java.net.HttpURLConnection; import java.net.URL; public class Resolver extends Activity { private String toastType; private boolean confirmOpen; private String resolveAllWhen; private boolean useUnshortenIt; private static final String TOAST_NONE = "0"; private static final String TOAST_DETAILED = "2"; private static final String UNSHORTEN_IT_API_KEY = "UcWGkhtMFdM4019XeI8lgfNOk875RL7K"; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedPreferences sharedPreferences = getSharedPreferences("com.germainz.crappalinks_preferences", Context.MODE_WORLD_READABLE); toastType = sharedPreferences.getString("pref_toast_type", TOAST_NONE); confirmOpen = sharedPreferences.getBoolean("pref_confirm_open", false); resolveAllWhen = sharedPreferences.getString("pref_resolve_all_when", "ALWAYS"); // Still called pref_use_long_url for backwards compatibility, as we used to use longurl.org instead. useUnshortenIt = sharedPreferences.getBoolean("pref_use_long_url", false); new ResolveUrl().execute(getIntent().getDataString()); /* Ideally, this would be a service, but we're redirecting intents via Xposed. * We finish the activity immediately so that the user can still interact with the * foreground app while we unshorten the URL in the background. */ finish(); } private class ResolveUrl extends AsyncTask<String, Void, String> { private Context context = null; // unknown error while connecting private boolean connectionError = false; // connection missing/not working private boolean noConnectionError = false; private ResolveUrl() { context = Resolver.this; } @Override protected void onPreExecute() { if (!toastType.equals(TOAST_NONE)) Toast.makeText(context, getString(R.string.toast_message_started), Toast.LENGTH_SHORT).show(); } private String getRedirect(String url) { HttpURLConnection c = null; try { c = (HttpURLConnection) new URL(url).openConnection(); c.setConnectTimeout(10000); c.setReadTimeout(15000); c.connect(); final int responseCode = c.getResponseCode(); // If the response code is 3xx, it's a redirection. Return the real location. if (responseCode >= 300 && responseCode < 400) { String location = c.getHeaderField("Location"); return RedirectHelper.getAbsoluteUrl(location, url); } // It might also be a redirection using meta tags. else if (responseCode >= 200 && responseCode < 300 ) { Document d = Jsoup.parse(c.getInputStream(), "UTF-8", url); Elements refresh = d.select("*:not(noscript) > meta[http-equiv=Refresh]"); if (!refresh.isEmpty()) { Element refreshElement = refresh.first(); if (refreshElement.hasAttr("url")) return RedirectHelper.getAbsoluteUrl(refreshElement.attr("url"), url); else if (refreshElement.hasAttr("content") && refreshElement.attr("content").contains("url=")) return RedirectHelper.getAbsoluteUrl(refreshElement.attr("content").split("url=")[1].replaceAll("^'|'$", ""), url); } } } catch (ConnectException | UnknownHostException e) { noConnectionError = true; e.printStackTrace(); } catch (Exception e) { connectionError = true; e.printStackTrace(); } finally { if (c != null) c.disconnect(); } return null; } private String getRedirectUsingLongURL(String url) { HttpURLConnection c = null; try { // http://unshorten.it/api/documentation Uri.Builder builder = new Uri.Builder(); builder.scheme("http").authority("api.unshorten.it").appendQueryParameter("shortURL", url) .appendQueryParameter("responseFormat", "json").appendQueryParameter("apiKey", UNSHORTEN_IT_API_KEY); String requestUrl = builder.build().toString(); c = (HttpURLConnection) new URL(requestUrl).openConnection(); c.setRequestProperty("User-Agent", "CrappaLinks"); c.setConnectTimeout(10000); c.setReadTimeout(15000); c.connect(); final int responseCode = c.getResponseCode(); if (responseCode == 200) { // Response format: {"fullurl": "URL"} JSONObject jsonObject = new JSONObject(new BufferedReader( new InputStreamReader(c.getInputStream())).readLine()); if (jsonObject.has("error")) { connectionError = true; Log.e("CrappaLinks", requestUrl); Log.e("CrappaLinks", jsonObject.toString()); return url; } else { return jsonObject.getString("fullurl"); } } } catch (ConnectException | UnknownHostException e) { noConnectionError = true; } catch (Exception e) { connectionError = true; e.printStackTrace(); } finally { if (c != null) c.disconnect(); } return url; } protected String doInBackground(String... urls) { String redirectUrl = urls[0]; // if there's no connection, fail and return the original URL. ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE); if (connectivityManager.getActiveNetworkInfo() == null) { noConnectionError = true; return redirectUrl; } if (useUnshortenIt) { return getRedirectUsingLongURL(redirectUrl); } else { HttpURLConnection.setFollowRedirects(false); // Use the cookie manager so that cookies are stored. Useful for some hosts that keep // redirecting us indefinitely unless the set cookie is detected. CookieManager cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); // Should we resolve all URLs? boolean resolveAll = true; NetworkInfo wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (resolveAllWhen.equals("NEVER") || (resolveAllWhen.equals("WIFI_ONLY") && !wifiInfo.isConnected())) resolveAll = false; // Keep trying to resolve the URL until we get a URL that isn't a redirect. String finalUrl = redirectUrl; while (redirectUrl != null && ((resolveAll) || (RedirectHelper.isRedirect(Uri.parse(redirectUrl).getHost())))) { redirectUrl = getRedirect(redirectUrl); if (redirectUrl != null) { // This should avoid infinite loops, just in case. if (redirectUrl.equals(finalUrl)) return finalUrl; finalUrl = redirectUrl; } } return finalUrl; } } protected void onPostExecute(final String uri) { if (noConnectionError) Toast.makeText(context, getString(R.string.toast_message_network) + uri, Toast.LENGTH_LONG).show(); else if (connectionError) Toast.makeText(context, getString(R.string.toast_message_error) + uri, Toast.LENGTH_LONG).show(); if (confirmOpen) { Intent confirmDialogIntent = new Intent(context, ConfirmDialog.class); confirmDialogIntent.putExtra("uri", uri); startActivity(confirmDialogIntent); } else { if (!noConnectionError && !connectionError && toastType.equals(TOAST_DETAILED)) Toast.makeText(context, getString(R.string.toast_message_done) + uri, Toast.LENGTH_LONG).show(); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("crappalinks", ""); startActivity(intent); } } } }