From ded15c0194af32814bd97e410677a6beb63477a6 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Thu, 28 Jul 2022 13:31:25 +0200 Subject: [PATCH] Try fixing staging Androidacy. (And fail) --- .../java/com/fox2code/mmm/MainActivity.java | 1 - .../com/fox2code/mmm/MainApplication.java | 7 -- .../main/java/com/fox2code/mmm/XHooks.java | 1 - .../mmm/androidacy/AndroidacyActivity.java | 99 +++++++++++++++---- .../mmm/androidacy/AndroidacyRepoData.java | 10 +- .../mmm/androidacy/AndroidacyUtil.java | 14 +++ .../mmm/androidacy/AndroidacyWebAPI.java | 18 +++- .../com/fox2code/mmm/repo/CustomRepoData.java | 1 - .../java/com/fox2code/mmm/repo/RepoData.java | 3 +- .../com/fox2code/mmm/repo/RepoManager.java | 7 +- .../com/fox2code/mmm/repo/RepoUpdater.java | 1 - .../java/com/fox2code/mmm/utils/Http.java | 39 ++++++-- 12 files changed, 157 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 8a2c99e..5c5943e 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -16,7 +16,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; -import androidx.core.app.NotificationManagerCompat; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index 7fe29bf..16cf224 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -17,16 +17,10 @@ import androidx.annotation.StyleRes; import androidx.emoji2.text.DefaultEmojiCompatConfig; import androidx.emoji2.text.EmojiCompat; import androidx.emoji2.text.FontRequestEmojiCompatConfig; -import androidx.work.Constraints; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.NetworkType; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkManager; import com.fox2code.foxcompat.FoxActivity; import com.fox2code.foxcompat.FoxApplication; import com.fox2code.foxcompat.FoxThemeWrapper; -import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.utils.GMSProviderInstaller; import com.fox2code.mmm.utils.Http; @@ -37,7 +31,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; -import java.util.concurrent.TimeUnit; import io.noties.markwon.Markwon; import io.noties.markwon.html.HtmlPlugin; diff --git a/app/src/main/java/com/fox2code/mmm/XHooks.java b/app/src/main/java/com/fox2code/mmm/XHooks.java index 50d0f63..1ad2bd9 100644 --- a/app/src/main/java/com/fox2code/mmm/XHooks.java +++ b/app/src/main/java/com/fox2code/mmm/XHooks.java @@ -8,7 +8,6 @@ import android.webkit.WebView; import androidx.annotation.Keep; import com.fox2code.mmm.manager.ModuleManager; -import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoManager; /** diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index 035d8c7..cd785b2 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -31,9 +31,14 @@ import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; import com.fox2code.mmm.XHooks; +import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; import java.util.HashMap; /** @@ -134,6 +139,8 @@ public class AndroidacyActivity extends FoxActivity { // Don't open non Androidacy urls inside WebView if (request.isForMainFrame() && !AndroidacyUtil.isAndroidacyLink(request.getUrl())) { + Log.i(TAG, "Exiting WebView " + // hideToken in case isAndroidacyLink fail. + AndroidacyUtil.hideToken(request.getUrl().toString())); IntentHelper.openUri(view.getContext(), request.getUrl().toString()); return true; } @@ -152,6 +159,7 @@ public class AndroidacyActivity extends FoxActivity { private void onReceivedError(String url, int errorCode) { if ((url.startsWith("https://api.androidacy.com/magisk/") || + url.startsWith("https://staging-api.androidacy.com/magisk/") || url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) { Toast.makeText(AndroidacyActivity.this, "Too many requests!", Toast.LENGTH_LONG).show(); @@ -188,38 +196,89 @@ public class AndroidacyActivity extends FoxActivity { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - switch (consoleMessage.messageLevel()) { - case TIP: - Log.v(TAG, consoleMessage.message()); - break; - case LOG: - Log.i(TAG, consoleMessage.message()); - break; - case WARNING: - Log.w(TAG, consoleMessage.message()); - break; - case ERROR: - Log.e(TAG, consoleMessage.message()); - break; - case DEBUG: - Log.d(TAG, consoleMessage.message()); - break; + if (BuildConfig.DEBUG) { + switch (consoleMessage.messageLevel()) { + case TIP: + Log.v(TAG, consoleMessage.message()); + break; + case LOG: + Log.i(TAG, consoleMessage.message()); + break; + case WARNING: + Log.w(TAG, consoleMessage.message()); + break; + case ERROR: + Log.e(TAG, consoleMessage.message()); + break; + case DEBUG: + Log.d(TAG, consoleMessage.message()); + break; + } } return super.onConsoleMessage(consoleMessage); } }); this.webView.setDownloadListener(( downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> { - if (AndroidacyUtil.isAndroidacyLink(downloadUrl)) { + if (AndroidacyUtil.isAndroidacyLink(downloadUrl) && !this.backOnResume) { AndroidacyWebAPI androidacyWebAPI = this.androidacyWebAPI; if (androidacyWebAPI != null) { - if (androidacyWebAPI.consumedAction && !androidacyWebAPI.downloadMode) { - return; // Native module popup may cause download after consumed action + if (!androidacyWebAPI.downloadMode) { + if (androidacyWebAPI.consumedAction) + return; // Native module popup may cause download after consumed action + int lenPrefix = 0; + // Workaround WebView/Chromium bug + for (String prefix : new String[]{ + "https://api.androidacy.com/magisk/download/", + "https://staging-api.androidacy.com/magisk/download/" + }) { // Make both staging and non staging act the same + if (downloadUrl.startsWith(prefix)) lenPrefix = prefix.length(); + } + if (lenPrefix != 0) { + final String moduleId = downloadUrl.substring(lenPrefix); + webView.evaluateJavascript("document.querySelector(" + + "\"#download-form input[name=_token]\").value", + result -> new Thread("Androidacy popup workaround thread") { + @Override + public void run() { + if (androidacyWebAPI.consumedAction) return; + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("moduleId", moduleId); + jsonObject.put("token", RepoManager.getINSTANCE() + .getAndroidacyRepoData().getToken()); + jsonObject.put("_token", result); + String realUrl = Http.doHttpPostRedirect(downloadUrl, + jsonObject.toString(), true); + if (downloadUrl.equals(realUrl)) { + Log.e(TAG, "Failed to resolve URL"); + return; + } + Log.i(TAG, "Got url: " + realUrl); + androidacyWebAPI.openNativeModuleDialogRaw(realUrl, + moduleId, "", androidacyWebAPI.canInstall()); + } catch (IOException | JSONException e) { + Log.e(TAG, "Failed redirect intercept", e); + } + } + }.start()); + return; + } } androidacyWebAPI.consumedAction = true; androidacyWebAPI.downloadMode = false; - } else if (this.backOnResume) return; + } this.backOnResume = true; + Log.i(TAG, "Exiting WebView " + + AndroidacyUtil.hideToken(downloadUrl)); + for (String prefix : new String[]{ + "https://api.androidacy.com/magisk/download/", + "https://staging-api.androidacy.com/magisk/download/" + }) { + if (downloadUrl.startsWith(prefix)) { + return; + } + } IntentHelper.openCustomTab(this, downloadUrl); } }); diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index d0a7922..a5fca9d 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -29,7 +29,7 @@ import okhttp3.Cookie; import okhttp3.HttpUrl; @SuppressWarnings("KotlinInternalInJava") -public class AndroidacyRepoData extends RepoData { +public final class AndroidacyRepoData extends RepoData { private static final String TAG = "AndroidacyRepoData"; private static final HttpUrl OK_HTTP_URL; static { @@ -284,6 +284,10 @@ public class AndroidacyRepoData extends RepoData { // Do not inject token for non Androidacy urls if (!AndroidacyUtil.isAndroidacyLink(url)) return url; + if (this.testMode && url.startsWith("https://api.androidacy.com/")) { + Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url)); + url = "https://staging-api.androidacy.com/" + url.substring(27); + } String token = "token=" + this.token; if (!url.contains(token)) { if (url.lastIndexOf('/') < url.lastIndexOf('?')) { @@ -300,4 +304,8 @@ public class AndroidacyRepoData extends RepoData { public String getName() { return this.testMode ? super.getName() + " (Test Mode)" : super.getName(); } + + String getToken() { + return this.token; + } } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java index 29ba8ab..e940dc7 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyUtil.java @@ -23,4 +23,18 @@ public class AndroidacyUtil { url.substring(8, i).endsWith(".androidacy.com") && uri.getHost().endsWith(".androidacy.com"); } + + // Avoid logging token + public static String hideToken(@NonNull String url) { + int i = url.lastIndexOf("token="); + if (i == -1) return url; + int i2 = url.indexOf('&', i); + if (i2 == -1) { + return url.substring(0, i + 6) + + ""; + } else { + return url.substring(0, i + 6) + + "" + url.substring(i2); + } + } } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index 25c4d25..e82b861 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -37,6 +37,8 @@ import java.nio.charset.StandardCharsets; @Keep public class AndroidacyWebAPI { + public static final int COMPAT_UNSUPPORTED = 0; + public static final int COMPAT_DOWNLOAD = 1; private static final String TAG = "AndroidacyWebAPI"; private static final int MAX_COMPAT_MODE = 1; private final AndroidacyActivity activity; @@ -46,6 +48,8 @@ public class AndroidacyWebAPI { boolean downloadMode; int effectiveCompatMode; int notifiedCompatMode; + String nonceToken; + Runnable nonceTask; public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { this.activity = activity; @@ -505,15 +509,25 @@ public class AndroidacyWebAPI { } } + @JavascriptInterface + public void setNonceToken(String nonceToken) { + this.nonceToken = nonceToken; + Runnable nonceTask = this.nonceTask; + if (nonceTask != null) { + this.nonceTask = null; + nonceTask.run(); + } + } + // Androidacy feature level declaration method @JavascriptInterface public void notifyCompatUnsupported() { - this.notifyCompatModeRaw(0); + this.notifyCompatModeRaw(COMPAT_UNSUPPORTED); } @JavascriptInterface public void notifyCompatDownloadButton() { - this.notifyCompatModeRaw(1); + this.notifyCompatModeRaw(COMPAT_DOWNLOAD); } } diff --git a/app/src/main/java/com/fox2code/mmm/repo/CustomRepoData.java b/app/src/main/java/com/fox2code/mmm/repo/CustomRepoData.java index 875a4e1..94ae434 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/CustomRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/CustomRepoData.java @@ -10,7 +10,6 @@ import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.List; public final class CustomRepoData extends RepoData { boolean loadedExternal; diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index fdd4f45..ef41099 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -47,10 +47,9 @@ public class RepoData extends XRepo { this.cachedPreferences = cachedPreferences; this.metaDataCache = new File(cacheRoot, "modules.json"); this.moduleHashMap = new HashMap<>(); - this.name = this.url; // Set url as default name + this.defaultName = url; // Set url as default name this.enabled = !this.isLimited() && MainApplication.getSharedPreferences() .getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault()); - this.defaultName = url; this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/"; if (!this.cacheRoot.isDirectory()) { this.cacheRoot.mkdirs(); diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 8edee2a..3fc42a6 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Log; -import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.XHooks; import com.fox2code.mmm.androidacy.AndroidacyRepoData; @@ -92,7 +91,11 @@ public final class RepoManager { this.androidacyRepoData = this.addAndroidacyRepoData(); this.customRepoManager = new CustomRepoManager(mainApplication, this); // Populate default cache + boolean x = false; for (RepoData repoData:this.repoData.values()) { + if (repoData == this.androidacyRepoData) { + if (x) return; x = true; + } this.populateDefaultCache(repoData); } this.initialized = true; @@ -319,12 +322,12 @@ public final class RepoManager { new CustomRepoData(url, cacheRoot, sharedPreferences) : new RepoData(url, cacheRoot, sharedPreferences); if (fallBackName != null && !fallBackName.isEmpty()) { + repoData.defaultName = fallBackName; if (repoData instanceof CustomRepoData) { ((CustomRepoData) repoData).loadedExternal = true; this.customRepoManager.dirty = true; repoData.updateEnabledState(); } - repoData.defaultName = fallBackName; } switch (url) { case MAGISK_REPO: diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java index 74d8e30..cd5c301 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java @@ -13,7 +13,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Set; public class RepoUpdater { private static final String TAG = "RepoUpdater"; diff --git a/app/src/main/java/com/fox2code/mmm/utils/Http.java b/app/src/main/java/com/fox2code/mmm/utils/Http.java index d229acd..c36472d 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Http.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Http.java @@ -2,7 +2,6 @@ package com.fox2code.mmm.utils; import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Resources; import android.os.Build; import android.system.ErrnoException; import android.system.Os; @@ -55,6 +54,8 @@ public class Http { private static final OkHttpClient httpClientDoH; private static final OkHttpClient httpClientWithCache; private static final OkHttpClient httpClientWithCacheDoH; + private static final OkHttpClient httpClientNoRedirect; + private static final OkHttpClient httpClientNoRedirectDoH; private static final FallBackDNS fallbackDNS; private static final CookieJar cookieJar; private static final String androidacyUA; @@ -152,24 +153,36 @@ public class Http { hasWebView = cookieManager != null; httpclientBuilder.cookieJar(cookieJar = new CDNCookieJar(cookieManager)); httpclientBuilder.dns(Dns.SYSTEM); - httpClient = httpclientBuilder.build(); + httpClient = followRedirects(httpclientBuilder, true).build(); + httpClientNoRedirect = followRedirects(httpclientBuilder, false).build(); httpclientBuilder.dns(fallbackDNS); - httpClientDoH = httpclientBuilder.build(); + httpClientDoH = followRedirects(httpclientBuilder, true).build(); + httpClientNoRedirectDoH = followRedirects(httpclientBuilder, false).build(); httpclientBuilder.cache(new Cache( new File(mainApplication.getCacheDir(), "http_cache"), 16L * 1024L * 1024L)); // 16Mib of cache httpclientBuilder.dns(Dns.SYSTEM); - httpClientWithCache = httpclientBuilder.build(); + httpClientWithCache = followRedirects(httpclientBuilder, true).build(); httpclientBuilder.dns(fallbackDNS); - httpClientWithCacheDoH = httpclientBuilder.build(); + httpClientWithCacheDoH = followRedirects(httpclientBuilder, true).build(); Log.i(TAG, "Initialized Http successfully!"); doh = MainApplication.isDohEnabled(); } + private static OkHttpClient.Builder followRedirects( + OkHttpClient.Builder builder, boolean followRedirects) { + return builder.followRedirects(followRedirects) + .followSslRedirects(followRedirects); + } + public static OkHttpClient getHttpClient() { return doh ? httpClientDoH : httpClient; } + public static OkHttpClient getHttpClientNoRedirect() { + return doh ? httpClientNoRedirectDoH : httpClientNoRedirect; + } + public static OkHttpClient getHttpClientWithCache() { return doh ? httpClientWithCacheDoH : httpClientWithCache; } @@ -194,15 +207,29 @@ public class Http { } public static byte[] doHttpPost(String url,String data,boolean allowCache) throws IOException { - Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall( + return (byte[]) doHttpPostRaw(url, data, allowCache, false); + } + + public static String doHttpPostRedirect(String url, String data, boolean allowCache) throws IOException { + return (String) doHttpPostRaw(url, data, allowCache, true); + } + + private static Object doHttpPostRaw(String url,String data, boolean allowCache, + boolean isRedirect) throws IOException { + Response response = (isRedirect ? getHttpClientNoRedirect() : + allowCache ? getHttpClientWithCache() : getHttpClient()).newCall( new Request.Builder().url(url).post(JsonRequestBody.from(data)) .header("Content-Type", "application/json").build() ).execute(); + if (isRedirect && response.isRedirect()) { + return response.request().url().uri().toString(); + } // 200/204 == success, 304 == cache valid if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { throw new IOException("Received error code: "+ response.code()); } + if (isRedirect) return url; ResponseBody responseBody = response.body(); // Use cache api if used cached response if (responseBody == null && response.code() == 304) {