Merge branch 'java8' into 'master'

update to Java8 and compileSdkVersion 29

See merge request fdroid/fdroidclient!974
This commit is contained in:
Hans-Christoph Steiner 2021-02-23 19:32:36 +00:00
commit b21dbb8646
19 changed files with 184 additions and 67 deletions

View File

@ -27,6 +27,7 @@ stages:
artifacts: artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths: paths:
- kernel.log
- logcat.txt - logcat.txt
- app/core* - app/core*
- app/*.log - app/*.log
@ -70,6 +71,10 @@ errorprone:
- cat config/errorprone.gradle >> app/build.gradle - cat config/errorprone.gradle >> app/build.gradle
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug - ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
# Run the tests in the emulator. Each step is broken out to run on
# its own since the CI runner can have limited RAM, and the emulator
# can take a while to start.
#
# once these prove stable, the task should be switched to # once these prove stable, the task should be switched to
# connectedCheck to test all the build flavors # connectedCheck to test all the build flavors
.connected-template: &connected-template .connected-template: &connected-template
@ -90,16 +95,14 @@ errorprone:
- wait-for-emulator - wait-for-emulator
- adb devices - adb devices
- adb shell input keyevent 82 & - adb shell input keyevent 82 &
- ./gradlew installFullDebug
- adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity
- if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then - if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest; export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest;
fi fi
- ./gradlew connectedFullDebugAndroidTest $FLAG - ./gradlew connectedFullDebugAndroidTest $FLAG
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|| (adb -e logcat -d > logcat.txt; exit 1)
connected 22 default armeabi-v7a: no-accel 22 default x86:
retry: 1
<<: *test-template <<: *test-template
<<: *connected-template <<: *connected-template
@ -107,15 +110,14 @@ connected 22 default armeabi-v7a:
tags: tags:
- fdroid - fdroid
- kvm - kvm
allow_failure: true only:
variables:
- $RUN_KVM_JOBS
<<: *test-template <<: *test-template
<<: *connected-template <<: *connected-template
connected 26 google_apis x86: kvm 29 microg x86_64:
<<: *kvm-template <<: *kvm-template
only:
- branches@fdroid/fdroidclient
- branches@eighthave/fdroidclient
deploy_nightly: deploy_nightly:
extends: .base extends: .base

View File

@ -21,7 +21,7 @@ def basicApplicationId = "org.fdroid.basic"
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"' def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android { android {
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
versionCode 1012000 versionCode 1012000
@ -75,6 +75,8 @@ android {
compileOptions { compileOptions {
compileOptions.encoding = "UTF-8" compileOptions.encoding = "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} }
aaptOptions { aaptOptions {

View File

@ -4,13 +4,11 @@
-keep class org.fdroid.fdroid.** {*;} -keep class org.fdroid.fdroid.** {*;}
-dontskipnonpubliclibraryclassmembers -dontskipnonpubliclibraryclassmembers
-dontwarn android.test.** -dontwarn android.test.**
-dontwarn com.android.support.test.**
-dontwarn javax.naming.** -dontwarn javax.naming.**
-dontwarn org.slf4j.** -dontwarn org.slf4j.**
-dontnote org.apache.http.** -dontnote org.apache.http.**
-dontnote android.net.http.** -dontnote android.net.http.**
-dontnote android.support.**
-dontnote **ILicensingService -dontnote **ILicensingService
# Needed for espresso https://stackoverflow.com/a/21706087 # Needed for espresso https://stackoverflow.com/a/21706087
@ -49,3 +47,8 @@ public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
-keep public class your.class.** { -keep public class your.class.** {
*; *;
} }
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
public <init>(...);
}

View File

@ -177,6 +177,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void showSettings() { public void showSettings() {
ViewInteraction settingsBottonNavButton = onView( ViewInteraction settingsBottonNavButton = onView(
allOf(withText(R.string.menu_settings), isDisplayed())); allOf(withText(R.string.menu_settings), isDisplayed()));
@ -211,6 +212,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void showUpdates() { public void showUpdates() {
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed())); ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
updatesBottonNavButton.perform(click()); updatesBottonNavButton.perform(click());
@ -218,6 +220,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void startSwap() { public void startSwap() {
if (!BuildConfig.FLAVOR.startsWith("full")) { if (!BuildConfig.FLAVOR.startsWith("full")) {
return; return;
@ -233,6 +236,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void showCategories() { public void showCategories() {
if (!BuildConfig.FLAVOR.startsWith("full")) { if (!BuildConfig.FLAVOR.startsWith("full")) {
return; return;
@ -258,6 +262,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void showLatest() { public void showLatest() {
if (!BuildConfig.FLAVOR.startsWith("full")) { if (!BuildConfig.FLAVOR.startsWith("full")) {
return; return;
@ -280,6 +285,7 @@ public class MainActivityEspressoTest {
} }
@LargeTest @LargeTest
@Test
public void showSearch() { public void showSearch() {
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click()); onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(withId(R.id.fab_search)).check(doesNotExist()); onView(withId(R.id.fab_search)).check(doesNotExist());

View File

@ -17,14 +17,13 @@ import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat; import androidx.core.app.ServiceCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import cc.mvdan.accesspoint.WifiApControl;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.NotificationHelper; import org.fdroid.fdroid.NotificationHelper;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
@ -48,8 +47,6 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import cc.mvdan.accesspoint.WifiApControl;
/** /**
* Central service which manages all of the different moving parts of swap which are required * Central service which manages all of the different moving parts of swap which are required
* to enable p2p swapping of apps. * to enable p2p swapping of apps.
@ -429,7 +426,9 @@ public class SwapService extends Service {
localBroadcastManager.unregisterReceiver(bonjourPeerFound); localBroadcastManager.unregisterReceiver(bonjourPeerFound);
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved); localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
unregisterReceiver(bluetoothScanModeChanged); if (bluetoothAdapter != null) {
unregisterReceiver(bluetoothScanModeChanged);
}
BluetoothManager.stop(this); BluetoothManager.stop(this);

View File

@ -18,7 +18,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/both_parties_need_fdroid_text" app:layout_constraintTop_toBottomOf="@+id/both_parties_need_fdroid_text"
android:layout_marginTop="36dp" android:layout_marginTop="36dp"
android:tint="?attr/mainTabSwapSplashTint" app:tint="?attr/mainTabSwapSplashTint"
android:scaleType="fitXY" android:scaleType="fitXY"
tools:targetApi="jelly_bean"/> tools:targetApi="jelly_bean"/>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeight" android:layout_height="?attr/listPreferredItemHeight"
@ -20,7 +21,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:src="@drawable/circle" android:src="@drawable/circle"
android:tint="@color/swap_light_grey_icon"/> app:tint="@color/swap_light_grey_icon"/>
<ImageView <ImageView
android:id="@+id/icon" android:id="@+id/icon"

View File

@ -266,8 +266,11 @@
android:name=".AddRepoIntentService" android:name=".AddRepoIntentService"
android:exported="false"/> android:exported="false"/>
<provider
<!-- Warning: Please add all new services to HidingManager --> android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
<activity <activity
android:name=".views.main.MainActivity" android:name=".views.main.MainActivity"

View File

@ -46,11 +46,10 @@ import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray; import androidx.collection.LongSparseArray;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.nostra13.universalimageloader.cache.disc.DiskCache; import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
@ -58,7 +57,8 @@ import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.process.BitmapProcessor; import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.acra.ACRA; import org.acra.ACRA;
import org.acra.ReportField; import org.acra.ReportField;
import org.acra.ReportingInteractionMode; import org.acra.ReportingInteractionMode;
@ -82,17 +82,13 @@ import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.panic.HidingManager; import org.fdroid.fdroid.panic.HidingManager;
import org.fdroid.fdroid.work.CleanCacheWorker; import org.fdroid.fdroid.work.CleanCacheWorker;
import javax.microedition.khronos.opengles.GL10;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.Security; import java.security.Security;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.microedition.khronos.opengles.GL10;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@ReportsCrashes(mailTo = BuildConfig.ACRA_REPORT_EMAIL, @ReportsCrashes(mailTo = BuildConfig.ACRA_REPORT_EMAIL,
mode = ReportingInteractionMode.DIALOG, mode = ReportingInteractionMode.DIALOG,
reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class, reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class,
@ -113,7 +109,7 @@ import info.guardianproject.netcipher.proxy.OrbotHelper;
ReportField.STACK_TRACE, ReportField.STACK_TRACE,
} }
) )
public class FDroidApp extends Application { public class FDroidApp extends Application implements androidx.work.Configuration.Provider {
private static final String TAG = "FDroidApp"; private static final String TAG = "FDroidApp";
private static final String ACRA_ID = BuildConfig.APPLICATION_ID + ":acra"; private static final String ACRA_ID = BuildConfig.APPLICATION_ID + ":acra";
@ -654,7 +650,7 @@ public class FDroidApp extends Application {
/** /**
* Put proxy settings (or Tor settings) globally into effect based on whats configured in Preferences. * Put proxy settings (or Tor settings) globally into effect based on whats configured in Preferences.
* * <p>
* Must be called on App startup and after every proxy configuration change. * Must be called on App startup and after every proxy configuration change.
*/ */
public static void configureProxy(Preferences preferences) { public static void configureProxy(Preferences preferences) {
@ -676,4 +672,26 @@ public class FDroidApp extends Application {
public static Context getInstance() { public static Context getInstance() {
return instance; return instance;
} }
/**
* Set up WorkManager on demand to avoid slowing down starts.
*
* @see CleanCacheWorker
* @see org.fdroid.fdroid.work.PopularityContestWorker
* @see org.fdroid.fdroid.work.UpdateWorker
* @see <a href="https://developer.android.com/codelabs/android-adv-workmanager#3">example</a>
*/
@NonNull
@Override
public androidx.work.Configuration getWorkManagerConfiguration() {
if (BuildConfig.DEBUG) {
return new androidx.work.Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.build();
} else {
return new androidx.work.Configuration.Builder()
.setMinimumLoggingLevel(Log.ERROR)
.build();
}
}
} }

View File

@ -33,10 +33,6 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.StatFs; import android.os.StatFs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@ -52,6 +48,10 @@ import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.assist.ImageScaleType;
@ -406,10 +406,26 @@ public final class Utils {
return ret; return ret;
} }
/** /**
* Get the fingerprint used to represent an APK signing key in F-Droid. * Get the fingerprint used to represent an APK signing key in F-Droid.
* This is a custom fingerprint algorithm that was kind of accidentally * This is a custom fingerprint algorithm that was kind of accidentally
* created, but is still in use. * created, but is still in use.
*
* @see #getPackageSig(PackageInfo)
* @see org.fdroid.fdroid.data.Apk#sig
*/
public static String getsig(byte[] rawCertBytes) {
return Utils.hashBytes(toHexString(rawCertBytes).getBytes(), "md5");
}
/**
* Get the fingerprint used to represent an APK signing key in F-Droid.
* This is a custom fingerprint algorithm that was kind of accidentally
* created, but is still in use.
*
* @see #getsig(byte[])
* @see org.fdroid.fdroid.data.Apk#sig
*/ */
public static String getPackageSig(PackageInfo info) { public static String getPackageSig(PackageInfo info) {
if (info == null || info.signatures == null || info.signatures.length < 1) { if (info == null || info.signatures == null || info.signatures.length < 1) {
@ -556,7 +572,7 @@ public final class Utils {
} }
byte[] mdbytes = md.digest(); byte[] mdbytes = md.digest();
return toHexString(mdbytes).toLowerCase(Locale.ENGLISH); return toHexString(mdbytes);
} catch (IOException e) { } catch (IOException e) {
String message = e.getMessage(); String message = e.getMessage();
if (message.contains("read failed: EIO (I/O error)")) { if (message.contains("read failed: EIO (I/O error)")) {
@ -576,7 +592,7 @@ public final class Utils {
* Computes the base 16 representation of the byte array argument. * Computes the base 16 representation of the byte array argument.
* *
* @param bytes an array of bytes. * @param bytes an array of bytes.
* @return the bytes represented as a string of hexadecimal digits. * @return the bytes represented as a string of lowercase hexadecimal digits.
* @see <a href="https://stackoverflow.com/a/9855338">source</a> * @see <a href="https://stackoverflow.com/a/9855338">source</a>
*/ */
public static String toHexString(byte[] bytes) { public static String toHexString(byte[] bytes) {
@ -589,7 +605,7 @@ public final class Utils {
return new String(hexChars); return new String(hexChars);
} }
private static final char[] HEX_LOOKUP_ARRAY = "0123456789ABCDEF".toCharArray(); private static final char[] HEX_LOOKUP_ARRAY = "0123456789abcdef".toCharArray();
public static int parseInt(String str, int fallback) { public static int parseInt(String str, int fallback) {
if (str == null || str.length() == 0) { if (str == null || str.length() == 0) {

View File

@ -949,24 +949,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
} }
apkJar.close(); apkJar.close();
/* apk.sig = Utils.getsig(rawCertBytes);
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
apk.sig = Utils.hashBytes(fdroidSig, "md5");
} }
/** /**

View File

@ -37,7 +37,6 @@ import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -45,11 +44,9 @@ import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.ashokvarma.bottomnavigation.BottomNavigationBar; import com.ashokvarma.bottomnavigation.BottomNavigationBar;
import com.ashokvarma.bottomnavigation.BottomNavigationItem; import com.ashokvarma.bottomnavigation.BottomNavigationItem;
import com.ashokvarma.bottomnavigation.TextBadgeItem; import com.ashokvarma.bottomnavigation.TextBadgeItem;
import org.fdroid.fdroid.AppUpdateStatusManager; import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus; import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
@ -305,6 +302,11 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
} }
/**
* Since any app could send this {@link Intent}, and the search terms are
* fed into a SQL query, the data must be strictly sanitized to avoid
* SQL injection attacks.
*/
private void handleSearchOrAppViewIntent(Intent intent) { private void handleSearchOrAppViewIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY); String query = intent.getStringExtra(SearchManager.QUERY);
@ -391,6 +393,8 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
} }
if (!TextUtils.isEmpty(packageName)) { if (!TextUtils.isEmpty(packageName)) {
// sanitize packageName to be a valid Java packageName and prevent exploits
packageName = packageName.replaceAll("[^A-Za-z\\d_.]", "");
Utils.debugLog(TAG, "FDroid launched via app link for '" + packageName + "'"); Utils.debugLog(TAG, "FDroid launched via app link for '" + packageName + "'");
Intent intentToInvoke = new Intent(this, AppDetailsActivity.class); Intent intentToInvoke = new Intent(this, AppDetailsActivity.class);
intentToInvoke.putExtra(AppDetailsActivity.EXTRA_APPID, packageName); intentToInvoke.putExtra(AppDetailsActivity.EXTRA_APPID, packageName);
@ -402,12 +406,19 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
} }
} }
/**
* These strings might end up in a SQL query, so strip all non-alpha-num
*/
static String sanitizeSearchTerms(String query) {
return query.replaceAll("[^\\p{L}\\d_ -]", " ");
}
/** /**
* Initiates the {@link AppListActivity} with the relevant search terms passed in via the query arg. * Initiates the {@link AppListActivity} with the relevant search terms passed in via the query arg.
*/ */
private void performSearch(String query) { private void performSearch(String query) {
Intent searchIntent = new Intent(this, AppListActivity.class); Intent searchIntent = new Intent(this, AppListActivity.class);
searchIntent.putExtra(AppListActivity.EXTRA_SEARCH_TERMS, query); searchIntent.putExtra(AppListActivity.EXTRA_SEARCH_TERMS, sanitizeSearchTerms(query));
startActivity(searchIntent); startActivity(searchIntent);
} }

View File

@ -20,6 +20,7 @@
<view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView" <view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
@ -34,7 +35,7 @@
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="@android:color/black" /> app:tint="@android:color/black" />
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -20,6 +20,7 @@
<view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView" <view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
@ -58,7 +59,7 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignBottom="@+id/perm_money_label" android:layout_alignBottom="@+id/perm_money_label"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="@color/perms_costs_money" app:tint="@color/perms_costs_money"
android:tintMode="src_in" android:tintMode="src_in"
android:src="@drawable/ic_coins_s" /> android:src="@drawable/ic_coins_s" />
<TextView <TextView

View File

@ -55,7 +55,7 @@
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/empty_state" app:layout_constraintTop_toBottomOf="@+id/empty_state"
android:tint="?attr/mainTabSwapSplashTint" app:tint="?attr/mainTabSwapSplashTint"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:visibility="gone" /> android:visibility="gone" />

View File

@ -2,9 +2,9 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter; import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -12,6 +12,7 @@ import org.robolectric.RobolectricTestRunner;
import java.io.File; import java.io.File;
import java.util.Date; import java.util.Date;
import java.util.Random;
import java.util.TimeZone; import java.util.TimeZone;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -215,4 +216,40 @@ public class UtilsTest {
} }
} }
} }
/**
* Test the replacement for the ancient fingerprint algorithm.
*
* @see org.fdroid.fdroid.data.Apk#sig
*/
@Test
public void testGetsig() {
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
for (int length : new int[]{256, 345, 1233, 4032, 12092}) {
byte[] rawCertBytes = new byte[length];
new Random().nextBytes(rawCertBytes);
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
String sig = Utils.hashBytes(fdroidSig, "md5");
assertEquals(sig, Utils.getsig(rawCertBytes));
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{new Signature(rawCertBytes)};
assertEquals(sig, Utils.getPackageSig(packageInfo));
}
}
} }

View File

@ -5,6 +5,7 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
public class SanitizedFileTest { public class SanitizedFileTest {
@ -45,4 +46,13 @@ public class SanitizedFileTest {
} }
@Test
public void testSanitizeFileName() {
for (String valid : new String[]{"An.stop", "a.0", "packageName", "com.this-and-that", "A_.o"}) {
assertEquals(valid, SanitizedFile.sanitizeFileName(valid));
}
for (String invalid : new String[]{"'--;DROP", "a.0)", "packageName\n"}) {
assertNotEquals(invalid, SanitizedFile.sanitizeFileName(invalid));
}
}
} }

View File

@ -0,0 +1,25 @@
package org.fdroid.fdroid.views.main;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class MainActivityTest {
@Test
public void testSanitizeSearchTerms() {
for (String valid : new String[]{"private browser", "πÇÇ", "现代 通用字", "български", "عربي"}) {
assertEquals(valid, MainActivity.sanitizeSearchTerms(valid));
}
for (String invalid : new String[]{
"Robert'); DROP TABLE Students; --",
"xxx') OR 1 = 1 -- ]",
"105 OR 1=1;",
"\" OR \"=\"",
}) {
assertNotEquals(invalid, MainActivity.sanitizeSearchTerms(invalid));
}
}
}

View File

@ -115,7 +115,6 @@
<module name="FinalClass" /> <module name="FinalClass" />
<module name="ArrayTypeStyle" /> <module name="ArrayTypeStyle" />
<module name="ArrayTrailingComma" />
<module name="UpperEll" /> <module name="UpperEll" />
<module name="StringLiteralEquality" /> <module name="StringLiteralEquality" />