From 50faa646f7aef8fea6e15d4df91b4e162e0e94eb Mon Sep 17 00:00:00 2001
From: Sam Steele <sam.steele@gmail.com>
Date: Fri, 5 Feb 2021 07:07:52 -0500
Subject: [PATCH] use happy-eyeballs HTTP class to fetch configuration

---
 build.gradle                                  |   2 +-
 .../irccloud/android/NetworkConnection.java   | 138 ++++++++++++------
 .../android/activity/LoginActivity.java       |  50 +++++--
 3 files changed, 127 insertions(+), 63 deletions(-)

diff --git a/build.gradle b/build.gradle
index 7a43e5c2..4e5d5d74 100644
--- a/build.gradle
+++ b/build.gradle
@@ -68,7 +68,7 @@ android {
     testBuildType "mockdata"
 
     defaultConfig {
-        versionCode 308
+        versionCode 309
         versionName "4.26"
         minSdkVersion 22
         targetSdkVersion 30
diff --git a/src/com/irccloud/android/NetworkConnection.java b/src/com/irccloud/android/NetworkConnection.java
index 0f6146a6..007ab555 100644
--- a/src/com/irccloud/android/NetworkConnection.java
+++ b/src/com/irccloud/android/NetworkConnection.java
@@ -676,51 +676,59 @@ public JSONObject fetchJSON(String url, HashMap<String, String>headers) throws I
         return null;
     }
 
-    public JSONObject fetchConfig() {
+    public void fetchConfig(ConfigCallback callback) {
+        IRCCloudLog.Log(Log.INFO, TAG, "Requesting configuration");
         try {
-            IRCCloudLog.Log(Log.INFO, TAG, "Requesting configuration");
-            JSONObject o = fetchJSON("https://" + IRCCLOUD_HOST + "/config");
-            if(o != null) {
-                config = o;
-                SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext()).edit();
-                prefs.putString("config", config.toString());
-                prefs.apply();
-
-                if(config.has("file_uri_template"))
-                    file_uri_template = config.getString("file_uri_template");
-                else
-                    file_uri_template = null;
-
-                if(config.has("pastebin_uri_template"))
-                    pastebin_uri_template = config.getString("pastebin_uri_template");
-                else
-                    pastebin_uri_template = null;
-
-                if(config.has("avatar_uri_template"))
-                    avatar_uri_template = config.getString("avatar_uri_template");
-                else
-                    avatar_uri_template = null;
-
-                if(config.has("avatar_redirect_uri_template"))
-                    avatar_redirect_uri_template = config.getString("avatar_redirect_uri_template");
-                else
-                    avatar_redirect_uri_template = null;
-
-                if(BuildConfig.ENTERPRISE && !(config.get("enterprise") instanceof JSONObject)) {
-                    globalMsg = "Some features, such as push notifications, may not work as expected.  Please download the standard IRCCloud app from the <a href=\"" + config.getString("android_app") + "\">Play Store</a>";
-                    notifyHandlers(EVENT_GLOBALMSG, null);
-                }
-                set_pastebin_cookie();
+            new ConfigFetcher(new ConfigCallback() {
+                @Override
+                public void onConfig(JSONObject o) {
+                    try {
+                        if (o != null) {
+                            config = o;
+                            SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext()).edit();
+                            prefs.putString("config", config.toString());
+                            prefs.apply();
+
+                            if (config.has("file_uri_template"))
+                                file_uri_template = config.getString("file_uri_template");
+                            else
+                                file_uri_template = null;
+
+                            if (config.has("pastebin_uri_template"))
+                                pastebin_uri_template = config.getString("pastebin_uri_template");
+                            else
+                                pastebin_uri_template = null;
+
+                            if (config.has("avatar_uri_template"))
+                                avatar_uri_template = config.getString("avatar_uri_template");
+                            else
+                                avatar_uri_template = null;
+
+                            if (config.has("avatar_redirect_uri_template"))
+                                avatar_redirect_uri_template = config.getString("avatar_redirect_uri_template");
+                            else
+                                avatar_redirect_uri_template = null;
+
+                            if (BuildConfig.ENTERPRISE && !(config.get("enterprise") instanceof JSONObject)) {
+                                globalMsg = "Some features, such as push notifications, may not work as expected.  Please download the standard IRCCloud app from the <a href=\"" + config.getString("android_app") + "\">Play Store</a>";
+                                notifyHandlers(EVENT_GLOBALMSG, null);
+                            }
+                            set_pastebin_cookie();
 
-                if (config.has("api_host")) {
-                    set_api_host(config.getString("api_host"));
+                            if (config.has("api_host")) {
+                                set_api_host(config.getString("api_host"));
+                            }
+                        }
+                        if(callback != null)
+                            callback.onConfig(o);
+                    } catch (Exception e) {
+                        printStackTraceToCrashlytics(e);
+                    }
                 }
-
-            }
+            }).connect();
         } catch (Exception e) {
             printStackTraceToCrashlytics(e);
         }
-        return config;
     }
 
     public static void set_api_host(String host) {
@@ -1043,20 +1051,58 @@ public synchronized void connect(boolean ignoreNetworkState) {
         resultCallbacks.clear();
         notifyHandlers(EVENT_CONNECTIVITY, null);
 
-        new ConnectTask().execute(limit);
+        fetchConfig(new ConnectCallback(limit));
     }
 
-    private class ConnectTask extends AsyncTaskEx<Integer, Void, JSONObject> {
+    public interface ConfigCallback {
+        void onConfig(JSONObject config);
+    }
+
+    private class ConfigFetcher extends HTTPFetcher {
+        ConfigCallback callback;
+        JSONObject result = null;
+
+        public ConfigFetcher(ConfigCallback callback) throws MalformedURLException {
+            super(new URL("https://" + IRCCLOUD_HOST + "/config"));
+            this.callback = callback;
+        }
+
+        protected void onFetchComplete() {
+            if(!isCancelled && callback != null)
+                callback.onConfig(result);
+        }
+
+        protected void onFetchFailed() {
+            if(!isCancelled && callback != null)
+                callback.onConfig(result);
+        }
+
+        protected void onStreamConnected(InputStream is) throws Exception {
+            if (isCancelled)
+                return;
+
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            byte[] buffer = new byte[8192];
+            int len;
+            while ((len = is.read(buffer)) != -1) {
+                os.write(buffer, 0, len);
+            }
+            String response = os.toString("UTF-8");
+            is.close();
+
+            result = new JSONObject(response);
+        }
+    }
+
+    private class ConnectCallback implements ConfigCallback {
         int limit;
 
-        @Override
-        protected JSONObject doInBackground(Integer... limits) {
-            limit = limits[0];
-            return fetchConfig();
+        public ConnectCallback(int limit) {
+            this.limit = limit;
         }
 
         @Override
-        protected void onPostExecute(JSONObject config) {
+        public void onConfig(JSONObject config) {
             try {
                 if (config != null) {
                     String host = null;
diff --git a/src/com/irccloud/android/activity/LoginActivity.java b/src/com/irccloud/android/activity/LoginActivity.java
index 8e28ca66..aaccc1ba 100644
--- a/src/com/irccloud/android/activity/LoginActivity.java
+++ b/src/com/irccloud/android/activity/LoginActivity.java
@@ -161,7 +161,7 @@ public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event
                     login.post(new Runnable() {
                         @Override
                         public void run() {
-                            new LoginTask().execute((Void) null);
+                            login();
                         }
                     });
                     return true;
@@ -183,7 +183,7 @@ public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event
                     login.post(new Runnable() {
                         @Override
                         public void run() {
-                            new LoginTask().execute((Void) null);
+                            login();
                         }
                     });
                     return true;
@@ -212,7 +212,7 @@ public void run() {
                             i.putExtra("title", loginBtn.getText().toString());
                             startActivityForResult(i, REQUEST_SAML);
                         } else {
-                            new LoginTask().execute((Void) null);
+                            login();
                         }
                     }
                 });
@@ -284,7 +284,7 @@ private boolean isPackageInstalled(String packagename, Context context) {
         signupBtn.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View view) {
-                new LoginTask().execute((Void) null);
+                login();
             }
         });
 
@@ -548,7 +548,7 @@ public void onResult(CredentialRequestResult result) {
                             email.setText(result.getCredential().getId());
                             password.setText(result.getCredential().getPassword());
                             loginHintClickListener.onClick(null);
-                            new LoginTask().execute((Void) null);
+                            login();
                         } else if (result.getStatus().getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
                             Log.e("IRCCloud", "Credentials request sign in");
                             loading.setVisibility(View.GONE);
@@ -699,7 +699,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                 loading.setVisibility(View.GONE);
                 login.setVisibility(View.VISIBLE);
                 loginHintClickListener.onClick(null);
-                new LoginTask().execute((Void) null);
+                login();
             } else {
                 loading.setVisibility(View.GONE);
                 login.setVisibility(View.VISIBLE);
@@ -722,6 +722,32 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         }
     }
 
+    private void login() {
+        LoginTask task = new LoginTask();
+        task.onPreExecute();
+        NetworkConnection.getInstance().fetchConfig(new NetworkConnection.ConfigCallback() {
+            @Override
+            public void onConfig(JSONObject config) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if(config != null) {
+                            task.execute((Void) null);
+                        } else {
+                            try {
+                                JSONObject result = new JSONObject();
+                                result.put("message", "config");
+                                task.onPostExecute(result);
+                            } catch (Exception e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                });
+            }
+        });
+    }
+
     private class LoginTask extends AsyncTaskEx<Void, Void, JSONObject> {
         @Override
         public void onPreExecute() {
@@ -748,16 +774,6 @@ public void onPreExecute() {
 
         @Override
         protected JSONObject doInBackground(Void... arg0) {
-            try {
-                if (!BuildConfig.ENTERPRISE)
-                    NetworkConnection.IRCCLOUD_HOST = BuildConfig.HOST;
-                JSONObject config = NetworkConnection.getInstance().fetchConfig();
-                NetworkConnection.IRCCLOUD_HOST = config.getString("api_host");
-                trimHost();
-            } catch (Exception e) {
-                NetworkConnection.printStackTraceToCrashlytics(e);
-                return null;
-            }
             if (name.getVisibility() == View.VISIBLE) {
                 if (name.getText() != null && name.getText().length() > 0 && email.getText() != null && email.getText().length() > 0 && password.getText() != null && password.getText().length() > 0)
                     return NetworkConnection.getInstance().signup(name.getText().toString(), email.getText().toString(), password.getText().toString(), (impression_id != null) ? impression_id : "");
@@ -920,6 +936,8 @@ else if (message.equals("tor_blocked"))
                                 message = "No signups allowed from TOR exit nodes";
                             else if (message.equals("signup_ip_blocked"))
                                 message = "Your IP address has been blacklisted";
+                            else if (message.equals("config"))
+                                message = "Unable to fetch configuration. Please try again shortly.";
                             else
                                 message = "Error: " + message;
                         }