172 lines
5.6 KiB
Java
172 lines
5.6 KiB
Java
package org.julianfamily.keepassgo;
|
|
|
|
import android.content.Context;
|
|
import android.util.JsonReader;
|
|
import android.util.Log;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
final class AutofillCacheStore {
|
|
private static final String TAG = "KeePassGOAutofill";
|
|
|
|
private AutofillCacheStore() {
|
|
}
|
|
|
|
static Entry findBestMatch(Context context, String webDomain) {
|
|
File cacheFile = findCacheFile(context);
|
|
if (cacheFile == null) {
|
|
Log.i(TAG, "autofill cache file not found");
|
|
return null;
|
|
}
|
|
List<Entry> entries;
|
|
try {
|
|
entries = readEntries(cacheFile);
|
|
} catch (IOException err) {
|
|
Log.e(TAG, "failed to read autofill cache", err);
|
|
return null;
|
|
}
|
|
if (entries.isEmpty()) {
|
|
return null;
|
|
}
|
|
String normalizedDomain = normalizeHost(webDomain);
|
|
if (normalizedDomain.isEmpty()) {
|
|
return entries.get(0);
|
|
}
|
|
Entry fallback = null;
|
|
for (Entry entry : entries) {
|
|
if (entry.host.equals(normalizedDomain)) {
|
|
return entry;
|
|
}
|
|
if (fallback == null && normalizedDomain.endsWith("." + entry.host)) {
|
|
fallback = entry;
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
private static File findCacheFile(Context context) {
|
|
List<File> candidates = new ArrayList<>();
|
|
File filesDir = context.getFilesDir();
|
|
if (filesDir != null) {
|
|
candidates.add(new File(filesDir, "keepassgo/autofill-cache.json"));
|
|
candidates.add(new File(filesDir, ".config/keepassgo/autofill-cache.json"));
|
|
candidates.add(new File(filesDir, "config/keepassgo/autofill-cache.json"));
|
|
}
|
|
File baseDir = context.getDataDir();
|
|
if (baseDir != null) {
|
|
candidates.add(new File(baseDir, "files/keepassgo/autofill-cache.json"));
|
|
candidates.add(new File(baseDir, "files/.config/keepassgo/autofill-cache.json"));
|
|
candidates.add(new File(baseDir, "files/config/keepassgo/autofill-cache.json"));
|
|
}
|
|
for (File candidate : candidates) {
|
|
if (candidate.isFile()) {
|
|
Log.i(TAG, "using autofill cache " + candidate.getAbsolutePath());
|
|
return candidate;
|
|
}
|
|
}
|
|
Log.i(TAG, "no autofill cache file in " + candidates);
|
|
return null;
|
|
}
|
|
|
|
private static List<Entry> readEntries(File cacheFile) throws IOException {
|
|
List<Entry> entries = new ArrayList<>();
|
|
try (JsonReader reader = new JsonReader(new InputStreamReader(new FileInputStream(cacheFile), StandardCharsets.UTF_8))) {
|
|
reader.beginObject();
|
|
while (reader.hasNext()) {
|
|
String name = reader.nextName();
|
|
if ("entries".equals(name)) {
|
|
reader.beginArray();
|
|
while (reader.hasNext()) {
|
|
entries.add(readEntry(reader));
|
|
}
|
|
reader.endArray();
|
|
} else {
|
|
reader.skipValue();
|
|
}
|
|
}
|
|
reader.endObject();
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
private static Entry readEntry(JsonReader reader) throws IOException {
|
|
String title = "";
|
|
String username = "";
|
|
String password = "";
|
|
String host = "";
|
|
reader.beginObject();
|
|
while (reader.hasNext()) {
|
|
String name = reader.nextName();
|
|
switch (name) {
|
|
case "title":
|
|
title = nextString(reader);
|
|
break;
|
|
case "username":
|
|
username = nextString(reader);
|
|
break;
|
|
case "password":
|
|
password = nextString(reader);
|
|
break;
|
|
case "host":
|
|
host = normalizeHost(nextString(reader));
|
|
break;
|
|
default:
|
|
reader.skipValue();
|
|
break;
|
|
}
|
|
}
|
|
reader.endObject();
|
|
return new Entry(title, username, password, host);
|
|
}
|
|
|
|
private static String nextString(JsonReader reader) throws IOException {
|
|
if (reader.peek() == android.util.JsonToken.NULL) {
|
|
reader.nextNull();
|
|
return "";
|
|
}
|
|
return reader.nextString();
|
|
}
|
|
|
|
private static String normalizeHost(String raw) {
|
|
if (raw == null) {
|
|
return "";
|
|
}
|
|
String value = raw.trim().toLowerCase(Locale.US);
|
|
if (value.startsWith("http://")) {
|
|
value = value.substring("http://".length());
|
|
} else if (value.startsWith("https://")) {
|
|
value = value.substring("https://".length());
|
|
}
|
|
int slash = value.indexOf('/');
|
|
if (slash >= 0) {
|
|
value = value.substring(0, slash);
|
|
}
|
|
int colon = value.indexOf(':');
|
|
if (colon >= 0) {
|
|
value = value.substring(0, colon);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static final class Entry {
|
|
final String title;
|
|
final String username;
|
|
final String password;
|
|
final String host;
|
|
|
|
Entry(String title, String username, String password, String host) {
|
|
this.title = title;
|
|
this.username = username;
|
|
this.password = password;
|
|
this.host = host;
|
|
}
|
|
}
|
|
}
|