Better search implementation, kotlinize, start remove clone

This commit is contained in:
J-Jamet
2019-06-01 12:34:06 +02:00
parent 0e701189a3
commit 6ba45c3d87
18 changed files with 414 additions and 458 deletions

View File

@@ -21,25 +21,25 @@ package com.kunzisoft.keepass.tests.utils;
import java.util.Locale;
import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.StringUtil;
import junit.framework.TestCase;
public class StrUtilTest extends TestCase {
public class StringUtilTest extends TestCase {
private final String text = "AbCdEfGhIj";
private final String search = "BcDe";
private final String badSearch = "Ed";
public void testIndexOfIgnoreCase1() {
assertEquals(1, StrUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH));
assertEquals(1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH));
}
public void testIndexOfIgnoreCase2() {
assertEquals(-1, StrUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
}
public void testIndexOfIgnoreCase3() {
assertEquals(-1, StrUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
}
private final String repText = "AbCtestingaBc";
@@ -48,10 +48,10 @@ public class StrUtilTest extends TestCase {
private final String repNew = "12345";
private final String repResult = "12345testing12345";
public void testReplaceAllIgnoresCase1() {
assertEquals(repResult, StrUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
assertEquals(repResult, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
}
public void testReplaceAllIgnoresCase2() {
assertEquals(repText, StrUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
assertEquals(repText, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
}
}

View File

@@ -13,7 +13,7 @@ package com.kunzisoft.keepass.compat;
import android.os.Build;
import android.os.Process;
import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.StringUtil;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -66,7 +66,7 @@ public final class PRNGFixes {
private static boolean supportedOnThisDevice() {
// Blacklist on samsung devices
if (StrUtil.indexOfIgnoreCase(Build.MANUFACTURER, "samsung", Locale.ENGLISH) >= 0) {
if (StringUtil.INSTANCE.indexOfIgnoreCase(Build.MANUFACTURER, "samsung", Locale.ENGLISH) >= 0) {
return false;
}

View File

@@ -172,7 +172,13 @@ public class NativeAESCipherSpi extends CipherSpi {
@Override
protected byte[] engineGetIV() {
return mIV.clone();
byte[] copyIV = new byte[0];
if (mIV != null) {
int lengthIV = mIV.length;
copyIV = new byte[lengthIV];
System.arraycopy(mIV, 0, copyIV, 0, lengthIV);
}
return copyIV;
}
@Override

View File

@@ -30,7 +30,7 @@ import java.util.Map;
import static com.kunzisoft.keepass.database.element.PwEntryV4.*;
public class ExtraFields implements Parcelable, Cloneable {
public class ExtraFields implements Parcelable {
private Map<String, ProtectedString> fields;
@@ -151,25 +151,4 @@ public class ExtraFields implements Parcelable, Cloneable {
&& !key.equals(STR_PASSWORD) && !key.equals(STR_URL)
&& !key.equals(STR_NOTES);
}
@Override
public ExtraFields clone() {
try {
ExtraFields clone = (ExtraFields) super.clone();
clone.fields = copyMap(this.fields);
return clone;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
private Map<String, ProtectedString> copyMap(
Map<String, ProtectedString> original) {
HashMap<String, ProtectedString> copy = new HashMap<>();
for (Map.Entry<String, ProtectedString> entry : original.entrySet()) {
copy.put(entry.getKey(), new ProtectedString(entry.getValue()));
}
return copy;
}
}

View File

@@ -145,13 +145,17 @@ public class PwEntryV3 extends PwEntry<PwGroupV3, PwEntryV3> {
super.updateWith(source);
title = source.title;
username = source.username;
if (source.password != null) {
int passLen = source.password.length;
password = new byte[passLen];
System.arraycopy(source.password, 0, password, 0, passLen);
}
url = source.url;
additional = source.additional;
binaryDesc = source.binaryDesc;
if ( source.binaryData != null ) {
int descLen = source.binaryData.length;
binaryData = new byte[descLen];

View File

@@ -172,7 +172,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4, PwEntryV4> implements ITimeLog
// newEntry.usageCount stay the same in copy
newEntry.parentGroupLastMod = this.parentGroupLastMod.clone();
newEntry.fields = this.fields.clone();
newEntry.fields = new ExtraFields(this.fields);
// TODO customData make copy from hashmap
newEntry.binaries = (HashMap<String, ProtectedBinary>) this.binaries.clone();
// newEntry.foregroundColor stay the same in copy
@@ -204,20 +204,22 @@ public class PwEntryV4 extends PwEntry<PwGroupV4, PwEntryV4> implements ITimeLog
this.mDecodeRef = false;
}
/**
* Decode a reference key woth the SprEngineV4
* @param decodeRef
* @param key
* @return
*/
private String decodeRefKey(boolean decodeRef, String key) {
String text = getProtectedStringValue(key);
if (decodeRef) {
text = decodeRef(text, mDatabase);
if (mDatabase == null)
return text;
return new SprEngineV4().compile(text, this, mDatabase);
}
return text;
}
private String decodeRef(String text, PwDatabase db) {
if (db == null) { return text; }
SprEngineV4 spr = new SprEngineV4();
return spr.compile(text, this, db);
}
@NonNull
@Override
public String getUsername() {

View File

@@ -19,23 +19,4 @@
*/
package com.kunzisoft.keepass.database.iterator
import com.kunzisoft.keepass.database.element.EntryVersioned
abstract class EntrySearchStringIterator : Iterator<String> {
companion object {
fun getInstance(entry: EntryVersioned): EntrySearchStringIterator {
if (entry.pwEntryV3 != null) {
return EntrySearchStringIteratorV3(entry.pwEntryV3)
}
if (entry.pwEntryV4 != null) {
return EntrySearchStringIteratorV4(entry.pwEntryV4!!)
}
throw RuntimeException("This should not be possible")
}
}
}
abstract class EntrySearchStringIterator : Iterator<String>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
@@ -26,17 +26,16 @@ import java.util.NoSuchElementException;
public class EntrySearchStringIteratorV3 extends EntrySearchStringIterator {
private PwEntryV3 entry;
private SearchParameters sp;
private PwEntryV3 mEntry;
private SearchParameters mSearchParameters;
public EntrySearchStringIteratorV3(PwEntryV3 entry) {
this.entry = entry;
this.sp = SearchParameters.DEFAULT;
this(entry, new SearchParameters());
}
public EntrySearchStringIteratorV3(PwEntryV3 entry, SearchParameters sp) {
this.entry = entry;
this.sp = sp;
public EntrySearchStringIteratorV3(PwEntryV3 entry, SearchParameters searchParameters) {
this.mEntry = entry;
this.mSearchParameters = searchParameters;
}
private static final int title = 0;
@@ -68,28 +67,23 @@ public class EntrySearchStringIteratorV3 extends EntrySearchStringIterator {
private void useSearchParameters() {
if (sp == null) { return; }
if (mSearchParameters == null) { return; }
boolean found = false;
while (!found) {
switch (current) {
case title:
found = sp.searchInTitles;
found = mSearchParameters.getSearchInTitles();
break;
case url:
found = sp.searchInUrls;
found = mSearchParameters.getSearchInUrls();
break;
case username:
found = sp.searchInUserNames;
found = mSearchParameters.getSearchInUserNames();
break;
case comment:
found = sp.searchInNotes;
found = mSearchParameters.getSearchInNotes();
break;
default:
found = true;
}
@@ -101,17 +95,13 @@ public class EntrySearchStringIteratorV3 extends EntrySearchStringIterator {
private String getCurrentString() {
switch (current) {
case title:
return entry.getTitle();
return mEntry.getTitle();
case url:
return entry.getUrl();
return mEntry.getUrl();
case username:
return entry.getUsername();
return mEntry.getUsername();
case comment:
return entry.getNotes();
return mEntry.getNotes();
default:
return "";
}

View File

@@ -27,60 +27,60 @@ import kotlin.collections.Map.Entry
class EntrySearchStringIteratorV4 : EntrySearchStringIterator {
private var current: String? = null
private var setIterator: Iterator<Entry<String, ProtectedString>>? = null
private var sp: SearchParametersV4? = null
private var mCurrent: String? = null
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
private var mSearchParametersV4: SearchParametersV4? = null
constructor(entry: PwEntryV4) {
this.sp = SearchParametersV4.DEFAULT
setIterator = entry.fields.listOfAllFields.entries.iterator()
this.mSearchParametersV4 = SearchParametersV4()
mSetIterator = entry.fields.listOfAllFields.entries.iterator()
advance()
}
constructor(entry: PwEntryV4, sp: SearchParametersV4) {
this.sp = sp
setIterator = entry.fields.listOfAllFields.entries.iterator()
constructor(entry: PwEntryV4, searchParametersV4: SearchParametersV4) {
this.mSearchParametersV4 = searchParametersV4
mSetIterator = entry.fields.listOfAllFields.entries.iterator()
advance()
}
override fun hasNext(): Boolean {
return current != null
return mCurrent != null
}
override fun next(): String {
if (current == null) {
if (mCurrent == null) {
throw NoSuchElementException("Past the end of the list.")
}
val next = current
val next = mCurrent
advance()
return next!!
}
private fun advance() {
while (setIterator!!.hasNext()) {
val entry = setIterator!!.next()
mSetIterator?.let {
while (it.hasNext()) {
val entry = it.next()
val key = entry.key
if (searchInField(key)) {
current = entry.value.toString()
mCurrent = entry.value.toString()
return
}
}
}
current = null
mCurrent = null
}
private fun searchInField(key: String): Boolean {
when (key) {
PwEntryV4.STR_TITLE -> return sp!!.searchInTitles
PwEntryV4.STR_USERNAME -> return sp!!.searchInUserNames
PwEntryV4.STR_PASSWORD -> return sp!!.searchInPasswords
PwEntryV4.STR_URL -> return sp!!.searchInUrls
PwEntryV4.STR_NOTES -> return sp!!.searchInNotes
else -> return sp!!.searchInOther
return when (key) {
PwEntryV4.STR_TITLE -> mSearchParametersV4!!.searchInTitles
PwEntryV4.STR_USERNAME -> mSearchParametersV4!!.searchInUserNames
PwEntryV4.STR_PASSWORD -> mSearchParametersV4!!.searchInPasswords
PwEntryV4.STR_URL -> mSearchParametersV4!!.searchInUrls
PwEntryV4.STR_NOTES -> mSearchParametersV4!!.searchInNotes
else -> mSearchParametersV4!!.searchInOther
}
}

View File

@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.database.element.PwEntryV4;
import com.kunzisoft.keepass.database.element.PwGroupV4;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4;
import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.StringUtil;
import com.kunzisoft.keepass.utils.UuidUtil;
import java.util.Date;
@@ -33,53 +33,53 @@ import java.util.Locale;
public class EntrySearchHandlerV4 extends NodeHandler<PwEntryV4> {
private List<PwEntryV4> listStorage;
protected SearchParametersV4 sp;
private List<PwEntryV4> mListStorage;
private SearchParametersV4 mSearchParametersV4;
protected Date now;
public EntrySearchHandlerV4(SearchParametersV4 sp, List<PwEntryV4> listStorage) {
this.listStorage = listStorage;
this.sp = sp;
public EntrySearchHandlerV4(SearchParametersV4 searchParametersV4, List<PwEntryV4> listStorage) {
this.mListStorage = listStorage;
this.mSearchParametersV4 = searchParametersV4;
this.now = new Date();
}
@Override
public boolean operate(PwEntryV4 entry) {
if (sp.respectEntrySearchingDisabled && !entry.isSearchingEnabled()) {
if (mSearchParametersV4.getRespectEntrySearchingDisabled() && !entry.isSearchingEnabled()) {
return true;
}
if (sp.excludeExpired && entry.isExpires() && now.after(entry.getExpiryTime().getDate())) {
if (mSearchParametersV4.getExcludeExpired() && entry.isExpires() && now.after(entry.getExpiryTime().getDate())) {
return true;
}
String term = sp.searchString;
if (sp.ignoreCase) {
String term = mSearchParametersV4.getSearchString();
if (mSearchParametersV4.getIgnoreCase()) {
term = term.toLowerCase();
}
if (searchStrings(entry, term)) {
listStorage.add(entry);
mListStorage.add(entry);
return true;
}
if (sp.searchInGroupNames) {
if (mSearchParametersV4.getSearchInGroupNames()) {
PwGroupV4 parent = entry.getParent();
if (parent != null) {
String groupName = parent.getTitle();
if (sp.ignoreCase) {
if (mSearchParametersV4.getIgnoreCase()) {
groupName = groupName.toLowerCase();
}
if (groupName.contains(term)) {
listStorage.add(entry);
mListStorage.add(entry);
return true;
}
}
}
if (searchID(entry)) {
listStorage.add(entry);
mListStorage.add(entry);
return true;
}
@@ -87,20 +87,20 @@ public class EntrySearchHandlerV4 extends NodeHandler<PwEntryV4> {
}
private boolean searchID(PwEntryV4 entry) {
if (sp.searchInUUIDs) {
if (mSearchParametersV4.getSearchInUUIDs()) {
String hex = UuidUtil.toHexString(entry.getNodeId().getId());
return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0;
return StringUtil.INSTANCE.indexOfIgnoreCase(hex, mSearchParametersV4.getSearchString(), Locale.ENGLISH) >= 0;
}
return false;
}
private boolean searchStrings(PwEntryV4 entry, String term) {
EntrySearchStringIterator iter = new EntrySearchStringIteratorV4(entry, sp);
while (iter.hasNext()) {
String str = iter.next();
EntrySearchStringIterator iterator = new EntrySearchStringIteratorV4(entry, mSearchParametersV4);
while (iterator.hasNext()) {
String str = iterator.next();
if (str.length() > 0) {
if (sp.ignoreCase) {
if (mSearchParametersV4.getIgnoreCase()) {
str = str.toLowerCase();
}

View File

@@ -24,6 +24,8 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV3
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIteratorV4
import java.util.*
class SearchDbHelper(private val isOmitBackup: Boolean) {
@@ -72,9 +74,18 @@ class SearchDbHelper(private val isOmitBackup: Boolean) {
private fun entryContainsString(entry: EntryVersioned, qStr: String, loc: Locale): Boolean {
// Search all strings in the entry
val iterator = EntrySearchStringIterator.getInstance(entry)
while (iterator.hasNext()) {
val str = iterator.next()
var iterator: EntrySearchStringIterator? = null
entry.pwEntryV3?.let {
iterator = EntrySearchStringIteratorV3(it)
}
entry.pwEntryV4?.let {
iterator = EntrySearchStringIteratorV4(it)
}
iterator?.let {
while (it.hasNext()) {
val str = it.next()
if (str.isNotEmpty()) {
val lower = str.toLowerCase(loc)
if (lower.contains(qStr)) {
@@ -82,6 +93,7 @@ class SearchDbHelper(private val isOmitBackup: Boolean) {
}
}
}
}
return false
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.search;
/**
* @author bpellin
* Parameters for searching strings in the database.
*
*/
public class SearchParameters implements Cloneable {
public static final SearchParameters DEFAULT = new SearchParameters();
public String searchString;
public boolean regularExpression = false;
public boolean searchInTitles = true;
public boolean searchInUserNames = true;
public boolean searchInPasswords = false;
public boolean searchInUrls = true;
public boolean searchInGroupNames = false;
public boolean searchInNotes = true;
public boolean ignoreCase = true;
public boolean ignoreExpired = false;
public boolean respectEntrySearchingDisabled = true;
public boolean excludeExpired = false;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
public void setupNone() {
searchInTitles = false;
searchInUserNames = false;
searchInPasswords = false;
searchInUrls = false;
searchInGroupNames = false;
searchInNotes = false;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.search
/**
* Parameters for searching strings in the database.
*/
open class SearchParameters {
var searchString: String = ""
var regularExpression = false
var searchInTitles = true
var searchInUserNames = true
var searchInPasswords = false
var searchInUrls = true
var searchInGroupNames = false
var searchInNotes = true
var ignoreCase = true
var ignoreExpired = false
var respectEntrySearchingDisabled = true
var excludeExpired = false
constructor()
/**
* Copy search parameters
* @param source
*/
constructor(source: SearchParameters) {
regularExpression = source.regularExpression
searchInTitles = source.searchInTitles
searchInUserNames = source.searchInUserNames
searchInPasswords = source.searchInPasswords
searchInUrls = source.searchInUrls
searchInGroupNames = source.searchInGroupNames
searchInNotes = source.searchInNotes
ignoreCase = source.ignoreCase
ignoreExpired = source.ignoreExpired
respectEntrySearchingDisabled = source.respectEntrySearchingDisabled
excludeExpired = source.excludeExpired
}
open fun setupNone() {
searchInTitles = false
searchInUserNames = false
searchInPasswords = false
searchInUrls = false
searchInGroupNames = false
searchInNotes = false
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.search;
public class SearchParametersV4 extends SearchParameters implements Cloneable {
public static SearchParametersV4 DEFAULT = new SearchParametersV4();
public boolean searchInOther = true;
public boolean searchInUUIDs = false;
public boolean searchInTags = true;
@Override
public Object clone() {
return super.clone();
}
@Override
public void setupNone() {
super.setupNone();
searchInOther = false;
searchInUUIDs = false;
searchInTags = false;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
@@ -17,31 +17,26 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils;
package com.kunzisoft.keepass.database.search
import com.kunzisoft.keepass.database.element.PwDatabaseV4;
import com.kunzisoft.keepass.database.element.PwEntryV4;
class SearchParametersV4 : SearchParameters {
import java.util.HashMap;
import java.util.Map;
var searchInOther = true
var searchInUUIDs = false
var searchInTags = true
public class SprContextV4 implements Cloneable {
constructor() : super()
public PwDatabaseV4 db;
public PwEntryV4 entry;
public Map<String, String> refsCache = new HashMap<>();
public SprContextV4(PwDatabaseV4 db, PwEntryV4 entry) {
this.db = db;
this.entry = entry;
constructor(searchParametersV4: SearchParametersV4) : super(searchParametersV4) {
this.searchInOther = searchParametersV4.searchInOther
this.searchInUUIDs = searchParametersV4.searchInUUIDs
this.searchInTags = searchParametersV4.searchInTags
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
override fun setupNone() {
super.setupNone()
searchInOther = false
searchInUUIDs = false
searchInTags = false
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
@@ -44,35 +44,53 @@ public class SprEngineV4 {
}
}
private class SprContextV4 {
public PwDatabaseV4 db;
public PwEntryV4 entry;
public Map<String, String> refsCache = new HashMap<>();
SprContextV4(PwDatabaseV4 db, PwEntryV4 entry) {
this.db = db;
this.entry = entry;
}
SprContextV4(SprContextV4 source) {
this.db = source.db;
this.entry = source.entry;
this.refsCache = source.refsCache;
}
}
public String compile(String text, PwEntryV4 entry, PwDatabase database) {
SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, entry);
return compileInternal(text, ctx, 0);
}
private String compileInternal(String text, SprContextV4 ctx, int recursionLevel) {
private String compileInternal(String text, SprContextV4 sprContextV4, int recursionLevel) {
if (text == null) { return ""; }
if (ctx == null) { return ""; }
if (sprContextV4 == null) { return ""; }
if (recursionLevel >= MAX_RECURSION_DEPTH) { return ""; }
return fillRefPlaceholders(text, ctx, recursionLevel);
return fillRefPlaceholders(text, sprContextV4, recursionLevel);
}
private String fillRefPlaceholders(String text, SprContextV4 ctx, int recursionLevel) {
private String fillRefPlaceholders(String text, SprContextV4 contextV4, int recursionLevel) {
if (ctx.db == null) { return text; }
if (contextV4.db == null) { return text; }
int offset = 0;
for (int i = 0; i < 20; ++i) {
text = fillRefsUsingCache(text, ctx);
text = fillRefsUsingCache(text, contextV4);
int start = StrUtil.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH);
int start = StringUtil.INSTANCE.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH);
if (start < 0) { break; }
int end = StrUtil.indexOfIgnoreCase(text, STR_REF_END, start + 1, Locale.ENGLISH);
int end = StringUtil.INSTANCE.indexOfIgnoreCase(text, STR_REF_END, start + 1, Locale.ENGLISH);
if (end <= start) { break; }
String fullRef = text.substring(start, end - start + 1);
TargetResult result = findRefTarget(fullRef, ctx);
TargetResult result = findRefTarget(fullRef, contextV4);
if (result != null) {
PwEntryV4 found = result.entry;
@@ -104,15 +122,14 @@ public class SprEngineV4 {
continue;
}
SprContextV4 subCtx = (SprContextV4) ctx.clone();
SprContextV4 subCtx = new SprContextV4(contextV4);
subCtx.entry = found;
String innerContent = compileInternal(data, subCtx, recursionLevel + 1);
addRefsToCache(fullRef, innerContent, ctx);
text = fillRefsUsingCache(text, ctx);
addRefsToCache(fullRef, innerContent, contextV4);
text = fillRefsUsingCache(text, contextV4);
} else {
offset = start + 1;
continue;
}
}
@@ -121,7 +138,7 @@ public class SprEngineV4 {
return text;
}
public TargetResult findRefTarget(String fullRef, SprContextV4 ctx) {
private TargetResult findRefTarget(String fullRef, SprContextV4 contextV4) {
if (fullRef == null) { return null; }
fullRef = fullRef.toUpperCase(Locale.ENGLISH);
@@ -134,28 +151,25 @@ public class SprEngineV4 {
if (ref.charAt(1) != '@') { return null; }
if (ref.charAt(3) != ':') { return null; }
char scan = Character.MIN_VALUE;
char wanted = Character.MIN_VALUE;
char scan = Character.toUpperCase(ref.charAt(2));
char wanted = Character.toUpperCase(ref.charAt(0));
scan = Character.toUpperCase(ref.charAt(2));
wanted = Character.toUpperCase(ref.charAt(0));
SearchParametersV4 searchParametersV4 = new SearchParametersV4();
searchParametersV4.setupNone();
SearchParametersV4 sp = new SearchParametersV4();
sp.setupNone();
sp.searchString = ref.substring(4);
if (scan == 'T') { sp.searchInTitles = true; }
else if (scan == 'U') { sp.searchInUserNames = true; }
else if (scan == 'A') { sp.searchInUrls = true; }
else if (scan == 'P') { sp.searchInPasswords = true; }
else if (scan == 'N') { sp.searchInNotes = true; }
else if (scan == 'I') { sp.searchInUUIDs = true; }
else if (scan == 'O') { sp.searchInOther = true; }
searchParametersV4.setSearchString(ref.substring(4));
if (scan == 'T') { searchParametersV4.setSearchInTitles(true); }
else if (scan == 'U') { searchParametersV4.setSearchInUserNames(true); }
else if (scan == 'A') { searchParametersV4.setSearchInUrls(true); }
else if (scan == 'P') { searchParametersV4.setSearchInPasswords(true); }
else if (scan == 'N') { searchParametersV4.setSearchInNotes(true); }
else if (scan == 'I') { searchParametersV4.setSearchInUUIDs(true); }
else if (scan == 'O') { searchParametersV4.setSearchInOther(true); }
else { return null; }
List<PwEntryV4> list = new ArrayList<>();
// TODO type parameter
searchEntries(ctx.db.getRootGroup(), sp, list);
searchEntries(contextV4.db.getRootGroup(), searchParametersV4, list);
if (list.size() > 0) {
return new TargetResult(list.get(0), wanted);
@@ -174,21 +188,21 @@ public class SprEngineV4 {
}
}
private String fillRefsUsingCache(String text, SprContextV4 ctx) {
for (Entry<String, String> entry : ctx.refsCache.entrySet()) {
text = StrUtil.replaceAllIgnoresCase(text, entry.getKey(), entry.getValue(), Locale.ENGLISH);
private String fillRefsUsingCache(String text, SprContextV4 sprContextV4) {
for (Entry<String, String> entry : sprContextV4.refsCache.entrySet()) {
text = StringUtil.INSTANCE.replaceAllIgnoresCase(text, entry.getKey(), entry.getValue(), Locale.ENGLISH);
}
return text;
}
private void searchEntries(PwGroupV4 root, SearchParametersV4 sp, List<PwEntryV4> listStorage) {
if (sp == null) { return; }
private void searchEntries(PwGroupV4 root, SearchParametersV4 searchParametersV4, List<PwEntryV4> listStorage) {
if (searchParametersV4 == null) { return; }
if (listStorage == null) { return; }
List<String> terms = StrUtil.splitSearchTerms(sp.searchString);
if (terms.size() <= 1 || sp.regularExpression) {
root.doForEachChild(new EntrySearchHandlerV4(sp, listStorage), null);
List<String> terms = StringUtil.INSTANCE.splitStringTerms(searchParametersV4.getSearchString());
if (terms.size() <= 1 || searchParametersV4.getRegularExpression()) {
root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, listStorage), null);
return;
}
@@ -196,41 +210,41 @@ public class SprEngineV4 {
Comparator<String> stringLengthComparator = (lhs, rhs) -> lhs.length() - rhs.length();
Collections.sort(terms, stringLengthComparator);
String fullSearch = sp.searchString;
List<PwEntryV4> pg = root.getChildEntries();
String fullSearch = searchParametersV4.getSearchString();
List<PwEntryV4> childEntries = root.getChildEntries();
for (int i = 0; i < terms.size(); i ++) {
List<PwEntryV4> pgNew = new ArrayList<>();
sp.searchString = terms.get(i);
searchParametersV4.setSearchString(terms.get(i));
boolean negate = false;
if (sp.searchString.startsWith("-")) {
sp.searchString = sp.searchString.substring(1);
negate = sp.searchString.length() > 0;
if (searchParametersV4.getSearchString().startsWith("-")) {
searchParametersV4.setSearchString(searchParametersV4.getSearchString().substring(1));
negate = searchParametersV4.getSearchString().length() > 0;
}
if (!root.doForEachChild(new EntrySearchHandlerV4(sp, pgNew), null)) {
pg = null;
if (!root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, pgNew), null)) {
childEntries = null;
break;
}
List<PwEntryV4> complement = new ArrayList<>();
if (negate) {
for (PwEntryV4 entry: pg) {
for (PwEntryV4 entry: childEntries) {
if (!pgNew.contains(entry)) {
complement.add(entry);
}
}
pg = complement;
childEntries = complement;
}
else {
pg = pgNew;
childEntries = pgNew;
}
}
if (pg != null) {
listStorage.addAll(pg);
if (childEntries != null) {
listStorage.addAll(childEntries);
}
sp.searchString = fullSearch;
searchParametersV4.setSearchString(fullSearch);
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class StrUtil {
public static List<String> splitSearchTerms(String search) {
List<String> list = new ArrayList<String>();
if (search == null) { return list; }
StringBuilder sb = new StringBuilder();
boolean quoted = false;
for (int i = 0; i < search.length(); i++) {
char ch = search.charAt(i);
if ( ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
&& !quoted) {
int len = sb.length();
if (len > 0) {
list.add(sb.toString());
sb.delete(0, len);
}
else if (ch == '\"') {
quoted = !quoted;
}
else {
sb.append(ch);
}
}
}
if (sb.length() > 0) {
list.add(sb.toString());
}
return list;
}
public static int indexOfIgnoreCase(String text, String search, int start, Locale locale) {
if (text == null || search == null) return -1;
return text.toLowerCase(locale).indexOf(search.toLowerCase(locale), start);
}
public static int indexOfIgnoreCase(String text, String search, Locale locale) {
return indexOfIgnoreCase(text, search, 0, locale);
}
public static String replaceAllIgnoresCase(String text, String find, String newText, Locale locale) {
if (text == null || find == null || newText == null) { return text; }
int pos = 0;
while (pos < text.length()) {
pos = indexOfIgnoreCase(text, find, pos, locale);
if (pos < 0) { break; }
String before = text.substring(0, pos);
String after = text.substring(pos + find.length());
text = before.concat(newText).concat(after);
pos += newText.length();
}
return text;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
import java.util.ArrayList
import java.util.Locale
object StringUtil {
/**
* Create a list of String by split text when ' ', '\t', '\r' or '\n' is found
*/
fun splitStringTerms(text: String?): List<String> {
val list = ArrayList<String>()
if (text == null) {
return list
}
val stringBuilder = StringBuilder()
var quoted = false
for (i in 0 until text.length) {
val ch = text[i]
if ((ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') && !quoted) {
val len = stringBuilder.length
when {
len > 0 -> {
list.add(stringBuilder.toString())
stringBuilder.delete(0, len)
}
ch == '\"' -> quoted = !quoted
else -> stringBuilder.append(ch)
}
}
}
if (stringBuilder.isNotEmpty()) {
list.add(stringBuilder.toString())
}
return list
}
fun indexOfIgnoreCase(text: String, search: String, start: Int, locale: Locale): Int {
return text.toLowerCase(locale).indexOf(search.toLowerCase(locale), start)
}
fun indexOfIgnoreCase(text: String, search: String, locale: Locale): Int {
return indexOfIgnoreCase(text, search, 0, locale)
}
fun replaceAllIgnoresCase(text: String?, find: String?, newText: String?, locale: Locale): String? {
var currentText = text
if (currentText == null || find == null || newText == null) {
return currentText
}
var pos = 0
while (pos < currentText!!.length) {
pos = indexOfIgnoreCase(currentText, find, pos, locale)
if (pos < 0) {
break
}
val before = currentText.substring(0, pos)
val after = currentText.substring(pos + find.length)
currentText = before + newText + after
pos += newText.length
}
return currentText
}
}