Merge pull request #1445 from alansley/SES1251_AppCrashOnNonAlphanumeric_REFIX

SES1251 - App crash on non alphanumeric RE-FIX
This commit is contained in:
Andrew 2024-04-24 14:14:47 +09:30 committed by GitHub
commit 0e7a981386
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 67 deletions

View File

@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.home.search
import android.content.Context
import android.text.Editable
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.KeyEvent
@ -34,6 +36,7 @@ class GlobalSearchInputLayout @JvmOverloads constructor(
binding.searchInput.onFocusChangeListener = this
binding.searchInput.addTextChangedListener(this)
binding.searchInput.setOnEditorActionListener(this)
binding.searchInput.setFilters( arrayOf<InputFilter>(LengthFilter(100)) ) // 100 char search limit
binding.searchCancel.setOnClickListener(this)
binding.searchClear.setOnClickListener(this)
}

View File

@ -24,8 +24,7 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
private val executor = viewModelScope + SupervisorJob()
private val _result: MutableStateFlow<GlobalSearchResult> =
MutableStateFlow(GlobalSearchResult.EMPTY)
private val _result: MutableStateFlow<GlobalSearchResult> = MutableStateFlow(GlobalSearchResult.EMPTY)
val result: StateFlow<GlobalSearchResult> = _result
@ -41,13 +40,14 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
_queryText
.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST)
.mapLatest { query ->
if (query.trim().length < 2) {
// Early exit on empty search query
if (query.trim().isEmpty()) {
SearchResult.EMPTY
} else {
// user input delay here in case we get a new query within a few hundred ms
// this coroutine will be cancelled and expensive query will not be run if typing quickly
// first query of 2 characters will be instant however
// User input delay in case we get a new query within a few hundred ms this
// coroutine will be cancelled and the expensive query will not be run.
delay(300)
val settableFuture = SettableFuture<SearchResult>()
searchRepository.query(query.toString(), settableFuture::set)
try {
@ -64,6 +64,4 @@ class GlobalSearchViewModel @Inject constructor(private val searchRepository: Se
}
.launchIn(executor)
}
}

View File

@ -4,12 +4,8 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.MergeCursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.contacts.Contact;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.GroupRecord;
@ -27,37 +23,25 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.search.model.SearchResult;
import org.thoughtcrime.securesms.util.Stopwatch;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import kotlin.Pair;
/**
* Manages data retrieval for search.
*/
// Class to manage data retrieval for search
public class SearchRepository {
private static final String TAG = SearchRepository.class.getSimpleName();
private static final Set<Character> BANNED_CHARACTERS = new HashSet<>();
static {
// Several ranges of invalid ASCII characters
for (int i = 33; i <= 47; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 58; i <= 64; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 91; i <= 96; i++) {
BANNED_CHARACTERS.add((char) i);
}
for (int i = 123; i <= 126; i++) {
BANNED_CHARACTERS.add((char) i);
}
// Construct a list containing several ranges of invalid ASCII characters
// See: https://www.ascii-code.com/
for (int i = 33; i <= 47; i++) { BANNED_CHARACTERS.add((char) i); } // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /
for (int i = 58; i <= 64; i++) { BANNED_CHARACTERS.add((char) i); } // :, ;, <, =, >, ?, @
for (int i = 91; i <= 96; i++) { BANNED_CHARACTERS.add((char) i); } // [, \, ], ^, _, `
for (int i = 123; i <= 126; i++) { BANNED_CHARACTERS.add((char) i); } // {, |, }, ~
}
private final Context context;
@ -86,35 +70,25 @@ public class SearchRepository {
}
public void query(@NonNull String query, @NonNull Callback<SearchResult> callback) {
if (TextUtils.isEmpty(query)) {
// If the sanitized search is empty then abort without search
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) {
callback.onResult(SearchResult.EMPTY);
return;
}
executor.execute(() -> {
Stopwatch timer = new Stopwatch("FtsQuery");
String cleanQuery = sanitizeQuery(query);
// If the search is for a single character and it was stripped by `sanitizeQuery` then abort
// the search for an empty string to avoid SQLite error.
if (cleanQuery.length() == 0)
{
Log.d(TAG, "Aborting empty search query.");
timer.stop(TAG);
return;
}
timer.split("clean");
Pair<CursorList<Contact>, List<String>> contacts = queryContacts(cleanQuery);
timer.split("contacts");
timer.split("Contacts");
CursorList<GroupRecord> conversations = queryConversations(cleanQuery, contacts.getSecond());
timer.split("conversations");
timer.split("Conversations");
CursorList<MessageResult> messages = queryMessages(cleanQuery);
timer.split("messages");
timer.split("Messages");
timer.stop(TAG);
@ -123,23 +97,20 @@ public class SearchRepository {
}
public void query(@NonNull String query, long threadId, @NonNull Callback<CursorList<MessageResult>> callback) {
if (TextUtils.isEmpty(query)) {
// If the sanitized search query is empty then abort the search
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) {
callback.onResult(CursorList.emptyList());
return;
}
executor.execute(() -> {
// If the sanitized search query is empty then abort the search to prevent SQLite errors.
String cleanQuery = sanitizeQuery(query).trim();
if (cleanQuery.isEmpty()) { return; }
CursorList<MessageResult> messages = queryMessages(cleanQuery, threadId);
callback.onResult(messages);
});
}
private Pair<CursorList<Contact>, List<String>> queryContacts(String query) {
Cursor contacts = contactDatabase.queryContactsByName(query);
List<Address> contactList = new ArrayList<>();
List<String> contactStrings = new ArrayList<>();
@ -166,11 +137,10 @@ public class SearchRepository {
MergeCursor merged = new MergeCursor(new Cursor[]{addressThreads, individualRecipients});
return new Pair<>(new CursorList<>(merged, new ContactModelBuilder(contactDatabase, threadDatabase)), contactStrings);
}
private CursorList<GroupRecord> queryConversations(@NonNull String query, List<String> matchingAddresses) {
List<String> numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query);
List<String> numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query);
String localUserNumber = TextSecurePreferences.getLocalNumber(context);
if (localUserNumber != null) {
matchingAddresses.remove(localUserNumber);
@ -189,9 +159,7 @@ public class SearchRepository {
membersGroupList.close();
}
Cursor conversations = threadDatabase.getFilteredConversationList(new ArrayList<>(addresses));
return conversations != null ? new CursorList<>(conversations, new GroupModelBuilder(threadDatabase, groupDatabase))
: CursorList.emptyList();
}
@ -256,9 +224,7 @@ public class SearchRepository {
private final Context context;
RecipientModelBuilder(@NonNull Context context) {
this.context = context;
}
RecipientModelBuilder(@NonNull Context context) { this.context = context; }
@Override
public Recipient build(@NonNull Cursor cursor) {
@ -301,9 +267,7 @@ public class SearchRepository {
private final Context context;
MessageModelBuilder(@NonNull Context context) {
this.context = context;
}
MessageModelBuilder(@NonNull Context context) { this.context = context; }
@Override
public MessageResult build(@NonNull Cursor cursor) {

View File

@ -37,12 +37,10 @@ public class Stopwatch {
for (int i = 1; i < splits.size(); i++) {
out.append(splits.get(i).label).append(": ");
out.append(splits.get(i).time - splits.get(i - 1).time);
out.append(" ");
out.append("ms ");
}
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime);
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime).append("ms.");
}
Log.d(tag, out.toString());
}