diff --git a/app/build.gradle b/app/build.gradle index a2e167e4..f8aee685 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,7 @@ dependencies { // More libraries compile 'com.getbase:floatingactionbutton:1.9.1' compile 'com.jakewharton:butterknife:8.0.1' + compile 'info.guardianproject.netcipher:netcipher:1.2.1' apt 'com.jakewharton:butterknife-compiler:8.0.1' } diff --git a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java index ead96cff..104ec5ce 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java @@ -23,7 +23,9 @@ import android.Manifest; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.AlarmManager; import android.app.AlertDialog; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -32,9 +34,11 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.StrictMode; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.design.widget.CollapsingToolbarLayout; @@ -46,6 +50,7 @@ import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.ActionMenuView; +import android.support.v7.widget.SwitchCompat; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.SpannableString; @@ -65,6 +70,7 @@ import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; @@ -94,6 +100,8 @@ import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.web.WebkitProxy; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, WebUserProfileChangedListener { @@ -171,6 +179,12 @@ public class MainActivity extends AppCompatActivity podUserProfile.setCallbackHandler(uiHandler); podUserProfile.setListener(this); + if(appSettings.isProxyEnabled()) { + if(!setProxy(appSettings.getProxyHost(), appSettings.getProxyPort())) { + Toast.makeText(MainActivity.this, R.string.toast_set_proxy_failed,Toast.LENGTH_SHORT).show(); + } + } + setupWebView(savedInstanceState); // Setup toolbar @@ -241,6 +255,11 @@ public class MainActivity extends AppCompatActivity //TODO: Dangerous on API < 17. Can we do anything about this? webView.addJavascriptInterface(new JavaScriptInterface(), "AndroidBridge"); + //Set proxy + if(appSettings.isProxyEnabled()) { + if(!setProxy()) Toast.makeText(this, R.string.toast_set_proxy_failed, Toast.LENGTH_LONG).show(); + } + /* * WebViewClient */ @@ -846,7 +865,7 @@ public class MainActivity extends AppCompatActivity case R.id.nav_aspects: { if (Helpers.isOnline(MainActivity.this)) { - // webView.loadUrl("https://" + podDomain + "/aspects"); + // webView.loadUrl("https://" + podDomain + "/aspects"); Helpers.showAspectList(webView, app); setTitle(R.string.title_aspects); } else { @@ -991,4 +1010,66 @@ public class MainActivity extends AppCompatActivity grantResults); } } + + /** + * Set proxy according to arguments. host must not be "" or null, port must be positive. + * Return true on success and update appSettings' proxy related values. + * @param host proxy host (eg. localhost or 127.0.0.1) + * @param port proxy port (eg. 8118) + * @return success + * @throws IllegalArgumentException if arguments do not fit specifications above + */ + private boolean setProxy(final String host, final int port) { + if(host != null && !host.equals("") && port >=0) { + //Temporary change thread policy + StrictMode.ThreadPolicy old = StrictMode.getThreadPolicy(); + StrictMode.ThreadPolicy tmp = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(tmp); + + NetCipher.setProxy(host, port); //Proxy for HttpsUrlConnections + try { + //Proxy for the webview + WebkitProxy.setProxy(MainActivity.class.getName(), getApplicationContext(), null, host, port); + } catch (Exception e) { /*Nothing we can do*/ } + + appSettings.setProxyEnabled(true); + appSettings.setProxyHost(host); + appSettings.setProxyPort(port); + + StrictMode.setThreadPolicy(old); + webView.reload(); + return true; + } else { + return false; + } + } + + private boolean setProxy() { + return setProxy(appSettings.getProxyHost(), appSettings.getProxyPort()); + } + + private void resetProxy() { + appSettings.setProxyEnabled(false); + + //Temporary change thread policy + StrictMode.ThreadPolicy old = StrictMode.getThreadPolicy(); + StrictMode.ThreadPolicy tmp = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(tmp); + + NetCipher.clearProxy(); + try{ + WebkitProxy.resetProxy(MainActivity.class.getName(), this); + } catch (Exception e) { + //Nothing we can do. + } + + StrictMode.setThreadPolicy(old); + + //Restart app + Intent restartActivity = new Intent(this, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 12374, restartActivity, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent); + System.exit(0); + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java b/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java index 4477e1cf..172ffa27 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java @@ -74,6 +74,9 @@ public class AppSettings { public static final String PODDOMAIN = "podDomain"; public static final String PODUSERPROFILE_ASPECTS = "podUserProfile_aspects"; public static final String IS_LOAD_DESKTOP_PAGE = "pref_key_desktop_mode"; + public static final String PROXY_ENABLED = "isProxyEnabled"; + public static final String PROXY_HOST = "proxyHost"; + public static final String PROXY_PORT = "proxyPort"; } @@ -159,4 +162,41 @@ public class AppSettings { } return aspects; } + + public void setProxyEnabled(boolean enabled) { + //commit instead of apply because the app is likely to be killed before apply is called. + prefApp.edit().putBoolean(PREF.PROXY_ENABLED, enabled).commit(); + } + + /** + * Default return value: false + * @return whether proxy is enabled or not + */ + public boolean isProxyEnabled() { + return prefApp.getBoolean(PREF.PROXY_ENABLED, false); + } + + public void setProxyHost(String host) { + setString(prefApp, PREF.PROXY_HOST, host); + } + + /** + * Default value: "" + * @return proxy host + */ + public String getProxyHost() { + return prefApp.getString(PREF.PROXY_HOST, ""); + } + + public void setProxyPort(int port) { + setInt(prefApp, PREF.PROXY_PORT, port); + } + + /** + * Default value: 0 + * @return proxy port + */ + public int getProxyPort() { + return prefApp.getInt(PREF.PROXY_PORT, 0); + } } diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java b/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java index 8791ac00..ba01716a 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java @@ -28,12 +28,6 @@ import android.util.Log; import com.github.dfa.diaspora_android.App; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONObject; @@ -44,6 +38,10 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + public class GetPodsService extends Service { public static final String MESSAGE_PODS_RECEIVED = "com.github.dfa.diaspora.podsreceived"; private static final String TAG = App.TAG; @@ -73,24 +71,28 @@ public class GetPodsService extends Service { // TODO: Update deprecated code StringBuilder builder = new StringBuilder(); - HttpClient client = new DefaultHttpClient(); + //HttpClient client = new DefaultHttpClient(); List list = null; + HttpsURLConnection connection; + InputStream inStream; try { - HttpGet httpGet = new HttpGet("http://podupti.me/api.php?key=4r45tg&format=json"); - HttpResponse response = client.execute(httpGet); - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); + connection = NetCipher.getHttpsURLConnection("https://podupti.me/api.php?key=4r45tg&format=json"); + int statusCode = connection.getResponseCode(); if (statusCode == 200) { - HttpEntity entity = response.getEntity(); - InputStream content = entity.getContent(); + inStream = connection.getInputStream(); BufferedReader reader = new BufferedReader( - new InputStreamReader(content)); + new InputStreamReader(inStream)); String line; while ((line = reader.readLine()) != null) { builder.append(line); } + + try { + inStream.close(); + } catch (IOException e) {/*Nothing to do*/} + + connection.disconnect(); } else { - //TODO Notify User about failure Log.e(TAG, "Failed to download list of pods"); } } catch (IOException e) { @@ -136,4 +138,4 @@ public class GetPodsService extends Service { throw new UnsupportedOperationException("Not yet implemented"); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java b/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java index 2b5a3d23..d9059344 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java @@ -13,7 +13,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + /** + * Task that can be used to download images from URLs and store them in storage * Created by Gregor Santner (gsantner) on 24.03.16. */ public class ImageDownloadTask extends AsyncTask { @@ -35,9 +40,12 @@ public class ImageDownloadTask extends AsyncTask { String url = urls[0]; Bitmap bitmap = null; FileOutputStream out = null; + InputStream inStream; + HttpsURLConnection connection; try { - InputStream in = new java.net.URL(url).openStream(); - bitmap = BitmapFactory.decodeStream(in); + connection = NetCipher.getHttpsURLConnection(url); + inStream = connection.getInputStream(); + bitmap = BitmapFactory.decodeStream(inStream); // Save to file if not null if (savePath != null) { @@ -45,6 +53,12 @@ public class ImageDownloadTask extends AsyncTask { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } + try { + inStream.close(); + } catch (IOException e) {/*Nothing*/} + + connection.disconnect(); + } catch (Exception e) { Log.e(App.TAG, e.getMessage()); } finally { @@ -64,4 +78,4 @@ public class ImageDownloadTask extends AsyncTask { imageView.setImageBitmap(result); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java b/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java index f54774f0..2771a4b7 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java @@ -10,11 +10,16 @@ import com.github.dfa.diaspora_android.data.PodUserProfile; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.net.HttpURLConnection; import java.net.URL; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + /** + * AsyncTask to fetch a users profile * Created by Gregor Santner (gsantner) on 30.03.16. */ public class ProfileFetchTask extends AsyncTask { @@ -37,18 +42,21 @@ public class ProfileFetchTask extends AsyncTask { String cookies = cookieManager.getCookie("https://" + app.getSettings().getPodDomain()); Log.d(App.TAG, cookies); + HttpsURLConnection connection; + InputStream inStream; try { URL url = new URL("https://" + app.getSettings().getPodDomain() + "/stream"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setReadTimeout(10000); - conn.setConnectTimeout(15000); - conn.setRequestMethod("GET"); + connection = NetCipher.getHttpsURLConnection(url); + connection.setReadTimeout(10000); + connection.setConnectTimeout(15000); + connection.setRequestMethod("GET"); if (cookies != null) { - conn.setRequestProperty("Cookie", cookies); + connection.setRequestProperty("Cookie", cookies); } - conn.connect(); + connection.connect(); - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + inStream = connection.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(inStream)); String line; final String TARGET_TAG = "window.gon={};gon.user="; while ((line = br.readLine()) != null && !line.startsWith(" { break; } } + + try{ + br.close(); + inStream.close(); + } catch (IOException e){/*Nothing*/} + + connection.disconnect(); + } catch (IOException e) { e.printStackTrace(); } @@ -70,4 +86,4 @@ public class ProfileFetchTask extends AsyncTask { return null; } -} +} \ No newline at end of file diff --git a/app/src/main/res/layout/proxy_configuration_dialog.xml b/app/src/main/res/layout/proxy_configuration_dialog.xml new file mode 100644 index 00000000..75bc4330 --- /dev/null +++ b/app/src/main/res/layout/proxy_configuration_dialog.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1fe9b95f..08562e77 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -172,5 +172,10 @@ along with this program. If not, see http://www.gnu.org/licenses.<br> <br Speichere Bild als Linkadresse kopiert … Teilen… + Aktiviert + Host + Proxy + Port + Warnung: Proxy konnte nicht aktiviert werden… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e4b5fcc..9567f116 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -78,6 +78,10 @@ Do load images Do not load images Change view + Proxy + Enabled + Host + Port Share link as text Share screenshot of webpage Take screenshot of webpage @@ -219,4 +223,6 @@ https:// Share… #DiasporaForAndroid + + Warning: Could not set network proxy…