mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
4 spaces files harmonisation #184
This commit is contained in:
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests;
|
package com.kunzisoft.keepass.tests;
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
@@ -24,19 +24,21 @@ import android.test.AndroidTestCase;
|
|||||||
import com.kunzisoft.keepass.tests.database.TestData;
|
import com.kunzisoft.keepass.tests.database.TestData;
|
||||||
|
|
||||||
public class AccentTest extends AndroidTestCase {
|
public class AccentTest extends AndroidTestCase {
|
||||||
|
|
||||||
private static final String KEYFILE = "";
|
|
||||||
private static final String PASSWORD = "é";
|
|
||||||
private static final String ASSET = "accent.kdb";
|
|
||||||
private static final String FILENAME = "/sdcard/accent.kdb";
|
|
||||||
|
|
||||||
public void testOpen() {
|
|
||||||
|
|
||||||
try {
|
private static final String KEYFILE = "";
|
||||||
TestData.GetDb(getContext(), ASSET, PASSWORD, KEYFILE, FILENAME);
|
private static final String PASSWORD = "é";
|
||||||
} catch (Exception e) {
|
private static final String ASSET = "accent.kdb";
|
||||||
assertTrue("Failed to open database", false);
|
private static final String FILENAME = "/sdcard/accent.kdb";
|
||||||
}
|
|
||||||
}
|
public void testOpen() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
TestData.GetDb(getContext(), ASSET, PASSWORD, KEYFILE, FILENAME);
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue("Failed to open database", false);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,10 +26,10 @@ import android.test.suitebuilder.TestSuiteBuilder;
|
|||||||
|
|
||||||
public class OutputTests extends TestSuite {
|
public class OutputTests extends TestSuite {
|
||||||
|
|
||||||
public static Test suite() {
|
public static Test suite() {
|
||||||
|
|
||||||
return new TestSuiteBuilder(AllTests.class)
|
return new TestSuiteBuilder(AllTests.class)
|
||||||
.includePackages("com.kunzisoft.keepass.tests.output")
|
.includePackages("com.kunzisoft.keepass.tests.output")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -24,15 +24,15 @@ import junit.framework.TestCase;
|
|||||||
import com.kunzisoft.keepass.database.element.PwDate;
|
import com.kunzisoft.keepass.database.element.PwDate;
|
||||||
|
|
||||||
public class PwDateTest extends TestCase {
|
public class PwDateTest extends TestCase {
|
||||||
public void testDate() {
|
public void testDate() {
|
||||||
PwDate jDate = new PwDate(System.currentTimeMillis());
|
PwDate jDate = new PwDate(System.currentTimeMillis());
|
||||||
|
|
||||||
PwDate intermediate = new PwDate(jDate);
|
PwDate intermediate = new PwDate(jDate);
|
||||||
|
|
||||||
PwDate cDate = new PwDate(intermediate.getCDate(), 0);
|
PwDate cDate = new PwDate(intermediate.getCDate(), 0);
|
||||||
|
|
||||||
assertTrue("jDate and intermediate not equal", jDate.equals(intermediate));
|
assertTrue("jDate and intermediate not equal", jDate.equals(intermediate));
|
||||||
assertTrue("jDate and cDate not equal", cDate.equals(jDate));
|
assertTrue("jDate and cDate not equal", cDate.equals(jDate));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -31,33 +31,33 @@ import com.kunzisoft.keepass.database.element.PwEntryV3;
|
|||||||
import com.kunzisoft.keepass.tests.database.TestData;
|
import com.kunzisoft.keepass.tests.database.TestData;
|
||||||
|
|
||||||
public class PwEntryTestV3 extends AndroidTestCase {
|
public class PwEntryTestV3 extends AndroidTestCase {
|
||||||
PwEntryV3 mPE;
|
PwEntryV3 mPE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
// mPE = (PwEntryV3) TestData.GetTest1(getContext()).getEntryAt(0);
|
// mPE = (PwEntryV3) TestData.GetTest1(getContext()).getEntryAt(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testName() {
|
public void testName() {
|
||||||
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
|
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPassword() throws UnsupportedEncodingException {
|
public void testPassword() throws UnsupportedEncodingException {
|
||||||
String sPass = "12345";
|
String sPass = "12345";
|
||||||
byte[] password = sPass.getBytes("UTF-8");
|
byte[] password = sPass.getBytes("UTF-8");
|
||||||
|
|
||||||
assertArrayEquals(password, mPE.getPasswordBytes());
|
assertArrayEquals(password, mPE.getPasswordBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCreation() {
|
public void testCreation() {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
cal.setTime(mPE.getCreationTime().getDate());
|
cal.setTime(mPE.getCreationTime().getDate());
|
||||||
|
|
||||||
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
|
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
|
||||||
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
|
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
|
||||||
assertEquals("Incorrect day.", cal.get(Calendar.DAY_OF_MONTH), 23);
|
assertEquals("Incorrect day.", cal.get(Calendar.DAY_OF_MONTH), 23);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.tests;
|
|||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
public class PwEntryTestV4 extends TestCase {
|
public class PwEntryTestV4 extends TestCase {
|
||||||
public void testAssign() {
|
public void testAssign() {
|
||||||
/*
|
/*
|
||||||
TODO Test
|
TODO Test
|
||||||
PwEntryV4 entry = new PwEntryV4();
|
PwEntryV4 entry = new PwEntryV4();
|
||||||
@@ -53,7 +53,7 @@ public class PwEntryTestV4 extends TestCase {
|
|||||||
/* This test is not so useful now that I am not implementing value equality for Entries
|
/* This test is not so useful now that I am not implementing value equality for Entries
|
||||||
assertTrue("Entries do not match.", entry.equals(target));
|
assertTrue("Entries do not match.", entry.equals(target));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -27,18 +27,18 @@ import com.kunzisoft.keepass.tests.database.TestData;
|
|||||||
|
|
||||||
public class PwGroupTest extends AndroidTestCase {
|
public class PwGroupTest extends AndroidTestCase {
|
||||||
|
|
||||||
PwGroupV3 mPG;
|
PwGroupV3 mPG;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
//mPG = (PwGroupV3) TestData.GetTest1(getContext()).getGroups().get(0);
|
//mPG = (PwGroupV3) TestData.GetTest1(getContext()).getGroups().get(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGroupName() {
|
public void testGroupName() {
|
||||||
//assertTrue("Name was " + mPG.getTitle(), mPG.getTitle().equals("Internet"));
|
//assertTrue("Name was " + mPG.getTitle(), mPG.getTitle().equals("Internet"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -33,29 +33,29 @@ import com.kunzisoft.keepass.utils.EmptyUtils;
|
|||||||
import com.kunzisoft.keepass.utils.UriUtil;
|
import com.kunzisoft.keepass.utils.UriUtil;
|
||||||
|
|
||||||
public class TestUtil {
|
public class TestUtil {
|
||||||
private static final File sdcard = Environment.getExternalStorageDirectory();
|
private static final File sdcard = Environment.getExternalStorageDirectory();
|
||||||
|
|
||||||
public static void extractKey(Context ctx, String asset, String target) throws Exception {
|
public static void extractKey(Context ctx, String asset, String target) throws Exception {
|
||||||
|
|
||||||
InputStream key = ctx.getAssets().open(asset, AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
FileOutputStream keyFile = new FileOutputStream(target);
|
|
||||||
while (true) {
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
int read = key.read(buf);
|
|
||||||
if ( read == -1 ) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
keyFile.write(buf, 0, read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyFile.close();
|
|
||||||
|
|
||||||
}
|
InputStream key = ctx.getAssets().open(asset, AssetManager.ACCESS_STREAMING);
|
||||||
|
|
||||||
public static String getSdPath(String filename) {
|
FileOutputStream keyFile = new FileOutputStream(target);
|
||||||
File file = new File(sdcard, filename);
|
while (true) {
|
||||||
return file.getAbsolutePath();
|
byte[] buf = new byte[1024];
|
||||||
}
|
int read = key.read(buf);
|
||||||
|
if ( read == -1 ) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
keyFile.write(buf, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyFile.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSdPath(String filename) {
|
||||||
|
File file = new File(sdcard, filename);
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -35,177 +35,177 @@ import com.kunzisoft.keepass.utils.Types;
|
|||||||
|
|
||||||
public class TypesTest extends TestCase {
|
public class TypesTest extends TestCase {
|
||||||
|
|
||||||
public void testReadWriteLongZero() {
|
public void testReadWriteLongZero() {
|
||||||
testReadWriteLong((byte) 0);
|
testReadWriteLong((byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadWriteLongMax() {
|
|
||||||
testReadWriteLong(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteLongMin() {
|
|
||||||
testReadWriteLong(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteLongRnd() {
|
|
||||||
Random rnd = new Random();
|
|
||||||
byte[] buf = new byte[1];
|
|
||||||
rnd.nextBytes(buf);
|
|
||||||
|
|
||||||
testReadWriteLong(buf[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteLong(byte value) {
|
|
||||||
byte[] orig = new byte[8];
|
|
||||||
byte[] dest = new byte[8];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 8);
|
|
||||||
|
|
||||||
long one = LEDataInputStream.readLong(orig, 0);
|
|
||||||
LEDataOutputStream.writeLong(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
public void testReadWriteLongMax() {
|
||||||
|
testReadWriteLong(Byte.MAX_VALUE);
|
||||||
public void testReadWriteIntZero() {
|
}
|
||||||
testReadWriteInt((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteIntMin() {
|
|
||||||
testReadWriteInt(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteIntMax() {
|
|
||||||
testReadWriteInt(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteInt(byte value) {
|
|
||||||
byte[] orig = new byte[4];
|
|
||||||
byte[] dest = new byte[4];
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++ ) {
|
|
||||||
orig[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 4);
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readInt(orig, 0);
|
|
||||||
|
|
||||||
LEDataOutputStream.writeInt(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
public void testReadWriteLongMin() {
|
||||||
|
testReadWriteLong(Byte.MIN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setArray(byte[] buf, byte value, int offset, int size) {
|
|
||||||
for (int i = offset; i < offset + size; i++) {
|
|
||||||
buf[i] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortOne() {
|
|
||||||
byte[] orig = new byte[2];
|
|
||||||
byte[] dest = new byte[2];
|
|
||||||
|
|
||||||
orig[0] = 0;
|
|
||||||
orig[1] = 1;
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readUShort(orig, 0);
|
|
||||||
dest = LEDataOutputStream.writeUShortBuf(one);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortMin() {
|
|
||||||
testReadWriteShort(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortMax() {
|
|
||||||
testReadWriteShort(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteShort(byte value) {
|
|
||||||
byte[] orig = new byte[2];
|
|
||||||
byte[] dest = new byte[2];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 2);
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readUShort(orig, 0);
|
|
||||||
LEDataOutputStream.writeUShort(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
public void testReadWriteLongRnd() {
|
||||||
|
Random rnd = new Random();
|
||||||
|
byte[] buf = new byte[1];
|
||||||
|
rnd.nextBytes(buf);
|
||||||
|
|
||||||
public void testReadWriteByteZero() {
|
testReadWriteLong(buf[0]);
|
||||||
testReadWriteByte((byte) 0);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteByteMin() {
|
|
||||||
testReadWriteByte(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteByteMax() {
|
|
||||||
testReadWriteShort(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteByte(byte value) {
|
|
||||||
byte[] orig = new byte[1];
|
|
||||||
byte[] dest = new byte[1];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 1);
|
|
||||||
|
|
||||||
int one = Types.readUByte(orig, 0);
|
|
||||||
Types.writeUByte(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDate() {
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
|
|
||||||
Calendar expected = Calendar.getInstance();
|
|
||||||
expected.set(2008, 1, 2, 3, 4, 5);
|
|
||||||
|
|
||||||
byte[] buf = PwDate.writeTime(expected.getTime(), cal);
|
|
||||||
Calendar actual = Calendar.getInstance();
|
|
||||||
actual.setTime(PwDate.readTime(buf, 0, cal));
|
|
||||||
|
|
||||||
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR));
|
|
||||||
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH));
|
|
||||||
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH));
|
|
||||||
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY));
|
|
||||||
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE));
|
|
||||||
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testUUID() {
|
|
||||||
Random rnd = new Random();
|
|
||||||
byte[] bUUID = new byte[16];
|
|
||||||
rnd.nextBytes(bUUID);
|
|
||||||
|
|
||||||
UUID uuid = Types.bytestoUUID(bUUID);
|
|
||||||
byte[] eUUID = Types.UUIDtoBytes(uuid);
|
|
||||||
|
|
||||||
assertArrayEquals("UUID match failed", bUUID, eUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testULongMax() throws Exception {
|
private void testReadWriteLong(byte value) {
|
||||||
byte[] ulongBytes = new byte[8];
|
byte[] orig = new byte[8];
|
||||||
for (int i = 0; i < ulongBytes.length; i++) {
|
byte[] dest = new byte[8];
|
||||||
ulongBytes[i] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
setArray(orig, value, 0, 8);
|
||||||
LEDataOutputStream leos = new LEDataOutputStream(bos);
|
|
||||||
leos.writeLong(Types.ULONG_MAX_VALUE);
|
|
||||||
leos.close();
|
|
||||||
|
|
||||||
byte[] uLongMax = bos.toByteArray();
|
long one = LEDataInputStream.readLong(orig, 0);
|
||||||
|
LEDataOutputStream.writeLong(one, dest, 0);
|
||||||
|
|
||||||
assertArrayEquals(ulongBytes, uLongMax);
|
assertArrayEquals(orig, dest);
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteIntZero() {
|
||||||
|
testReadWriteInt((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteIntMin() {
|
||||||
|
testReadWriteInt(Byte.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteIntMax() {
|
||||||
|
testReadWriteInt(Byte.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReadWriteInt(byte value) {
|
||||||
|
byte[] orig = new byte[4];
|
||||||
|
byte[] dest = new byte[4];
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++ ) {
|
||||||
|
orig[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setArray(orig, value, 0, 4);
|
||||||
|
|
||||||
|
int one = LEDataInputStream.readInt(orig, 0);
|
||||||
|
|
||||||
|
LEDataOutputStream.writeInt(one, dest, 0);
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setArray(byte[] buf, byte value, int offset, int size) {
|
||||||
|
for (int i = offset; i < offset + size; i++) {
|
||||||
|
buf[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteShortOne() {
|
||||||
|
byte[] orig = new byte[2];
|
||||||
|
byte[] dest = new byte[2];
|
||||||
|
|
||||||
|
orig[0] = 0;
|
||||||
|
orig[1] = 1;
|
||||||
|
|
||||||
|
int one = LEDataInputStream.readUShort(orig, 0);
|
||||||
|
dest = LEDataOutputStream.writeUShortBuf(one);
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteShortMin() {
|
||||||
|
testReadWriteShort(Byte.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteShortMax() {
|
||||||
|
testReadWriteShort(Byte.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReadWriteShort(byte value) {
|
||||||
|
byte[] orig = new byte[2];
|
||||||
|
byte[] dest = new byte[2];
|
||||||
|
|
||||||
|
setArray(orig, value, 0, 2);
|
||||||
|
|
||||||
|
int one = LEDataInputStream.readUShort(orig, 0);
|
||||||
|
LEDataOutputStream.writeUShort(one, dest, 0);
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteByteZero() {
|
||||||
|
testReadWriteByte((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteByteMin() {
|
||||||
|
testReadWriteByte(Byte.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadWriteByteMax() {
|
||||||
|
testReadWriteShort(Byte.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReadWriteByte(byte value) {
|
||||||
|
byte[] orig = new byte[1];
|
||||||
|
byte[] dest = new byte[1];
|
||||||
|
|
||||||
|
setArray(orig, value, 0, 1);
|
||||||
|
|
||||||
|
int one = Types.readUByte(orig, 0);
|
||||||
|
Types.writeUByte(one, dest, 0);
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDate() {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
|
||||||
|
Calendar expected = Calendar.getInstance();
|
||||||
|
expected.set(2008, 1, 2, 3, 4, 5);
|
||||||
|
|
||||||
|
byte[] buf = PwDate.writeTime(expected.getTime(), cal);
|
||||||
|
Calendar actual = Calendar.getInstance();
|
||||||
|
actual.setTime(PwDate.readTime(buf, 0, cal));
|
||||||
|
|
||||||
|
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR));
|
||||||
|
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH));
|
||||||
|
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH));
|
||||||
|
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY));
|
||||||
|
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE));
|
||||||
|
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUUID() {
|
||||||
|
Random rnd = new Random();
|
||||||
|
byte[] bUUID = new byte[16];
|
||||||
|
rnd.nextBytes(bUUID);
|
||||||
|
|
||||||
|
UUID uuid = Types.bytestoUUID(bUUID);
|
||||||
|
byte[] eUUID = Types.UUIDtoBytes(uuid);
|
||||||
|
|
||||||
|
assertArrayEquals("UUID match failed", bUUID, eUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testULongMax() throws Exception {
|
||||||
|
byte[] ulongBytes = new byte[8];
|
||||||
|
for (int i = 0; i < ulongBytes.length; i++) {
|
||||||
|
ulongBytes[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
LEDataOutputStream leos = new LEDataOutputStream(bos);
|
||||||
|
leos.writeLong(Types.ULONG_MAX_VALUE);
|
||||||
|
leos.close();
|
||||||
|
|
||||||
|
byte[] uLongMax = bos.toByteArray();
|
||||||
|
|
||||||
|
assertArrayEquals(ulongBytes, uLongMax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
package com.kunzisoft.keepass.tests.crypto;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
import com.kunzisoft.keepass.crypto.CipherFactory;
|
||||||
@@ -38,46 +38,46 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
public class AESTest extends TestCase {
|
public class AESTest extends TestCase {
|
||||||
|
|
||||||
private Random mRand = new Random();
|
private Random mRand = new Random();
|
||||||
|
|
||||||
public void testEncrypt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
public void testEncrypt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||||
// Test above below and at the blocksize
|
// Test above below and at the blocksize
|
||||||
testFinal(15);
|
testFinal(15);
|
||||||
testFinal(16);
|
testFinal(16);
|
||||||
testFinal(17);
|
testFinal(17);
|
||||||
|
|
||||||
// Test random larger sizes
|
// Test random larger sizes
|
||||||
int size = mRand.nextInt(494) + 18;
|
int size = mRand.nextInt(494) + 18;
|
||||||
testFinal(size);
|
testFinal(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testFinal(int dataSize) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
private void testFinal(int dataSize) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
// Generate some input
|
// Generate some input
|
||||||
byte[] input = new byte[dataSize];
|
byte[] input = new byte[dataSize];
|
||||||
mRand.nextBytes(input);
|
mRand.nextBytes(input);
|
||||||
|
|
||||||
// Generate key
|
// Generate key
|
||||||
byte[] keyArray = new byte[32];
|
byte[] keyArray = new byte[32];
|
||||||
mRand.nextBytes(keyArray);
|
mRand.nextBytes(keyArray);
|
||||||
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
|
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
|
||||||
|
|
||||||
// Generate IV
|
// Generate IV
|
||||||
byte[] ivArray = new byte[16];
|
byte[] ivArray = new byte[16];
|
||||||
mRand.nextBytes(ivArray);
|
mRand.nextBytes(ivArray);
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivArray);
|
IvParameterSpec iv = new IvParameterSpec(ivArray);
|
||||||
|
|
||||||
Cipher android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true);
|
Cipher android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true);
|
||||||
android.init(Cipher.ENCRYPT_MODE, key, iv);
|
android.init(Cipher.ENCRYPT_MODE, key, iv);
|
||||||
byte[] outAndroid = android.doFinal(input, 0, dataSize);
|
byte[] outAndroid = android.doFinal(input, 0, dataSize);
|
||||||
|
|
||||||
Cipher nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
Cipher nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
||||||
nat.init(Cipher.ENCRYPT_MODE, key, iv);
|
nat.init(Cipher.ENCRYPT_MODE, key, iv);
|
||||||
byte[] outNative = nat.doFinal(input, 0, dataSize);
|
byte[] outNative = nat.doFinal(input, 0, dataSize);
|
||||||
|
|
||||||
assertArrayEquals("Arrays differ on size: " + dataSize, outAndroid, outNative);
|
assertArrayEquals("Arrays differ on size: " + dataSize, outAndroid, outNative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
package com.kunzisoft.keepass.tests.crypto;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
@@ -44,57 +44,57 @@ import com.kunzisoft.keepass.stream.BetterCipherInputStream;
|
|||||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||||
|
|
||||||
public class CipherTest extends TestCase {
|
public class CipherTest extends TestCase {
|
||||||
private Random rand = new Random();
|
private Random rand = new Random();
|
||||||
|
|
||||||
public void testCipherFactory() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
|
|
||||||
byte[] plaintext = new byte[1024];
|
|
||||||
|
|
||||||
rand.nextBytes(key);
|
|
||||||
rand.nextBytes(iv);
|
|
||||||
rand.nextBytes(plaintext);
|
|
||||||
|
|
||||||
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
|
||||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
|
||||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
|
||||||
|
|
||||||
byte[] secrettext = encrypt.doFinal(plaintext);
|
public void testCipherFactory() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||||
byte[] decrypttext = decrypt.doFinal(secrettext);
|
byte[] key = new byte[32];
|
||||||
|
byte[] iv = new byte[16];
|
||||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCipherStreams() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
|
byte[] plaintext = new byte[1024];
|
||||||
final int MESSAGE_LENGTH = 1024;
|
|
||||||
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
|
|
||||||
byte[] plaintext = new byte[MESSAGE_LENGTH];
|
|
||||||
|
|
||||||
rand.nextBytes(key);
|
|
||||||
rand.nextBytes(iv);
|
|
||||||
rand.nextBytes(plaintext);
|
|
||||||
|
|
||||||
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
rand.nextBytes(key);
|
||||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
rand.nextBytes(iv);
|
||||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
rand.nextBytes(plaintext);
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
||||||
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
|
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||||
cos.write(plaintext);
|
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||||
cos.close();
|
|
||||||
|
byte[] secrettext = encrypt.doFinal(plaintext);
|
||||||
byte[] secrettext = bos.toByteArray();
|
byte[] decrypttext = decrypt.doFinal(secrettext);
|
||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(secrettext);
|
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
||||||
BetterCipherInputStream cis = new BetterCipherInputStream(bis, decrypt);
|
}
|
||||||
LEDataInputStream lis = new LEDataInputStream(cis);
|
|
||||||
|
public void testCipherStreams() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
|
||||||
byte[] decrypttext = lis.readBytes(MESSAGE_LENGTH);
|
final int MESSAGE_LENGTH = 1024;
|
||||||
|
|
||||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
byte[] key = new byte[32];
|
||||||
}
|
byte[] iv = new byte[16];
|
||||||
|
|
||||||
|
byte[] plaintext = new byte[MESSAGE_LENGTH];
|
||||||
|
|
||||||
|
rand.nextBytes(key);
|
||||||
|
rand.nextBytes(iv);
|
||||||
|
rand.nextBytes(plaintext);
|
||||||
|
|
||||||
|
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
||||||
|
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||||
|
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
|
||||||
|
cos.write(plaintext);
|
||||||
|
cos.close();
|
||||||
|
|
||||||
|
byte[] secrettext = bos.toByteArray();
|
||||||
|
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(secrettext);
|
||||||
|
BetterCipherInputStream cis = new BetterCipherInputStream(bis, decrypt);
|
||||||
|
LEDataInputStream lis = new LEDataInputStream(cis);
|
||||||
|
|
||||||
|
byte[] decrypttext = lis.readBytes(MESSAGE_LENGTH);
|
||||||
|
|
||||||
|
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
package com.kunzisoft.keepass.tests.crypto;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
@@ -30,37 +30,37 @@ import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey;
|
|||||||
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey;
|
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey;
|
||||||
|
|
||||||
public class FinalKeyTest extends TestCase {
|
public class FinalKeyTest extends TestCase {
|
||||||
private Random mRand;
|
private Random mRand;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
mRand = new Random();
|
mRand = new Random();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNativeAndroid() throws IOException {
|
public void testNativeAndroid() throws IOException {
|
||||||
// Test both an old and an even number to test my flip variable
|
// Test both an old and an even number to test my flip variable
|
||||||
testNativeFinalKey(5);
|
testNativeFinalKey(5);
|
||||||
testNativeFinalKey(6);
|
testNativeFinalKey(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testNativeFinalKey(int rounds) throws IOException {
|
private void testNativeFinalKey(int rounds) throws IOException {
|
||||||
byte[] seed = new byte[32];
|
byte[] seed = new byte[32];
|
||||||
byte[] key = new byte[32];
|
byte[] key = new byte[32];
|
||||||
byte[] nativeKey;
|
byte[] nativeKey;
|
||||||
byte[] androidKey;
|
byte[] androidKey;
|
||||||
|
|
||||||
mRand.nextBytes(seed);
|
mRand.nextBytes(seed);
|
||||||
mRand.nextBytes(key);
|
mRand.nextBytes(key);
|
||||||
|
|
||||||
AndroidFinalKey aKey = new AndroidFinalKey();
|
AndroidFinalKey aKey = new AndroidFinalKey();
|
||||||
androidKey = aKey.transformMasterKey(seed, key, rounds);
|
androidKey = aKey.transformMasterKey(seed, key, rounds);
|
||||||
|
|
||||||
NativeFinalKey nKey = new NativeFinalKey();
|
NativeFinalKey nKey = new NativeFinalKey();
|
||||||
nativeKey = nKey.transformMasterKey(seed, key, rounds);
|
nativeKey = nKey.transformMasterKey(seed, key, rounds);
|
||||||
|
|
||||||
assertArrayEquals("Does not match", androidKey, nativeKey);
|
assertArrayEquals("Does not match", androidKey, nativeKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,15 +26,15 @@ import com.kunzisoft.keepass.database.element.PwDatabaseV3;
|
|||||||
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
||||||
|
|
||||||
public class DeleteEntry extends AndroidTestCase {
|
public class DeleteEntry extends AndroidTestCase {
|
||||||
private static final String GROUP1_NAME = "Group1";
|
private static final String GROUP1_NAME = "Group1";
|
||||||
private static final String ENTRY1_NAME = "Test1";
|
private static final String ENTRY1_NAME = "Test1";
|
||||||
private static final String ENTRY2_NAME = "Test2";
|
private static final String ENTRY2_NAME = "Test2";
|
||||||
private static final String KEYFILE = "";
|
private static final String KEYFILE = "";
|
||||||
private static final String PASSWORD = "12345";
|
private static final String PASSWORD = "12345";
|
||||||
private static final String ASSET = "delete.kdb";
|
private static final String ASSET = "delete.kdb";
|
||||||
private static final String FILENAME = "/sdcard/delete.kdb";
|
private static final String FILENAME = "/sdcard/delete.kdb";
|
||||||
|
|
||||||
public void testDelete() {
|
public void testDelete() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Database db;
|
Database db;
|
||||||
@@ -76,9 +76,9 @@ public class DeleteEntry extends AndroidTestCase {
|
|||||||
assertNull("Group 1 was not removed.", group1);
|
assertNull("Group 1 was not removed.", group1);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PwEntryV3 getEntry(PwDatabaseV3 pm, String name) {
|
private PwEntryV3 getEntry(PwDatabaseV3 pm, String name) {
|
||||||
/*
|
/*
|
||||||
TODO test
|
TODO test
|
||||||
List<PwEntryV3> entries = pm.getEntries();
|
List<PwEntryV3> entries = pm.getEntries();
|
||||||
@@ -89,11 +89,11 @@ public class DeleteEntry extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupVersioned getGroup(PwDatabase pm, String name) {
|
private GroupVersioned getGroup(PwDatabase pm, String name) {
|
||||||
/*
|
/*
|
||||||
List<GroupVersioned> groups = pm.getGroups();
|
List<GroupVersioned> groups = pm.getGroups();
|
||||||
for ( int i = 0; i < groups.size(); i++ ) {
|
for ( int i = 0; i < groups.size(); i++ ) {
|
||||||
@@ -103,9 +103,9 @@ public class DeleteEntry extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,7 +26,7 @@ import junit.framework.TestCase;
|
|||||||
|
|
||||||
public class EntryV4 extends TestCase {
|
public class EntryV4 extends TestCase {
|
||||||
|
|
||||||
public void testBackup() {
|
public void testBackup() {
|
||||||
/*
|
/*
|
||||||
PwDatabaseV4 db = new PwDatabaseV4();
|
PwDatabaseV4 db = new PwDatabaseV4();
|
||||||
|
|
||||||
@@ -51,6 +51,6 @@ public class EntryV4 extends TestCase {
|
|||||||
assertEquals("Title2", backup.getTitle());
|
assertEquals("Title2", backup.getTitle());
|
||||||
assertEquals("User2", backup.getUsername());
|
assertEquals("User2", backup.getUsername());
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.tests.database;
|
|||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
public class Kdb3 extends AndroidTestCase {
|
public class Kdb3 extends AndroidTestCase {
|
||||||
|
|
||||||
private void testKeyfile(String dbAsset, String keyAsset, String password) throws Exception {
|
private void testKeyfile(String dbAsset, String keyAsset, String password) throws Exception {
|
||||||
/*
|
/*
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
||||||
@@ -40,14 +40,14 @@ public class Kdb3 extends AndroidTestCase {
|
|||||||
|
|
||||||
is.close();
|
is.close();
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testXMLKeyFile() throws Exception {
|
public void testXMLKeyFile() throws Exception {
|
||||||
testKeyfile("kdb_with_xml_keyfile.kdb", "keyfile.key", "12345");
|
testKeyfile("kdb_with_xml_keyfile.kdb", "keyfile.key", "12345");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBinary64KeyFile() throws Exception {
|
public void testBinary64KeyFile() throws Exception {
|
||||||
testKeyfile("binary-key.kdb", "binary.key", "12345");
|
testKeyfile("binary-key.kdb", "binary.key", "12345");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.tests.database;
|
|||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
public class Kdb3Twofish extends AndroidTestCase {
|
public class Kdb3Twofish extends AndroidTestCase {
|
||||||
public void testReadTwofish() throws Exception {
|
public void testReadTwofish() throws Exception {
|
||||||
/*
|
/*
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
||||||
@@ -37,5 +37,5 @@ public class Kdb3Twofish extends AndroidTestCase {
|
|||||||
|
|
||||||
is.close();
|
is.close();
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,11 +26,11 @@ import android.test.AndroidTestCase;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class Kdb4Header extends AndroidTestCase {
|
public class Kdb4Header extends AndroidTestCase {
|
||||||
public void testReadHeader() throws Exception {
|
public void testReadHeader() throws Exception {
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
AssetManager am = ctx.getAssets();
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO Test
|
TODO Test
|
||||||
@@ -45,5 +45,5 @@ public class Kdb4Header extends AndroidTestCase {
|
|||||||
is.close();
|
is.close();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -35,17 +35,17 @@ import java.util.UUID;
|
|||||||
import biz.source_code.base64Coder.Base64Coder;
|
import biz.source_code.base64Coder.Base64Coder;
|
||||||
|
|
||||||
public class SprEngineTest extends AndroidTestCase {
|
public class SprEngineTest extends AndroidTestCase {
|
||||||
private PwDatabaseV4 db;
|
private PwDatabaseV4 db;
|
||||||
private SprEngineV4 spr;
|
private SprEngineV4 spr;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
AssetManager am = ctx.getAssets();
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO Test
|
TODO Test
|
||||||
@@ -56,12 +56,12 @@ public class SprEngineTest extends AndroidTestCase {
|
|||||||
|
|
||||||
spr = new SprEngineV4();
|
spr = new SprEngineV4();
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
|
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
|
||||||
private final String ENCODE_UUID = "IN7RkON49Ui1UZ2ddqmLcw==";
|
private final String ENCODE_UUID = "IN7RkON49Ui1UZ2ddqmLcw==";
|
||||||
private final String RESULT = "Password";
|
private final String RESULT = "Password";
|
||||||
public void testRefReplace() {
|
public void testRefReplace() {
|
||||||
/*
|
/*
|
||||||
TODO TEST
|
TODO TEST
|
||||||
UUID entryUUID = decodeUUID(ENCODE_UUID);
|
UUID entryUUID = decodeUUID(ENCODE_UUID);
|
||||||
@@ -71,16 +71,16 @@ public class SprEngineTest extends AndroidTestCase {
|
|||||||
|
|
||||||
assertEquals(RESULT, spr.compile(REF, entry, db));
|
assertEquals(RESULT, spr.compile(REF, entry, db));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private UUID decodeUUID(String encoded) {
|
private UUID decodeUUID(String encoded) {
|
||||||
if (encoded == null || encoded.length() == 0 ) {
|
if (encoded == null || encoded.length() == 0 ) {
|
||||||
return PwDatabase.UUID_ZERO;
|
return PwDatabase.UUID_ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] buf = Base64Coder.decode(encoded);
|
byte[] buf = Base64Coder.decode(encoded);
|
||||||
return Types.bytestoUUID(buf);
|
return Types.bytestoUUID(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,11 +22,11 @@ package com.kunzisoft.keepass.tests.database;
|
|||||||
import com.kunzisoft.keepass.database.element.Database;
|
import com.kunzisoft.keepass.database.element.Database;
|
||||||
|
|
||||||
public class TestData {
|
public class TestData {
|
||||||
private static final String TEST1_KEYFILE = "";
|
private static final String TEST1_KEYFILE = "";
|
||||||
private static final String TEST1_KDB = "test1.kdb";
|
private static final String TEST1_KDB = "test1.kdb";
|
||||||
private static final String TEST1_PASSWORD = "12345";
|
private static final String TEST1_PASSWORD = "12345";
|
||||||
|
|
||||||
private static Database mDb1;
|
private static Database mDb1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.search;
|
package com.kunzisoft.keepass.tests.search;
|
||||||
|
|
||||||
|
|
||||||
@@ -28,43 +28,43 @@ import com.kunzisoft.keepass.database.element.Database;
|
|||||||
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
||||||
|
|
||||||
public class SearchTest extends AndroidTestCase {
|
public class SearchTest extends AndroidTestCase {
|
||||||
|
|
||||||
private Database mDb;
|
private Database mDb;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
//mDb = TestData.GetDb1(getContext(), true);
|
//mDb = TestData.GetDb1(getContext(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSearch() {
|
public void testSearch() {
|
||||||
GroupVersioned results = mDb.search("Amazon");
|
GroupVersioned results = mDb.search("Amazon");
|
||||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBackupIncluded() {
|
public void testBackupIncluded() {
|
||||||
updateOmitSetting(false);
|
updateOmitSetting(false);
|
||||||
GroupVersioned results = mDb.search("BackupOnly");
|
GroupVersioned results = mDb.search("BackupOnly");
|
||||||
|
|
||||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBackupExcluded() {
|
public void testBackupExcluded() {
|
||||||
updateOmitSetting(true);
|
updateOmitSetting(true);
|
||||||
GroupVersioned results = mDb.search("BackupOnly");
|
GroupVersioned results = mDb.search("BackupOnly");
|
||||||
|
|
||||||
//assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
|
//assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOmitSetting(boolean setting) {
|
private void updateOmitSetting(boolean setting) {
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
editor.putBoolean("settings_omitbackup_key", setting);
|
editor.putBoolean("settings_omitbackup_key", setting);
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.stream;
|
package com.kunzisoft.keepass.tests.stream;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
@@ -34,77 +34,77 @@ import com.kunzisoft.keepass.stream.HashedBlockInputStream;
|
|||||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream;
|
import com.kunzisoft.keepass.stream.HashedBlockOutputStream;
|
||||||
|
|
||||||
public class HashedBlock extends TestCase {
|
public class HashedBlock extends TestCase {
|
||||||
|
|
||||||
private static Random rand = new Random();
|
|
||||||
|
|
||||||
public void testBlockAligned() throws IOException {
|
private static Random rand = new Random();
|
||||||
testSize(1024, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testOffset() throws IOException {
|
|
||||||
testSize(1500, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testSize(int blockSize, int bufferSize) throws IOException {
|
|
||||||
byte[] orig = new byte[blockSize];
|
|
||||||
|
|
||||||
rand.nextBytes(orig);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, bufferSize);
|
|
||||||
output.write(orig);
|
|
||||||
output.close();
|
|
||||||
|
|
||||||
byte[] encoded = bos.toByteArray();
|
|
||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
|
||||||
HashedBlockInputStream input = new HashedBlockInputStream(bis);
|
|
||||||
|
|
||||||
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
|
public void testBlockAligned() throws IOException {
|
||||||
while ( true ) {
|
testSize(1024, 1024);
|
||||||
byte[] buf = new byte[1024];
|
}
|
||||||
int read = input.read(buf);
|
|
||||||
if ( read == -1 ) {
|
public void testOffset() throws IOException {
|
||||||
break;
|
testSize(1500, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
decoded.write(buf, 0, read);
|
private void testSize(int blockSize, int bufferSize) throws IOException {
|
||||||
}
|
byte[] orig = new byte[blockSize];
|
||||||
|
|
||||||
byte[] out = decoded.toByteArray();
|
rand.nextBytes(orig);
|
||||||
|
|
||||||
assertArrayEquals(orig, out);
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, bufferSize);
|
||||||
}
|
output.write(orig);
|
||||||
|
output.close();
|
||||||
public void testGZIPStream() throws IOException {
|
|
||||||
final int testLength = 32000;
|
byte[] encoded = bos.toByteArray();
|
||||||
|
|
||||||
byte[] orig = new byte[testLength];
|
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||||
rand.nextBytes(orig);
|
HashedBlockInputStream input = new HashedBlockInputStream(bis);
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
|
||||||
HashedBlockOutputStream hos = new HashedBlockOutputStream(bos);
|
while ( true ) {
|
||||||
GZIPOutputStream zos = new GZIPOutputStream(hos);
|
byte[] buf = new byte[1024];
|
||||||
|
int read = input.read(buf);
|
||||||
zos.write(orig);
|
if ( read == -1 ) {
|
||||||
zos.close();
|
break;
|
||||||
|
}
|
||||||
byte[] compressed = bos.toByteArray();
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
|
decoded.write(buf, 0, read);
|
||||||
HashedBlockInputStream his = new HashedBlockInputStream(bis);
|
}
|
||||||
GZIPInputStream zis = new GZIPInputStream(his);
|
|
||||||
|
byte[] out = decoded.toByteArray();
|
||||||
byte[] uncompressed = new byte[testLength];
|
|
||||||
|
assertArrayEquals(orig, out);
|
||||||
int read = 0;
|
|
||||||
while (read != -1 && testLength - read > 0) {
|
}
|
||||||
read += zis.read(uncompressed, read, testLength - read);
|
|
||||||
|
public void testGZIPStream() throws IOException {
|
||||||
}
|
final int testLength = 32000;
|
||||||
|
|
||||||
assertArrayEquals("Output not equal to input", orig, uncompressed);
|
byte[] orig = new byte[testLength];
|
||||||
|
rand.nextBytes(orig);
|
||||||
|
|
||||||
}
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
HashedBlockOutputStream hos = new HashedBlockOutputStream(bos);
|
||||||
|
GZIPOutputStream zos = new GZIPOutputStream(hos);
|
||||||
|
|
||||||
|
zos.write(orig);
|
||||||
|
zos.close();
|
||||||
|
|
||||||
|
byte[] compressed = bos.toByteArray();
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
|
||||||
|
HashedBlockInputStream his = new HashedBlockInputStream(bis);
|
||||||
|
GZIPInputStream zis = new GZIPInputStream(his);
|
||||||
|
|
||||||
|
byte[] uncompressed = new byte[testLength];
|
||||||
|
|
||||||
|
int read = 0;
|
||||||
|
while (read != -1 && testLength - read > 0) {
|
||||||
|
read += zis.read(uncompressed, read, testLength - read);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals("Output not equal to input", orig, uncompressed);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -30,28 +30,28 @@ public class StringUtilTest extends TestCase {
|
|||||||
private final String search = "BcDe";
|
private final String search = "BcDe";
|
||||||
private final String badSearch = "Ed";
|
private final String badSearch = "Ed";
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase1() {
|
public void testIndexOfIgnoreCase1() {
|
||||||
assertEquals(1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH));
|
assertEquals(1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase2() {
|
public void testIndexOfIgnoreCase2() {
|
||||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
|
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase3() {
|
public void testIndexOfIgnoreCase3() {
|
||||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
|
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String repText = "AbCtestingaBc";
|
|
||||||
private final String repSearch = "ABc";
|
|
||||||
private final String repSearchBad = "CCCCCC";
|
|
||||||
private final String repNew = "12345";
|
|
||||||
private final String repResult = "12345testing12345";
|
|
||||||
public void testReplaceAllIgnoresCase1() {
|
|
||||||
assertEquals(repResult, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReplaceAllIgnoresCase2() {
|
private final String repText = "AbCtestingaBc";
|
||||||
assertEquals(repText, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
|
private final String repSearch = "ABc";
|
||||||
}
|
private final String repSearchBad = "CCCCCC";
|
||||||
|
private final String repNew = "12345";
|
||||||
|
private final String repResult = "12345testing12345";
|
||||||
|
public void testReplaceAllIgnoresCase1() {
|
||||||
|
assertEquals(repResult, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReplaceAllIgnoresCase2() {
|
||||||
|
assertEquals(repText, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -29,46 +29,46 @@ import com.kunzisoft.keepass.stylish.Stylish;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
public class App extends MultiDexApplication {
|
public class App extends MultiDexApplication {
|
||||||
private static Database db = null;
|
private static Database db = null;
|
||||||
private static Calendar calendar = null;
|
private static Calendar calendar = null;
|
||||||
private static RecentFileHistory fileHistory = null;
|
private static RecentFileHistory fileHistory = null;
|
||||||
|
|
||||||
public static Database getDB() {
|
|
||||||
if ( db == null ) {
|
|
||||||
db = new Database();
|
|
||||||
}
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RecentFileHistory getFileHistory() {
|
public static Database getDB() {
|
||||||
return fileHistory;
|
if ( db == null ) {
|
||||||
}
|
db = new Database();
|
||||||
|
}
|
||||||
public static void setDB(Database d) {
|
return db;
|
||||||
db = d;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static Calendar getCalendar() {
|
public static RecentFileHistory getFileHistory() {
|
||||||
if ( calendar == null ) {
|
return fileHistory;
|
||||||
calendar = Calendar.getInstance();
|
}
|
||||||
}
|
|
||||||
return calendar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public static void setDB(Database d) {
|
||||||
public void onCreate() {
|
db = d;
|
||||||
super.onCreate();
|
}
|
||||||
|
|
||||||
Stylish.init(this);
|
public static Calendar getCalendar() {
|
||||||
fileHistory = new RecentFileHistory(this);
|
if ( calendar == null ) {
|
||||||
PRNGFixes.apply();
|
calendar = Calendar.getInstance();
|
||||||
}
|
}
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onCreate() {
|
||||||
if ( db != null ) {
|
super.onCreate();
|
||||||
db.closeAndClear(getApplicationContext());
|
|
||||||
}
|
Stylish.init(this);
|
||||||
super.onTerminate();
|
fileHistory = new RecentFileHistory(this);
|
||||||
}
|
PRNGFixes.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTerminate() {
|
||||||
|
if ( db != null ) {
|
||||||
|
db.closeAndClear(getApplicationContext());
|
||||||
|
}
|
||||||
|
super.onTerminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -25,15 +25,15 @@ import android.app.backup.SharedPreferencesBackupHelper;
|
|||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
public class SettingsBackupAgent extends BackupAgentHelper {
|
public class SettingsBackupAgent extends BackupAgentHelper {
|
||||||
|
|
||||||
private static final String PREFS_BACKUP_KEY = "prefs";
|
private static final String PREFS_BACKUP_KEY = "prefs";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
String defaultPrefs = this.getPackageName() + "_preferences";
|
String defaultPrefs = this.getPackageName() + "_preferences";
|
||||||
|
|
||||||
SharedPreferencesBackupHelper prefHelper = new SharedPreferencesBackupHelper(this, defaultPrefs);
|
SharedPreferencesBackupHelper prefHelper = new SharedPreferencesBackupHelper(this, defaultPrefs);
|
||||||
addHelper(PREFS_BACKUP_KEY, prefHelper);
|
addHelper(PREFS_BACKUP_KEY, prefHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -23,14 +23,14 @@ import java.security.Provider;
|
|||||||
|
|
||||||
public final class AESProvider extends Provider {
|
public final class AESProvider extends Provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -3846349284296062658L;
|
private static final long serialVersionUID = -3846349284296062658L;
|
||||||
|
|
||||||
public AESProvider() {
|
public AESProvider() {
|
||||||
super("AESProvider", 1.0, "");
|
super("AESProvider", 1.0, "");
|
||||||
put("Cipher.AES",NativeAESCipherSpi.class.getName());
|
put("Cipher.AES",NativeAESCipherSpi.class.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -38,58 +38,58 @@ import javax.crypto.Cipher;
|
|||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
public class CipherFactory {
|
public class CipherFactory {
|
||||||
private static boolean blacklistInit = false;
|
private static boolean blacklistInit = false;
|
||||||
private static boolean blacklisted;
|
private static boolean blacklisted;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException {
|
public static Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException {
|
||||||
return getInstance(transformation, false);
|
return getInstance(transformation, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cipher getInstance(String transformation, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException {
|
public static Cipher getInstance(String transformation, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException {
|
||||||
// Return the native AES if it is possible
|
// Return the native AES if it is possible
|
||||||
if ( (!deviceBlacklisted()) && (!androidOverride) && hasNativeImplementation(transformation) && NativeLib.loaded() ) {
|
if ( (!deviceBlacklisted()) && (!androidOverride) && hasNativeImplementation(transformation) && NativeLib.loaded() ) {
|
||||||
return Cipher.getInstance(transformation, new AESProvider());
|
return Cipher.getInstance(transformation, new AESProvider());
|
||||||
} else {
|
} else {
|
||||||
return Cipher.getInstance(transformation);
|
return Cipher.getInstance(transformation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean deviceBlacklisted() {
|
public static boolean deviceBlacklisted() {
|
||||||
if (!blacklistInit) {
|
if (!blacklistInit) {
|
||||||
blacklistInit = true;
|
blacklistInit = true;
|
||||||
|
|
||||||
// The Acer Iconia A500 is special and seems to always crash in the native crypto libraries
|
// The Acer Iconia A500 is special and seems to always crash in the native crypto libraries
|
||||||
blacklisted = Build.MODEL.equals("A500");
|
blacklisted = Build.MODEL.equals("A500");
|
||||||
}
|
}
|
||||||
return blacklisted;
|
return blacklisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasNativeImplementation(String transformation) {
|
private static boolean hasNativeImplementation(String transformation) {
|
||||||
return transformation.equals("AES/CBC/PKCS5Padding");
|
return transformation.equals("AES/CBC/PKCS5Padding");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Generate appropriate cipher based on KeePass 2.x UUID's
|
/** Generate appropriate cipher based on KeePass 2.x UUID's
|
||||||
* @param uuid
|
* @param uuid
|
||||||
* @return
|
* @return
|
||||||
* @throws NoSuchPaddingException
|
* @throws NoSuchPaddingException
|
||||||
* @throws NoSuchAlgorithmException
|
* @throws NoSuchAlgorithmException
|
||||||
* @throws InvalidAlgorithmParameterException
|
* @throws InvalidAlgorithmParameterException
|
||||||
* @throws InvalidKeyException
|
* @throws InvalidKeyException
|
||||||
*/
|
*/
|
||||||
public static CipherEngine getInstance(UUID uuid) throws NoSuchAlgorithmException {
|
public static CipherEngine getInstance(UUID uuid) throws NoSuchAlgorithmException {
|
||||||
if ( uuid.equals(AesEngine.CIPHER_UUID) ) {
|
if ( uuid.equals(AesEngine.CIPHER_UUID) ) {
|
||||||
return new AesEngine();
|
return new AesEngine();
|
||||||
} else if ( uuid.equals(TwofishEngine.CIPHER_UUID) ) {
|
} else if ( uuid.equals(TwofishEngine.CIPHER_UUID) ) {
|
||||||
return new TwofishEngine();
|
return new TwofishEngine();
|
||||||
} else if ( uuid.equals(ChaCha20Engine.CIPHER_UUID)) {
|
} else if ( uuid.equals(ChaCha20Engine.CIPHER_UUID)) {
|
||||||
return new ChaCha20Engine();
|
return new ChaCha20Engine();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -44,284 +44,284 @@ import javax.crypto.spec.IvParameterSpec;
|
|||||||
|
|
||||||
public class NativeAESCipherSpi extends CipherSpi {
|
public class NativeAESCipherSpi extends CipherSpi {
|
||||||
|
|
||||||
private static final String TAG = NativeAESCipherSpi.class.getName();
|
private static final String TAG = NativeAESCipherSpi.class.getName();
|
||||||
|
|
||||||
private static boolean mIsStaticInit = false;
|
private static boolean mIsStaticInit = false;
|
||||||
private static HashMap<PhantomReference<NativeAESCipherSpi>, Long> mCleanup = new HashMap<PhantomReference<NativeAESCipherSpi>, Long>();
|
private static HashMap<PhantomReference<NativeAESCipherSpi>, Long> mCleanup = new HashMap<PhantomReference<NativeAESCipherSpi>, Long>();
|
||||||
private static ReferenceQueue<NativeAESCipherSpi> mQueue = new ReferenceQueue<NativeAESCipherSpi>();
|
private static ReferenceQueue<NativeAESCipherSpi> mQueue = new ReferenceQueue<NativeAESCipherSpi>();
|
||||||
|
|
||||||
private final int AES_BLOCK_SIZE = 16;
|
|
||||||
private byte[] mIV;
|
|
||||||
|
|
||||||
private boolean mIsInited = false;
|
|
||||||
private boolean mEncrypting = false;
|
|
||||||
private long mCtxPtr;
|
|
||||||
|
|
||||||
private boolean mPadding = false;
|
|
||||||
|
|
||||||
private static void staticInit() {
|
|
||||||
mIsStaticInit = true;
|
|
||||||
|
|
||||||
// Start the cipher context cleanup thread to run forever
|
|
||||||
(new Thread(new Cleanup())).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addToCleanupQueue(NativeAESCipherSpi ref, long ptr) {
|
|
||||||
Log.d(TAG, "queued cipher context: " + ptr);
|
|
||||||
mCleanup.put(new PhantomReference<NativeAESCipherSpi>(ref, mQueue), ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Work with the garbage collector to clean up openssl memory when the cipher
|
|
||||||
* context is garbage collected.
|
|
||||||
* @author bpellin
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private static class Cleanup implements Runnable {
|
|
||||||
|
|
||||||
public void run() {
|
private final int AES_BLOCK_SIZE = 16;
|
||||||
while (true) {
|
private byte[] mIV;
|
||||||
try {
|
|
||||||
Reference<? extends NativeAESCipherSpi> ref = mQueue.remove();
|
|
||||||
|
|
||||||
long ctx = mCleanup.remove(ref);
|
|
||||||
nCleanup(ctx);
|
|
||||||
Log.d(TAG, "Cleaned up cipher context: " + ctx);
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Do nothing, but resume looping if mQueue.remove is interrupted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static native void nCleanup(long ctxPtr);
|
|
||||||
|
|
||||||
public NativeAESCipherSpi() {
|
private boolean mIsInited = false;
|
||||||
if ( ! mIsStaticInit ) {
|
private boolean mEncrypting = false;
|
||||||
staticInit();
|
private long mCtxPtr;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
|
|
||||||
throws IllegalBlockSizeException, BadPaddingException {
|
|
||||||
int maxSize = engineGetOutputSize(inputLen);
|
|
||||||
byte[] output = new byte[maxSize];
|
|
||||||
|
|
||||||
int finalSize;
|
|
||||||
|
|
||||||
try {
|
|
||||||
finalSize = doFinal(input, inputOffset, inputLen, output, 0);
|
|
||||||
} catch (ShortBufferException e) {
|
|
||||||
// This shouldn't be possible rethrow as RuntimeException
|
|
||||||
throw new RuntimeException("Short buffer exception shouldn't be possible from here.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( maxSize == finalSize ) {
|
|
||||||
return output;
|
|
||||||
} else {
|
|
||||||
// TODO: Special doFinal to avoid this copy
|
|
||||||
byte[] exact = new byte[finalSize];
|
|
||||||
System.arraycopy(output, 0, exact, 0, finalSize);
|
|
||||||
return exact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private boolean mPadding = false;
|
||||||
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
|
|
||||||
byte[] output, int outputOffset) throws ShortBufferException,
|
|
||||||
IllegalBlockSizeException, BadPaddingException {
|
|
||||||
|
|
||||||
int result = doFinal(input, inputOffset, inputLen, output, outputOffset);
|
|
||||||
|
|
||||||
if ( result == -1 ) {
|
|
||||||
throw new ShortBufferException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
|
|
||||||
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
|
|
||||||
|
|
||||||
int outputSize = engineGetOutputSize(inputLen);
|
|
||||||
|
|
||||||
int updateAmt;
|
|
||||||
if (input != null && inputLen > 0) {
|
|
||||||
updateAmt = nUpdate(mCtxPtr, input, inputOffset, inputLen, output, outputOffset, outputSize);
|
|
||||||
} else {
|
|
||||||
updateAmt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int finalAmt = nFinal(mCtxPtr, mPadding, output, outputOffset + updateAmt, outputSize - updateAmt);
|
|
||||||
|
|
||||||
int out = updateAmt + finalAmt;
|
|
||||||
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native int nFinal(long ctxPtr, boolean usePadding, byte[] output, int outputOffest, int outputSize)
|
|
||||||
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException;
|
|
||||||
|
|
||||||
@Override
|
private static void staticInit() {
|
||||||
protected int engineGetBlockSize() {
|
mIsStaticInit = true;
|
||||||
return AES_BLOCK_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// Start the cipher context cleanup thread to run forever
|
||||||
protected byte[] engineGetIV() {
|
(new Thread(new Cleanup())).start();
|
||||||
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
|
private static void addToCleanupQueue(NativeAESCipherSpi ref, long ptr) {
|
||||||
protected int engineGetOutputSize(int inputLen) {
|
Log.d(TAG, "queued cipher context: " + ptr);
|
||||||
return inputLen + nGetCacheSize(mCtxPtr) + AES_BLOCK_SIZE;
|
mCleanup.put(new PhantomReference<NativeAESCipherSpi>(ref, mQueue), ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private native int nGetCacheSize(long ctxPtr);
|
|
||||||
|
|
||||||
@Override
|
/** Work with the garbage collector to clean up openssl memory when the cipher
|
||||||
protected AlgorithmParameters engineGetParameters() {
|
* context is garbage collected.
|
||||||
// TODO Auto-generated method stub
|
* @author bpellin
|
||||||
return null;
|
*
|
||||||
}
|
*/
|
||||||
|
private static class Cleanup implements Runnable {
|
||||||
|
|
||||||
@Override
|
public void run() {
|
||||||
protected void engineInit(int opmode, Key key, SecureRandom random)
|
while (true) {
|
||||||
throws InvalidKeyException {
|
try {
|
||||||
|
Reference<? extends NativeAESCipherSpi> ref = mQueue.remove();
|
||||||
|
|
||||||
byte[] ivArray = new byte[16];
|
long ctx = mCleanup.remove(ref);
|
||||||
random.nextBytes(ivArray);
|
nCleanup(ctx);
|
||||||
|
Log.d(TAG, "Cleaned up cipher context: " + ctx);
|
||||||
init(opmode, key, new IvParameterSpec(ivArray));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
} catch (InterruptedException e) {
|
||||||
protected void engineInit(int opmode, Key key,
|
// Do nothing, but resume looping if mQueue.remove is interrupted
|
||||||
AlgorithmParameterSpec params, SecureRandom random)
|
}
|
||||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
}
|
||||||
|
}
|
||||||
IvParameterSpec ivparam;
|
|
||||||
|
|
||||||
if ( params instanceof IvParameterSpec ) {
|
|
||||||
ivparam = (IvParameterSpec) params;
|
|
||||||
} else {
|
|
||||||
throw new InvalidAlgorithmParameterException("params must be an IvParameterSpec.");
|
|
||||||
}
|
|
||||||
|
|
||||||
init(opmode, key, ivparam);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
}
|
||||||
protected void engineInit(int opmode, Key key, AlgorithmParameters params,
|
|
||||||
SecureRandom random) throws InvalidKeyException,
|
|
||||||
InvalidAlgorithmParameterException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
engineInit(opmode, key, params.getParameterSpec(AlgorithmParameterSpec.class), random);
|
|
||||||
} catch (InvalidParameterSpecException e) {
|
|
||||||
throw new InvalidAlgorithmParameterException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
private static native void nCleanup(long ctxPtr);
|
||||||
|
|
||||||
private void init(int opmode, Key key, IvParameterSpec params) {
|
public NativeAESCipherSpi() {
|
||||||
if ( mIsInited ) {
|
if ( ! mIsStaticInit ) {
|
||||||
// Do not allow multiple inits
|
staticInit();
|
||||||
assert(true);
|
}
|
||||||
throw new RuntimeException("Don't allow multiple inits");
|
}
|
||||||
} else {
|
|
||||||
NativeLib.init();
|
|
||||||
mIsInited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
mIV = params.getIV();
|
|
||||||
mEncrypting = opmode == Cipher.ENCRYPT_MODE;
|
|
||||||
mCtxPtr = nInit(mEncrypting, key.getEncoded(), mIV);
|
|
||||||
addToCleanupQueue(this, mCtxPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long nInit(boolean encrypting, byte[] key, byte[] iv);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
|
|
||||||
if ( ! mode.equals("CBC") ) {
|
|
||||||
throw new NoSuchAlgorithmException("This only supports CBC mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void engineSetPadding(String padding)
|
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
|
||||||
throws NoSuchPaddingException {
|
throws IllegalBlockSizeException, BadPaddingException {
|
||||||
|
int maxSize = engineGetOutputSize(inputLen);
|
||||||
if ( ! mIsInited ) {
|
byte[] output = new byte[maxSize];
|
||||||
NativeLib.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( padding.length() == 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! padding.equals("PKCS5Padding") ) {
|
int finalSize;
|
||||||
throw new NoSuchPaddingException("Only supports PKCS5Padding.");
|
|
||||||
}
|
try {
|
||||||
|
finalSize = doFinal(input, inputOffset, inputLen, output, 0);
|
||||||
mPadding = true;
|
} catch (ShortBufferException e) {
|
||||||
|
// This shouldn't be possible rethrow as RuntimeException
|
||||||
}
|
throw new RuntimeException("Short buffer exception shouldn't be possible from here.");
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
|
if ( maxSize == finalSize ) {
|
||||||
int maxSize = engineGetOutputSize(inputLen);
|
return output;
|
||||||
byte output[] = new byte[maxSize];
|
} else {
|
||||||
|
// TODO: Special doFinal to avoid this copy
|
||||||
int updateSize = update(input, inputOffset, inputLen, output, 0);
|
byte[] exact = new byte[finalSize];
|
||||||
|
System.arraycopy(output, 0, exact, 0, finalSize);
|
||||||
if ( updateSize == maxSize ) {
|
return exact;
|
||||||
return output;
|
}
|
||||||
} else {
|
}
|
||||||
// TODO: We could optimize update for this case to avoid this extra copy
|
|
||||||
byte[] exact = new byte[updateSize];
|
@Override
|
||||||
System.arraycopy(output, 0, exact, 0, updateSize);
|
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
|
||||||
return exact;
|
byte[] output, int outputOffset) throws ShortBufferException,
|
||||||
}
|
IllegalBlockSizeException, BadPaddingException {
|
||||||
|
|
||||||
}
|
int result = doFinal(input, inputOffset, inputLen, output, outputOffset);
|
||||||
|
|
||||||
|
if ( result == -1 ) {
|
||||||
|
throw new ShortBufferException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
|
||||||
|
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
|
||||||
|
int outputSize = engineGetOutputSize(inputLen);
|
||||||
|
|
||||||
|
int updateAmt;
|
||||||
|
if (input != null && inputLen > 0) {
|
||||||
|
updateAmt = nUpdate(mCtxPtr, input, inputOffset, inputLen, output, outputOffset, outputSize);
|
||||||
|
} else {
|
||||||
|
updateAmt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finalAmt = nFinal(mCtxPtr, mPadding, output, outputOffset + updateAmt, outputSize - updateAmt);
|
||||||
|
|
||||||
|
int out = updateAmt + finalAmt;
|
||||||
|
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int nFinal(long ctxPtr, boolean usePadding, byte[] output, int outputOffest, int outputSize)
|
||||||
|
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int engineGetBlockSize() {
|
||||||
|
return AES_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGetIV() {
|
||||||
|
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
|
||||||
|
protected int engineGetOutputSize(int inputLen) {
|
||||||
|
return inputLen + nGetCacheSize(mCtxPtr) + AES_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int nGetCacheSize(long ctxPtr);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AlgorithmParameters engineGetParameters() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key, SecureRandom random)
|
||||||
|
throws InvalidKeyException {
|
||||||
|
|
||||||
|
byte[] ivArray = new byte[16];
|
||||||
|
random.nextBytes(ivArray);
|
||||||
|
|
||||||
|
init(opmode, key, new IvParameterSpec(ivArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key,
|
||||||
|
AlgorithmParameterSpec params, SecureRandom random)
|
||||||
|
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
|
IvParameterSpec ivparam;
|
||||||
|
|
||||||
|
if ( params instanceof IvParameterSpec ) {
|
||||||
|
ivparam = (IvParameterSpec) params;
|
||||||
|
} else {
|
||||||
|
throw new InvalidAlgorithmParameterException("params must be an IvParameterSpec.");
|
||||||
|
}
|
||||||
|
|
||||||
|
init(opmode, key, ivparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(int opmode, Key key, AlgorithmParameters params,
|
||||||
|
SecureRandom random) throws InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
engineInit(opmode, key, params.getParameterSpec(AlgorithmParameterSpec.class), random);
|
||||||
|
} catch (InvalidParameterSpecException e) {
|
||||||
|
throw new InvalidAlgorithmParameterException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(int opmode, Key key, IvParameterSpec params) {
|
||||||
|
if ( mIsInited ) {
|
||||||
|
// Do not allow multiple inits
|
||||||
|
assert(true);
|
||||||
|
throw new RuntimeException("Don't allow multiple inits");
|
||||||
|
} else {
|
||||||
|
NativeLib.init();
|
||||||
|
mIsInited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mIV = params.getIV();
|
||||||
|
mEncrypting = opmode == Cipher.ENCRYPT_MODE;
|
||||||
|
mCtxPtr = nInit(mEncrypting, key.getEncoded(), mIV);
|
||||||
|
addToCleanupQueue(this, mCtxPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long nInit(boolean encrypting, byte[] key, byte[] iv);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
|
||||||
|
if ( ! mode.equals("CBC") ) {
|
||||||
|
throw new NoSuchAlgorithmException("This only supports CBC mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineSetPadding(String padding)
|
||||||
|
throws NoSuchPaddingException {
|
||||||
|
|
||||||
|
if ( ! mIsInited ) {
|
||||||
|
NativeLib.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( padding.length() == 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! padding.equals("PKCS5Padding") ) {
|
||||||
|
throw new NoSuchPaddingException("Only supports PKCS5Padding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mPadding = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
|
||||||
|
int maxSize = engineGetOutputSize(inputLen);
|
||||||
|
byte output[] = new byte[maxSize];
|
||||||
|
|
||||||
|
int updateSize = update(input, inputOffset, inputLen, output, 0);
|
||||||
|
|
||||||
|
if ( updateSize == maxSize ) {
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
// TODO: We could optimize update for this case to avoid this extra copy
|
||||||
|
byte[] exact = new byte[updateSize];
|
||||||
|
System.arraycopy(output, 0, exact, 0, updateSize);
|
||||||
|
return exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
|
||||||
|
byte[] output, int outputOffset) throws ShortBufferException {
|
||||||
|
|
||||||
|
int result = update(input, inputOffset, inputLen, output, outputOffset);
|
||||||
|
|
||||||
|
if ( result == -1 ) {
|
||||||
|
throw new ShortBufferException("Insufficient buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) {
|
||||||
|
int outputSize = engineGetOutputSize(inputLen);
|
||||||
|
|
||||||
|
int out = nUpdate(mCtxPtr, input, inputOffset, inputLen, output, outputOffset, outputSize);
|
||||||
|
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int nUpdate(long ctxPtr, byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset, int outputSize);
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
|
|
||||||
byte[] output, int outputOffset) throws ShortBufferException {
|
|
||||||
|
|
||||||
int result = update(input, inputOffset, inputLen, output, outputOffset);
|
|
||||||
|
|
||||||
if ( result == -1 ) {
|
|
||||||
throw new ShortBufferException("Insufficient buffer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) {
|
|
||||||
int outputSize = engineGetOutputSize(inputLen);
|
|
||||||
|
|
||||||
int out = nUpdate(mCtxPtr, input, inputOffset, inputLen, output, outputOffset, outputSize);
|
|
||||||
|
|
||||||
|
|
||||||
return out;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private native int nUpdate(long ctxPtr, byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset, int outputSize);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -20,27 +20,27 @@
|
|||||||
package com.kunzisoft.keepass.crypto;
|
package com.kunzisoft.keepass.crypto;
|
||||||
|
|
||||||
public class NativeLib {
|
public class NativeLib {
|
||||||
private static boolean isLoaded = false;
|
private static boolean isLoaded = false;
|
||||||
private static boolean loadSuccess = false;
|
private static boolean loadSuccess = false;
|
||||||
|
|
||||||
public static boolean loaded() {
|
public static boolean loaded() {
|
||||||
return init();
|
return init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean init() {
|
public static boolean init() {
|
||||||
if ( ! isLoaded ) {
|
if ( ! isLoaded ) {
|
||||||
try {
|
try {
|
||||||
System.loadLibrary("final-key");
|
System.loadLibrary("final-key");
|
||||||
System.loadLibrary("argon2");
|
System.loadLibrary("argon2");
|
||||||
} catch ( UnsatisfiedLinkError e) {
|
} catch ( UnsatisfiedLinkError e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
loadSuccess = true;
|
loadSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadSuccess;
|
return loadSuccess;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,48 +26,48 @@ import org.spongycastle.crypto.params.KeyParameter;
|
|||||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||||
|
|
||||||
public class PwStreamCipherFactory {
|
public class PwStreamCipherFactory {
|
||||||
public static StreamCipher getInstance(CrsAlgorithm alg, byte[] key) {
|
public static StreamCipher getInstance(CrsAlgorithm alg, byte[] key) {
|
||||||
if ( alg == CrsAlgorithm.Salsa20 ) {
|
if ( alg == CrsAlgorithm.Salsa20 ) {
|
||||||
return getSalsa20(key);
|
return getSalsa20(key);
|
||||||
} else if (alg == CrsAlgorithm.ChaCha20) {
|
} else if (alg == CrsAlgorithm.ChaCha20) {
|
||||||
return getChaCha20(key);
|
return getChaCha20(key);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final byte[] SALSA_IV = new byte[]{ (byte)0xE8, 0x30, 0x09, 0x4B,
|
private static final byte[] SALSA_IV = new byte[]{ (byte)0xE8, 0x30, 0x09, 0x4B,
|
||||||
(byte)0x97, 0x20, 0x5D, 0x2A };
|
(byte)0x97, 0x20, 0x5D, 0x2A };
|
||||||
|
|
||||||
private static StreamCipher getSalsa20(byte[] key) {
|
private static StreamCipher getSalsa20(byte[] key) {
|
||||||
// Build stream cipher key
|
// Build stream cipher key
|
||||||
byte[] key32 = CryptoUtil.hashSha256(key);
|
byte[] key32 = CryptoUtil.hashSha256(key);
|
||||||
|
|
||||||
KeyParameter keyParam = new KeyParameter(key32);
|
KeyParameter keyParam = new KeyParameter(key32);
|
||||||
ParametersWithIV ivParam = new ParametersWithIV(keyParam, SALSA_IV);
|
ParametersWithIV ivParam = new ParametersWithIV(keyParam, SALSA_IV);
|
||||||
|
|
||||||
StreamCipher cipher = new Salsa20Engine();
|
StreamCipher cipher = new Salsa20Engine();
|
||||||
cipher.init(true, ivParam);
|
cipher.init(true, ivParam);
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StreamCipher getChaCha20(byte[] key) {
|
private static StreamCipher getChaCha20(byte[] key) {
|
||||||
// Build stream cipher key
|
// Build stream cipher key
|
||||||
byte[] hash = CryptoUtil.hashSha512(key);
|
byte[] hash = CryptoUtil.hashSha512(key);
|
||||||
byte[] key32 = new byte[32];
|
byte[] key32 = new byte[32];
|
||||||
byte[] iv = new byte[12];
|
byte[] iv = new byte[12];
|
||||||
|
|
||||||
System.arraycopy(hash, 0, key32, 0, 32);
|
System.arraycopy(hash, 0, key32, 0, 32);
|
||||||
System.arraycopy(hash, 32, iv, 0, 12);
|
System.arraycopy(hash, 32, iv, 0, 12);
|
||||||
|
|
||||||
KeyParameter keyParam = new KeyParameter(key32);
|
KeyParameter keyParam = new KeyParameter(key32);
|
||||||
ParametersWithIV ivParam = new ParametersWithIV(keyParam, iv);
|
ParametersWithIV ivParam = new ParametersWithIV(keyParam, iv);
|
||||||
|
|
||||||
StreamCipher cipher = new ChaCha7539Engine();
|
StreamCipher cipher = new ChaCha7539Engine();
|
||||||
cipher.init(true, ivParam);
|
cipher.init(true, ivParam);
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -31,48 +31,48 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
|
|
||||||
public class AndroidFinalKey extends FinalKey {
|
public class AndroidFinalKey extends FinalKey {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
|
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
|
||||||
Cipher cipher;
|
Cipher cipher;
|
||||||
try {
|
try {
|
||||||
cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
|
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
|
||||||
} catch (NoSuchPaddingException e) {
|
} catch (NoSuchPaddingException e) {
|
||||||
throw new IOException("NoSuchPadding: " + e.getMessage());
|
throw new IOException("NoSuchPadding: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new IOException("InvalidPasswordException: " + e.getMessage());
|
throw new IOException("InvalidPasswordException: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt key rounds times
|
// Encrypt key rounds times
|
||||||
byte[] newKey = new byte[pKey.length];
|
byte[] newKey = new byte[pKey.length];
|
||||||
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
|
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
|
||||||
byte[] destKey = new byte[pKey.length];
|
byte[] destKey = new byte[pKey.length];
|
||||||
for (int i = 0; i < rounds; i++) {
|
for (int i = 0; i < rounds; i++) {
|
||||||
try {
|
try {
|
||||||
cipher.update(newKey, 0, newKey.length, destKey, 0);
|
cipher.update(newKey, 0, newKey.length, destKey, 0);
|
||||||
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
|
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
|
||||||
|
|
||||||
} catch (ShortBufferException e) {
|
} catch (ShortBufferException e) {
|
||||||
throw new IOException("Short buffer: " + e.getMessage());
|
throw new IOException("Short buffer: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the key
|
// Hash the key
|
||||||
MessageDigest md = null;
|
MessageDigest md = null;
|
||||||
try {
|
try {
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
assert true;
|
assert true;
|
||||||
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
|
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
md.update(newKey);
|
md.update(newKey);
|
||||||
return md.digest();
|
return md.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,5 +22,5 @@ package com.kunzisoft.keepass.crypto.finalkey;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public abstract class FinalKey {
|
public abstract class FinalKey {
|
||||||
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
|
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,17 +22,17 @@ package com.kunzisoft.keepass.crypto.finalkey;
|
|||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
import com.kunzisoft.keepass.crypto.CipherFactory;
|
||||||
|
|
||||||
public class FinalKeyFactory {
|
public class FinalKeyFactory {
|
||||||
public static FinalKey createFinalKey() {
|
public static FinalKey createFinalKey() {
|
||||||
return createFinalKey(false);
|
return createFinalKey(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FinalKey createFinalKey(boolean androidOverride) {
|
public static FinalKey createFinalKey(boolean androidOverride) {
|
||||||
// Prefer the native final key implementation
|
// Prefer the native final key implementation
|
||||||
if ( !CipherFactory.deviceBlacklisted() && !androidOverride && NativeFinalKey.availble() ) {
|
if ( !CipherFactory.deviceBlacklisted() && !androidOverride && NativeFinalKey.availble() ) {
|
||||||
return new NativeFinalKey();
|
return new NativeFinalKey();
|
||||||
} else {
|
} else {
|
||||||
// Fall back on the android crypto implementation
|
// Fall back on the android crypto implementation
|
||||||
return new AndroidFinalKey();
|
return new AndroidFinalKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -25,22 +25,22 @@ import java.io.IOException;
|
|||||||
|
|
||||||
|
|
||||||
public class NativeFinalKey extends FinalKey {
|
public class NativeFinalKey extends FinalKey {
|
||||||
|
|
||||||
public static boolean availble() {
|
|
||||||
return NativeLib.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public static boolean availble() {
|
||||||
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
|
return NativeLib.init();
|
||||||
NativeLib.init();
|
}
|
||||||
|
|
||||||
return nTransformMasterKey(seed, key, rounds);
|
|
||||||
|
|
||||||
}
|
@Override
|
||||||
|
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
|
||||||
private static native byte[] nTransformMasterKey(byte[] seed, byte[] key, long rounds);
|
NativeLib.init();
|
||||||
|
|
||||||
// For testing
|
return nTransformMasterKey(seed, key, rounds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native byte[] nTransformMasterKey(byte[] seed, byte[] key, long rounds);
|
||||||
|
|
||||||
|
// For testing
|
||||||
/*
|
/*
|
||||||
public static byte[] reflect(byte[] key) {
|
public static byte[] reflect(byte[] key) {
|
||||||
NativeLib.init();
|
NativeLib.init();
|
||||||
@@ -50,6 +50,6 @@ public class NativeFinalKey extends FinalKey {
|
|||||||
|
|
||||||
private static native byte[] nativeReflect(byte[] key);
|
private static native byte[] nativeReflect(byte[] key);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -39,126 +39,126 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||||
|
|
||||||
private static final int DEFAULT_ENCRYPTION_ROUNDS = 300;
|
private static final int DEFAULT_ENCRYPTION_ROUNDS = 300;
|
||||||
|
|
||||||
private int numKeyEncRounds;
|
private int numKeyEncRounds;
|
||||||
|
|
||||||
protected PwGroupV3 rootGroup;
|
protected PwGroupV3 rootGroup;
|
||||||
|
|
||||||
public PwDatabaseV3() {
|
public PwDatabaseV3() {
|
||||||
algorithm = PwEncryptionAlgorithm.AESRijndael;
|
algorithm = PwEncryptionAlgorithm.AESRijndael;
|
||||||
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS;
|
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
return "KeePass 1";
|
return "KeePass 1";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PwEncryptionAlgorithm> getAvailableEncryptionAlgorithms() {
|
public List<PwEncryptionAlgorithm> getAvailableEncryptionAlgorithms() {
|
||||||
List<PwEncryptionAlgorithm> list = new ArrayList<>();
|
List<PwEncryptionAlgorithm> list = new ArrayList<>();
|
||||||
list.add(PwEncryptionAlgorithm.AESRijndael);
|
list.add(PwEncryptionAlgorithm.AESRijndael);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PwGroupV3> getRootGroups() {
|
public List<PwGroupV3> getRootGroups() {
|
||||||
List<PwGroupV3> kids = new ArrayList<>();
|
List<PwGroupV3> kids = new ArrayList<>();
|
||||||
for (Map.Entry<PwNodeId, PwGroupV3> group : groupIndexes.entrySet()) {
|
for (Map.Entry<PwNodeId, PwGroupV3> group : groupIndexes.entrySet()) {
|
||||||
if (group.getValue().getLevel() == 0)
|
if (group.getValue().getLevel() == 0)
|
||||||
kids.add(group.getValue());
|
kids.add(group.getValue());
|
||||||
}
|
}
|
||||||
return kids;
|
return kids;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignGroupsChildren(PwGroupV3 parent) {
|
private void assignGroupsChildren(PwGroupV3 parent) {
|
||||||
int levelToCheck = parent.getLevel() + 1;
|
int levelToCheck = parent.getLevel() + 1;
|
||||||
boolean startFromParentPosition = false;
|
boolean startFromParentPosition = false;
|
||||||
for (PwGroupV3 groupToCheck: getGroupIndexes()) {
|
for (PwGroupV3 groupToCheck: getGroupIndexes()) {
|
||||||
if (getRootGroup().getNodeId().equals(parent.getNodeId())
|
if (getRootGroup().getNodeId().equals(parent.getNodeId())
|
||||||
|| groupToCheck.getNodeId().equals(parent.getNodeId())) {
|
|| groupToCheck.getNodeId().equals(parent.getNodeId())) {
|
||||||
startFromParentPosition = true;
|
startFromParentPosition = true;
|
||||||
}
|
}
|
||||||
if (startFromParentPosition) {
|
if (startFromParentPosition) {
|
||||||
if (groupToCheck.getLevel() < levelToCheck)
|
if (groupToCheck.getLevel() < levelToCheck)
|
||||||
break;
|
break;
|
||||||
else if (groupToCheck.getLevel() == levelToCheck)
|
else if (groupToCheck.getLevel() == levelToCheck)
|
||||||
parent.addChildGroup(groupToCheck);
|
parent.addChildGroup(groupToCheck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignEntriesChildren(PwGroupV3 parent) {
|
private void assignEntriesChildren(PwGroupV3 parent) {
|
||||||
for (PwEntryV3 entry : getEntryIndexes()) {
|
for (PwEntryV3 entry : getEntryIndexes()) {
|
||||||
if (entry.getParent().getNodeId().equals(parent.getNodeId()))
|
if (entry.getParent().getNodeId().equals(parent.getNodeId()))
|
||||||
parent.addChildEntry(entry);
|
parent.addChildEntry(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void constructTreeFromIndex(PwGroupV3 currentGroup) {
|
private void constructTreeFromIndex(PwGroupV3 currentGroup) {
|
||||||
|
|
||||||
assignGroupsChildren(currentGroup);
|
assignGroupsChildren(currentGroup);
|
||||||
assignEntriesChildren(currentGroup);
|
assignEntriesChildren(currentGroup);
|
||||||
|
|
||||||
// set parent in child entries (normally useless but to be sure or to update parent metadata)
|
// set parent in child entries (normally useless but to be sure or to update parent metadata)
|
||||||
for (PwEntryV3 childEntry : currentGroup.getChildEntries()) {
|
for (PwEntryV3 childEntry : currentGroup.getChildEntries()) {
|
||||||
childEntry.setParent(currentGroup);
|
childEntry.setParent(currentGroup);
|
||||||
}
|
}
|
||||||
// recursively construct child groups
|
// recursively construct child groups
|
||||||
for (PwGroupV3 childGroup : currentGroup.getChildGroups()) {
|
for (PwGroupV3 childGroup : currentGroup.getChildGroups()) {
|
||||||
childGroup.setParent(currentGroup);
|
childGroup.setParent(currentGroup);
|
||||||
constructTreeFromIndex(childGroup);
|
constructTreeFromIndex(childGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void constructTreeFromIndex() {
|
public void constructTreeFromIndex() {
|
||||||
constructTreeFromIndex(getRootGroup());
|
constructTreeFromIndex(getRootGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an unused random tree id
|
* Generates an unused random tree id
|
||||||
*
|
*
|
||||||
* @return new tree id
|
* @return new tree id
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PwNodeIdInt newGroupId() {
|
public PwNodeIdInt newGroupId() {
|
||||||
PwNodeIdInt newId;
|
PwNodeIdInt newId;
|
||||||
do {
|
do {
|
||||||
newId = new PwNodeIdInt();
|
newId = new PwNodeIdInt();
|
||||||
} while (isGroupIdUsed(newId));
|
} while (isGroupIdUsed(newId));
|
||||||
|
|
||||||
return newId;
|
return newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an unused random tree id
|
* Generates an unused random tree id
|
||||||
*
|
*
|
||||||
* @return new tree id
|
* @return new tree id
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PwNodeIdUUID newEntryId() {
|
public PwNodeIdUUID newEntryId() {
|
||||||
PwNodeIdUUID newId;
|
PwNodeIdUUID newId;
|
||||||
do {
|
do {
|
||||||
newId = new PwNodeIdUUID();
|
newId = new PwNodeIdUUID();
|
||||||
} while (isEntryIdUsed(newId));
|
} while (isEntryIdUsed(newId));
|
||||||
|
|
||||||
return newId;
|
return newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getMasterKey(@Nullable String key, @Nullable InputStream keyInputStream)
|
public byte[] getMasterKey(@Nullable String key, @Nullable InputStream keyInputStream)
|
||||||
throws InvalidKeyFileException, IOException {
|
throws InvalidKeyFileException, IOException {
|
||||||
|
|
||||||
if (key != null && keyInputStream != null) {
|
if (key != null && keyInputStream != null) {
|
||||||
return getCompositeKey(key, keyInputStream);
|
return getCompositeKey(key, keyInputStream);
|
||||||
} else if (key != null) { // key.length() >= 0
|
} else if (key != null) { // key.length() >= 0
|
||||||
return getPasswordKey(key);
|
return getPasswordKey(key);
|
||||||
} else if (keyInputStream != null) { // key == null
|
} else if (keyInputStream != null) { // key == null
|
||||||
return getFileKey(keyInputStream);
|
return getFileKey(keyInputStream);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Key cannot be empty.");
|
throw new IllegalArgumentException("Key cannot be empty.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt the master key a few times to make brute-force key-search harder
|
* Encrypt the master key a few times to make brute-force key-search harder
|
||||||
@@ -170,84 +170,84 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
|||||||
return key.transformMasterKey(pKeySeed, pKey, rounds);
|
return key.transformMasterKey(pKeySeed, pKey, rounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeFinalKey(byte[] masterSeed, byte[] masterSeed2, long numRounds) throws IOException {
|
public void makeFinalKey(byte[] masterSeed, byte[] masterSeed2, long numRounds) throws IOException {
|
||||||
|
|
||||||
// Write checksum Checksum
|
// Write checksum Checksum
|
||||||
MessageDigest md;
|
MessageDigest md;
|
||||||
try {
|
try {
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new IOException("SHA-256 not implemented here.");
|
throw new IOException("SHA-256 not implemented here.");
|
||||||
}
|
}
|
||||||
NullOutputStream nos = new NullOutputStream();
|
NullOutputStream nos = new NullOutputStream();
|
||||||
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
||||||
|
|
||||||
byte[] transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds);
|
byte[] transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds);
|
||||||
dos.write(masterSeed);
|
dos.write(masterSeed);
|
||||||
dos.write(transformedMasterKey);
|
dos.write(transformedMasterKey);
|
||||||
|
|
||||||
finalKey = md.digest();
|
finalKey = md.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getPasswordEncoding() {
|
protected String getPasswordEncoding() {
|
||||||
return "ISO-8859-1";
|
return "ISO-8859-1";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] loadXmlKeyFile(InputStream keyInputStream) {
|
protected byte[] loadXmlKeyFile(InputStream keyInputStream) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNumberKeyEncryptionRounds() {
|
public long getNumberKeyEncryptionRounds() {
|
||||||
return numKeyEncRounds;
|
return numKeyEncRounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNumberKeyEncryptionRounds(long rounds) throws NumberFormatException {
|
public void setNumberKeyEncryptionRounds(long rounds) throws NumberFormatException {
|
||||||
if (rounds > Integer.MAX_VALUE || rounds < Integer.MIN_VALUE) {
|
if (rounds > Integer.MAX_VALUE || rounds < Integer.MIN_VALUE) {
|
||||||
throw new NumberFormatException();
|
throw new NumberFormatException();
|
||||||
}
|
}
|
||||||
numKeyEncRounds = (int) rounds;
|
numKeyEncRounds = (int) rounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwGroupV3 createGroup() {
|
public PwGroupV3 createGroup() {
|
||||||
return new PwGroupV3();
|
return new PwGroupV3();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRootGroup(PwGroupV3 rootGroup) {
|
public void setRootGroup(PwGroupV3 rootGroup) {
|
||||||
this.rootGroup = rootGroup;
|
this.rootGroup = rootGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwGroupV3 getRootGroup() {
|
public PwGroupV3 getRootGroup() {
|
||||||
return rootGroup;
|
return rootGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwEntryV3 createEntry() {
|
public PwEntryV3 createEntry() {
|
||||||
return new PwEntryV3();
|
return new PwEntryV3();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBackup(PwGroupV3 group) {
|
public boolean isBackup(PwGroupV3 group) {
|
||||||
while (group != null) {
|
while (group != null) {
|
||||||
if (group.getLevel() == 0 && group.getTitle().equalsIgnoreCase("Backup")) {
|
if (group.getLevel() == 0 && group.getTitle().equalsIgnoreCase("Backup")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
group = group.getParent();
|
group = group.getParent();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isGroupSearchable(PwGroupV3 group, boolean omitBackup) {
|
public boolean isGroupSearchable(PwGroupV3 group, boolean omitBackup) {
|
||||||
if (!super.isGroupSearchable(group, omitBackup)) {
|
if (!super.isGroupSearchable(group, omitBackup)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !(omitBackup && isBackup(group));
|
return !(omitBackup && isBackup(group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -37,11 +37,11 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
public class PwDate implements Parcelable {
|
public class PwDate implements Parcelable {
|
||||||
|
|
||||||
private static final int DATE_SIZE = 5;
|
private static final int DATE_SIZE = 5;
|
||||||
|
|
||||||
private Date jDate = null;
|
private Date jDate = null;
|
||||||
private boolean jDateBuilt = false;
|
private boolean jDateBuilt = false;
|
||||||
transient private byte[] cDate = null;
|
transient private byte[] cDate = null;
|
||||||
transient private boolean cDateBuilt = false;
|
transient private boolean cDateBuilt = false;
|
||||||
|
|
||||||
public static final Date NEVER_EXPIRE = getNeverExpire();
|
public static final Date NEVER_EXPIRE = getNeverExpire();
|
||||||
@@ -73,47 +73,47 @@ public class PwDate implements Parcelable {
|
|||||||
|
|
||||||
return cal.getTime();
|
return cal.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwDate(byte[] buf, int offset) {
|
|
||||||
cDate = new byte[DATE_SIZE];
|
|
||||||
System.arraycopy(buf, offset, cDate, 0, DATE_SIZE);
|
|
||||||
cDateBuilt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PwDate(PwDate source) {
|
public PwDate(byte[] buf, int offset) {
|
||||||
if (source.jDate != null) {
|
cDate = new byte[DATE_SIZE];
|
||||||
this.jDate = new Date(source.jDate.getTime());
|
System.arraycopy(buf, offset, cDate, 0, DATE_SIZE);
|
||||||
}
|
cDateBuilt = true;
|
||||||
this.jDateBuilt = source.jDateBuilt;
|
}
|
||||||
|
|
||||||
if (source.cDate != null) {
|
public PwDate(PwDate source) {
|
||||||
int dateLength = source.cDate.length;
|
if (source.jDate != null) {
|
||||||
this.cDate = new byte[dateLength];
|
this.jDate = new Date(source.jDate.getTime());
|
||||||
System.arraycopy(source.cDate, 0, this.cDate, 0, dateLength);
|
}
|
||||||
}
|
this.jDateBuilt = source.jDateBuilt;
|
||||||
this.cDateBuilt = source.cDateBuilt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PwDate(Date date) {
|
if (source.cDate != null) {
|
||||||
jDate = new Date(date.getTime());
|
int dateLength = source.cDate.length;
|
||||||
jDateBuilt = true;
|
this.cDate = new byte[dateLength];
|
||||||
}
|
System.arraycopy(source.cDate, 0, this.cDate, 0, dateLength);
|
||||||
|
}
|
||||||
public PwDate(long millis) {
|
this.cDateBuilt = source.cDateBuilt;
|
||||||
jDate = new Date(millis);
|
}
|
||||||
jDateBuilt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PwDate() {
|
|
||||||
jDate = new Date();
|
|
||||||
jDateBuilt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PwDate(Parcel in) {
|
public PwDate(Date date) {
|
||||||
jDate = (Date) in.readSerializable();
|
jDate = new Date(date.getTime());
|
||||||
jDateBuilt = in.readByte() != 0;
|
jDateBuilt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwDate(long millis) {
|
||||||
|
jDate = new Date(millis);
|
||||||
|
jDateBuilt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwDate() {
|
||||||
|
jDate = new Date();
|
||||||
|
jDateBuilt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PwDate(Parcel in) {
|
||||||
|
jDate = (Date) in.readSerializable();
|
||||||
|
jDateBuilt = in.readByte() != 0;
|
||||||
cDateBuilt = false;
|
cDateBuilt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
@@ -121,146 +121,146 @@ public class PwDate implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeSerializable(getDate());
|
dest.writeSerializable(getDate());
|
||||||
dest.writeByte((byte) (jDateBuilt ? 1 : 0));
|
dest.writeByte((byte) (jDateBuilt ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<PwDate> CREATOR = new Creator<PwDate>() {
|
public static final Creator<PwDate> CREATOR = new Creator<PwDate>() {
|
||||||
@Override
|
@Override
|
||||||
public PwDate createFromParcel(Parcel in) {
|
public PwDate createFromParcel(Parcel in) {
|
||||||
return new PwDate(in);
|
return new PwDate(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwDate[] newArray(int size) {
|
public PwDate[] newArray(int size) {
|
||||||
return new PwDate[size];
|
return new PwDate[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public Date getDate() {
|
public Date getDate() {
|
||||||
if ( ! jDateBuilt ) {
|
if ( ! jDateBuilt ) {
|
||||||
jDate = readTime(cDate, 0, App.getCalendar());
|
jDate = readTime(cDate, 0, App.getCalendar());
|
||||||
jDateBuilt = true;
|
jDateBuilt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getCDate() {
|
|
||||||
if ( ! cDateBuilt ) {
|
|
||||||
cDate = writeTime(jDate, App.getCalendar());
|
|
||||||
cDateBuilt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
|
|
||||||
* to a java.util.Date instance.
|
|
||||||
*/
|
|
||||||
public static Date readTime(byte[] buf, int offset, Calendar time) {
|
|
||||||
int dw1 = Types.readUByte(buf, offset);
|
|
||||||
int dw2 = Types.readUByte(buf, offset + 1);
|
|
||||||
int dw3 = Types.readUByte(buf, offset + 2);
|
|
||||||
int dw4 = Types.readUByte(buf, offset + 3);
|
|
||||||
int dw5 = Types.readUByte(buf, offset + 4);
|
|
||||||
|
|
||||||
// Unpack 5 byte structure to date and time
|
return jDate;
|
||||||
int year = (dw1 << 6) | (dw2 >> 2);
|
}
|
||||||
int month = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
|
|
||||||
|
|
||||||
int day = (dw3 >> 1) & 0x0000001F;
|
public byte[] getCDate() {
|
||||||
int hour = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
|
if ( ! cDateBuilt ) {
|
||||||
int minute = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
|
cDate = writeTime(jDate, App.getCalendar());
|
||||||
int second = dw5 & 0x0000003F;
|
cDateBuilt = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (time == null) {
|
return cDate;
|
||||||
time = Calendar.getInstance();
|
}
|
||||||
}
|
|
||||||
// File format is a 1 based month, java Calendar uses a zero based month
|
|
||||||
// File format is a 1 based day, java Calendar uses a 1 based day
|
|
||||||
time.set(year, month - 1, day, hour, minute, second);
|
|
||||||
|
|
||||||
return time.getTime();
|
/**
|
||||||
|
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
|
||||||
|
* to a java.util.Date instance.
|
||||||
|
*/
|
||||||
|
public static Date readTime(byte[] buf, int offset, Calendar time) {
|
||||||
|
int dw1 = Types.readUByte(buf, offset);
|
||||||
|
int dw2 = Types.readUByte(buf, offset + 1);
|
||||||
|
int dw3 = Types.readUByte(buf, offset + 2);
|
||||||
|
int dw4 = Types.readUByte(buf, offset + 3);
|
||||||
|
int dw5 = Types.readUByte(buf, offset + 4);
|
||||||
|
|
||||||
}
|
// Unpack 5 byte structure to date and time
|
||||||
|
int year = (dw1 << 6) | (dw2 >> 2);
|
||||||
|
int month = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
|
||||||
|
|
||||||
public static byte[] writeTime(Date date) {
|
int day = (dw3 >> 1) & 0x0000001F;
|
||||||
return writeTime(date, null);
|
int hour = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
|
||||||
}
|
int minute = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
|
||||||
|
int second = dw5 & 0x0000003F;
|
||||||
public static byte[] writeTime(Date date, Calendar cal) {
|
|
||||||
if (date == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] buf = new byte[5];
|
if (time == null) {
|
||||||
if (cal == null) {
|
time = Calendar.getInstance();
|
||||||
cal = Calendar.getInstance();
|
}
|
||||||
}
|
// File format is a 1 based month, java Calendar uses a zero based month
|
||||||
cal.setTime(date);
|
// File format is a 1 based day, java Calendar uses a 1 based day
|
||||||
|
time.set(year, month - 1, day, hour, minute, second);
|
||||||
|
|
||||||
int year = cal.get(Calendar.YEAR);
|
return time.getTime();
|
||||||
// File format is a 1 based month, java Calendar uses a zero based month
|
|
||||||
int month = cal.get(Calendar.MONTH) + 1;
|
|
||||||
// File format is a 0 based day, java Calendar uses a 1 based day
|
|
||||||
int day = cal.get(Calendar.DAY_OF_MONTH) - 1;
|
|
||||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
|
||||||
int minute = cal.get(Calendar.MINUTE);
|
|
||||||
int second = cal.get(Calendar.SECOND);
|
|
||||||
|
|
||||||
buf[0] = Types.writeUByte(((year >> 6) & 0x0000003F));
|
}
|
||||||
buf[1] = Types.writeUByte(((year & 0x0000003F) << 2)
|
|
||||||
| ((month >> 2) & 0x00000003));
|
|
||||||
buf[2] = (byte) (((month & 0x00000003) << 6)
|
|
||||||
| ((day & 0x0000001F) << 1) | ((hour >> 4) & 0x00000001));
|
|
||||||
buf[3] = (byte) (((hour & 0x0000000F) << 4) | ((minute >> 2) & 0x0000000F));
|
|
||||||
buf[4] = (byte) (((minute & 0x00000003) << 6) | (second & 0x0000003F));
|
|
||||||
|
|
||||||
return buf;
|
public static byte[] writeTime(Date date) {
|
||||||
}
|
return writeTime(date, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public static byte[] writeTime(Date date, Calendar cal) {
|
||||||
public boolean equals(Object o) {
|
if (date == null) {
|
||||||
if ( this == o ) {
|
return null;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
if ( o == null ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( getClass() != o.getClass() ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PwDate date = (PwDate) o;
|
|
||||||
if ( cDateBuilt && date.cDateBuilt ) {
|
|
||||||
return Arrays.equals(cDate, date.cDate);
|
|
||||||
} else if ( jDateBuilt && date.jDateBuilt ) {
|
|
||||||
return IsSameDate(jDate, date.jDate);
|
|
||||||
} else if ( cDateBuilt && date.jDateBuilt ) {
|
|
||||||
return Arrays.equals(date.getCDate(), cDate);
|
|
||||||
} else {
|
|
||||||
return IsSameDate(date.getDate(), jDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean IsSameDate(Date d1, Date d2) {
|
byte[] buf = new byte[5];
|
||||||
Calendar cal1 = Calendar.getInstance();
|
if (cal == null) {
|
||||||
cal1.setTime(d1);
|
cal = Calendar.getInstance();
|
||||||
cal1.set(Calendar.MILLISECOND, 0);
|
}
|
||||||
|
cal.setTime(date);
|
||||||
Calendar cal2 = Calendar.getInstance();
|
|
||||||
cal2.setTime(d2);
|
int year = cal.get(Calendar.YEAR);
|
||||||
cal2.set(Calendar.MILLISECOND, 0);
|
// File format is a 1 based month, java Calendar uses a zero based month
|
||||||
|
int month = cal.get(Calendar.MONTH) + 1;
|
||||||
return (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) &&
|
// File format is a 0 based day, java Calendar uses a 1 based day
|
||||||
(cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH)) &&
|
int day = cal.get(Calendar.DAY_OF_MONTH) - 1;
|
||||||
(cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH)) &&
|
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||||
(cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR)) &&
|
int minute = cal.get(Calendar.MINUTE);
|
||||||
(cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE)) &&
|
int second = cal.get(Calendar.SECOND);
|
||||||
(cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND));
|
|
||||||
|
buf[0] = Types.writeUByte(((year >> 6) & 0x0000003F));
|
||||||
}
|
buf[1] = Types.writeUByte(((year & 0x0000003F) << 2)
|
||||||
|
| ((month >> 2) & 0x00000003));
|
||||||
|
buf[2] = (byte) (((month & 0x00000003) << 6)
|
||||||
|
| ((day & 0x0000001F) << 1) | ((hour >> 4) & 0x00000001));
|
||||||
|
buf[3] = (byte) (((hour & 0x0000000F) << 4) | ((minute >> 2) & 0x0000000F));
|
||||||
|
buf[4] = (byte) (((minute & 0x00000003) << 6) | (second & 0x0000003F));
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( o == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( getClass() != o.getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PwDate date = (PwDate) o;
|
||||||
|
if ( cDateBuilt && date.cDateBuilt ) {
|
||||||
|
return Arrays.equals(cDate, date.cDate);
|
||||||
|
} else if ( jDateBuilt && date.jDateBuilt ) {
|
||||||
|
return IsSameDate(jDate, date.jDate);
|
||||||
|
} else if ( cDateBuilt && date.jDateBuilt ) {
|
||||||
|
return Arrays.equals(date.getCDate(), cDate);
|
||||||
|
} else {
|
||||||
|
return IsSameDate(date.getDate(), jDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean IsSameDate(Date d1, Date d2) {
|
||||||
|
Calendar cal1 = Calendar.getInstance();
|
||||||
|
cal1.setTime(d1);
|
||||||
|
cal1.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
Calendar cal2 = Calendar.getInstance();
|
||||||
|
cal2.setTime(d2);
|
||||||
|
cal2.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
return (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) &&
|
||||||
|
(cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH)) &&
|
||||||
|
(cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH)) &&
|
||||||
|
(cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR)) &&
|
||||||
|
(cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE)) &&
|
||||||
|
(cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -45,16 +45,16 @@ import com.kunzisoft.keepass.stylish.StylishActivity;
|
|||||||
|
|
||||||
public class IconPickerDialogFragment extends DialogFragment {
|
public class IconPickerDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
public static final String KEY_ICON_STANDARD = "KEY_ICON_STANDARD";
|
public static final String KEY_ICON_STANDARD = "KEY_ICON_STANDARD";
|
||||||
|
|
||||||
private IconPickerListener iconPickerListener;
|
private IconPickerListener iconPickerListener;
|
||||||
private IconPack iconPack;
|
private IconPack iconPack;
|
||||||
|
|
||||||
public static void launch(StylishActivity activity) {
|
public static void launch(StylishActivity activity) {
|
||||||
// Create an instance of the dialog fragment and show it
|
// Create an instance of the dialog fragment and show it
|
||||||
IconPickerDialogFragment dialog = new IconPickerDialogFragment();
|
IconPickerDialogFragment dialog = new IconPickerDialogFragment();
|
||||||
dialog.show(activity.getSupportFragmentManager(), "IconPickerDialogFragment");
|
dialog.show(activity.getSupportFragmentManager(), "IconPickerDialogFragment");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
@@ -66,86 +66,86 @@ public class IconPickerDialogFragment extends DialogFragment {
|
|||||||
throw new ClassCastException(context.toString()
|
throw new ClassCastException(context.toString()
|
||||||
+ " must implement " + IconPickerListener.class.getName());
|
+ " must implement " + IconPickerListener.class.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
// Get the layout inflater
|
// Get the layout inflater
|
||||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||||
|
|
||||||
iconPack = IconPackChooser.getSelectedIconPack(getContext());
|
iconPack = IconPackChooser.getSelectedIconPack(getContext());
|
||||||
|
|
||||||
// Inflate and set the layout for the dialog
|
// Inflate and set the layout for the dialog
|
||||||
// Pass null as the parent view because its going in the dialog layout
|
// Pass null as the parent view because its going in the dialog layout
|
||||||
View root = inflater.inflate(R.layout.icon_picker, null);
|
View root = inflater.inflate(R.layout.icon_picker, null);
|
||||||
builder.setView(root);
|
builder.setView(root);
|
||||||
|
|
||||||
GridView currIconGridView = root.findViewById(R.id.IconGridView);
|
GridView currIconGridView = root.findViewById(R.id.IconGridView);
|
||||||
currIconGridView.setAdapter(new ImageAdapter(this.getContext()));
|
currIconGridView.setAdapter(new ImageAdapter(this.getContext()));
|
||||||
|
|
||||||
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
|
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(KEY_ICON_STANDARD, new PwIconStandard(position));
|
bundle.putParcelable(KEY_ICON_STANDARD, new PwIconStandard(position));
|
||||||
iconPickerListener.iconPicked(bundle);
|
iconPickerListener.iconPicked(bundle);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setNegativeButton(R.string.cancel, (dialog, id) ->
|
builder.setNegativeButton(R.string.cancel, (dialog, id) ->
|
||||||
IconPickerDialogFragment.this.getDialog().cancel());
|
IconPickerDialogFragment.this.getDialog().cancel());
|
||||||
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImageAdapter extends BaseAdapter {
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
ImageAdapter(Context c) {
|
public class ImageAdapter extends BaseAdapter {
|
||||||
context = c;
|
private Context context;
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
ImageAdapter(Context c) {
|
||||||
/* Return number of KeePass icons */
|
context = c;
|
||||||
return iconPack.numberOfIcons();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Object getItem(int position) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getItemId(int position) {
|
public int getCount() {
|
||||||
return 0;
|
/* Return number of KeePass icons */
|
||||||
}
|
return iconPack.numberOfIcons();
|
||||||
|
}
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
View currView;
|
public Object getItem(int position) {
|
||||||
if(convertView == null) {
|
return null;
|
||||||
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
}
|
||||||
|
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View currView;
|
||||||
|
if(convertView == null) {
|
||||||
|
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
assert li != null;
|
assert li != null;
|
||||||
currView = li.inflate(R.layout.icon, parent, false);
|
currView = li.inflate(R.layout.icon, parent, false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
currView = convertView;
|
currView = convertView;
|
||||||
}
|
}
|
||||||
ImageView iv = currView.findViewById(R.id.icon_image);
|
ImageView iv = currView.findViewById(R.id.icon_image);
|
||||||
iv.setImageResource(iconPack.iconToResId(position));
|
iv.setImageResource(iconPack.iconToResId(position));
|
||||||
|
|
||||||
// Assign color if icons are tintable
|
// Assign color if icons are tintable
|
||||||
if (iconPack.tintable()) {
|
if (iconPack.tintable()) {
|
||||||
// Retrieve the textColor to tint the icon
|
// Retrieve the textColor to tint the icon
|
||||||
int[] attrs = {android.R.attr.textColor};
|
int[] attrs = {android.R.attr.textColor};
|
||||||
assert getContext() != null;
|
assert getContext() != null;
|
||||||
TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs);
|
TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs);
|
||||||
int iconColor = ta.getColor(0, Color.BLACK);
|
int iconColor = ta.getColor(0, Color.BLACK);
|
||||||
ImageViewCompat.setImageTintList(iv, ColorStateList.valueOf(iconColor));
|
ImageViewCompat.setImageTintList(iv, ColorStateList.valueOf(iconColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
return currView;
|
return currView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IconPickerListener {
|
public interface IconPickerListener {
|
||||||
void iconPicked(Bundle bundle);
|
void iconPicked(Bundle bundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -25,14 +25,14 @@ import android.os.Build;
|
|||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
|
||||||
public class ReadOnlyDialog extends WarningDialog {
|
public class ReadOnlyDialog extends WarningDialog {
|
||||||
|
|
||||||
public ReadOnlyDialog(Context context) {
|
public ReadOnlyDialog(Context context) {
|
||||||
super(context, R.string.show_read_only_warning);
|
super(context, R.string.show_read_only_warning);
|
||||||
|
|
||||||
warning = context.getString(R.string.read_only_warning);
|
warning = context.getString(R.string.read_only_warning);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
warning = warning.concat("\n\n").concat(context.getString(R.string.read_only_kitkat_warning));
|
warning = warning.concat("\n\n").concat(context.getString(R.string.read_only_kitkat_warning));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -29,50 +29,50 @@ import android.preference.PreferenceManager;
|
|||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
|
||||||
public class WarningDialog extends AlertDialog {
|
public class WarningDialog extends AlertDialog {
|
||||||
|
|
||||||
protected String warning;
|
|
||||||
private int showKey;
|
|
||||||
|
|
||||||
public WarningDialog(Context context, int dontShowKey) {
|
protected String warning;
|
||||||
super(context);
|
private int showKey;
|
||||||
|
|
||||||
this.showKey = dontShowKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WarningDialog(Context context, int warningKey, int dontShowKey) {
|
|
||||||
this(context, dontShowKey);
|
|
||||||
|
|
||||||
warning = context.getString(warningKey);
|
public WarningDialog(Context context, int dontShowKey) {
|
||||||
}
|
super(context);
|
||||||
|
|
||||||
@Override
|
this.showKey = dontShowKey;
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
}
|
||||||
Context ctx = getContext();
|
|
||||||
setMessage(warning);
|
public WarningDialog(Context context, int warningKey, int dontShowKey) {
|
||||||
|
this(context, dontShowKey);
|
||||||
setButton(AlertDialog.BUTTON1, ctx.getText(android.R.string.ok), new DialogInterface.OnClickListener() {
|
|
||||||
|
warning = context.getString(warningKey);
|
||||||
@Override
|
}
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dismiss();
|
@Override
|
||||||
}
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
});
|
Context ctx = getContext();
|
||||||
|
setMessage(warning);
|
||||||
setButton(AlertDialog.BUTTON2, ctx.getText(R.string.beta_dontask), new DialogInterface.OnClickListener() {
|
|
||||||
|
setButton(AlertDialog.BUTTON1, ctx.getText(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
@Override
|
||||||
Context ctx = getContext();
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
dismiss();
|
||||||
SharedPreferences.Editor edit = prefs.edit();
|
}
|
||||||
edit.putBoolean(ctx.getString(showKey), false);
|
});
|
||||||
edit.commit();
|
|
||||||
|
setButton(AlertDialog.BUTTON2, ctx.getText(R.string.beta_dontask), new DialogInterface.OnClickListener() {
|
||||||
dismiss();
|
|
||||||
}
|
@Override
|
||||||
});
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Context ctx = getContext();
|
||||||
super.onCreate(savedInstanceState);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
}
|
SharedPreferences.Editor edit = prefs.edit();
|
||||||
|
edit.putBoolean(ctx.getString(showKey), false);
|
||||||
|
edit.commit();
|
||||||
|
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -33,29 +33,29 @@ import com.kunzisoft.keepass.utils.Util;
|
|||||||
|
|
||||||
public class BrowserDialog extends DialogFragment {
|
public class BrowserDialog extends DialogFragment {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
// Get the layout inflater
|
// Get the layout inflater
|
||||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||||
View root = inflater.inflate(R.layout.browser_install, null);
|
View root = inflater.inflate(R.layout.browser_install, null);
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
.setNegativeButton(R.string.cancel, (dialog, id) -> { });
|
.setNegativeButton(R.string.cancel, (dialog, id) -> { });
|
||||||
|
|
||||||
Button market = root.findViewById(R.id.install_market);
|
Button market = root.findViewById(R.id.install_market);
|
||||||
market.setOnClickListener((view) -> {
|
market.setOnClickListener((view) -> {
|
||||||
Util.gotoUrl(getContext(), R.string.filemanager_play_store);
|
Util.gotoUrl(getContext(), R.string.filemanager_play_store);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
Button web = root.findViewById(R.id.install_web);
|
Button web = root.findViewById(R.id.install_web);
|
||||||
web.setOnClickListener(view -> {
|
web.setOnClickListener(view -> {
|
||||||
Util.gotoUrl(getContext(), R.string.filemanager_f_droid);
|
Util.gotoUrl(getContext(), R.string.filemanager_f_droid);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -34,221 +34,221 @@ import java.io.FileFilter;
|
|||||||
public class FileDbHelper {
|
public class FileDbHelper {
|
||||||
|
|
||||||
private static final String TAG = FileDbHelper.class.getName();
|
private static final String TAG = FileDbHelper.class.getName();
|
||||||
|
|
||||||
public static final String LAST_FILENAME = "lastFile";
|
|
||||||
public static final String LAST_KEYFILE = "lastKey";
|
|
||||||
|
|
||||||
public static final String DATABASE_NAME = "keepassdroid"; // TODO Change db name
|
|
||||||
private static final String FILE_TABLE = "files";
|
|
||||||
private static final int DATABASE_VERSION = 1;
|
|
||||||
|
|
||||||
public static final int MAX_FILES = 5;
|
|
||||||
|
|
||||||
public static final String KEY_FILE_ID = "_id";
|
|
||||||
public static final String KEY_FILE_FILENAME = "fileName";
|
|
||||||
public static final String KEY_FILE_KEYFILE = "keyFile";
|
|
||||||
public static final String KEY_FILE_UPDATED = "updated";
|
|
||||||
|
|
||||||
private static final String DATABASE_CREATE =
|
public static final String LAST_FILENAME = "lastFile";
|
||||||
"create table " + FILE_TABLE + " ( " + KEY_FILE_ID + " integer primary key autoincrement, "
|
public static final String LAST_KEYFILE = "lastKey";
|
||||||
+ KEY_FILE_FILENAME + " text not null, " + KEY_FILE_KEYFILE + " text, "
|
|
||||||
+ KEY_FILE_UPDATED + " integer not null);";
|
|
||||||
|
|
||||||
private final Context mCtx;
|
|
||||||
private DatabaseHelper mDbHelper;
|
|
||||||
private SQLiteDatabase mDb;
|
|
||||||
|
|
||||||
private static class DatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
private final Context mCtx;
|
|
||||||
|
|
||||||
DatabaseHelper(Context ctx) {
|
|
||||||
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
|
|
||||||
mCtx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public static final String DATABASE_NAME = "keepassdroid"; // TODO Change db name
|
||||||
public void onCreate(SQLiteDatabase db) {
|
private static final String FILE_TABLE = "files";
|
||||||
db.execSQL(DATABASE_CREATE);
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
|
||||||
// Migrate preference to database if it is set.
|
public static final int MAX_FILES = 5;
|
||||||
SharedPreferences settings = mCtx.getSharedPreferences("PasswordActivity", Context.MODE_PRIVATE);
|
|
||||||
String lastFile = settings.getString(LAST_FILENAME, "");
|
public static final String KEY_FILE_ID = "_id";
|
||||||
String lastKey = settings.getString(LAST_KEYFILE,"");
|
public static final String KEY_FILE_FILENAME = "fileName";
|
||||||
|
public static final String KEY_FILE_KEYFILE = "keyFile";
|
||||||
if ( lastFile.length() > 0 ) {
|
public static final String KEY_FILE_UPDATED = "updated";
|
||||||
ContentValues vals = new ContentValues();
|
|
||||||
vals.put(KEY_FILE_FILENAME, lastFile);
|
private static final String DATABASE_CREATE =
|
||||||
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
"create table " + FILE_TABLE + " ( " + KEY_FILE_ID + " integer primary key autoincrement, "
|
||||||
|
+ KEY_FILE_FILENAME + " text not null, " + KEY_FILE_KEYFILE + " text, "
|
||||||
if ( lastKey.length() > 0 ) {
|
+ KEY_FILE_UPDATED + " integer not null);";
|
||||||
vals.put(KEY_FILE_KEYFILE, lastKey);
|
|
||||||
}
|
private final Context mCtx;
|
||||||
|
private DatabaseHelper mDbHelper;
|
||||||
db.insert(FILE_TABLE, null, vals);
|
private SQLiteDatabase mDb;
|
||||||
|
|
||||||
// Clear old preferences
|
private static class DatabaseHelper extends SQLiteOpenHelper {
|
||||||
deletePrefs(settings);
|
private final Context mCtx;
|
||||||
|
|
||||||
}
|
DatabaseHelper(Context ctx) {
|
||||||
}
|
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
mCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(DATABASE_CREATE);
|
||||||
|
|
||||||
|
// Migrate preference to database if it is set.
|
||||||
|
SharedPreferences settings = mCtx.getSharedPreferences("PasswordActivity", Context.MODE_PRIVATE);
|
||||||
|
String lastFile = settings.getString(LAST_FILENAME, "");
|
||||||
|
String lastKey = settings.getString(LAST_KEYFILE,"");
|
||||||
|
|
||||||
|
if ( lastFile.length() > 0 ) {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(KEY_FILE_FILENAME, lastFile);
|
||||||
|
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
||||||
|
|
||||||
|
if ( lastKey.length() > 0 ) {
|
||||||
|
vals.put(KEY_FILE_KEYFILE, lastKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.insert(FILE_TABLE, null, vals);
|
||||||
|
|
||||||
|
// Clear old preferences
|
||||||
|
deletePrefs(settings);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// Only one database version so far
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePrefs(SharedPreferences prefs) {
|
||||||
|
// We won't worry too much if this fails
|
||||||
|
try {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.remove(LAST_FILENAME);
|
||||||
|
editor.remove(LAST_KEYFILE);
|
||||||
|
editor.apply();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Unable to delete database preference", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileDbHelper(Context ctx) {
|
||||||
|
mCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileDbHelper open() throws SQLException {
|
||||||
|
mDbHelper = new DatabaseHelper(mCtx);
|
||||||
|
mDb = mDbHelper.getWritableDatabase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpen() {
|
||||||
|
return mDb.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
mDb.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long createFile(String fileName, String keyFile) {
|
||||||
|
|
||||||
|
// Check to see if this filename is already used
|
||||||
|
Cursor cursor;
|
||||||
|
try {
|
||||||
|
cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_ID},
|
||||||
|
KEY_FILE_FILENAME + "=?", new String[] {fileName}, null, null, null, null);
|
||||||
|
} catch (Exception e ) {
|
||||||
|
assert(true);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long result;
|
||||||
|
// If there is an existing entry update it with the new key file
|
||||||
|
if ( cursor.getCount() > 0 ) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_ID));
|
||||||
|
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(KEY_FILE_KEYFILE, keyFile);
|
||||||
|
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
||||||
|
|
||||||
|
result = mDb.update(FILE_TABLE, vals, KEY_FILE_ID + " = " + id, null);
|
||||||
|
|
||||||
|
// Otherwise add the new entry
|
||||||
|
} else {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(KEY_FILE_FILENAME, fileName);
|
||||||
|
vals.put(KEY_FILE_KEYFILE, keyFile);
|
||||||
|
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
||||||
|
|
||||||
|
result = mDb.insert(FILE_TABLE, null, vals);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Delete all but the last five records
|
||||||
|
try {
|
||||||
|
deleteAllBut(MAX_FILES);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
assert(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAllBut(int limit) {
|
||||||
|
Cursor cursor = mDb.query(FILE_TABLE, new String[] {KEY_FILE_UPDATED}, null, null, null, null, KEY_FILE_UPDATED);
|
||||||
|
|
||||||
|
if ( cursor.getCount() > limit ) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
long time = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_UPDATED));
|
||||||
|
|
||||||
|
mDb.execSQL("DELETE FROM " + FILE_TABLE + " WHERE " + KEY_FILE_UPDATED + "<" + time + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteAllKeys() {
|
||||||
|
ContentValues vals = new ContentValues();
|
||||||
|
vals.put(KEY_FILE_KEYFILE, "");
|
||||||
|
|
||||||
|
mDb.update(FILE_TABLE, vals, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteFile(String filename) {
|
||||||
|
mDb.delete(FILE_TABLE, KEY_FILE_FILENAME + " = ?", new String[] {filename});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Cursor fetchAllFiles() {
|
||||||
|
Cursor ret;
|
||||||
|
ret = mDb.query(FILE_TABLE, new String[] {KEY_FILE_ID, KEY_FILE_FILENAME, KEY_FILE_KEYFILE }, null, null, null, null, KEY_FILE_UPDATED + " DESC", Integer.toString(MAX_FILES));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor fetchFile(long fileId) throws SQLException {
|
||||||
|
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_FILENAME, KEY_FILE_KEYFILE},
|
||||||
|
KEY_FILE_ID + "=" + fileId, null, null, null, null, null);
|
||||||
|
|
||||||
|
if ( cursor != null ) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileByName(String name) {
|
||||||
|
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_KEYFILE},
|
||||||
|
KEY_FILE_FILENAME + "= ?", new String[] {name}, null, null, null, null);
|
||||||
|
|
||||||
|
if ( cursor == null ) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String filename;
|
||||||
|
|
||||||
|
if ( cursor.moveToFirst() ) {
|
||||||
|
filename = cursor.getString(0);
|
||||||
|
} else {
|
||||||
|
// Cursor is empty
|
||||||
|
filename = "";
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRecentFiles() {
|
||||||
|
Cursor cursor = fetchAllFiles();
|
||||||
|
|
||||||
|
boolean hasRecent = cursor.getCount() > 0;
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
return hasRecent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
// Only one database version so far
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deletePrefs(SharedPreferences prefs) {
|
|
||||||
// We won't worry too much if this fails
|
|
||||||
try {
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
editor.remove(LAST_FILENAME);
|
|
||||||
editor.remove(LAST_KEYFILE);
|
|
||||||
editor.apply();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Unable to delete database preference", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileDbHelper(Context ctx) {
|
|
||||||
mCtx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileDbHelper open() throws SQLException {
|
|
||||||
mDbHelper = new DatabaseHelper(mCtx);
|
|
||||||
mDb = mDbHelper.getWritableDatabase();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOpen() {
|
|
||||||
return mDb.isOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
mDb.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long createFile(String fileName, String keyFile) {
|
|
||||||
|
|
||||||
// Check to see if this filename is already used
|
|
||||||
Cursor cursor;
|
|
||||||
try {
|
|
||||||
cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_ID},
|
|
||||||
KEY_FILE_FILENAME + "=?", new String[] {fileName}, null, null, null, null);
|
|
||||||
} catch (Exception e ) {
|
|
||||||
assert(true);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
long result;
|
|
||||||
// If there is an existing entry update it with the new key file
|
|
||||||
if ( cursor.getCount() > 0 ) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_ID));
|
|
||||||
|
|
||||||
ContentValues vals = new ContentValues();
|
|
||||||
vals.put(KEY_FILE_KEYFILE, keyFile);
|
|
||||||
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
|
||||||
|
|
||||||
result = mDb.update(FILE_TABLE, vals, KEY_FILE_ID + " = " + id, null);
|
|
||||||
|
|
||||||
// Otherwise add the new entry
|
|
||||||
} else {
|
|
||||||
ContentValues vals = new ContentValues();
|
|
||||||
vals.put(KEY_FILE_FILENAME, fileName);
|
|
||||||
vals.put(KEY_FILE_KEYFILE, keyFile);
|
|
||||||
vals.put(KEY_FILE_UPDATED, System.currentTimeMillis());
|
|
||||||
|
|
||||||
result = mDb.insert(FILE_TABLE, null, vals);
|
|
||||||
|
|
||||||
}
|
|
||||||
// Delete all but the last five records
|
|
||||||
try {
|
|
||||||
deleteAllBut(MAX_FILES);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
assert(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteAllBut(int limit) {
|
|
||||||
Cursor cursor = mDb.query(FILE_TABLE, new String[] {KEY_FILE_UPDATED}, null, null, null, null, KEY_FILE_UPDATED);
|
|
||||||
|
|
||||||
if ( cursor.getCount() > limit ) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
long time = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_FILE_UPDATED));
|
|
||||||
|
|
||||||
mDb.execSQL("DELETE FROM " + FILE_TABLE + " WHERE " + KEY_FILE_UPDATED + "<" + time + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteAllKeys() {
|
|
||||||
ContentValues vals = new ContentValues();
|
|
||||||
vals.put(KEY_FILE_KEYFILE, "");
|
|
||||||
|
|
||||||
mDb.update(FILE_TABLE, vals, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteFile(String filename) {
|
|
||||||
mDb.delete(FILE_TABLE, KEY_FILE_FILENAME + " = ?", new String[] {filename});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Cursor fetchAllFiles() {
|
|
||||||
Cursor ret;
|
|
||||||
ret = mDb.query(FILE_TABLE, new String[] {KEY_FILE_ID, KEY_FILE_FILENAME, KEY_FILE_KEYFILE }, null, null, null, null, KEY_FILE_UPDATED + " DESC", Integer.toString(MAX_FILES));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cursor fetchFile(long fileId) throws SQLException {
|
|
||||||
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_FILENAME, KEY_FILE_KEYFILE},
|
|
||||||
KEY_FILE_ID + "=" + fileId, null, null, null, null, null);
|
|
||||||
|
|
||||||
if ( cursor != null ) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileByName(String name) {
|
|
||||||
Cursor cursor = mDb.query(true, FILE_TABLE, new String[] {KEY_FILE_KEYFILE},
|
|
||||||
KEY_FILE_FILENAME + "= ?", new String[] {name}, null, null, null, null);
|
|
||||||
|
|
||||||
if ( cursor == null ) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String filename;
|
|
||||||
|
|
||||||
if ( cursor.moveToFirst() ) {
|
|
||||||
filename = cursor.getString(0);
|
|
||||||
} else {
|
|
||||||
// Cursor is empty
|
|
||||||
filename = "";
|
|
||||||
}
|
|
||||||
cursor.close();
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasRecentFiles() {
|
|
||||||
Cursor cursor = fetchAllFiles();
|
|
||||||
|
|
||||||
boolean hasRecent = cursor.getCount() > 0;
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
return hasRecent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a database including its journal file and other auxiliary files
|
* Deletes a database including its journal file and other auxiliary files
|
||||||
* that may have been created by the database engine.
|
* that may have been created by the database engine.
|
||||||
@@ -257,7 +257,7 @@ public class FileDbHelper {
|
|||||||
* @return True if the database was successfully deleted.
|
* @return True if the database was successfully deleted.
|
||||||
*/
|
*/
|
||||||
public static boolean deleteDatabase(Context ctx) {
|
public static boolean deleteDatabase(Context ctx) {
|
||||||
File file = ctx.getDatabasePath(DATABASE_NAME);
|
File file = ctx.getDatabasePath(DATABASE_NAME);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new IllegalArgumentException("file must not be null");
|
throw new IllegalArgumentException("file must not be null");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -86,7 +86,7 @@ import permissions.dispatcher.RuntimePermissions;
|
|||||||
public class FileSelectActivity extends StylishActivity implements
|
public class FileSelectActivity extends StylishActivity implements
|
||||||
CreateFileDialogFragment.DefinePathDialogListener,
|
CreateFileDialogFragment.DefinePathDialogListener,
|
||||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||||
FileSelectAdapter.FileItemOpenListener,
|
FileSelectAdapter.FileItemOpenListener,
|
||||||
FileSelectAdapter.FileSelectClearListener,
|
FileSelectAdapter.FileSelectClearListener,
|
||||||
FileSelectAdapter.FileInformationShowListener {
|
FileSelectAdapter.FileInformationShowListener {
|
||||||
|
|
||||||
@@ -95,64 +95,64 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
private static final String EXTRA_STAY = "EXTRA_STAY";
|
private static final String EXTRA_STAY = "EXTRA_STAY";
|
||||||
|
|
||||||
private FileSelectAdapter mAdapter;
|
private FileSelectAdapter mAdapter;
|
||||||
private View fileListContainer;
|
private View fileListContainer;
|
||||||
private View createButtonView;
|
private View createButtonView;
|
||||||
private View browseButtonView;
|
private View browseButtonView;
|
||||||
private View openButtonView;
|
private View openButtonView;
|
||||||
|
|
||||||
private RecentFileHistory fileHistory;
|
private RecentFileHistory fileHistory;
|
||||||
|
|
||||||
private View fileSelectExpandableButton;
|
private View fileSelectExpandableButton;
|
||||||
private ExpandableLayout fileSelectExpandable;
|
private ExpandableLayout fileSelectExpandable;
|
||||||
private EditText openFileNameView;
|
private EditText openFileNameView;
|
||||||
|
|
||||||
private Uri databaseUri;
|
private Uri databaseUri;
|
||||||
|
|
||||||
private KeyFileHelper keyFileHelper;
|
private KeyFileHelper keyFileHelper;
|
||||||
|
|
||||||
private String defaultPath;
|
private String defaultPath;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* No Standard Launch, pass by PasswordActivity
|
* No Standard Launch, pass by PasswordActivity
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* Keyboard Launch
|
* Keyboard Launch
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static void launchForKeyboardSelection(Activity activity) {
|
public static void launchForKeyboardSelection(Activity activity) {
|
||||||
KeyboardHelper.INSTANCE.startActivityForKeyboardSelection(activity, new Intent(activity, FileSelectActivity.class));
|
KeyboardHelper.INSTANCE.startActivityForKeyboardSelection(activity, new Intent(activity, FileSelectActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* Autofill Launch
|
* Autofill Launch
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void launchForAutofillResult(Activity activity, @NonNull AssistStructure assistStructure) {
|
public static void launchForAutofillResult(Activity activity, @NonNull AssistStructure assistStructure) {
|
||||||
AutofillHelper.INSTANCE.startActivityForAutofillResult(activity,
|
AutofillHelper.INSTANCE.startActivityForAutofillResult(activity,
|
||||||
new Intent(activity, FileSelectActivity.class),
|
new Intent(activity, FileSelectActivity.class),
|
||||||
assistStructure);
|
assistStructure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
fileHistory = App.getFileHistory();
|
fileHistory = App.getFileHistory();
|
||||||
|
|
||||||
setContentView(R.layout.file_selection);
|
setContentView(R.layout.file_selection);
|
||||||
fileListContainer = findViewById(R.id.container_file_list);
|
fileListContainer = findViewById(R.id.container_file_list);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
toolbar.setTitle("");
|
toolbar.setTitle("");
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
openFileNameView = findViewById(R.id.file_filename);
|
openFileNameView = findViewById(R.id.file_filename);
|
||||||
|
|
||||||
@@ -175,37 +175,37 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
|
|
||||||
// History list
|
// History list
|
||||||
RecyclerView mListFiles = findViewById(R.id.file_list);
|
RecyclerView mListFiles = findViewById(R.id.file_list);
|
||||||
mListFiles.setLayoutManager(new LinearLayoutManager(this));
|
mListFiles.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
|
||||||
// Open button
|
// Open button
|
||||||
openButtonView = findViewById(R.id.open_database);
|
openButtonView = findViewById(R.id.open_database);
|
||||||
openButtonView.setOnClickListener(v -> {
|
openButtonView.setOnClickListener(v -> {
|
||||||
String fileName = openFileNameView.getText().toString();
|
String fileName = openFileNameView.getText().toString();
|
||||||
if (fileName.isEmpty())
|
if (fileName.isEmpty())
|
||||||
fileName = defaultPath;
|
fileName = defaultPath;
|
||||||
launchPasswordActivityWithPath(fileName);
|
launchPasswordActivityWithPath(fileName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create button
|
// Create button
|
||||||
createButtonView = findViewById(R.id.create_database);
|
createButtonView = findViewById(R.id.create_database);
|
||||||
createButtonView .setOnClickListener(v ->
|
createButtonView .setOnClickListener(v ->
|
||||||
FileSelectActivityPermissionsDispatcher
|
FileSelectActivityPermissionsDispatcher
|
||||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this)
|
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this)
|
||||||
);
|
);
|
||||||
|
|
||||||
keyFileHelper = new KeyFileHelper(this);
|
keyFileHelper = new KeyFileHelper(this);
|
||||||
browseButtonView = findViewById(R.id.browse_button);
|
browseButtonView = findViewById(R.id.browse_button);
|
||||||
browseButtonView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
browseButtonView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
||||||
() -> Uri.parse("file://" + openFileNameView.getText().toString())));
|
() -> Uri.parse("file://" + openFileNameView.getText().toString())));
|
||||||
|
|
||||||
// Construct adapter with listeners
|
// Construct adapter with listeners
|
||||||
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
|
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
|
||||||
mAdapter.setOnItemClickListener(this);
|
mAdapter.setOnItemClickListener(this);
|
||||||
mAdapter.setFileSelectClearListener(this);
|
mAdapter.setFileSelectClearListener(this);
|
||||||
mAdapter.setFileInformationShowListener(this);
|
mAdapter.setFileInformationShowListener(this);
|
||||||
mListFiles.setAdapter(mAdapter);
|
mListFiles.setAdapter(mAdapter);
|
||||||
|
|
||||||
// Load default database if not an orientation change
|
// Load default database if not an orientation change
|
||||||
if (! (savedInstanceState != null
|
if (! (savedInstanceState != null
|
||||||
&& savedInstanceState.containsKey(EXTRA_STAY)
|
&& savedInstanceState.containsKey(EXTRA_STAY)
|
||||||
&& savedInstanceState.getBoolean(EXTRA_STAY, false)) ) {
|
&& savedInstanceState.getBoolean(EXTRA_STAY, false)) ) {
|
||||||
@@ -234,55 +234,55 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
|
|
||||||
// For the first time show education
|
// For the first time show education
|
||||||
checkAndPerformedEducation();
|
checkAndPerformedEducation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileNoFoundAction(FileNotFoundException e) {
|
private void fileNoFoundAction(FileNotFoundException e) {
|
||||||
String error = getString(R.string.file_not_found_content);
|
String error = getString(R.string.file_not_found_content);
|
||||||
Toast.makeText(FileSelectActivity.this,
|
Toast.makeText(FileSelectActivity.this,
|
||||||
error, Toast.LENGTH_LONG).show();
|
error, Toast.LENGTH_LONG).show();
|
||||||
Log.e(TAG, error, e);
|
Log.e(TAG, error, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchPasswordActivity(String fileName, String keyFile) {
|
private void launchPasswordActivity(String fileName, String keyFile) {
|
||||||
EntrySelectionHelper.INSTANCE.doEntrySelectionAction(getIntent(),
|
EntrySelectionHelper.INSTANCE.doEntrySelectionAction(getIntent(),
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launch(FileSelectActivity.this,
|
PasswordActivity.launch(FileSelectActivity.this,
|
||||||
fileName, keyFile);
|
fileName, keyFile);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
fileNoFoundAction(e);
|
fileNoFoundAction(e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launchForKeyboardResult(FileSelectActivity.this,
|
PasswordActivity.launchForKeyboardResult(FileSelectActivity.this,
|
||||||
fileName, keyFile);
|
fileName, keyFile);
|
||||||
finish();
|
finish();
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
fileNoFoundAction(e);
|
fileNoFoundAction(e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
assistStructure -> {
|
assistStructure -> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launchForAutofillResult(FileSelectActivity.this,
|
PasswordActivity.launchForAutofillResult(FileSelectActivity.this,
|
||||||
fileName, keyFile,
|
fileName, keyFile,
|
||||||
assistStructure);
|
assistStructure);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
fileNoFoundAction(e);
|
fileNoFoundAction(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchPasswordActivityWithPath(String path) {
|
private void launchPasswordActivityWithPath(String path) {
|
||||||
launchPasswordActivity(path, "");
|
launchPasswordActivity(path, "");
|
||||||
// Delete flickering for kitkat <=
|
// Delete flickering for kitkat <=
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||||
overridePendingTransition(0, 0);
|
overridePendingTransition(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateExternalStorageWarning() {
|
private void updateExternalStorageWarning() {
|
||||||
@@ -440,10 +440,10 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
|
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFileListVisibility() {
|
private void updateFileListVisibility() {
|
||||||
if(mAdapter.getItemCount() == 0)
|
if(mAdapter.getItemCount() == 0)
|
||||||
fileListContainer.setVisibility(View.INVISIBLE);
|
fileListContainer.setVisibility(View.INVISIBLE);
|
||||||
else
|
else
|
||||||
fileListContainer.setVisibility(View.VISIBLE);
|
fileListContainer.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,64 +451,64 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
* Create file for database
|
* Create file for database
|
||||||
* @return If not created, return false
|
* @return If not created, return false
|
||||||
*/
|
*/
|
||||||
private boolean createDatabaseFile(Uri path) {
|
private boolean createDatabaseFile(Uri path) {
|
||||||
|
|
||||||
String pathString = URLDecoder.decode(path.getPath());
|
String pathString = URLDecoder.decode(path.getPath());
|
||||||
// Make sure file name exists
|
// Make sure file name exists
|
||||||
if (pathString.length() == 0) {
|
if (pathString.length() == 0) {
|
||||||
Log.e(TAG, getString(R.string.error_filename_required));
|
Log.e(TAG, getString(R.string.error_filename_required));
|
||||||
Toast.makeText(FileSelectActivity.this,
|
Toast.makeText(FileSelectActivity.this,
|
||||||
R.string.error_filename_required,
|
R.string.error_filename_required,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to create the file
|
// Try to create the file
|
||||||
File file = new File(pathString);
|
File file = new File(pathString);
|
||||||
try {
|
try {
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
Log.e(TAG, getString(R.string.error_database_exists) + " " + file);
|
Log.e(TAG, getString(R.string.error_database_exists) + " " + file);
|
||||||
Toast.makeText(FileSelectActivity.this,
|
Toast.makeText(FileSelectActivity.this,
|
||||||
R.string.error_database_exists,
|
R.string.error_database_exists,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
File parent = file.getParentFile();
|
File parent = file.getParentFile();
|
||||||
|
|
||||||
if ( parent == null || (parent.exists() && ! parent.isDirectory()) ) {
|
if ( parent == null || (parent.exists() && ! parent.isDirectory()) ) {
|
||||||
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file);
|
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file);
|
||||||
Toast.makeText(FileSelectActivity.this,
|
Toast.makeText(FileSelectActivity.this,
|
||||||
R.string.error_invalid_path,
|
R.string.error_invalid_path,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! parent.exists() ) {
|
if ( ! parent.exists() ) {
|
||||||
// Create parent directory
|
// Create parent directory
|
||||||
if ( ! parent.mkdirs() ) {
|
if ( ! parent.mkdirs() ) {
|
||||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent);
|
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent);
|
||||||
Toast.makeText(FileSelectActivity.this,
|
Toast.makeText(FileSelectActivity.this,
|
||||||
R.string.error_could_not_create_parent,
|
R.string.error_could_not_create_parent,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.createNewFile();
|
return file.createNewFile();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.getLocalizedMessage());
|
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.getLocalizedMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
FileSelectActivity.this,
|
FileSelectActivity.this,
|
||||||
getText(R.string.error_file_not_create) + " "
|
getText(R.string.error_file_not_create) + " "
|
||||||
+ e.getLocalizedMessage(),
|
+ e.getLocalizedMessage(),
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDefinePathDialogPositiveClick(Uri pathFile) {
|
public boolean onDefinePathDialogPositiveClick(Uri pathFile) {
|
||||||
databaseUri = pathFile;
|
databaseUri = pathFile;
|
||||||
if(createDatabaseFile(pathFile)) {
|
if(createDatabaseFile(pathFile)) {
|
||||||
AssignMasterKeyDialogFragment assignMasterKeyDialogFragment = new AssignMasterKeyDialogFragment();
|
AssignMasterKeyDialogFragment assignMasterKeyDialogFragment = new AssignMasterKeyDialogFragment();
|
||||||
@@ -516,97 +516,97 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
return true;
|
return true;
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onDefinePathDialogNegativeClick(Uri pathFile) {
|
public boolean onDefinePathDialogNegativeClick(Uri pathFile) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAssignKeyDialogPositiveClick(
|
public void onAssignKeyDialogPositiveClick(
|
||||||
boolean masterPasswordChecked, String masterPassword,
|
boolean masterPasswordChecked, String masterPassword,
|
||||||
boolean keyFileChecked, Uri keyFile) {
|
boolean keyFileChecked, Uri keyFile) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String databaseFilename = databaseUri.getPath();
|
String databaseFilename = databaseUri.getPath();
|
||||||
|
|
||||||
if (databaseFilename != null) {
|
if (databaseFilename != null) {
|
||||||
// Create the new database and start prof
|
// Create the new database and start prof
|
||||||
new Thread(new ProgressDialogRunnable(this,
|
new Thread(new ProgressDialogRunnable(this,
|
||||||
R.string.progress_create,
|
R.string.progress_create,
|
||||||
progressTaskUpdater ->
|
progressTaskUpdater ->
|
||||||
new CreateDatabaseRunnable(databaseFilename, database -> {
|
new CreateDatabaseRunnable(databaseFilename, database -> {
|
||||||
// TODO store database created
|
// TODO store database created
|
||||||
return new AssignPasswordInDatabaseRunnable(FileSelectActivity.this,
|
return new AssignPasswordInDatabaseRunnable(FileSelectActivity.this,
|
||||||
database,
|
database,
|
||||||
masterPasswordChecked,
|
masterPasswordChecked,
|
||||||
masterPassword,
|
masterPassword,
|
||||||
keyFileChecked,
|
keyFileChecked,
|
||||||
keyFile,
|
keyFile,
|
||||||
new LaunchGroupActivityFinish(UriUtil.parseDefaultFile(databaseFilename)),
|
new LaunchGroupActivityFinish(UriUtil.parseDefaultFile(databaseFilename)),
|
||||||
true // TODO get readonly
|
true // TODO get readonly
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)).start();
|
)).start();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String error = "Unable to create database with this password and key file";
|
String error = "Unable to create database with this password and key file";
|
||||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
|
||||||
Log.e(TAG, error + " " + e.getMessage());
|
Log.e(TAG, error + " " + e.getMessage());
|
||||||
// TODO remove
|
// TODO remove
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LaunchGroupActivityFinish extends ActionRunnable {
|
private class LaunchGroupActivityFinish extends ActionRunnable {
|
||||||
|
|
||||||
private Uri fileURI;
|
private Uri fileURI;
|
||||||
|
|
||||||
LaunchGroupActivityFinish(Uri fileUri) {
|
LaunchGroupActivityFinish(Uri fileUri) {
|
||||||
super();
|
super();
|
||||||
this.fileURI = fileUri;
|
this.fileURI = fileUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
finishRun(true, null);
|
finishRun(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinishRun(boolean isSuccess, @Nullable String message) {
|
public void onFinishRun(boolean isSuccess, @Nullable String message) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
// Add database to recent files
|
// Add database to recent files
|
||||||
fileHistory.createFile(fileURI);
|
fileHistory.createFile(fileURI);
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
updateFileListVisibility();
|
updateFileListVisibility();
|
||||||
GroupActivity.launch(FileSelectActivity.this);
|
GroupActivity.launch(FileSelectActivity.this);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Unable to open the database");
|
Log.e(TAG, "Unable to open the database");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAssignKeyDialogNegativeClick(
|
public void onAssignKeyDialogNegativeClick(
|
||||||
boolean masterPasswordChecked, String masterPassword,
|
boolean masterPasswordChecked, String masterPassword,
|
||||||
boolean keyFileChecked, Uri keyFile) {
|
boolean keyFileChecked, Uri keyFile) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFileItemOpenListener(int itemPosition) {
|
public void onFileItemOpenListener(int itemPosition) {
|
||||||
new OpenFileHistoryAsyncTask((fileName, keyFile) -> {
|
new OpenFileHistoryAsyncTask((fileName, keyFile) -> {
|
||||||
launchPasswordActivity(fileName, keyFile);
|
launchPasswordActivity(fileName, keyFile);
|
||||||
updateFileListVisibility();
|
updateFileListVisibility();
|
||||||
}, fileHistory).execute(itemPosition);
|
}, fileHistory).execute(itemPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClickFileInformation(FileSelectBean fileSelectBean) {
|
public void onClickFileInformation(FileSelectBean fileSelectBean) {
|
||||||
if (fileSelectBean != null) {
|
if (fileSelectBean != null) {
|
||||||
FileInformationDialogFragment fileInformationDialogFragment =
|
FileInformationDialogFragment fileInformationDialogFragment =
|
||||||
FileInformationDialogFragment.newInstance(fileSelectBean);
|
FileInformationDialogFragment.newInstance(fileSelectBean);
|
||||||
fileInformationDialogFragment.show(getSupportFragmentManager(), "fileInformation");
|
fileInformationDialogFragment.show(getSupportFragmentManager(), "fileInformation");
|
||||||
@@ -623,15 +623,15 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
AutofillHelper.INSTANCE.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
AutofillHelper.INSTANCE.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||||
uri -> {
|
uri -> {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (PreferencesUtil.autoOpenSelectedFile(FileSelectActivity.this)) {
|
if (PreferencesUtil.autoOpenSelectedFile(FileSelectActivity.this)) {
|
||||||
@@ -642,7 +642,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
void showRationaleForExternalStorage(final PermissionRequest request) {
|
void showRationaleForExternalStorage(final PermissionRequest request) {
|
||||||
@@ -663,16 +663,16 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
MenuUtil.INSTANCE.defaultMenuInflater(getMenuInflater(), menu);
|
MenuUtil.INSTANCE.defaultMenuInflater(getMenuInflater(), menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
return MenuUtil.INSTANCE.onDefaultMenuOptionsItemSelected(this, item)
|
return MenuUtil.INSTANCE.onDefaultMenuOptionsItemSelected(this, item)
|
||||||
&& super.onOptionsItemSelected(item);
|
&& super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -48,21 +48,21 @@ public class IconDrawableFactory {
|
|||||||
|
|
||||||
private static final String TAG = IconDrawableFactory.class.getName();
|
private static final String TAG = IconDrawableFactory.class.getName();
|
||||||
|
|
||||||
private static Drawable blank = null;
|
private static Drawable blank = null;
|
||||||
private static int blankWidth = -1;
|
private static int blankWidth = -1;
|
||||||
private static int blankHeight = -1;
|
private static int blankHeight = -1;
|
||||||
|
|
||||||
/** customIconMap
|
/** customIconMap
|
||||||
* Cache for icon drawable.
|
* Cache for icon drawable.
|
||||||
* Keys: UUID, Values: Drawables
|
* Keys: UUID, Values: Drawables
|
||||||
*/
|
*/
|
||||||
private ReferenceMap customIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
private ReferenceMap customIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||||
|
|
||||||
/** standardIconMap
|
/** standardIconMap
|
||||||
* Cache for icon drawable.
|
* Cache for icon drawable.
|
||||||
* Keys: Integer, Values: Drawables
|
* Keys: Integer, Values: Drawables
|
||||||
*/
|
*/
|
||||||
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a default database icon to an ImageView and tint it if needed
|
* Assign a default database icon to an ImageView and tint it if needed
|
||||||
@@ -95,19 +95,19 @@ public class IconDrawableFactory {
|
|||||||
* @param icon The icon from the database
|
* @param icon The icon from the database
|
||||||
* @param tintColor Use this color to tint tintable icon
|
* @param tintColor Use this color to tint tintable icon
|
||||||
*/
|
*/
|
||||||
public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
|
public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
|
||||||
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
||||||
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
||||||
iconView,
|
iconView,
|
||||||
true,
|
true,
|
||||||
tintColor);
|
tintColor);
|
||||||
} else {
|
} else {
|
||||||
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
||||||
iconView,
|
iconView,
|
||||||
false,
|
false,
|
||||||
Color.WHITE);
|
Color.WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign an image by its resourceId to an ImageView and tint it
|
* Assign an image by its resourceId to an ImageView and tint it
|
||||||
@@ -146,9 +146,9 @@ public class IconDrawableFactory {
|
|||||||
* @param icon The icon from database
|
* @param icon The icon from database
|
||||||
* @return The build drawable
|
* @return The build drawable
|
||||||
*/
|
*/
|
||||||
public Drawable getIconDrawable(Context context, PwIcon icon) {
|
public Drawable getIconDrawable(Context context, PwIcon icon) {
|
||||||
return getIconDrawable(context, icon, false, Color.WHITE).drawable;
|
return getIconDrawable(context, icon, false, Color.WHITE).drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet then tint it if needed
|
* Get the drawable icon from cache or build it and add it to the cache if not exists yet then tint it if needed
|
||||||
@@ -171,27 +171,27 @@ public class IconDrawableFactory {
|
|||||||
* Build a blank drawable
|
* Build a blank drawable
|
||||||
* @param res Resource to build the drawable
|
* @param res Resource to build the drawable
|
||||||
*/
|
*/
|
||||||
private static void initBlank(Resources res) {
|
private static void initBlank(Resources res) {
|
||||||
if (blank==null) {
|
if (blank==null) {
|
||||||
blankWidth = (int) res.getDimension(R.dimen.icon_size);
|
blankWidth = (int) res.getDimension(R.dimen.icon_size);
|
||||||
blankHeight = (int) res.getDimension(R.dimen.icon_size);
|
blankHeight = (int) res.getDimension(R.dimen.icon_size);
|
||||||
blank = new ColorDrawable(Color.TRANSPARENT);
|
blank = new ColorDrawable(Color.TRANSPARENT);
|
||||||
blank.setBounds(0, 0, blankWidth, blankHeight);
|
blank.setBounds(0, 0, blankWidth, blankHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key class to retrieve a Drawable in the cache if it's tinted or not
|
* Key class to retrieve a Drawable in the cache if it's tinted or not
|
||||||
*/
|
*/
|
||||||
private class CacheKey {
|
private class CacheKey {
|
||||||
int resId;
|
int resId;
|
||||||
boolean isTint;
|
boolean isTint;
|
||||||
int color;
|
int color;
|
||||||
|
|
||||||
CacheKey(int resId, boolean isTint, int color) {
|
CacheKey(int resId, boolean isTint, int color) {
|
||||||
this.resId = resId;
|
this.resId = resId;
|
||||||
this.isTint = isTint;
|
this.isTint = isTint;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -202,7 +202,7 @@ public class IconDrawableFactory {
|
|||||||
if (isTint)
|
if (isTint)
|
||||||
return resId == cacheKey.resId &&
|
return resId == cacheKey.resId &&
|
||||||
cacheKey.isTint &&
|
cacheKey.isTint &&
|
||||||
color == cacheKey.color;
|
color == cacheKey.color;
|
||||||
else
|
else
|
||||||
return resId == cacheKey.resId &&
|
return resId == cacheKey.resId &&
|
||||||
!cacheKey.isTint;
|
!cacheKey.isTint;
|
||||||
@@ -218,11 +218,11 @@ public class IconDrawableFactory {
|
|||||||
* @param tintColor Use this color if tint is true
|
* @param tintColor Use this color if tint is true
|
||||||
* @return The drawable
|
* @return The drawable
|
||||||
*/
|
*/
|
||||||
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
||||||
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
|
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
|
||||||
|
|
||||||
return getIconDrawable(context, resId, isTint, tintColor);
|
return getIconDrawable(context, resId, isTint, tintColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
|
* Get the drawable icon from cache or build it and add it to the cache if not exists yet
|
||||||
@@ -257,23 +257,23 @@ public class IconDrawableFactory {
|
|||||||
return draw;
|
return draw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to prevent a custom icon to be tint
|
* Utility class to prevent a custom icon to be tint
|
||||||
*/
|
*/
|
||||||
private class SuperDrawable {
|
private class SuperDrawable {
|
||||||
Drawable drawable;
|
Drawable drawable;
|
||||||
boolean custom;
|
boolean custom;
|
||||||
|
|
||||||
SuperDrawable(Drawable drawable) {
|
SuperDrawable(Drawable drawable) {
|
||||||
this.drawable = drawable;
|
this.drawable = drawable;
|
||||||
this.custom = false;
|
this.custom = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SuperDrawable(Drawable drawable, boolean custom) {
|
SuperDrawable(Drawable drawable, boolean custom) {
|
||||||
this.drawable = drawable;
|
this.drawable = drawable;
|
||||||
this.custom = custom;
|
this.custom = custom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a custom icon from database
|
* Build a custom icon from database
|
||||||
@@ -281,55 +281,55 @@ public class IconDrawableFactory {
|
|||||||
* @param icon Icon from database
|
* @param icon Icon from database
|
||||||
* @return The drawable
|
* @return The drawable
|
||||||
*/
|
*/
|
||||||
private Drawable getIconDrawable(Context context, PwIconCustom icon) {
|
private Drawable getIconDrawable(Context context, PwIconCustom icon) {
|
||||||
initBlank(context.getResources());
|
initBlank(context.getResources());
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return blank;
|
return blank;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable draw = (Drawable) customIconMap.get(icon.getUuid());
|
|
||||||
|
|
||||||
if (draw == null) {
|
|
||||||
|
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
|
Drawable draw = (Drawable) customIconMap.get(icon.getUuid());
|
||||||
|
|
||||||
// Could not understand custom icon
|
|
||||||
if (bitmap == null) {
|
|
||||||
return blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap = resize(bitmap);
|
|
||||||
|
|
||||||
draw = new BitmapDrawable(context.getResources(), bitmap);
|
|
||||||
customIconMap.put(icon.getUuid(), draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
return draw;
|
if (draw == null) {
|
||||||
}
|
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
|
||||||
/**
|
|
||||||
|
// Could not understand custom icon
|
||||||
|
if (bitmap == null) {
|
||||||
|
return blank;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap = resize(bitmap);
|
||||||
|
|
||||||
|
draw = new BitmapDrawable(context.getResources(), bitmap);
|
||||||
|
customIconMap.put(icon.getUuid(), draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Resize the custom icon to match the built in icons
|
* Resize the custom icon to match the built in icons
|
||||||
*
|
*
|
||||||
* @param bitmap Bitmap to resize
|
* @param bitmap Bitmap to resize
|
||||||
* @return Bitmap resized
|
* @return Bitmap resized
|
||||||
*/
|
*/
|
||||||
private Bitmap resize(Bitmap bitmap) {
|
private Bitmap resize(Bitmap bitmap) {
|
||||||
int width = bitmap.getWidth();
|
int width = bitmap.getWidth();
|
||||||
int height = bitmap.getHeight();
|
int height = bitmap.getHeight();
|
||||||
|
|
||||||
if (width == blankWidth && height == blankHeight) {
|
if (width == blankWidth && height == blankHeight) {
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bitmap.createScaledBitmap(bitmap, blankWidth, blankHeight, true);
|
return Bitmap.createScaledBitmap(bitmap, blankWidth, blankHeight, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the cache of icons
|
* Clear the cache of icons
|
||||||
*/
|
*/
|
||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
standardIconMap.clear();
|
standardIconMap.clear();
|
||||||
customIconMap.clear();
|
customIconMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,28 @@ import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_CHANGE_KEYBOARD;
|
|||||||
|
|
||||||
public class MagikeyboardView extends KeyboardView {
|
public class MagikeyboardView extends KeyboardView {
|
||||||
|
|
||||||
public MagikeyboardView(Context context, AttributeSet attrs) {
|
public MagikeyboardView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
|
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onLongPress(Keyboard.Key key) {
|
protected boolean onLongPress(Keyboard.Key key) {
|
||||||
// TODO Action on long press
|
// TODO Action on long press
|
||||||
if (key.codes[0] == KEY_BACK_KEYBOARD) {
|
if (key.codes[0] == KEY_BACK_KEYBOARD) {
|
||||||
getOnKeyboardActionListener().onKey(KEY_CHANGE_KEYBOARD, null);
|
getOnKeyboardActionListener().onKey(KEY_CHANGE_KEYBOARD, null);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
|
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
|
||||||
return super.onLongPress(key);
|
return super.onLongPress(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,14 +26,14 @@ import com.kunzisoft.keepass.R;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class PasswordGenerator {
|
public class PasswordGenerator {
|
||||||
private static final String UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
private static final String UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
private static final String LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
private static final String LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
||||||
private static final String DIGIT_CHARS = "0123456789";
|
private static final String DIGIT_CHARS = "0123456789";
|
||||||
private static final String MINUS_CHAR = "-";
|
private static final String MINUS_CHAR = "-";
|
||||||
private static final String UNDERLINE_CHAR = "_";
|
private static final String UNDERLINE_CHAR = "_";
|
||||||
private static final String SPACE_CHAR = " ";
|
private static final String SPACE_CHAR = " ";
|
||||||
private static final String SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`";
|
private static final String SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`";
|
||||||
private static final String BRACKET_CHARS = "[]{}()<>";
|
private static final String BRACKET_CHARS = "[]{}()<>";
|
||||||
|
|
||||||
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
|
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
|
||||||
private String extendedChars() {
|
private String extendedChars() {
|
||||||
@@ -48,14 +48,14 @@ public class PasswordGenerator {
|
|||||||
charSet.append('\u00FF');
|
charSet.append('\u00FF');
|
||||||
return charSet.toString();
|
return charSet.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context cxt;
|
private Context cxt;
|
||||||
|
|
||||||
public PasswordGenerator(Context cxt) {
|
public PasswordGenerator(Context cxt) {
|
||||||
this.cxt = cxt;
|
this.cxt = cxt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generatePassword(int length,
|
public String generatePassword(int length,
|
||||||
boolean upperCase,
|
boolean upperCase,
|
||||||
boolean lowerCase,
|
boolean lowerCase,
|
||||||
boolean digits,
|
boolean digits,
|
||||||
@@ -65,13 +65,13 @@ public class PasswordGenerator {
|
|||||||
boolean specials,
|
boolean specials,
|
||||||
boolean brackets,
|
boolean brackets,
|
||||||
boolean extended) throws IllegalArgumentException{
|
boolean extended) throws IllegalArgumentException{
|
||||||
// Desired password length is 0 or less
|
// Desired password length is 0 or less
|
||||||
if (length <= 0) {
|
if (length <= 0) {
|
||||||
throw new IllegalArgumentException(cxt.getString(R.string.error_wrong_length));
|
throw new IllegalArgumentException(cxt.getString(R.string.error_wrong_length));
|
||||||
}
|
}
|
||||||
|
|
||||||
// No option has been checked
|
// No option has been checked
|
||||||
if ( !upperCase
|
if ( !upperCase
|
||||||
&& !lowerCase
|
&& !lowerCase
|
||||||
&& !digits
|
&& !digits
|
||||||
&& !minus
|
&& !minus
|
||||||
@@ -80,11 +80,11 @@ public class PasswordGenerator {
|
|||||||
&& !specials
|
&& !specials
|
||||||
&& !brackets
|
&& !brackets
|
||||||
&& !extended) {
|
&& !extended) {
|
||||||
throw new IllegalArgumentException(cxt.getString(R.string.error_pass_gen_type));
|
throw new IllegalArgumentException(cxt.getString(R.string.error_pass_gen_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
String characterSet = getCharacterSet(
|
String characterSet = getCharacterSet(
|
||||||
upperCase,
|
upperCase,
|
||||||
lowerCase,
|
lowerCase,
|
||||||
digits,
|
digits,
|
||||||
minus,
|
minus,
|
||||||
@@ -93,68 +93,68 @@ public class PasswordGenerator {
|
|||||||
specials,
|
specials,
|
||||||
brackets,
|
brackets,
|
||||||
extended);
|
extended);
|
||||||
|
|
||||||
int size = characterSet.length();
|
|
||||||
|
|
||||||
StringBuilder buffer = new StringBuilder();
|
|
||||||
|
|
||||||
SecureRandom random = new SecureRandom(); // use more secure variant of Random!
|
int size = characterSet.length();
|
||||||
if (size > 0) {
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
char c = characterSet.charAt((char) random.nextInt(size));
|
|
||||||
buffer.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCharacterSet(boolean upperCase,
|
|
||||||
boolean lowerCase,
|
|
||||||
boolean digits,
|
|
||||||
boolean minus,
|
|
||||||
boolean underline,
|
|
||||||
boolean space,
|
|
||||||
boolean specials,
|
|
||||||
boolean brackets,
|
|
||||||
boolean extended) {
|
|
||||||
StringBuilder charSet = new StringBuilder();
|
|
||||||
|
|
||||||
if (upperCase) {
|
|
||||||
charSet.append(UPPERCASE_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lowerCase) {
|
|
||||||
charSet.append(LOWERCASE_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digits) {
|
|
||||||
charSet.append(DIGIT_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minus) {
|
|
||||||
charSet.append(MINUS_CHAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underline) {
|
|
||||||
charSet.append(UNDERLINE_CHAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (space) {
|
|
||||||
charSet.append(SPACE_CHAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (specials) {
|
|
||||||
charSet.append(SPECIAL_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (brackets) {
|
|
||||||
charSet.append(BRACKET_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extended) {
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
|
||||||
|
SecureRandom random = new SecureRandom(); // use more secure variant of Random!
|
||||||
|
if (size > 0) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
char c = characterSet.charAt((char) random.nextInt(size));
|
||||||
|
buffer.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCharacterSet(boolean upperCase,
|
||||||
|
boolean lowerCase,
|
||||||
|
boolean digits,
|
||||||
|
boolean minus,
|
||||||
|
boolean underline,
|
||||||
|
boolean space,
|
||||||
|
boolean specials,
|
||||||
|
boolean brackets,
|
||||||
|
boolean extended) {
|
||||||
|
StringBuilder charSet = new StringBuilder();
|
||||||
|
|
||||||
|
if (upperCase) {
|
||||||
|
charSet.append(UPPERCASE_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowerCase) {
|
||||||
|
charSet.append(LOWERCASE_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digits) {
|
||||||
|
charSet.append(DIGIT_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minus) {
|
||||||
|
charSet.append(MINUS_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underline) {
|
||||||
|
charSet.append(UNDERLINE_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space) {
|
||||||
|
charSet.append(SPACE_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (specials) {
|
||||||
|
charSet.append(SPECIAL_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brackets) {
|
||||||
|
charSet.append(BRACKET_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extended) {
|
||||||
charSet.append(extendedChars());
|
charSet.append(extendedChars());
|
||||||
}
|
}
|
||||||
|
|
||||||
return charSet.toString();
|
return charSet.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -28,76 +28,76 @@ import java.io.OutputStream;
|
|||||||
* output stream.
|
* output stream.
|
||||||
*/
|
*/
|
||||||
public class CopyInputStream extends InputStream {
|
public class CopyInputStream extends InputStream {
|
||||||
private InputStream is;
|
private InputStream is;
|
||||||
private OutputStream os;
|
private OutputStream os;
|
||||||
|
|
||||||
public CopyInputStream(InputStream is, OutputStream os) {
|
|
||||||
this.is = is;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public CopyInputStream(InputStream is, OutputStream os) {
|
||||||
public int available() throws IOException {
|
this.is = is;
|
||||||
return is.available();
|
this.os = os;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public int available() throws IOException {
|
||||||
is.close();
|
return is.available();
|
||||||
os.close();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int readlimit) {
|
public void close() throws IOException {
|
||||||
is.mark(readlimit);
|
is.close();
|
||||||
}
|
os.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean markSupported() {
|
public void mark(int readlimit) {
|
||||||
return is.markSupported();
|
is.mark(readlimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public boolean markSupported() {
|
||||||
int data = is.read();
|
return is.markSupported();
|
||||||
|
}
|
||||||
if (data != -1) {
|
|
||||||
os.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int offset, int length) throws IOException {
|
public int read() throws IOException {
|
||||||
int len = is.read(b, offset, length);
|
int data = is.read();
|
||||||
|
|
||||||
if (len != -1) {
|
|
||||||
os.write(b, offset, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (data != -1) {
|
||||||
public int read(byte[] b) throws IOException {
|
os.write(data);
|
||||||
int len = is.read(b);
|
}
|
||||||
|
|
||||||
if (len != -1) {
|
|
||||||
os.write(b, 0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return data;
|
||||||
public synchronized void reset() throws IOException {
|
}
|
||||||
is.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long byteCount) throws IOException {
|
public int read(byte[] b, int offset, int length) throws IOException {
|
||||||
return is.skip(byteCount);
|
int len = is.read(b, offset, length);
|
||||||
}
|
|
||||||
|
if (len != -1) {
|
||||||
|
os.write(b, offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b) throws IOException {
|
||||||
|
int len = is.read(b);
|
||||||
|
|
||||||
|
if (len != -1) {
|
||||||
|
os.write(b, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
is.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long byteCount) throws IOException {
|
||||||
|
return is.skip(byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -23,60 +23,60 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class CountInputStream extends InputStream {
|
public class CountInputStream extends InputStream {
|
||||||
InputStream is;
|
InputStream is;
|
||||||
long bytes = 0;
|
long bytes = 0;
|
||||||
|
|
||||||
public CountInputStream(InputStream is) {
|
|
||||||
this.is = is;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public CountInputStream(InputStream is) {
|
||||||
public int available() throws IOException {
|
this.is = is;
|
||||||
return is.available();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public int available() throws IOException {
|
||||||
is.close();
|
return is.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int readlimit) {
|
public void close() throws IOException {
|
||||||
is.mark(readlimit);
|
is.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean markSupported() {
|
public void mark(int readlimit) {
|
||||||
return is.markSupported();
|
is.mark(readlimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public boolean markSupported() {
|
||||||
bytes++;
|
return is.markSupported();
|
||||||
return is.read();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
public int read() throws IOException {
|
||||||
bytes += length;
|
bytes++;
|
||||||
return is.read(buffer, offset, length);
|
return is.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] buffer) throws IOException {
|
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||||
bytes += buffer.length;
|
bytes += length;
|
||||||
return is.read(buffer);
|
return is.read(buffer, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void reset() throws IOException {
|
public int read(byte[] buffer) throws IOException {
|
||||||
is.reset();
|
bytes += buffer.length;
|
||||||
}
|
return is.read(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long byteCount) throws IOException {
|
public synchronized void reset() throws IOException {
|
||||||
bytes += byteCount;
|
is.reset();
|
||||||
return is.skip(byteCount);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public long skip(long byteCount) throws IOException {
|
||||||
|
bytes += byteCount;
|
||||||
|
return is.skip(byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -23,40 +23,40 @@ import java.io.IOException;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class CountOutputStream extends OutputStream {
|
public class CountOutputStream extends OutputStream {
|
||||||
OutputStream os;
|
OutputStream os;
|
||||||
long bytes = 0;
|
long bytes = 0;
|
||||||
|
|
||||||
public CountOutputStream(OutputStream os) {
|
public CountOutputStream(OutputStream os) {
|
||||||
this.os = os;
|
this.os = os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
os.flush();
|
os.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer, int offset, int count) throws IOException {
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
bytes += count;
|
bytes += count;
|
||||||
os.write(buffer, offset, count);
|
os.write(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer) throws IOException {
|
public void write(byte[] buffer) throws IOException {
|
||||||
bytes += buffer.length;
|
bytes += buffer.length;
|
||||||
os.write(buffer);
|
os.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int oneByte) throws IOException {
|
public void write(int oneByte) throws IOException {
|
||||||
bytes++;
|
bytes++;
|
||||||
os.write(oneByte);
|
os.write(oneByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -29,137 +29,137 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
|
|
||||||
public class HashedBlockInputStream extends InputStream {
|
public class HashedBlockInputStream extends InputStream {
|
||||||
|
|
||||||
private final static int HASH_SIZE = 32;
|
|
||||||
|
|
||||||
private LEDataInputStream baseStream;
|
private final static int HASH_SIZE = 32;
|
||||||
private int bufferPos = 0;
|
|
||||||
private byte[] buffer = new byte[0];
|
|
||||||
private long bufferIndex = 0;
|
|
||||||
private boolean atEnd = false;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashedBlockInputStream(InputStream is) {
|
private LEDataInputStream baseStream;
|
||||||
baseStream = new LEDataInputStream(is);
|
private int bufferPos = 0;
|
||||||
}
|
private byte[] buffer = new byte[0];
|
||||||
|
private long bufferIndex = 0;
|
||||||
@Override
|
private boolean atEnd = false;
|
||||||
public int read(byte[] b, int offset, int length) throws IOException {
|
|
||||||
if ( atEnd ) return -1;
|
|
||||||
|
|
||||||
int remaining = length;
|
|
||||||
|
|
||||||
while ( remaining > 0 ) {
|
|
||||||
if ( bufferPos == buffer.length ) {
|
|
||||||
// Get more from the source into the buffer
|
|
||||||
if ( ! ReadHashedBlock() ) {
|
|
||||||
return length - remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy from buffer out
|
|
||||||
int copyLen = Math.min(buffer.length - bufferPos, remaining);
|
|
||||||
|
|
||||||
System.arraycopy(buffer, bufferPos, b, offset, copyLen);
|
|
||||||
|
|
||||||
offset += copyLen;
|
|
||||||
bufferPos += copyLen;
|
|
||||||
|
|
||||||
remaining -= copyLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* @return false, when the end of the source stream is reached
|
public int read(byte[] b) throws IOException {
|
||||||
* @throws IOException
|
return read(b, 0, b.length);
|
||||||
*/
|
}
|
||||||
private boolean ReadHashedBlock() throws IOException {
|
|
||||||
if ( atEnd ) return false;
|
|
||||||
|
|
||||||
bufferPos = 0;
|
|
||||||
|
|
||||||
long index = baseStream.readUInt();
|
|
||||||
if ( index != bufferIndex ) {
|
|
||||||
throw new IOException("Invalid data format");
|
|
||||||
}
|
|
||||||
bufferIndex++;
|
|
||||||
|
|
||||||
byte[] storedHash = baseStream.readBytes(32);
|
|
||||||
if ( storedHash == null || storedHash.length != HASH_SIZE) {
|
|
||||||
throw new IOException("Invalid data format");
|
|
||||||
}
|
|
||||||
|
|
||||||
int bufferSize = LEDataInputStream.readInt(baseStream);
|
|
||||||
if ( bufferSize < 0 ) {
|
|
||||||
throw new IOException("Invalid data format");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( bufferSize == 0 ) {
|
|
||||||
for (int hash = 0; hash < HASH_SIZE; hash++) {
|
|
||||||
if ( storedHash[hash] != 0 ) {
|
|
||||||
throw new IOException("Invalid data format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
atEnd = true;
|
|
||||||
buffer = new byte[0];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer = baseStream.readBytes(bufferSize);
|
|
||||||
if ( buffer == null || buffer.length != bufferSize ) {
|
|
||||||
throw new IOException("Invalid data format");
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDigest md = null;
|
|
||||||
try {
|
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IOException("SHA-256 not implemented here.");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] computedHash = md.digest(buffer);
|
|
||||||
if ( computedHash == null || computedHash.length != HASH_SIZE ) {
|
|
||||||
throw new IOException("Hash wrong size");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! Arrays.equals(storedHash, computedHash) ) {
|
|
||||||
throw new IOException("Hashes didn't match.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
public HashedBlockInputStream(InputStream is) {
|
||||||
}
|
baseStream = new LEDataInputStream(is);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long n) throws IOException {
|
public int read(byte[] b, int offset, int length) throws IOException {
|
||||||
return 0;
|
if ( atEnd ) return -1;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
int remaining = length;
|
||||||
public int read() throws IOException {
|
|
||||||
if ( atEnd ) return -1;
|
|
||||||
|
|
||||||
if ( bufferPos == buffer.length ) {
|
|
||||||
if ( ! ReadHashedBlock() ) return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int output = Types.readUByte(buffer, bufferPos);
|
|
||||||
bufferPos++;
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
while ( remaining > 0 ) {
|
||||||
public void close() throws IOException {
|
if ( bufferPos == buffer.length ) {
|
||||||
baseStream.close();
|
// Get more from the source into the buffer
|
||||||
}
|
if ( ! ReadHashedBlock() ) {
|
||||||
|
return length - remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from buffer out
|
||||||
|
int copyLen = Math.min(buffer.length - bufferPos, remaining);
|
||||||
|
|
||||||
|
System.arraycopy(buffer, bufferPos, b, offset, copyLen);
|
||||||
|
|
||||||
|
offset += copyLen;
|
||||||
|
bufferPos += copyLen;
|
||||||
|
|
||||||
|
remaining -= copyLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return false, when the end of the source stream is reached
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private boolean ReadHashedBlock() throws IOException {
|
||||||
|
if ( atEnd ) return false;
|
||||||
|
|
||||||
|
bufferPos = 0;
|
||||||
|
|
||||||
|
long index = baseStream.readUInt();
|
||||||
|
if ( index != bufferIndex ) {
|
||||||
|
throw new IOException("Invalid data format");
|
||||||
|
}
|
||||||
|
bufferIndex++;
|
||||||
|
|
||||||
|
byte[] storedHash = baseStream.readBytes(32);
|
||||||
|
if ( storedHash == null || storedHash.length != HASH_SIZE) {
|
||||||
|
throw new IOException("Invalid data format");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bufferSize = LEDataInputStream.readInt(baseStream);
|
||||||
|
if ( bufferSize < 0 ) {
|
||||||
|
throw new IOException("Invalid data format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( bufferSize == 0 ) {
|
||||||
|
for (int hash = 0; hash < HASH_SIZE; hash++) {
|
||||||
|
if ( storedHash[hash] != 0 ) {
|
||||||
|
throw new IOException("Invalid data format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atEnd = true;
|
||||||
|
buffer = new byte[0];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = baseStream.readBytes(bufferSize);
|
||||||
|
if ( buffer == null || buffer.length != bufferSize ) {
|
||||||
|
throw new IOException("Invalid data format");
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDigest md = null;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IOException("SHA-256 not implemented here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] computedHash = md.digest(buffer);
|
||||||
|
if ( computedHash == null || computedHash.length != HASH_SIZE ) {
|
||||||
|
throw new IOException("Hash wrong size");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! Arrays.equals(storedHash, computedHash) ) {
|
||||||
|
throw new IOException("Hashes didn't match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if ( atEnd ) return -1;
|
||||||
|
|
||||||
|
if ( bufferPos == buffer.length ) {
|
||||||
|
if ( ! ReadHashedBlock() ) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int output = Types.readUByte(buffer, bufferPos);
|
||||||
|
bufferPos++;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
baseStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -26,90 +26,90 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
|
|
||||||
public class HashedBlockOutputStream extends OutputStream {
|
public class HashedBlockOutputStream extends OutputStream {
|
||||||
|
|
||||||
private final static int DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
private final static int DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
private LEDataOutputStream baseStream;
|
|
||||||
private int bufferPos = 0;
|
|
||||||
private byte[] buffer;
|
|
||||||
private long bufferIndex = 0;
|
|
||||||
|
|
||||||
public HashedBlockOutputStream(OutputStream os) {
|
|
||||||
init(os, DEFAULT_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashedBlockOutputStream(OutputStream os, int bufferSize) {
|
|
||||||
if ( bufferSize <= 0 ) {
|
|
||||||
bufferSize = DEFAULT_BUFFER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
init(os, bufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(OutputStream os, int bufferSize) {
|
|
||||||
baseStream = new LEDataOutputStream(os);
|
|
||||||
buffer = new byte[bufferSize];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private LEDataOutputStream baseStream;
|
||||||
public void write(int oneByte) throws IOException {
|
private int bufferPos = 0;
|
||||||
byte[] buf = new byte[1];
|
private byte[] buffer;
|
||||||
buf[0] = (byte)oneByte;
|
private long bufferIndex = 0;
|
||||||
write(buf, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public HashedBlockOutputStream(OutputStream os) {
|
||||||
public void close() throws IOException {
|
init(os, DEFAULT_BUFFER_SIZE);
|
||||||
if ( bufferPos != 0 ) {
|
}
|
||||||
// Write remaining buffered amount
|
|
||||||
WriteHashedBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write terminating block
|
|
||||||
WriteHashedBlock();
|
|
||||||
|
|
||||||
flush();
|
|
||||||
baseStream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public HashedBlockOutputStream(OutputStream os, int bufferSize) {
|
||||||
public void flush() throws IOException {
|
if ( bufferSize <= 0 ) {
|
||||||
baseStream.flush();
|
bufferSize = DEFAULT_BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
init(os, bufferSize);
|
||||||
public void write(byte[] b, int offset, int count) throws IOException {
|
}
|
||||||
while ( count > 0 ) {
|
|
||||||
if ( bufferPos == buffer.length ) {
|
|
||||||
WriteHashedBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
int copyLen = Math.min(buffer.length - bufferPos, count);
|
|
||||||
|
|
||||||
System.arraycopy(b, offset, buffer, bufferPos, copyLen);
|
|
||||||
|
|
||||||
offset += copyLen;
|
|
||||||
bufferPos += copyLen;
|
|
||||||
|
|
||||||
count -= copyLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteHashedBlock() throws IOException {
|
private void init(OutputStream os, int bufferSize) {
|
||||||
baseStream.writeUInt(bufferIndex);
|
baseStream = new LEDataOutputStream(os);
|
||||||
bufferIndex++;
|
buffer = new byte[bufferSize];
|
||||||
|
|
||||||
if ( bufferPos > 0 ) {
|
}
|
||||||
MessageDigest md = null;
|
|
||||||
try {
|
@Override
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
public void write(int oneByte) throws IOException {
|
||||||
} catch (NoSuchAlgorithmException e) {
|
byte[] buf = new byte[1];
|
||||||
throw new IOException("SHA-256 not implemented here.");
|
buf[0] = (byte)oneByte;
|
||||||
}
|
write(buf, 0, 1);
|
||||||
|
}
|
||||||
byte[] hash;
|
|
||||||
md.update(buffer, 0, bufferPos);
|
@Override
|
||||||
hash = md.digest();
|
public void close() throws IOException {
|
||||||
|
if ( bufferPos != 0 ) {
|
||||||
|
// Write remaining buffered amount
|
||||||
|
WriteHashedBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write terminating block
|
||||||
|
WriteHashedBlock();
|
||||||
|
|
||||||
|
flush();
|
||||||
|
baseStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
baseStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int offset, int count) throws IOException {
|
||||||
|
while ( count > 0 ) {
|
||||||
|
if ( bufferPos == buffer.length ) {
|
||||||
|
WriteHashedBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
int copyLen = Math.min(buffer.length - bufferPos, count);
|
||||||
|
|
||||||
|
System.arraycopy(b, offset, buffer, bufferPos, copyLen);
|
||||||
|
|
||||||
|
offset += copyLen;
|
||||||
|
bufferPos += copyLen;
|
||||||
|
|
||||||
|
count -= copyLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteHashedBlock() throws IOException {
|
||||||
|
baseStream.writeUInt(bufferIndex);
|
||||||
|
bufferIndex++;
|
||||||
|
|
||||||
|
if ( bufferPos > 0 ) {
|
||||||
|
MessageDigest md = null;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IOException("SHA-256 not implemented here.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] hash;
|
||||||
|
md.update(buffer, 0, bufferPos);
|
||||||
|
hash = md.digest();
|
||||||
/*
|
/*
|
||||||
if ( bufferPos == buffer.length) {
|
if ( bufferPos == buffer.length) {
|
||||||
hash = md.digest(buffer);
|
hash = md.digest(buffer);
|
||||||
@@ -119,30 +119,30 @@ public class HashedBlockOutputStream extends OutputStream {
|
|||||||
hash = md.digest(b);
|
hash = md.digest(b);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
baseStream.write(hash);
|
|
||||||
|
|
||||||
} else {
|
baseStream.write(hash);
|
||||||
// Write 32-bits of zeros
|
|
||||||
baseStream.writeLong(0L);
|
|
||||||
baseStream.writeLong(0L);
|
|
||||||
baseStream.writeLong(0L);
|
|
||||||
baseStream.writeLong(0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
baseStream.writeInt(bufferPos);
|
|
||||||
|
|
||||||
if ( bufferPos > 0 ) {
|
|
||||||
baseStream.write(buffer, 0, bufferPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferPos = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
} else {
|
||||||
public void write(byte[] buffer) throws IOException {
|
// Write 32-bits of zeros
|
||||||
write(buffer, 0, buffer.length);
|
baseStream.writeLong(0L);
|
||||||
}
|
baseStream.writeLong(0L);
|
||||||
|
baseStream.writeLong(0L);
|
||||||
|
baseStream.writeLong(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseStream.writeInt(bufferPos);
|
||||||
|
|
||||||
|
if ( bufferPos > 0 ) {
|
||||||
|
baseStream.write(buffer, 0, bufferPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferPos = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer) throws IOException {
|
||||||
|
write(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -30,100 +30,100 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class LEDataInputStream extends InputStream {
|
public class LEDataInputStream extends InputStream {
|
||||||
|
|
||||||
private static final long INT_TO_LONG_MASK = 0xffffffffL;
|
private static final long INT_TO_LONG_MASK = 0xffffffffL;
|
||||||
|
|
||||||
private InputStream baseStream;
|
|
||||||
|
|
||||||
public LEDataInputStream(InputStream in) {
|
private InputStream baseStream;
|
||||||
baseStream = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Read a 32-bit value and return it as a long, so that it can
|
|
||||||
* be interpreted as an unsigned integer.
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public long readUInt() throws IOException {
|
|
||||||
return readUInt(baseStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readInt() throws IOException {
|
|
||||||
return readInt(baseStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long readLong() throws IOException {
|
|
||||||
byte[] buf = readBytes(8);
|
|
||||||
|
|
||||||
return readLong(buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
return baseStream.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public LEDataInputStream(InputStream in) {
|
||||||
public void close() throws IOException {
|
baseStream = in;
|
||||||
baseStream.close();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/** Read a 32-bit value and return it as a long, so that it can
|
||||||
public void mark(int readlimit) {
|
* be interpreted as an unsigned integer.
|
||||||
baseStream.mark(readlimit);
|
* @return
|
||||||
}
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public long readUInt() throws IOException {
|
||||||
|
return readUInt(baseStream);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public int readInt() throws IOException {
|
||||||
public boolean markSupported() {
|
return readInt(baseStream);
|
||||||
return baseStream.markSupported();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public long readLong() throws IOException {
|
||||||
public int read() throws IOException {
|
byte[] buf = readBytes(8);
|
||||||
return baseStream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return readLong(buf, 0);
|
||||||
public int read(byte[] b, int offset, int length) throws IOException {
|
}
|
||||||
return baseStream.read(b, offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b) throws IOException {
|
public int available() throws IOException {
|
||||||
// TODO Auto-generated method stub
|
return baseStream.available();
|
||||||
return super.read(b);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void reset() throws IOException {
|
public void close() throws IOException {
|
||||||
baseStream.reset();
|
baseStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long n) throws IOException {
|
public void mark(int readlimit) {
|
||||||
return baseStream.skip(n);
|
baseStream.mark(readlimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readBytes(int length) throws IOException {
|
@Override
|
||||||
// TODO Exception max length < buffer size
|
public boolean markSupported() {
|
||||||
byte[] buf = new byte[length];
|
return baseStream.markSupported();
|
||||||
|
}
|
||||||
|
|
||||||
int count = 0;
|
@Override
|
||||||
while ( count < length ) {
|
public int read() throws IOException {
|
||||||
int read = read(buf, count, length - count);
|
return baseStream.read();
|
||||||
|
}
|
||||||
// Reached end
|
|
||||||
if ( read == -1 ) {
|
@Override
|
||||||
// Stop early
|
public int read(byte[] b, int offset, int length) throws IOException {
|
||||||
byte[] early = new byte[count];
|
return baseStream.read(b, offset, length);
|
||||||
System.arraycopy(buf, 0, early, 0, count);
|
}
|
||||||
return early;
|
|
||||||
}
|
@Override
|
||||||
|
public int read(byte[] b) throws IOException {
|
||||||
count += read;
|
// TODO Auto-generated method stub
|
||||||
}
|
return super.read(b);
|
||||||
|
}
|
||||||
return buf;
|
|
||||||
}
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
baseStream.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
return baseStream.skip(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readBytes(int length) throws IOException {
|
||||||
|
// TODO Exception max length < buffer size
|
||||||
|
byte[] buf = new byte[length];
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while ( count < length ) {
|
||||||
|
int read = read(buf, count, length - count);
|
||||||
|
|
||||||
|
// Reached end
|
||||||
|
if ( read == -1 ) {
|
||||||
|
// Stop early
|
||||||
|
byte[] early = new byte[count];
|
||||||
|
System.arraycopy(buf, 0, early, 0, count);
|
||||||
|
return early;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException {
|
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException {
|
||||||
int bufferSize = 256 * 3; // TODO Buffer size
|
int bufferSize = 256 * 3; // TODO Buffer size
|
||||||
@@ -152,62 +152,62 @@ public class LEDataInputStream extends InputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int readUShort(InputStream is) throws IOException {
|
public static int readUShort(InputStream is) throws IOException {
|
||||||
byte[] buf = new byte[2];
|
byte[] buf = new byte[2];
|
||||||
|
|
||||||
is.read(buf, 0, 2);
|
|
||||||
|
|
||||||
return readUShort(buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int readUShort() throws IOException {
|
|
||||||
return readUShort(baseStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
is.read(buf, 0, 2);
|
||||||
* Read an unsigned 16-bit value.
|
|
||||||
*
|
|
||||||
* @param buf
|
|
||||||
* @param offset
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static int readUShort( byte[] buf, int offset ) {
|
|
||||||
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long readLong( byte buf[], int offset ) {
|
return readUShort(buf, 0);
|
||||||
return ((long)buf[offset] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
|
}
|
||||||
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
|
|
||||||
+ (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40)
|
|
||||||
+ (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long readUInt( byte buf[], int offset ) {
|
public int readUShort() throws IOException {
|
||||||
return (readInt(buf, offset) & INT_TO_LONG_MASK);
|
return readUShort(baseStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int readInt(InputStream is) throws IOException {
|
/**
|
||||||
byte[] buf = new byte[4];
|
* Read an unsigned 16-bit value.
|
||||||
|
*
|
||||||
is.read(buf, 0, 4);
|
* @param buf
|
||||||
|
* @param offset
|
||||||
return readInt(buf, 0);
|
* @return
|
||||||
}
|
*/
|
||||||
|
public static int readUShort( byte[] buf, int offset ) {
|
||||||
|
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
public static long readUInt(InputStream is) throws IOException {
|
public static long readLong( byte buf[], int offset ) {
|
||||||
return (readInt(is) & INT_TO_LONG_MASK);
|
return ((long)buf[offset] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
|
||||||
}
|
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
|
||||||
|
+ (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40)
|
||||||
|
+ (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public static long readUInt( byte buf[], int offset ) {
|
||||||
* Read a 32-bit value.
|
return (readInt(buf, offset) & INT_TO_LONG_MASK);
|
||||||
*
|
}
|
||||||
* @param buf
|
|
||||||
* @param offset
|
public static int readInt(InputStream is) throws IOException {
|
||||||
* @return
|
byte[] buf = new byte[4];
|
||||||
*/
|
|
||||||
public static int readInt( byte buf[], int offset ) {
|
is.read(buf, 0, 4);
|
||||||
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16)
|
|
||||||
+ ((buf[offset + 3] & 0xFF) << 24);
|
return readInt(buf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long readUInt(InputStream is) throws IOException {
|
||||||
|
return (readInt(is) & INT_TO_LONG_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a 32-bit value.
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* @param offset
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int readInt( byte buf[], int offset ) {
|
||||||
|
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16)
|
||||||
|
+ ((buf[offset + 3] & 0xFF) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -29,116 +29,116 @@ import java.io.OutputStream;
|
|||||||
*/
|
*/
|
||||||
public class LEDataOutputStream extends OutputStream {
|
public class LEDataOutputStream extends OutputStream {
|
||||||
|
|
||||||
private OutputStream baseStream;
|
private OutputStream baseStream;
|
||||||
|
|
||||||
public LEDataOutputStream(OutputStream out) {
|
|
||||||
baseStream = out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUInt(long uint) throws IOException {
|
|
||||||
baseStream.write(LEDataOutputStream.writeIntBuf((int) uint));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public LEDataOutputStream(OutputStream out) {
|
||||||
public void close() throws IOException {
|
baseStream = out;
|
||||||
baseStream.close();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public void writeUInt(long uint) throws IOException {
|
||||||
public void flush() throws IOException {
|
baseStream.write(LEDataOutputStream.writeIntBuf((int) uint));
|
||||||
baseStream.flush();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer, int offset, int count) throws IOException {
|
public void close() throws IOException {
|
||||||
baseStream.write(buffer, offset, count);
|
baseStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer) throws IOException {
|
public void flush() throws IOException {
|
||||||
baseStream.write(buffer);
|
baseStream.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int oneByte) throws IOException {
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
baseStream.write(oneByte);
|
baseStream.write(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeLong(long val) throws IOException {
|
|
||||||
byte[] buf = new byte[8];
|
|
||||||
|
|
||||||
writeLong(val, buf, 0);
|
|
||||||
baseStream.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeInt(int val) throws IOException {
|
|
||||||
byte[] buf = new byte[4];
|
|
||||||
writeInt(val, buf, 0);
|
|
||||||
|
|
||||||
baseStream.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUShort(int val) throws IOException {
|
|
||||||
byte[] buf = new byte[2];
|
|
||||||
writeUShort(val, buf, 0);
|
|
||||||
baseStream.write(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] writeIntBuf(int val) {
|
@Override
|
||||||
byte[] buf = new byte[4];
|
public void write(byte[] buffer) throws IOException {
|
||||||
writeInt(val, buf, 0);
|
baseStream.write(buffer);
|
||||||
|
}
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] writeUShortBuf(int val) {
|
@Override
|
||||||
byte[] buf = new byte[2];
|
public void write(int oneByte) throws IOException {
|
||||||
|
baseStream.write(oneByte);
|
||||||
writeUShort(val, buf, 0);
|
}
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Write an unsigned 16-bit value
|
public void writeLong(long val) throws IOException {
|
||||||
*
|
byte[] buf = new byte[8];
|
||||||
* @param val
|
|
||||||
* @param buf
|
|
||||||
* @param offset
|
|
||||||
*/
|
|
||||||
public static void writeUShort(int val, byte[] buf, int offset) {
|
|
||||||
buf[offset + 0] = (byte)(val & 0x00FF);
|
|
||||||
buf[offset + 1] = (byte)((val & 0xFF00) >> 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
writeLong(val, buf, 0);
|
||||||
* Write a 32-bit value.
|
baseStream.write(buf);
|
||||||
*
|
}
|
||||||
* @param val
|
|
||||||
* @param buf
|
|
||||||
* @param offset
|
|
||||||
*/
|
|
||||||
public static void writeInt( int val, byte[] buf, int offset ) {
|
|
||||||
buf[offset + 0] = (byte)(val & 0xFF);
|
|
||||||
buf[offset + 1] = (byte)((val >>> 8) & 0xFF);
|
|
||||||
buf[offset + 2] = (byte)((val >>> 16) & 0xFF);
|
|
||||||
buf[offset + 3] = (byte)((val >>> 24) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] writeLongBuf(long val) {
|
|
||||||
byte[] buf = new byte[8];
|
|
||||||
writeLong(val, buf, 0);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeLong( long val, byte[] buf, int offset ) {
|
public void writeInt(int val) throws IOException {
|
||||||
buf[offset + 0] = (byte)(val & 0xFF);
|
byte[] buf = new byte[4];
|
||||||
buf[offset + 1] = (byte)((val >>> 8) & 0xFF);
|
writeInt(val, buf, 0);
|
||||||
buf[offset + 2] = (byte)((val >>> 16) & 0xFF);
|
|
||||||
buf[offset + 3] = (byte)((val >>> 24) & 0xFF);
|
baseStream.write(buf);
|
||||||
buf[offset + 4] = (byte)((val >>> 32) & 0xFF);
|
}
|
||||||
buf[offset + 5] = (byte)((val >>> 40) & 0xFF);
|
|
||||||
buf[offset + 6] = (byte)((val >>> 48) & 0xFF);
|
public void writeUShort(int val) throws IOException {
|
||||||
buf[offset + 7] = (byte)((val >>> 56) & 0xFF);
|
byte[] buf = new byte[2];
|
||||||
}
|
writeUShort(val, buf, 0);
|
||||||
|
baseStream.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] writeIntBuf(int val) {
|
||||||
|
byte[] buf = new byte[4];
|
||||||
|
writeInt(val, buf, 0);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] writeUShortBuf(int val) {
|
||||||
|
byte[] buf = new byte[2];
|
||||||
|
|
||||||
|
writeUShort(val, buf, 0);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write an unsigned 16-bit value
|
||||||
|
*
|
||||||
|
* @param val
|
||||||
|
* @param buf
|
||||||
|
* @param offset
|
||||||
|
*/
|
||||||
|
public static void writeUShort(int val, byte[] buf, int offset) {
|
||||||
|
buf[offset + 0] = (byte)(val & 0x00FF);
|
||||||
|
buf[offset + 1] = (byte)((val & 0xFF00) >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a 32-bit value.
|
||||||
|
*
|
||||||
|
* @param val
|
||||||
|
* @param buf
|
||||||
|
* @param offset
|
||||||
|
*/
|
||||||
|
public static void writeInt( int val, byte[] buf, int offset ) {
|
||||||
|
buf[offset + 0] = (byte)(val & 0xFF);
|
||||||
|
buf[offset + 1] = (byte)((val >>> 8) & 0xFF);
|
||||||
|
buf[offset + 2] = (byte)((val >>> 16) & 0xFF);
|
||||||
|
buf[offset + 3] = (byte)((val >>> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] writeLongBuf(long val) {
|
||||||
|
byte[] buf = new byte[8];
|
||||||
|
writeLong(val, buf, 0);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeLong( long val, byte[] buf, int offset ) {
|
||||||
|
buf[offset + 0] = (byte)(val & 0xFF);
|
||||||
|
buf[offset + 1] = (byte)((val >>> 8) & 0xFF);
|
||||||
|
buf[offset + 2] = (byte)((val >>> 16) & 0xFF);
|
||||||
|
buf[offset + 3] = (byte)((val >>> 24) & 0xFF);
|
||||||
|
buf[offset + 4] = (byte)((val >>> 32) & 0xFF);
|
||||||
|
buf[offset + 5] = (byte)((val >>> 40) & 0xFF);
|
||||||
|
buf[offset + 6] = (byte)((val >>> 48) & 0xFF);
|
||||||
|
buf[offset + 7] = (byte)((val >>> 56) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -24,28 +24,28 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
public class NullOutputStream extends OutputStream {
|
public class NullOutputStream extends OutputStream {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
super.flush();
|
super.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer, int offset, int count) throws IOException {
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
super.write(buffer, offset, count);
|
super.write(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer) throws IOException {
|
public void write(byte[] buffer) throws IOException {
|
||||||
super.write(buffer);
|
super.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int oneByte) throws IOException {
|
public void write(int oneByte) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -25,40 +25,40 @@ import java.io.RandomAccessFile;
|
|||||||
|
|
||||||
public class RandomFileOutputStream extends OutputStream {
|
public class RandomFileOutputStream extends OutputStream {
|
||||||
|
|
||||||
RandomAccessFile mFile;
|
RandomAccessFile mFile;
|
||||||
|
|
||||||
RandomFileOutputStream(RandomAccessFile file) {
|
|
||||||
mFile = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
super.close();
|
|
||||||
|
|
||||||
mFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
RandomFileOutputStream(RandomAccessFile file) {
|
||||||
public void write(byte[] buffer, int offset, int count) throws IOException {
|
mFile = file;
|
||||||
super.write(buffer, offset, count);
|
}
|
||||||
|
|
||||||
mFile.write(buffer, offset, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer) throws IOException {
|
public void close() throws IOException {
|
||||||
super.write(buffer);
|
super.close();
|
||||||
|
|
||||||
mFile.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
mFile.close();
|
||||||
public void write(int oneByte) throws IOException {
|
}
|
||||||
mFile.write(oneByte);
|
|
||||||
}
|
@Override
|
||||||
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
public void seek(long pos) throws IOException {
|
super.write(buffer, offset, count);
|
||||||
mFile.seek(pos);
|
|
||||||
}
|
mFile.write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer) throws IOException {
|
||||||
|
super.write(buffer);
|
||||||
|
|
||||||
|
mFile.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int oneByte) throws IOException {
|
||||||
|
mFile.write(oneByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seek(long pos) throws IOException {
|
||||||
|
mFile.seek(pos);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -24,19 +24,19 @@ import android.net.Uri;
|
|||||||
import com.kunzisoft.keepass.database.element.PwDate;
|
import com.kunzisoft.keepass.database.element.PwDate;
|
||||||
|
|
||||||
public class EmptyUtils {
|
public class EmptyUtils {
|
||||||
public static boolean isNullOrEmpty(String str) {
|
public static boolean isNullOrEmpty(String str) {
|
||||||
return (str == null) || (str.length() == 0);
|
return (str == null) || (str.length() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNullOrEmpty(byte[] buf) {
|
|
||||||
return (buf == null) || (buf.length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isNullOrEmpty(PwDate date) {
|
|
||||||
return (date == null) || date.equals(PwDate.DEFAULT_PWDATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isNullOrEmpty(Uri uri) {
|
public static boolean isNullOrEmpty(byte[] buf) {
|
||||||
return (uri==null) || (uri.toString().length() == 0);
|
return (buf == null) || (buf.length == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(PwDate date) {
|
||||||
|
return (date == null) || date.equals(PwDate.DEFAULT_PWDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(Uri uri) {
|
||||||
|
return (uri==null) || (uri.toString().length() == 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -27,24 +27,24 @@ import android.content.pm.ResolveInfo;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Interaction {
|
public class Interaction {
|
||||||
/**
|
/**
|
||||||
* Indicates whether the specified action can be used as an intent. This
|
* Indicates whether the specified action can be used as an intent. This
|
||||||
* method queries the package manager for installed packages that can
|
* method queries the package manager for installed packages that can
|
||||||
* respond to an intent with the specified action. If no suitable package is
|
* respond to an intent with the specified action. If no suitable package is
|
||||||
* found, this method returns false.
|
* found, this method returns false.
|
||||||
*
|
*
|
||||||
* @param context The application's environment.
|
* @param context The application's environment.
|
||||||
* @param action The Intent action to check for availability.
|
* @param action The Intent action to check for availability.
|
||||||
*
|
*
|
||||||
* @return True if an Intent with the specified action can be sent and
|
* @return True if an Intent with the specified action can be sent and
|
||||||
* responded to, false otherwise.
|
* responded to, false otherwise.
|
||||||
*/
|
*/
|
||||||
public static boolean isIntentAvailable(Context context, String action) {
|
public static boolean isIntentAvailable(Context context, String action) {
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
final PackageManager packageManager = context.getPackageManager();
|
||||||
final Intent intent = new Intent(action);
|
final Intent intent = new Intent(action);
|
||||||
List<ResolveInfo> list =
|
List<ResolveInfo> list =
|
||||||
packageManager.queryIntentActivities(intent,
|
packageManager.queryIntentActivities(intent,
|
||||||
PackageManager.MATCH_DEFAULT_ONLY);
|
PackageManager.MATCH_DEFAULT_ONLY);
|
||||||
return list.size() > 0;
|
return list.size() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -30,221 +30,221 @@ import java.util.*;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
public class SprEngineV4 {
|
public class SprEngineV4 {
|
||||||
private static final int MAX_RECURSION_DEPTH = 12;
|
private static final int MAX_RECURSION_DEPTH = 12;
|
||||||
private static final String STR_REF_START = "{REF:";
|
private static final String STR_REF_START = "{REF:";
|
||||||
private static final String STR_REF_END = "}";
|
private static final String STR_REF_END = "}";
|
||||||
|
|
||||||
public class TargetResult {
|
public class TargetResult {
|
||||||
public PwEntryV4 entry;
|
public PwEntryV4 entry;
|
||||||
public char wanted;
|
public char wanted;
|
||||||
|
|
||||||
public TargetResult(PwEntryV4 entry, char wanted) {
|
public TargetResult(PwEntryV4 entry, char wanted) {
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
this.wanted = wanted;
|
this.wanted = wanted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SprContextV4 {
|
private class SprContextV4 {
|
||||||
|
|
||||||
public PwDatabaseV4 db;
|
public PwDatabaseV4 db;
|
||||||
public PwEntryV4 entry;
|
public PwEntryV4 entry;
|
||||||
public Map<String, String> refsCache = new HashMap<>();
|
public Map<String, String> refsCache = new HashMap<>();
|
||||||
|
|
||||||
SprContextV4(PwDatabaseV4 db, PwEntryV4 entry) {
|
SprContextV4(PwDatabaseV4 db, PwEntryV4 entry) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
SprContextV4(SprContextV4 source) {
|
SprContextV4(SprContextV4 source) {
|
||||||
this.db = source.db;
|
this.db = source.db;
|
||||||
this.entry = source.entry;
|
this.entry = source.entry;
|
||||||
this.refsCache = source.refsCache;
|
this.refsCache = source.refsCache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String compile(String text, PwEntryV4 entry, PwDatabase database) {
|
public String compile(String text, PwEntryV4 entry, PwDatabase database) {
|
||||||
SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, entry);
|
SprContextV4 ctx = new SprContextV4((PwDatabaseV4)database, entry);
|
||||||
|
|
||||||
return compileInternal(text, ctx, 0);
|
return compileInternal(text, ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String compileInternal(String text, SprContextV4 sprContextV4, int recursionLevel) {
|
private String compileInternal(String text, SprContextV4 sprContextV4, int recursionLevel) {
|
||||||
if (text == null) { return ""; }
|
if (text == null) { return ""; }
|
||||||
if (sprContextV4 == null) { return ""; }
|
if (sprContextV4 == null) { return ""; }
|
||||||
if (recursionLevel >= MAX_RECURSION_DEPTH) { return ""; }
|
if (recursionLevel >= MAX_RECURSION_DEPTH) { return ""; }
|
||||||
|
|
||||||
return fillRefPlaceholders(text, sprContextV4, recursionLevel);
|
return fillRefPlaceholders(text, sprContextV4, recursionLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fillRefPlaceholders(String text, SprContextV4 contextV4, int recursionLevel) {
|
private String fillRefPlaceholders(String text, SprContextV4 contextV4, int recursionLevel) {
|
||||||
|
|
||||||
if (contextV4.db == null) { return text; }
|
if (contextV4.db == null) { return text; }
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (int i = 0; i < 20; ++i) {
|
for (int i = 0; i < 20; ++i) {
|
||||||
text = fillRefsUsingCache(text, contextV4);
|
text = fillRefsUsingCache(text, contextV4);
|
||||||
|
|
||||||
int start = StringUtil.INSTANCE.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH);
|
int start = StringUtil.INSTANCE.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH);
|
||||||
if (start < 0) { break; }
|
if (start < 0) { break; }
|
||||||
int end = StringUtil.INSTANCE.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; }
|
if (end <= start) { break; }
|
||||||
|
|
||||||
String fullRef = text.substring(start, end - start + 1);
|
String fullRef = text.substring(start, end - start + 1);
|
||||||
TargetResult result = findRefTarget(fullRef, contextV4);
|
TargetResult result = findRefTarget(fullRef, contextV4);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
PwEntryV4 found = result.entry;
|
PwEntryV4 found = result.entry;
|
||||||
char wanted = result.wanted;
|
char wanted = result.wanted;
|
||||||
|
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
String data;
|
String data;
|
||||||
switch (wanted) {
|
switch (wanted) {
|
||||||
case 'T':
|
case 'T':
|
||||||
data = found.getTitle();
|
data = found.getTitle();
|
||||||
break;
|
break;
|
||||||
case 'U':
|
case 'U':
|
||||||
data = found.getUsername();
|
data = found.getUsername();
|
||||||
break;
|
break;
|
||||||
case 'A':
|
case 'A':
|
||||||
data = found.getUrl();
|
data = found.getUrl();
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
data = found.getPassword();
|
data = found.getPassword();
|
||||||
break;
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
data = found.getNotes();
|
data = found.getNotes();
|
||||||
break;
|
break;
|
||||||
case 'I':
|
case 'I':
|
||||||
data = found.getNodeId().toString();
|
data = found.getNodeId().toString();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
offset = start + 1;
|
offset = start + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SprContextV4 subCtx = new SprContextV4(contextV4);
|
SprContextV4 subCtx = new SprContextV4(contextV4);
|
||||||
subCtx.entry = found;
|
subCtx.entry = found;
|
||||||
|
|
||||||
String innerContent = compileInternal(data, subCtx, recursionLevel + 1);
|
String innerContent = compileInternal(data, subCtx, recursionLevel + 1);
|
||||||
addRefsToCache(fullRef, innerContent, contextV4);
|
addRefsToCache(fullRef, innerContent, contextV4);
|
||||||
text = fillRefsUsingCache(text, contextV4);
|
text = fillRefsUsingCache(text, contextV4);
|
||||||
} else {
|
} else {
|
||||||
offset = start + 1;
|
offset = start + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TargetResult findRefTarget(String fullRef, SprContextV4 contextV4) {
|
|
||||||
if (fullRef == null) { return null; }
|
|
||||||
|
|
||||||
fullRef = fullRef.toUpperCase(Locale.ENGLISH);
|
|
||||||
if (!fullRef.startsWith(STR_REF_START) || !fullRef.endsWith(STR_REF_END)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ref = fullRef.substring(STR_REF_START.length(), fullRef.length() - STR_REF_START.length() - STR_REF_END.length());
|
|
||||||
if (ref.length() <= 4) { return null; }
|
|
||||||
if (ref.charAt(1) != '@') { return null; }
|
|
||||||
if (ref.charAt(3) != ':') { return null; }
|
|
||||||
|
|
||||||
char scan = Character.toUpperCase(ref.charAt(2));
|
|
||||||
char wanted = Character.toUpperCase(ref.charAt(0));
|
|
||||||
|
|
||||||
SearchParametersV4 searchParametersV4 = new SearchParametersV4();
|
|
||||||
searchParametersV4.setupNone();
|
|
||||||
|
|
||||||
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(contextV4.db.getRootGroup(), searchParametersV4, list);
|
|
||||||
|
|
||||||
if (list.size() > 0) {
|
|
||||||
return new TargetResult(list.get(0), wanted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRefsToCache(String ref, String value, SprContextV4 ctx) {
|
private TargetResult findRefTarget(String fullRef, SprContextV4 contextV4) {
|
||||||
if (ref == null) { return; }
|
if (fullRef == null) { return null; }
|
||||||
if (value == null) { return; }
|
|
||||||
if (ctx == null) { return; }
|
|
||||||
|
|
||||||
if (!ctx.refsCache.containsKey(ref)) {
|
fullRef = fullRef.toUpperCase(Locale.ENGLISH);
|
||||||
ctx.refsCache.put(ref, value);
|
if (!fullRef.startsWith(STR_REF_START) || !fullRef.endsWith(STR_REF_END)) {
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fillRefsUsingCache(String text, SprContextV4 sprContextV4) {
|
String ref = fullRef.substring(STR_REF_START.length(), fullRef.length() - STR_REF_START.length() - STR_REF_END.length());
|
||||||
for (Entry<String, String> entry : sprContextV4.refsCache.entrySet()) {
|
if (ref.length() <= 4) { return null; }
|
||||||
text = StringUtil.INSTANCE.replaceAllIgnoresCase(text, entry.getKey(), entry.getValue(), Locale.ENGLISH);
|
if (ref.charAt(1) != '@') { return null; }
|
||||||
}
|
if (ref.charAt(3) != ':') { return null; }
|
||||||
|
|
||||||
return text;
|
char scan = Character.toUpperCase(ref.charAt(2));
|
||||||
}
|
char wanted = Character.toUpperCase(ref.charAt(0));
|
||||||
|
|
||||||
private void searchEntries(PwGroupV4 root, SearchParametersV4 searchParametersV4, List<PwEntryV4> listStorage) {
|
SearchParametersV4 searchParametersV4 = new SearchParametersV4();
|
||||||
if (searchParametersV4 == null) { return; }
|
searchParametersV4.setupNone();
|
||||||
if (listStorage == null) { return; }
|
|
||||||
|
|
||||||
List<String> terms = StringUtil.INSTANCE.splitStringTerms(searchParametersV4.getSearchString());
|
searchParametersV4.setSearchString(ref.substring(4));
|
||||||
if (terms.size() <= 1 || searchParametersV4.getRegularExpression()) {
|
if (scan == 'T') { searchParametersV4.setSearchInTitles(true); }
|
||||||
root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, listStorage), null);
|
else if (scan == 'U') { searchParametersV4.setSearchInUserNames(true); }
|
||||||
return;
|
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; }
|
||||||
|
|
||||||
// Search longest term first
|
List<PwEntryV4> list = new ArrayList<>();
|
||||||
Comparator<String> stringLengthComparator = (lhs, rhs) -> lhs.length() - rhs.length();
|
// TODO type parameter
|
||||||
Collections.sort(terms, stringLengthComparator);
|
searchEntries(contextV4.db.getRootGroup(), searchParametersV4, list);
|
||||||
|
|
||||||
String fullSearch = searchParametersV4.getSearchString();
|
if (list.size() > 0) {
|
||||||
List<PwEntryV4> childEntries = root.getChildEntries();
|
return new TargetResult(list.get(0), wanted);
|
||||||
for (int i = 0; i < terms.size(); i ++) {
|
}
|
||||||
List<PwEntryV4> pgNew = new ArrayList<>();
|
|
||||||
|
|
||||||
searchParametersV4.setSearchString(terms.get(i));
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
boolean negate = false;
|
private void addRefsToCache(String ref, String value, SprContextV4 ctx) {
|
||||||
if (searchParametersV4.getSearchString().startsWith("-")) {
|
if (ref == null) { return; }
|
||||||
searchParametersV4.setSearchString(searchParametersV4.getSearchString().substring(1));
|
if (value == null) { return; }
|
||||||
negate = searchParametersV4.getSearchString().length() > 0;
|
if (ctx == null) { return; }
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, pgNew), null)) {
|
if (!ctx.refsCache.containsKey(ref)) {
|
||||||
childEntries = null;
|
ctx.refsCache.put(ref, value);
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PwEntryV4> complement = new ArrayList<>();
|
private String fillRefsUsingCache(String text, SprContextV4 sprContextV4) {
|
||||||
if (negate) {
|
for (Entry<String, String> entry : sprContextV4.refsCache.entrySet()) {
|
||||||
for (PwEntryV4 entry: childEntries) {
|
text = StringUtil.INSTANCE.replaceAllIgnoresCase(text, entry.getKey(), entry.getValue(), Locale.ENGLISH);
|
||||||
if (!pgNew.contains(entry)) {
|
}
|
||||||
complement.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
childEntries = complement;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
childEntries = pgNew;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childEntries != null) {
|
return text;
|
||||||
listStorage.addAll(childEntries);
|
}
|
||||||
}
|
|
||||||
searchParametersV4.setSearchString(fullSearch);
|
private void searchEntries(PwGroupV4 root, SearchParametersV4 searchParametersV4, List<PwEntryV4> listStorage) {
|
||||||
}
|
if (searchParametersV4 == null) { return; }
|
||||||
|
if (listStorage == null) { return; }
|
||||||
|
|
||||||
|
List<String> terms = StringUtil.INSTANCE.splitStringTerms(searchParametersV4.getSearchString());
|
||||||
|
if (terms.size() <= 1 || searchParametersV4.getRegularExpression()) {
|
||||||
|
root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, listStorage), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search longest term first
|
||||||
|
Comparator<String> stringLengthComparator = (lhs, rhs) -> lhs.length() - rhs.length();
|
||||||
|
Collections.sort(terms, stringLengthComparator);
|
||||||
|
|
||||||
|
String fullSearch = searchParametersV4.getSearchString();
|
||||||
|
List<PwEntryV4> childEntries = root.getChildEntries();
|
||||||
|
for (int i = 0; i < terms.size(); i ++) {
|
||||||
|
List<PwEntryV4> pgNew = new ArrayList<>();
|
||||||
|
|
||||||
|
searchParametersV4.setSearchString(terms.get(i));
|
||||||
|
|
||||||
|
boolean negate = false;
|
||||||
|
if (searchParametersV4.getSearchString().startsWith("-")) {
|
||||||
|
searchParametersV4.setSearchString(searchParametersV4.getSearchString().substring(1));
|
||||||
|
negate = searchParametersV4.getSearchString().length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.doForEachChild(new EntrySearchHandlerV4(searchParametersV4, pgNew), null)) {
|
||||||
|
childEntries = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PwEntryV4> complement = new ArrayList<>();
|
||||||
|
if (negate) {
|
||||||
|
for (PwEntryV4 entry: childEntries) {
|
||||||
|
if (!pgNew.contains(entry)) {
|
||||||
|
complement.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childEntries = complement;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
childEntries = pgNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childEntries != null) {
|
||||||
|
listStorage.addAll(childEntries);
|
||||||
|
}
|
||||||
|
searchParametersV4.setSearchString(fullSearch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,131 +52,131 @@ import java.util.UUID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tools for slicing and dicing Java and KeePass data types.
|
* Tools for slicing and dicing Java and KeePass data types.
|
||||||
*
|
*
|
||||||
* @author Bill Zwicky <wrzwicky@pobox.com>
|
* @author Bill Zwicky <wrzwicky@pobox.com>
|
||||||
*/
|
*/
|
||||||
public class Types {
|
public class Types {
|
||||||
|
|
||||||
public static long ULONG_MAX_VALUE = -1;
|
public static long ULONG_MAX_VALUE = -1;
|
||||||
|
|
||||||
/** Read an unsigned byte */
|
/** Read an unsigned byte */
|
||||||
public static int readUByte( byte[] buf, int offset ) {
|
public static int readUByte( byte[] buf, int offset ) {
|
||||||
return ((int)buf[offset] & 0xFF);
|
return ((int)buf[offset] & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Write an unsigned byte
|
/** Write an unsigned byte
|
||||||
*
|
*
|
||||||
* @param val
|
* @param val
|
||||||
* @param buf
|
* @param buf
|
||||||
* @param offset
|
* @param offset
|
||||||
*/
|
*/
|
||||||
public static void writeUByte(int val, byte[] buf, int offset) {
|
public static void writeUByte(int val, byte[] buf, int offset) {
|
||||||
buf[offset] = (byte)(val & 0xFF);
|
buf[offset] = (byte)(val & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte writeUByte(int val) {
|
|
||||||
byte[] buf = new byte[1];
|
|
||||||
|
|
||||||
writeUByte(val, buf, 0);
|
|
||||||
|
|
||||||
return buf[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static byte writeUByte(int val) {
|
||||||
* Return len of null-terminated string (i.e. distance to null)
|
byte[] buf = new byte[1];
|
||||||
* within a byte buffer.
|
|
||||||
*
|
writeUByte(val, buf, 0);
|
||||||
* @param buf
|
|
||||||
* @param offset
|
return buf[0];
|
||||||
* @return
|
}
|
||||||
*/
|
|
||||||
public static int strlen( byte[] buf, int offset ) {
|
/**
|
||||||
int len = 0;
|
* Return len of null-terminated string (i.e. distance to null)
|
||||||
while( buf[offset + len] != 0 )
|
* within a byte buffer.
|
||||||
len++;
|
*
|
||||||
return len;
|
* @param buf
|
||||||
}
|
* @param offset
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int strlen( byte[] buf, int offset ) {
|
||||||
|
int len = 0;
|
||||||
|
while( buf[offset + len] != 0 )
|
||||||
|
len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a sequence of bytes into a new array.
|
* Copy a sequence of bytes into a new array.
|
||||||
*
|
*
|
||||||
* @param b - source array
|
* @param b - source array
|
||||||
* @param offset - first byte
|
* @param offset - first byte
|
||||||
* @param len - number of bytes
|
* @param len - number of bytes
|
||||||
* @return new byte[len]
|
* @return new byte[len]
|
||||||
*/
|
*/
|
||||||
public static byte[] extract( byte[] b, int offset, int len ) {
|
public static byte[] extract( byte[] b, int offset, int len ) {
|
||||||
byte[] b2 = new byte[len];
|
byte[] b2 = new byte[len];
|
||||||
System.arraycopy( b, offset, b2, 0, len );
|
System.arraycopy( b, offset, b2, 0, len );
|
||||||
return b2;
|
return b2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final byte[] CRLFbuf = { 0x0D, 0x0A };
|
|
||||||
private static final String CRLF = new String(CRLFbuf);
|
|
||||||
private static final String SEP = System.getProperty("line.separator");
|
|
||||||
private static final boolean REPLACE = ! SEP.equals(CRLF);
|
|
||||||
|
|
||||||
public static String readCString(byte[] buf, int offset) throws UnsupportedEncodingException {
|
|
||||||
String jstring = new String(buf, offset, strlen(buf, offset), "UTF-8");
|
|
||||||
|
|
||||||
if ( REPLACE ) {
|
|
||||||
jstring = jstring.replace(CRLF, SEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jstring;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int writeCString(String str, OutputStream os) throws IOException {
|
|
||||||
if ( str == null ) {
|
|
||||||
// Write out a null character
|
|
||||||
os.write(LEDataOutputStream.writeIntBuf(1));
|
|
||||||
os.write(0x00);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( REPLACE ) {
|
|
||||||
str = str.replace(SEP, CRLF);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] initial = str.getBytes("UTF-8");
|
|
||||||
|
|
||||||
int length = initial.length+1;
|
|
||||||
os.write(LEDataOutputStream.writeIntBuf(length));
|
|
||||||
os.write(initial);
|
|
||||||
os.write(0x00);
|
|
||||||
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID bytestoUUID(byte[] buf) {
|
|
||||||
return bytestoUUID(buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID bytestoUUID(byte[] buf, int offset) {
|
|
||||||
long lsb = 0;
|
|
||||||
for (int i = 15; i >= 8; i--) {
|
|
||||||
lsb = (lsb << 8) | (buf[i + offset] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
long msb = 0;
|
|
||||||
for (int i = 7; i >= 0; i--) {
|
|
||||||
msb = (msb << 8) | (buf[i + offset] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UUID(msb, lsb);
|
private static final byte[] CRLFbuf = { 0x0D, 0x0A };
|
||||||
|
private static final String CRLF = new String(CRLFbuf);
|
||||||
|
private static final String SEP = System.getProperty("line.separator");
|
||||||
|
private static final boolean REPLACE = ! SEP.equals(CRLF);
|
||||||
|
|
||||||
|
public static String readCString(byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||||
|
String jstring = new String(buf, offset, strlen(buf, offset), "UTF-8");
|
||||||
|
|
||||||
|
if ( REPLACE ) {
|
||||||
|
jstring = jstring.replace(CRLF, SEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int writeCString(String str, OutputStream os) throws IOException {
|
||||||
|
if ( str == null ) {
|
||||||
|
// Write out a null character
|
||||||
|
os.write(LEDataOutputStream.writeIntBuf(1));
|
||||||
|
os.write(0x00);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( REPLACE ) {
|
||||||
|
str = str.replace(SEP, CRLF);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] initial = str.getBytes("UTF-8");
|
||||||
|
|
||||||
|
int length = initial.length+1;
|
||||||
|
os.write(LEDataOutputStream.writeIntBuf(length));
|
||||||
|
os.write(initial);
|
||||||
|
os.write(0x00);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UUID bytestoUUID(byte[] buf) {
|
||||||
|
return bytestoUUID(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UUID bytestoUUID(byte[] buf, int offset) {
|
||||||
|
long lsb = 0;
|
||||||
|
for (int i = 15; i >= 8; i--) {
|
||||||
|
lsb = (lsb << 8) | (buf[i + offset] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
long msb = 0;
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
msb = (msb << 8) | (buf[i + offset] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UUID(msb, lsb);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] UUIDtoBytes(UUID uuid) {
|
||||||
|
byte[] buf = new byte[16];
|
||||||
|
|
||||||
|
LEDataOutputStream.writeLong(uuid.getMostSignificantBits(), buf, 0);
|
||||||
|
LEDataOutputStream.writeLong(uuid.getLeastSignificantBits(), buf, 8);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] UUIDtoBytes(UUID uuid) {
|
|
||||||
byte[] buf = new byte[16];
|
|
||||||
|
|
||||||
LEDataOutputStream.writeLong(uuid.getMostSignificantBits(), buf, 0);
|
|
||||||
LEDataOutputStream.writeLong(uuid.getLeastSignificantBits(), buf, 8);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -34,18 +34,18 @@ import com.kunzisoft.keepass.R;
|
|||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
|
|
||||||
public static void gotoUrl(Context context, String url) throws ActivityNotFoundException {
|
public static void gotoUrl(Context context, String url) throws ActivityNotFoundException {
|
||||||
if ( url != null && url.length() > 0 ) {
|
if ( url != null && url.length() > 0 ) {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void gotoUrl(Context context, int resId) throws ActivityNotFoundException {
|
|
||||||
gotoUrl(context, context.getString(resId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static void gotoUrl(Context context, int resId) throws ActivityNotFoundException {
|
||||||
|
gotoUrl(context, context.getString(resId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Replace font by monospace, must be called after seText()
|
* Replace font by monospace, must be called after seText()
|
||||||
*/
|
*/
|
||||||
public static void applyFontVisibilityTo(final Context context, final TextView textView) {
|
public static void applyFontVisibilityTo(final Context context, final TextView textView) {
|
||||||
@@ -56,28 +56,28 @@ public class Util {
|
|||||||
/**
|
/**
|
||||||
* Replace font by monospace, must be called after seText()
|
* Replace font by monospace, must be called after seText()
|
||||||
*/
|
*/
|
||||||
public static void applyFontVisibilityTo(final Context context, final EditText editText) {
|
public static void applyFontVisibilityTo(final Context context, final EditText editText) {
|
||||||
applyFontVisibilityTo(context, (TextView) editText);
|
applyFontVisibilityTo(context, (TextView) editText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float getListTextDefaultSize(Context context) {
|
public static float getListTextDefaultSize(Context context) {
|
||||||
return Float.parseFloat(context.getString(R.string.list_size_default));
|
return Float.parseFloat(context.getString(R.string.list_size_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void lockScreenOrientation(Activity activity) {
|
public static void lockScreenOrientation(Activity activity) {
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
int currentOrientation = activity.getResources().getConfiguration().orientation;
|
int currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||||
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||||
} else {
|
} else {
|
||||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unlockScreenOrientation(Activity activity) {
|
public static void unlockScreenOrientation(Activity activity) {
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
@@ -22,44 +22,44 @@ package com.kunzisoft.keepass.utils;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class UuidUtil {
|
public class UuidUtil {
|
||||||
public static String toHexString(UUID uuid) {
|
public static String toHexString(UUID uuid) {
|
||||||
if (uuid == null) { return null; }
|
if (uuid == null) { return null; }
|
||||||
|
|
||||||
byte[] buf = Types.UUIDtoBytes(uuid);
|
|
||||||
if (buf == null) { return null; }
|
|
||||||
|
|
||||||
int len = buf.length;
|
|
||||||
if (len == 0) { return ""; }
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
short bt;
|
|
||||||
char high, low;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
bt = (short)(buf[i] & 0xFF);
|
|
||||||
high = (char)(bt >>> 4);
|
|
||||||
|
|
||||||
|
|
||||||
low = (char)(bt & 0x0F);
|
|
||||||
|
|
||||||
char h,l;
|
|
||||||
h = byteToChar(high);
|
|
||||||
l = byteToChar(low);
|
|
||||||
|
|
||||||
sb.append(byteToChar(high));
|
byte[] buf = Types.UUIDtoBytes(uuid);
|
||||||
sb.append(byteToChar(low));
|
if (buf == null) { return null; }
|
||||||
}
|
|
||||||
|
int len = buf.length;
|
||||||
return sb.toString();
|
if (len == 0) { return ""; }
|
||||||
}
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
// Use short to represent unsigned byte
|
|
||||||
private static char byteToChar(char bt) {
|
short bt;
|
||||||
|
char high, low;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
bt = (short)(buf[i] & 0xFF);
|
||||||
|
high = (char)(bt >>> 4);
|
||||||
|
|
||||||
|
|
||||||
|
low = (char)(bt & 0x0F);
|
||||||
|
|
||||||
|
char h,l;
|
||||||
|
h = byteToChar(high);
|
||||||
|
l = byteToChar(low);
|
||||||
|
|
||||||
|
sb.append(byteToChar(high));
|
||||||
|
sb.append(byteToChar(low));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use short to represent unsigned byte
|
||||||
|
private static char byteToChar(char bt) {
|
||||||
if (bt >= 10) {
|
if (bt >= 10) {
|
||||||
return (char)('A' + bt - 10);
|
return (char)('A' + bt - 10);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return (char)('0' + bt);
|
return (char)('0' + bt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user