Overall optimizations

- Clarify some failures
- Optimize build times
- Update dependencies
- Make absolute sure sentry doesn't send PII

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/213/head
androidacy-user 2 years ago
parent adc7ddb731
commit ff1afd0416

@ -80,7 +80,7 @@ aboutLibraries {
sentry {
// Disable sentry on F-Droid flavor
ignoredFlavors = [ "fdroid" ]
ignoredFlavors = ["fdroid"]
// Disables or enables the handling of Proguard mapping for Sentry.
// If enabled the plugin will generate a UUID and will take care of
@ -116,7 +116,7 @@ sentry {
// Does auto instrumentation for specified features through bytecode manipulation.
// Default is enabled.
tracingInstrumentation {
enabled = false
enabled = true
}
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
@ -134,7 +134,7 @@ sentry {
// as Gradle will resolve it to the latest version.
//
// Defaults to the latest published sentry version.
sentryVersion = '6.4.1'
sentryVersion = '6.5.0'
}
}
@ -153,7 +153,7 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.webkit:webkit:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}"
implementation "com.mikepenz:aboutlibraries:10.5.0"
implementation "dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0"
implementation "dev.rikka.rikkax.insets:insets:1.3.0"
implementation 'com.github.Dimezis:BlurView:version-2.0.2'
@ -162,18 +162,18 @@ dependencies {
// Utils
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.10'
implementation 'com.github.topjohnwu.libsu:io:5.0.1'
implementation 'com.github.Fox2Code:RosettaX:1.0.9'
implementation 'com.github.Fox2Code:AndroidANSI:1.0.1'
// Error reporting
defaultImplementation 'io.sentry:sentry-android:6.4.1'
defaultImplementation 'io.sentry:sentry-android-fragment:6.4.1'
defaultImplementation 'io.sentry:sentry-android-okhttp:6.4.1'
defaultImplementation 'io.sentry:sentry-android-core:6.4.1'
defaultImplementation 'io.sentry:sentry-android-ndk:6.4.1'
defaultImplementation 'io.sentry:sentry-android:6.5.0'
defaultImplementation 'io.sentry:sentry-android-fragment:6.5.0'
defaultImplementation 'io.sentry:sentry-android-okhttp:6.5.0'
defaultImplementation 'io.sentry:sentry-android-core:6.5.0'
defaultImplementation 'io.sentry:sentry-android-ndk:6.5.0'
// Markdown
implementation "io.noties.markwon:core:4.6.2"
@ -184,13 +184,13 @@ dependencies {
implementation "com.caverock:androidsvg:1.4"
// Test
testImplementation 'junit:junit:4.+'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
if (hasSentryConfig) {
Properties properties = new Properties();
Properties properties = new Properties()
try (FileInputStream fis = new FileInputStream(sentryConfigFile)) {
properties.load(fis)
}

@ -4,7 +4,9 @@
tools:ignore="QueryAllPackagesPermission">
<application android:icon="@mipmap/ic_launcher">
<meta-data android:name="io.sentry.auto-init" android:value="false" />
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />
<meta-data
android:name="io.sentry.dsn"
android:value="https://cdcdb0efca4a42a28df90e4b7f087347@sentry.androidacy.com/2" />
@ -20,5 +22,13 @@
<meta-data
android:name="io.sentry.attach-screenshot"
android:value="true" />
<!-- Just the current activity at the time of the crash -->
<meta-data
android:name="io.sentry.attach-stacktrace"
android:value="true" />
<!-- Don't send PII, this is actually default but let's be explicit -->
<meta-data
android:name="io.sentry.sendDefaultPii"
android:value="false" />
</application>
</manifest>

@ -135,7 +135,10 @@ public final class AndroidacyActivity extends FoxActivity {
webSettings.setUserAgentString(Http.getAndroidacyUA());
webSettings.setDomStorageEnabled(true);
webSettings.setJavaScriptEnabled(true);
// Disable cache
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setAllowFileAccess(false);
webSettings.setAllowContentAccess(false);
// Attempt at fixing CloudFlare captcha.
if (WebViewFeature.isFeatureSupported(WebViewFeature.REQUESTED_WITH_HEADER_CONTROL)) {
WebSettingsCompat.setRequestedWithHeaderMode(
@ -448,6 +451,7 @@ public final class AndroidacyActivity extends FoxActivity {
fileOutputStream.write(module);
}
} finally {
//noinspection UnusedAssignment
module = null;
this.runOnUiThread(() ->
progressIndicator.setVisibility(View.INVISIBLE));

@ -3,9 +3,11 @@ package com.fox2code.mmm.androidacy;
import android.content.SharedPreferences;
import android.util.Log;
import android.webkit.CookieManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoData;
@ -31,27 +33,24 @@ public final class AndroidacyRepoData extends RepoData {
private static final String TAG = "AndroidacyRepoData";
static {
HttpUrl.Builder OK_HTTP_URL_BUILDER =
new HttpUrl.Builder().scheme("https");
HttpUrl.Builder OK_HTTP_URL_BUILDER = new HttpUrl.Builder().scheme("https");
// Using HttpUrl.Builder.host(String) crash the app
OK_HTTP_URL_BUILDER.setHost$okhttp(".androidacy.com");
OK_HTTP_URL_BUILDER.build();
}
// Avoid spamming requests to Androidacy
private long androidacyBlockade = 0;
private String token = null;
private final boolean testMode;
private final String host;
// Avoid spamming requests to Androidacy
private long androidacyBlockade = 0;
private String token = this.cachedPreferences.getString("pref_androidacy_api_token", null);
public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences,
boolean testMode) {
super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT :
RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences);
public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, boolean testMode) {
super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences);
if (this.metaDataCache.exists() && !testMode) {
this.androidacyBlockade = this.metaDataCache.lastModified() + 30_000L;
if (this.androidacyBlockade - 60_000L > System.currentTimeMillis()) {
this.androidacyBlockade = 0; // Don't allow time travel. Well why not???
this.androidacyBlockade = 0; // Don't allow time travel. // Well why not???
}
}
this.defaultName = "Androidacy Modules Repo";
@ -67,12 +66,18 @@ public final class AndroidacyRepoData extends RepoData {
return RepoManager.getINSTANCE().getAndroidacyRepoData();
}
private static String filterURL(String url) {
if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) {
return null;
}
return url;
}
public <string> boolean isValidToken(string token) {
try {
Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token, false);
} catch (Exception e) {
if ("Received error code: 419".equals(e.getMessage()) ||
"Received error code: 429".equals(e.getMessage())) {
if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage())) {
Log.e(TAG, "We are being rate limited!", e);
long time = System.currentTimeMillis();
this.androidacyBlockade = time + 3_600_000L;
@ -92,37 +97,52 @@ public final class AndroidacyRepoData extends RepoData {
@Override
protected boolean prepare() {
// Implementation details discussed on telegram
// DEPRECATED. Please switch to new implementation before v7 hits production
// First, ping the server to check if it's alive
try {
Http.doHttpGet("https://" + this.host + "/ping", false);
} catch (Exception e) {
Log.e(TAG, "Failed to ping server", e);
// Inform user
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show();
return false;
}
long time = System.currentTimeMillis();
if (this.androidacyBlockade > time) return false;
this.androidacyBlockade = time + 30_000L;
// Get token from androidacy_api_token shared preference
String token = this.cachedPreferences.getString("androidacy_api_token", null);
if (token != null) {
this.token = token;
if (!isValidToken(token)) {
token = null;
if (this.token == null) {
this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null);
if (this.token != null && !this.isValidToken(this.token)) {
this.token = null;
}
} else if (!this.isValidToken(this.token)) {
this.token = null;
}
if (token == null) {
try {
Log.i(TAG, "Refreshing token...");
// POST request to https://production-api.androidacy.com/auth/register
token = new String(Http.doHttpPost(
"https://" + this.host + "/auth/register",
"foxmmm=true", true), StandardCharsets.UTF_8);
token = new String(Http.doHttpPost("https://" + this.host + "/auth/register", "foxmmm=true", false), StandardCharsets.UTF_8);
// Parse token
JSONObject jsonObject = new JSONObject(token);
token = jsonObject.getString("token");
try {
JSONObject jsonObject = new JSONObject(token);
token = jsonObject.getString("token");
} catch (JSONException e) {
Log.e(TAG, "Failed to parse token", e);
// Show a toast
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_parse_token, Toast.LENGTH_LONG).show();
return false;
}
// Ensure token is valid
if (!isValidToken(token)) {
Log.e(TAG, "Failed to validate token");
// Show a toast
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_validate_token, Toast.LENGTH_LONG).show();
return false;
}
// Save token to shared preference
SharedPreferences.Editor editor = this.cachedPreferences.edit();
editor.putString("androidacy_api_token", token);
editor.apply();
MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", token).apply();
} catch (Exception e) {
if ("Received error code: 419".equals(e.getMessage()) ||
"Received error code: 429".equals(e.getMessage()) ||
"Received error code: 503".equals(e.getMessage())
) {
if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage()) || "Received error code: 503".equals(e.getMessage())) {
Log.e(TAG, "We are being rate limited!", e);
this.androidacyBlockade = time + 3_600_000L;
}
@ -130,6 +150,7 @@ public final class AndroidacyRepoData extends RepoData {
return false;
}
}
//noinspection SillyAssignment // who are you calling silly?
this.token = token;
return true;
}
@ -138,10 +159,8 @@ public final class AndroidacyRepoData extends RepoData {
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException {
if (!jsonObject.getString("status").equals("success"))
throw new JSONException("Response is not a success!");
String name = jsonObject.optString(
"name", "Androidacy Modules Repo");
String nameForModules = name.endsWith(" (Official)") ?
name.substring(0, name.length() - 11) : name;
String name = jsonObject.optString("name", "Androidacy Modules Repo");
String nameForModules = name.endsWith(" (Official)") ? name.substring(0, name.length() - 11) : name;
JSONArray jsonArray = jsonObject.getJSONArray("data");
for (RepoModule repoModule : this.moduleHashMap.values()) {
repoModule.processed = false;
@ -153,8 +172,8 @@ public final class AndroidacyRepoData extends RepoData {
jsonObject = jsonArray.getJSONObject(i);
String moduleId = jsonObject.getString("codename");
// Deny remote modules ids shorter than 3 chars or containing null char or space
if (moduleId.length() < 3 || moduleId.indexOf('\0') != -1 ||
moduleId.indexOf(' ') != -1 || "ak3-helper".equals(moduleId)) continue;
if (moduleId.length() < 3 || moduleId.indexOf('\0') != -1 || moduleId.indexOf(' ') != -1 || "ak3-helper".equals(moduleId))
continue;
long lastUpdate = jsonObject.getLong("updated_at") * 1000;
lastLastUpdate = Math.max(lastLastUpdate, lastUpdate);
RepoModule repoModule = this.moduleHashMap.get(moduleId);
@ -171,10 +190,8 @@ public final class AndroidacyRepoData extends RepoData {
repoModule.processed = true;
repoModule.lastUpdated = lastUpdate;
repoModule.repoName = nameForModules;
repoModule.zipUrl = filterURL(
jsonObject.optString("zipUrl", ""));
repoModule.notesUrl = filterURL(
jsonObject.optString("notesUrl", ""));
repoModule.zipUrl = filterURL(jsonObject.optString("zipUrl", ""));
repoModule.notesUrl = filterURL(jsonObject.optString("notesUrl", ""));
if (repoModule.zipUrl == null) {
repoModule.zipUrl = // Fallback url in case the API doesn't have zipUrl
"https://" + this.host + "/magisk/info/" + moduleId;
@ -192,8 +209,7 @@ public final class AndroidacyRepoData extends RepoData {
ModuleInfo moduleInfo = repoModule.moduleInfo;
moduleInfo.name = jsonObject.getString("name");
moduleInfo.versionCode = jsonObject.getLong("versionCode");
moduleInfo.version = jsonObject.optString(
"version", "v" + moduleInfo.versionCode);
moduleInfo.version = jsonObject.optString("version", "v" + moduleInfo.versionCode);
moduleInfo.author = jsonObject.optString("author", "Unknown");
moduleInfo.description = jsonObject.optString("description", "");
moduleInfo.minApi = jsonObject.getInt("minApi");
@ -205,8 +221,7 @@ public final class AndroidacyRepoData extends RepoData {
moduleInfo.minMagisk = Integer.parseInt(minMagisk);
} else {
moduleInfo.minMagisk = // Allow 24.1 to mean 24100
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) +
(Integer.parseInt(minMagisk.substring(c + 1)) * 100);
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) + (Integer.parseInt(minMagisk.substring(c + 1)) * 100);
}
} catch (Exception e) {
moduleInfo.minMagisk = 0;
@ -219,8 +234,7 @@ public final class AndroidacyRepoData extends RepoData {
String config = jsonObject.optString("config", "");
moduleInfo.config = config.isEmpty() ? null : config;
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks
Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " +
moduleInfo.version + " " + moduleInfo.versionCode);
Log.d(TAG, "Module " + moduleInfo.name + " " + moduleInfo.id + " " + moduleInfo.version + " " + moduleInfo.versionCode);
}
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
while (moduleInfoIterator.hasNext()) {
@ -240,13 +254,6 @@ public final class AndroidacyRepoData extends RepoData {
return newModules;
}
private static String filterURL(String url) {
if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) {
return null;
}
return url;
}
@Override
public void storeMetadata(RepoModule repoModule, byte[] data) {
}
@ -254,34 +261,30 @@ public final class AndroidacyRepoData extends RepoData {
@Override
public boolean tryLoadMetadata(RepoModule repoModule) {
if (this.moduleHashMap.containsKey(repoModule.id)) {
repoModule.moduleInfo.flags &=
~ModuleInfo.FLAG_METADATA_INVALID;
repoModule.moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID;
return true;
}
repoModule.moduleInfo.flags |=
ModuleInfo.FLAG_METADATA_INVALID;
repoModule.moduleInfo.flags |= ModuleInfo.FLAG_METADATA_INVALID;
return false;
}
@Override
public String getUrl() {
return this.token == null ? this.url :
this.url + "?token=" + this.token;
return this.token == null ? this.url : this.url + "?token=" + this.token;
}
private String injectToken(String url) {
// Do not inject token for non Androidacy urls
if (!AndroidacyUtil.isAndroidacyLink(url))
return url;
if (!AndroidacyUtil.isAndroidacyLink(url)) return url;
if (this.testMode) {
if (url.startsWith("https://api.androidacy.com/")) {
if (url.startsWith("https://production-api.androidacy.com/")) {
Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url));
url = "https://staging-api.androidacy.com/" + url.substring(27);
}
} else {
if (url.startsWith("https://staging-api.androidacy.com/")) {
Log.e(TAG, "Got test mode url: " + AndroidacyUtil.hideToken(url));
url = "https://api.androidacy.com/" + url.substring(35);
url = "https://production-api.androidacy.com/" + url.substring(35);
}
}
String token = "token=" + this.token;
@ -307,9 +310,8 @@ public final class AndroidacyRepoData extends RepoData {
void setToken(String token) {
if (Http.hasWebView()) {
CookieManager.getInstance().setCookie("https://.androidacy.com/",
"USER=" + token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" +
" path=/; secure; domain=.androidacy.com");
// TODO: Figure out why this is needed
CookieManager.getInstance().setCookie("https://.androidacy.com/", "USER=" + token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" + " path=/; secure; domain=.androidacy.com");
this.token = token;
}
}

@ -53,6 +53,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@SuppressWarnings("IOStreamConstructor")
public class InstallerActivity extends FoxActivity {
private static final String TAG = "InstallerActivity";
public LinearProgressIndicator progressIndicator;
@ -156,8 +157,9 @@ public class InstallerActivity extends FoxActivity {
!new SuFile(moduleCache.getAbsolutePath()).delete())
Log.e(TAG, "Failed to delete module cache");
String errMessage = "Failed to download module zip";
// Set this to the error message if it's a HTTP error
byte[] rawModule;
boolean androidacyBlame = false; // In case Androidacy mess-up again...
boolean androidacyBlame = false; // In case Androidacy mess-up again... yeah screw you too jk jk
try {
Log.i(TAG, (urlMode ? "Downloading: " : "Loading: ") + target);
rawModule = urlMode ? Http.doHttpGet(target, (progress, max, done) -> {
@ -254,11 +256,9 @@ public class InstallerActivity extends FoxActivity {
} catch (IOException e) {
Log.e(TAG, errMessage, e);
if (androidacyBlame) {
this.installerTerminal.addLine(
"! Note: The following error is probably an Androidacy backend error");
errMessage += " (" + e.getLocalizedMessage() + ")";
}
this.setInstallStateFinished(false,
"! " + errMessage, "");
this.setInstallStateFinished(false, errMessage, null);
} catch (OutOfMemoryError e) {
//noinspection UnusedAssignment (Important to avoid OutOfMemoryError)
rawModule = null; // Because reference is kept when calling setInstallStateFinished

@ -36,7 +36,6 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import io.sentry.android.okhttp.SentryOkHttpInterceptor;
import okhttp3.Cache;
import okhttp3.Cookie;
import okhttp3.CookieJar;
@ -100,7 +99,7 @@ public class Http {
httpclientBuilder.proxy(Proxy.NO_PROXY); // Do not use system proxy
Dns dns = Dns.SYSTEM;
try {
InetAddress[] cloudflareBootstrap = new InetAddress[] {
InetAddress[] cloudflareBootstrap = new InetAddress[]{
InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"),
InetAddress.getByName("1.1.1.1"),
@ -120,18 +119,18 @@ public class Http {
httpclientBuilder.dns(dns);
httpclientBuilder.cookieJar(new CDNCookieJar());
dns = new DnsOverHttps.Builder().client(httpclientBuilder.build()).url(
Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query")))
Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query")))
.bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build();
} catch (UnknownHostException|RuntimeException e) {
} catch (UnknownHostException | RuntimeException e) {
Log.e(TAG, "Failed to init DoH", e);
}
httpclientBuilder.cookieJar(CookieJar.NO_COOKIES);
// User-Agent format was agreed on telegram
if (hasWebView) {
androidacyUA = WebSettings.getDefaultUserAgent(mainApplication).replace("; wv", "")
+ " FoxMmm/" + BuildConfig.VERSION_CODE;
androidacyUA = WebSettings.getDefaultUserAgent(mainApplication).replace("wv", "FoxMmm" +
"/" + BuildConfig.VERSION_CODE);
} else {
androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE +")" +
androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" +
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36"
+ " FoxMmm/" + BuildConfig.VERSION_CODE;
}
@ -197,7 +196,7 @@ public class Http {
}
@SuppressWarnings("resource")
public static byte[] doHttpGet(String url,boolean allowCache) throws IOException {
public static byte[] doHttpGet(String url, boolean allowCache) throws IOException {
if (!RepoManager.isAndroidacyRepoEnabled() &&
AndroidacyUtil.isAndroidacyLink(url)) {
throw new IOException("Androidacy repo is disabled, blocking url: " + url);
@ -208,19 +207,19 @@ public class Http {
// 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());
throw new IOException("Received error code: " + response.code());
}
ResponseBody responseBody = response.body();
// Use cache api if used cached response
if (responseBody == null && response.code() == 304) {
if (response.code() == 304) {
response = response.cacheResponse();
if (response != null)
responseBody = response.body();
}
return responseBody == null ? new byte[0] : responseBody.bytes();
return responseBody.bytes();
}
public static byte[] doHttpPost(String url,String data,boolean allowCache) throws IOException {
public static byte[] doHttpPost(String url, String data, boolean allowCache) throws IOException {
return (byte[]) doHttpPostRaw(url, data, allowCache, false);
}
@ -229,8 +228,8 @@ public class Http {
}
@SuppressWarnings("resource")
private static Object doHttpPostRaw(String url,String data, boolean allowCache,
boolean isRedirect) throws IOException {
private static Object doHttpPostRaw(String url, String data, boolean allowCache,
boolean isRedirect) throws IOException {
if (!RepoManager.isAndroidacyRepoEnabled() &&
AndroidacyUtil.isAndroidacyLink(url)) {
throw new IOException("Androidacy repo is disabled, blocking url: " + url);
@ -246,20 +245,20 @@ public class Http {
// 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());
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) {
if (response.code() == 304) {
response = response.cacheResponse();
if (response != null)
responseBody = response.body();
}
return responseBody == null ? new byte[0] : responseBody.bytes();
return responseBody.bytes();
}
public static byte[] doHttpGet(String url,ProgressListener progressListener) throws IOException {
public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException {
Log.d("Http", "Progress URL: " + url);
if (!RepoManager.isAndroidacyRepoEnabled() &&
AndroidacyUtil.isAndroidacyLink(url)) {
@ -268,7 +267,7 @@ public class Http {
Response response = getHttpClient().newCall(
new Request.Builder().url(url).get().build()).execute();
if (response.code() != 200 && response.code() != 204) {
throw new IOException("Received error code: "+ response.code());
throw new IOException("Received error code: " + response.code());
}
ResponseBody responseBody = Objects.requireNonNull(response.body());
InputStream inputStream = responseBody.byteStream();
@ -287,7 +286,7 @@ public class Http {
progressListener.onUpdate(0, (int) (target / divider), false);
while (true) {
int read = inputStream.read(buff);
if(read == -1) break;
if (read == -1) break;
byteArrayOutputStream.write(buff, 0, read);
downloaded += read;
currentUpdate = System.currentTimeMillis();
@ -325,15 +324,65 @@ public class Http {
return cookieJar.getAndroidacyCookies(url);
}
public static boolean hasWebView() {
return hasWebView;
}
/**
* Change URL to appropriate url and force Magisk link to use latest version.
*/
public static String updateLink(String string) {
if (string.startsWith("https://cdn.jsdelivr.net/gh/Magisk-Modules-Repo/")) {
String tmp = string.substring(48);
int start = tmp.lastIndexOf('@'),
end = tmp.lastIndexOf('/');
if ((end - 8) <= start) return string; // Skip if not a commit id
return "https://raw.githubusercontent.com/" +
tmp.substring(0, start) + "/master" + string.substring(end);
}
if (string.startsWith("https://github.com/Magisk-Modules-Repo/")) {
int i = string.lastIndexOf("/archive/");
if (i != -1 && string.indexOf('/', i + 9) == -1)
return string.substring(0, i + 9) + "master.zip";
}
return string;
}
/**
* Change GitHub user-content url to jsdelivr url
* (Unused but kept as a documentation)
*/
public static String cdnIfyLink(String string) {
if (string.startsWith("https://raw.githubusercontent.com/")) {
String[] tokens = string.substring(34).split("/", 4);
if (tokens.length != 4) return string;
return "https://cdn.jsdelivr.net/gh/" +
tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3];
}
if (string.startsWith("https://github.com/")) {
int i = string.lastIndexOf("/archive/");
if (i == -1 || string.indexOf('/', i + 9) != -1)
return string; // Not an archive link
String[] tokens = string.substring(19).split("/", 4);
return "https://cdn.jsdelivr.net/gh/" +
tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3];
}
return string;
}
public interface ProgressListener {
void onUpdate(int downloaded, int total, boolean done);
}
/**
* Cookie jar that allow CDN cookies, reset on app relaunch
* Note: An argument can be made that it allow tracking but
* caching is a better attack vector for tracking, this system
* only exist to improve CDN response time, any other cookies
* that are not CDN related are just simply ignored.
*
* <p>
* Note: CDNCookies are only stored in RAM unlike https cache
* */
*/
private static class CDNCookieJar implements CookieJar {
private final HashMap<String, Cookie> cookieMap = new HashMap<>();
private final boolean androidacySupport;
@ -425,17 +474,13 @@ public class Http {
}
}
public interface ProgressListener {
void onUpdate(int downloaded,int total, boolean done);
}
/**
* FallBackDNS store successful DNS request to return them later
* can help make the app to work later when the current DNS system
* isn't functional or available.
*
* <p>
* Note: DNS Cache is stored in user data.
* */
*/
private static class FallBackDNS implements Dns {
private final Dns parent;
private final SharedPreferences sharedPreferences;
@ -450,6 +495,31 @@ public class Http {
this.fallbackCache = new HashMap<>();
}
@NonNull
private static String toString(@NonNull List<InetAddress> inetAddresses) {
if (inetAddresses.isEmpty()) return "";
Iterator<InetAddress> inetAddressIterator = inetAddresses.iterator();
StringBuilder stringBuilder = new StringBuilder();
while (true) {
stringBuilder.append(inetAddressIterator.next().getHostAddress());
if (!inetAddressIterator.hasNext())
return stringBuilder.toString();
stringBuilder.append("|");
}
}
@NonNull
private static List<InetAddress> fromString(@NonNull String string)
throws UnknownHostException {
if (string.isEmpty()) return Collections.emptyList();
String[] strings = string.split("\\|");
ArrayList<InetAddress> inetAddresses = new ArrayList<>(strings.length);
for (String address : strings) {
inetAddresses.add(InetAddress.getByName(address));
}
return inetAddresses;
}
@NonNull
@Override
public List<InetAddress> lookup(@NonNull String s) throws UnknownHostException {
@ -491,36 +561,16 @@ public class Http {
this.fallbackCache.clear();
}
}
@NonNull
private static String toString(@NonNull List<InetAddress> inetAddresses) {
if (inetAddresses.isEmpty()) return "";
Iterator<InetAddress> inetAddressIterator = inetAddresses.iterator();
StringBuilder stringBuilder = new StringBuilder();
while (true) {
stringBuilder.append(inetAddressIterator.next().getHostAddress());
if (!inetAddressIterator.hasNext())
return stringBuilder.toString();
stringBuilder.append("|");
}
}
@NonNull
private static List<InetAddress> fromString(@NonNull String string)
throws UnknownHostException {
if (string.isEmpty()) return Collections.emptyList();
String[] strings = string.split("\\|");
ArrayList<InetAddress> inetAddresses = new ArrayList<>(strings.length);
for (String address : strings) {
inetAddresses.add(InetAddress.getByName(address));
}
return inetAddresses;
}
}
private static class JsonRequestBody extends RequestBody {
private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json");
private static final JsonRequestBody EMPTY = new JsonRequestBody(new byte[0]);
final byte[] data;
private JsonRequestBody(byte[] data) {
this.data = data;
}
static JsonRequestBody from(String data) {
if (data == null || data.length() == 0) {
@ -529,12 +579,6 @@ public class Http {
return new JsonRequestBody(data.getBytes(StandardCharsets.UTF_8));
}
final byte[] data;
private JsonRequestBody(byte[] data) {
this.data = data;
}
@Nullable
@Override
public MediaType contentType() {
@ -551,50 +595,4 @@ public class Http {
bufferedSink.write(this.data);
}
}
public static boolean hasWebView() {
return hasWebView;
}
/**
* Change URL to appropriate url and force Magisk link to use latest version.
*/
public static String updateLink(String string) {
if (string.startsWith("https://cdn.jsdelivr.net/gh/Magisk-Modules-Repo/")) {
String tmp = string.substring(48);
int start = tmp.lastIndexOf('@'),
end = tmp.lastIndexOf('/');
if ((end - 8) <= start) return string; // Skip if not a commit id
return "https://raw.githubusercontent.com/" +
tmp.substring(0, start) + "/master" + string.substring(end);
}
if (string.startsWith("https://github.com/Magisk-Modules-Repo/")) {
int i = string.lastIndexOf("/archive/");
if (i != -1 && string.indexOf('/', i + 9) == -1)
return string.substring(0, i + 9) + "master.zip";
}
return string;
}
/**
* Change GitHub user-content url to jsdelivr url
* (Unused but kept as a documentation)
*/
public static String cdnIfyLink(String string) {
if (string.startsWith("https://raw.githubusercontent.com/")) {
String[] tokens = string.substring(34).split("/", 4);
if (tokens.length != 4) return string;
return "https://cdn.jsdelivr.net/gh/" +
tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3];
}
if (string.startsWith("https://github.com/")) {
int i = string.lastIndexOf("/archive/");
if (i == -1 || string.indexOf('/', i + 9) != -1)
return string; // Not an archive link
String[] tokens = string.substring(19).split("/", 4);
return "https://cdn.jsdelivr.net/gh/" +
tokens[0] + "/" + tokens[1] + "@" + tokens[2] + "/" + tokens[3];
}
return string;
}
}

@ -176,4 +176,7 @@
<string name="crash_reporting_restart_message">The app needs to restart to apply this setting</string>
<string name="restart">Restart</string>
<string name="androidacy_test_mode_disable_warning">App will be restarted to disable staging endpoint</string>
<string name="androidacy_failed_to_parse_token">Could not retrieve token from Androidacy. Please try again later.</string>
<string name="androidacy_failed_to_validate_token">Could not validate token for Androidacy. Please try again later.</string>
<string name="androidacy_server_down">Unable to contact Androidacy server. Check your connection and try again.</string>
</resources>

@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@ -21,3 +21,4 @@ android.enableJetifier=true
# Fox builds props mods
org.gradle.parallel=true
android.enableR8.fullMode=true
org.gradle.unsafe.configuration-cache=true

Loading…
Cancel
Save