Try fixing staging Androidacy. (And fail)

This commit is contained in:
Fox2Code 2022-07-28 13:31:25 +02:00
parent c63f0b75a2
commit ded15c0194
12 changed files with 157 additions and 44 deletions

View File

@ -16,7 +16,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView; import androidx.cardview.widget.CardView;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;

View File

@ -17,16 +17,10 @@ import androidx.annotation.StyleRes;
import androidx.emoji2.text.DefaultEmojiCompatConfig; import androidx.emoji2.text.DefaultEmojiCompatConfig;
import androidx.emoji2.text.EmojiCompat; import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.FontRequestEmojiCompatConfig; 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.FoxActivity;
import com.fox2code.foxcompat.FoxApplication; import com.fox2code.foxcompat.FoxApplication;
import com.fox2code.foxcompat.FoxThemeWrapper; import com.fox2code.foxcompat.FoxThemeWrapper;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.GMSProviderInstaller; import com.fox2code.mmm.utils.GMSProviderInstaller;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
@ -37,7 +31,6 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeUnit;
import io.noties.markwon.Markwon; import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin; import io.noties.markwon.html.HtmlPlugin;

View File

@ -8,7 +8,6 @@ import android.webkit.WebView;
import androidx.annotation.Keep; import androidx.annotation.Keep;
import com.fox2code.mmm.manager.ModuleManager; import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoData;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
/** /**

View File

@ -31,9 +31,14 @@ import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R; import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks; import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper; import com.fox2code.mmm.utils.IntentHelper;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
/** /**
@ -134,6 +139,8 @@ public class AndroidacyActivity extends FoxActivity {
// Don't open non Androidacy urls inside WebView // Don't open non Androidacy urls inside WebView
if (request.isForMainFrame() && if (request.isForMainFrame() &&
!AndroidacyUtil.isAndroidacyLink(request.getUrl())) { !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()); IntentHelper.openUri(view.getContext(), request.getUrl().toString());
return true; return true;
} }
@ -152,6 +159,7 @@ public class AndroidacyActivity extends FoxActivity {
private void onReceivedError(String url, int errorCode) { private void onReceivedError(String url, int errorCode) {
if ((url.startsWith("https://api.androidacy.com/magisk/") || 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)) { url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) {
Toast.makeText(AndroidacyActivity.this, Toast.makeText(AndroidacyActivity.this,
"Too many requests!", Toast.LENGTH_LONG).show(); "Too many requests!", Toast.LENGTH_LONG).show();
@ -188,38 +196,89 @@ public class AndroidacyActivity extends FoxActivity {
@Override @Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) { public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
switch (consoleMessage.messageLevel()) { if (BuildConfig.DEBUG) {
case TIP: switch (consoleMessage.messageLevel()) {
Log.v(TAG, consoleMessage.message()); case TIP:
break; Log.v(TAG, consoleMessage.message());
case LOG: break;
Log.i(TAG, consoleMessage.message()); case LOG:
break; Log.i(TAG, consoleMessage.message());
case WARNING: break;
Log.w(TAG, consoleMessage.message()); case WARNING:
break; Log.w(TAG, consoleMessage.message());
case ERROR: break;
Log.e(TAG, consoleMessage.message()); case ERROR:
break; Log.e(TAG, consoleMessage.message());
case DEBUG: break;
Log.d(TAG, consoleMessage.message()); case DEBUG:
break; Log.d(TAG, consoleMessage.message());
break;
}
} }
return super.onConsoleMessage(consoleMessage); return super.onConsoleMessage(consoleMessage);
} }
}); });
this.webView.setDownloadListener(( this.webView.setDownloadListener((
downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> { downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> {
if (AndroidacyUtil.isAndroidacyLink(downloadUrl)) { if (AndroidacyUtil.isAndroidacyLink(downloadUrl) && !this.backOnResume) {
AndroidacyWebAPI androidacyWebAPI = this.androidacyWebAPI; AndroidacyWebAPI androidacyWebAPI = this.androidacyWebAPI;
if (androidacyWebAPI != null) { if (androidacyWebAPI != null) {
if (androidacyWebAPI.consumedAction && !androidacyWebAPI.downloadMode) { if (!androidacyWebAPI.downloadMode) {
return; // Native module popup may cause download after consumed action 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.consumedAction = true;
androidacyWebAPI.downloadMode = false; androidacyWebAPI.downloadMode = false;
} else if (this.backOnResume) return; }
this.backOnResume = true; 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); IntentHelper.openCustomTab(this, downloadUrl);
} }
}); });

View File

@ -29,7 +29,7 @@ import okhttp3.Cookie;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@SuppressWarnings("KotlinInternalInJava") @SuppressWarnings("KotlinInternalInJava")
public class AndroidacyRepoData extends RepoData { public final class AndroidacyRepoData extends RepoData {
private static final String TAG = "AndroidacyRepoData"; private static final String TAG = "AndroidacyRepoData";
private static final HttpUrl OK_HTTP_URL; private static final HttpUrl OK_HTTP_URL;
static { static {
@ -284,6 +284,10 @@ public class AndroidacyRepoData extends RepoData {
// Do not inject token for non Androidacy urls // Do not inject token for non Androidacy urls
if (!AndroidacyUtil.isAndroidacyLink(url)) if (!AndroidacyUtil.isAndroidacyLink(url))
return 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; String token = "token=" + this.token;
if (!url.contains(token)) { if (!url.contains(token)) {
if (url.lastIndexOf('/') < url.lastIndexOf('?')) { if (url.lastIndexOf('/') < url.lastIndexOf('?')) {
@ -300,4 +304,8 @@ public class AndroidacyRepoData extends RepoData {
public String getName() { public String getName() {
return this.testMode ? super.getName() + " (Test Mode)" : super.getName(); return this.testMode ? super.getName() + " (Test Mode)" : super.getName();
} }
String getToken() {
return this.token;
}
} }

View File

@ -23,4 +23,18 @@ public class AndroidacyUtil {
url.substring(8, i).endsWith(".androidacy.com") && url.substring(8, i).endsWith(".androidacy.com") &&
uri.getHost().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) +
"<token>";
} else {
return url.substring(0, i + 6) +
"<token>" + url.substring(i2);
}
}
} }

View File

@ -37,6 +37,8 @@ import java.nio.charset.StandardCharsets;
@Keep @Keep
public class AndroidacyWebAPI { 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 String TAG = "AndroidacyWebAPI";
private static final int MAX_COMPAT_MODE = 1; private static final int MAX_COMPAT_MODE = 1;
private final AndroidacyActivity activity; private final AndroidacyActivity activity;
@ -46,6 +48,8 @@ public class AndroidacyWebAPI {
boolean downloadMode; boolean downloadMode;
int effectiveCompatMode; int effectiveCompatMode;
int notifiedCompatMode; int notifiedCompatMode;
String nonceToken;
Runnable nonceTask;
public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) { public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) {
this.activity = activity; 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 // Androidacy feature level declaration method
@JavascriptInterface @JavascriptInterface
public void notifyCompatUnsupported() { public void notifyCompatUnsupported() {
this.notifyCompatModeRaw(0); this.notifyCompatModeRaw(COMPAT_UNSUPPORTED);
} }
@JavascriptInterface @JavascriptInterface
public void notifyCompatDownloadButton() { public void notifyCompatDownloadButton() {
this.notifyCompatModeRaw(1); this.notifyCompatModeRaw(COMPAT_DOWNLOAD);
} }
} }

View File

@ -10,7 +10,6 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
public final class CustomRepoData extends RepoData { public final class CustomRepoData extends RepoData {
boolean loadedExternal; boolean loadedExternal;

View File

@ -47,10 +47,9 @@ public class RepoData extends XRepo {
this.cachedPreferences = cachedPreferences; this.cachedPreferences = cachedPreferences;
this.metaDataCache = new File(cacheRoot, "modules.json"); this.metaDataCache = new File(cacheRoot, "modules.json");
this.moduleHashMap = new HashMap<>(); 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() this.enabled = !this.isLimited() && MainApplication.getSharedPreferences()
.getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault()); .getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault());
this.defaultName = url;
this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/"; this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/";
if (!this.cacheRoot.isDirectory()) { if (!this.cacheRoot.isDirectory()) {
this.cacheRoot.mkdirs(); this.cacheRoot.mkdirs();

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.XHooks; import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.androidacy.AndroidacyRepoData; import com.fox2code.mmm.androidacy.AndroidacyRepoData;
@ -92,7 +91,11 @@ public final class RepoManager {
this.androidacyRepoData = this.addAndroidacyRepoData(); this.androidacyRepoData = this.addAndroidacyRepoData();
this.customRepoManager = new CustomRepoManager(mainApplication, this); this.customRepoManager = new CustomRepoManager(mainApplication, this);
// Populate default cache // Populate default cache
boolean x = false;
for (RepoData repoData:this.repoData.values()) { for (RepoData repoData:this.repoData.values()) {
if (repoData == this.androidacyRepoData) {
if (x) return; x = true;
}
this.populateDefaultCache(repoData); this.populateDefaultCache(repoData);
} }
this.initialized = true; this.initialized = true;
@ -319,12 +322,12 @@ public final class RepoManager {
new CustomRepoData(url, cacheRoot, sharedPreferences) : new CustomRepoData(url, cacheRoot, sharedPreferences) :
new RepoData(url, cacheRoot, sharedPreferences); new RepoData(url, cacheRoot, sharedPreferences);
if (fallBackName != null && !fallBackName.isEmpty()) { if (fallBackName != null && !fallBackName.isEmpty()) {
repoData.defaultName = fallBackName;
if (repoData instanceof CustomRepoData) { if (repoData instanceof CustomRepoData) {
((CustomRepoData) repoData).loadedExternal = true; ((CustomRepoData) repoData).loadedExternal = true;
this.customRepoManager.dirty = true; this.customRepoManager.dirty = true;
repoData.updateEnabledState(); repoData.updateEnabledState();
} }
repoData.defaultName = fallBackName;
} }
switch (url) { switch (url) {
case MAGISK_REPO: case MAGISK_REPO:

View File

@ -13,7 +13,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
public class RepoUpdater { public class RepoUpdater {
private static final String TAG = "RepoUpdater"; private static final String TAG = "RepoUpdater";

View File

@ -2,7 +2,6 @@ package com.fox2code.mmm.utils;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
@ -55,6 +54,8 @@ public class Http {
private static final OkHttpClient httpClientDoH; private static final OkHttpClient httpClientDoH;
private static final OkHttpClient httpClientWithCache; private static final OkHttpClient httpClientWithCache;
private static final OkHttpClient httpClientWithCacheDoH; private static final OkHttpClient httpClientWithCacheDoH;
private static final OkHttpClient httpClientNoRedirect;
private static final OkHttpClient httpClientNoRedirectDoH;
private static final FallBackDNS fallbackDNS; private static final FallBackDNS fallbackDNS;
private static final CookieJar cookieJar; private static final CookieJar cookieJar;
private static final String androidacyUA; private static final String androidacyUA;
@ -152,24 +153,36 @@ public class Http {
hasWebView = cookieManager != null; hasWebView = cookieManager != null;
httpclientBuilder.cookieJar(cookieJar = new CDNCookieJar(cookieManager)); httpclientBuilder.cookieJar(cookieJar = new CDNCookieJar(cookieManager));
httpclientBuilder.dns(Dns.SYSTEM); httpclientBuilder.dns(Dns.SYSTEM);
httpClient = httpclientBuilder.build(); httpClient = followRedirects(httpclientBuilder, true).build();
httpClientNoRedirect = followRedirects(httpclientBuilder, false).build();
httpclientBuilder.dns(fallbackDNS); httpclientBuilder.dns(fallbackDNS);
httpClientDoH = httpclientBuilder.build(); httpClientDoH = followRedirects(httpclientBuilder, true).build();
httpClientNoRedirectDoH = followRedirects(httpclientBuilder, false).build();
httpclientBuilder.cache(new Cache( httpclientBuilder.cache(new Cache(
new File(mainApplication.getCacheDir(), "http_cache"), new File(mainApplication.getCacheDir(), "http_cache"),
16L * 1024L * 1024L)); // 16Mib of cache 16L * 1024L * 1024L)); // 16Mib of cache
httpclientBuilder.dns(Dns.SYSTEM); httpclientBuilder.dns(Dns.SYSTEM);
httpClientWithCache = httpclientBuilder.build(); httpClientWithCache = followRedirects(httpclientBuilder, true).build();
httpclientBuilder.dns(fallbackDNS); httpclientBuilder.dns(fallbackDNS);
httpClientWithCacheDoH = httpclientBuilder.build(); httpClientWithCacheDoH = followRedirects(httpclientBuilder, true).build();
Log.i(TAG, "Initialized Http successfully!"); Log.i(TAG, "Initialized Http successfully!");
doh = MainApplication.isDohEnabled(); doh = MainApplication.isDohEnabled();
} }
private static OkHttpClient.Builder followRedirects(
OkHttpClient.Builder builder, boolean followRedirects) {
return builder.followRedirects(followRedirects)
.followSslRedirects(followRedirects);
}
public static OkHttpClient getHttpClient() { public static OkHttpClient getHttpClient() {
return doh ? httpClientDoH : httpClient; return doh ? httpClientDoH : httpClient;
} }
public static OkHttpClient getHttpClientNoRedirect() {
return doh ? httpClientNoRedirectDoH : httpClientNoRedirect;
}
public static OkHttpClient getHttpClientWithCache() { public static OkHttpClient getHttpClientWithCache() {
return doh ? httpClientWithCacheDoH : httpClientWithCache; return doh ? httpClientWithCacheDoH : httpClientWithCache;
} }
@ -194,15 +207,29 @@ public class Http {
} }
public static byte[] doHttpPost(String url,String data,boolean allowCache) throws IOException { 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)) new Request.Builder().url(url).post(JsonRequestBody.from(data))
.header("Content-Type", "application/json").build() .header("Content-Type", "application/json").build()
).execute(); ).execute();
if (isRedirect && response.isRedirect()) {
return response.request().url().uri().toString();
}
// 200/204 == success, 304 == cache valid // 200/204 == success, 304 == cache valid
if (response.code() != 200 && response.code() != 204 && if (response.code() != 200 && response.code() != 204 &&
(response.code() != 304 || !allowCache)) { (response.code() != 304 || !allowCache)) {
throw new IOException("Received error code: "+ response.code()); throw new IOException("Received error code: "+ response.code());
} }
if (isRedirect) return url;
ResponseBody responseBody = response.body(); ResponseBody responseBody = response.body();
// Use cache api if used cached response // Use cache api if used cached response
if (responseBody == null && response.code() == 304) { if (responseBody == null && response.code() == 304) {