Extract parsing code from SettingsImporter
This commit is contained in:
parent
5a4e576d79
commit
ca9fa1527d
4 changed files with 524 additions and 497 deletions
|
@ -0,0 +1,414 @@
|
|||
package com.fsck.k9.preferences;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
class SettingsFileParser {
|
||||
Imported parseSettings(InputStream inputStream, boolean globalSettings, List<String> accountUuids,
|
||||
boolean overview) throws SettingsImportExportException {
|
||||
|
||||
if (!overview && accountUuids == null) {
|
||||
throw new IllegalArgumentException("Argument 'accountUuids' must not be null.");
|
||||
}
|
||||
|
||||
try {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
XmlPullParser xpp = factory.newPullParser();
|
||||
|
||||
InputStreamReader reader = new InputStreamReader(inputStream);
|
||||
xpp.setInput(reader);
|
||||
|
||||
Imported imported = null;
|
||||
int eventType = xpp.getEventType();
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (SettingsExporter.ROOT_ELEMENT.equals(xpp.getName())) {
|
||||
imported = parseRoot(xpp, globalSettings, accountUuids, overview);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
if (imported == null || (overview && imported.globalSettings == null && imported.accounts == null)) {
|
||||
throw new SettingsImportExportException("Invalid import data");
|
||||
}
|
||||
|
||||
return imported;
|
||||
} catch (Exception e) {
|
||||
throw new SettingsImportExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Imported parseRoot(XmlPullParser xpp, boolean globalSettings, List<String> accountUuids,
|
||||
boolean overview) throws XmlPullParserException, IOException, SettingsImportExportException {
|
||||
|
||||
Imported result = new Imported();
|
||||
|
||||
String fileFormatVersionString = xpp.getAttributeValue(null, SettingsExporter.FILE_FORMAT_ATTRIBUTE);
|
||||
validateFileFormatVersion(fileFormatVersionString);
|
||||
|
||||
String contentVersionString = xpp.getAttributeValue(null, SettingsExporter.VERSION_ATTRIBUTE);
|
||||
result.contentVersion = validateContentVersion(contentVersionString);
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ROOT_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.GLOBAL_ELEMENT.equals(element)) {
|
||||
if (overview || globalSettings) {
|
||||
if (result.globalSettings == null) {
|
||||
if (overview) {
|
||||
result.globalSettings = new ImportedSettings();
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
} else {
|
||||
result.globalSettings = parseSettings(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
Timber.w("More than one global settings element. Only using the first one!");
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
Timber.i("Skipping global settings");
|
||||
}
|
||||
} else if (SettingsExporter.ACCOUNTS_ELEMENT.equals(element)) {
|
||||
if (result.accounts == null) {
|
||||
result.accounts = parseAccounts(xpp, accountUuids, overview);
|
||||
} else {
|
||||
Timber.w("More than one accounts element. Only using the first one!");
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int validateFileFormatVersion(String versionString) throws SettingsImportExportException {
|
||||
if (versionString == null) {
|
||||
throw new SettingsImportExportException("Missing file format version");
|
||||
}
|
||||
|
||||
int version;
|
||||
try {
|
||||
version = Integer.parseInt(versionString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SettingsImportExportException("Invalid file format version: " + versionString);
|
||||
}
|
||||
|
||||
if (version != SettingsExporter.FILE_FORMAT_VERSION) {
|
||||
throw new SettingsImportExportException("Unsupported file format version: " + versionString);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private int validateContentVersion(String versionString) throws SettingsImportExportException {
|
||||
if (versionString == null) {
|
||||
throw new SettingsImportExportException("Missing content version");
|
||||
}
|
||||
|
||||
int version;
|
||||
try {
|
||||
version = Integer.parseInt(versionString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SettingsImportExportException("Invalid content version: " + versionString);
|
||||
}
|
||||
|
||||
if (version < 1) {
|
||||
throw new SettingsImportExportException("Unsupported content version: " + versionString);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private ImportedSettings parseSettings(XmlPullParser xpp, String endTag)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
ImportedSettings result = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.VALUE_ELEMENT.equals(element)) {
|
||||
String key = xpp.getAttributeValue(null, SettingsExporter.KEY_ATTRIBUTE);
|
||||
String value = getText(xpp);
|
||||
|
||||
if (result == null) {
|
||||
result = new ImportedSettings();
|
||||
}
|
||||
|
||||
if (result.settings.containsKey(key)) {
|
||||
Timber.w("Already read key \"%s\". Ignoring value \"%s\"", key, value);
|
||||
} else {
|
||||
result.settings.put(key, value);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, ImportedAccount> parseAccounts(XmlPullParser xpp, List<String> accountUuids,
|
||||
boolean overview) throws XmlPullParserException, IOException {
|
||||
|
||||
Map<String, ImportedAccount> accounts = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.ACCOUNT_ELEMENT.equals(element)) {
|
||||
if (accounts == null) {
|
||||
accounts = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
ImportedAccount account = parseAccount(xpp, accountUuids, overview);
|
||||
|
||||
if (account == null) {
|
||||
// Do nothing - parseAccount() already logged a message
|
||||
} else if (!accounts.containsKey(account.uuid)) {
|
||||
accounts.put(account.uuid, account);
|
||||
} else {
|
||||
Timber.w("Duplicate account entries with UUID %s. Ignoring!", account.uuid);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private ImportedAccount parseAccount(XmlPullParser xpp, List<String> accountUuids, boolean overview)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
String uuid = xpp.getAttributeValue(null, SettingsExporter.UUID_ATTRIBUTE);
|
||||
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
} catch (Exception e) {
|
||||
skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT);
|
||||
Timber.w("Skipping account with invalid UUID %s", uuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
ImportedAccount account = new ImportedAccount();
|
||||
account.uuid = uuid;
|
||||
|
||||
if (overview || accountUuids.contains(uuid)) {
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
||||
account.name = getText(xpp);
|
||||
} else if (SettingsExporter.INCOMING_SERVER_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
||||
} else {
|
||||
account.incoming = parseServerSettings(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.OUTGOING_SERVER_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
||||
} else {
|
||||
account.outgoing = parseServerSettings(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
} else {
|
||||
account.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.IDENTITIES_ELEMENT.equals(element)) {
|
||||
account.identities = parseIdentities(xpp);
|
||||
} else if (SettingsExporter.FOLDERS_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.FOLDERS_ELEMENT);
|
||||
} else {
|
||||
account.folders = parseFolders(xpp);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT);
|
||||
Timber.i("Skipping account with UUID %s", uuid);
|
||||
}
|
||||
|
||||
// If we couldn't find an account name use the UUID
|
||||
if (account.name == null) {
|
||||
account.name = uuid;
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private ImportedServer parseServerSettings(XmlPullParser xpp, String endTag)
|
||||
throws XmlPullParserException, IOException {
|
||||
ImportedServer server = new ImportedServer();
|
||||
|
||||
server.type = xpp.getAttributeValue(null, SettingsExporter.TYPE_ATTRIBUTE);
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.HOST_ELEMENT.equals(element)) {
|
||||
server.host = getText(xpp);
|
||||
} else if (SettingsExporter.PORT_ELEMENT.equals(element)) {
|
||||
server.port = getText(xpp);
|
||||
} else if (SettingsExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) {
|
||||
server.connectionSecurity = getText(xpp);
|
||||
} else if (SettingsExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) {
|
||||
String text = getText(xpp);
|
||||
server.authenticationType = AuthType.valueOf(text);
|
||||
} else if (SettingsExporter.USERNAME_ELEMENT.equals(element)) {
|
||||
server.username = getText(xpp);
|
||||
} else if (SettingsExporter.CLIENT_CERTIFICATE_ALIAS_ELEMENT.equals(element)) {
|
||||
server.clientCertificateAlias = getText(xpp);
|
||||
} else if (SettingsExporter.PASSWORD_ELEMENT.equals(element)) {
|
||||
server.password = getText(xpp);
|
||||
} else if (SettingsExporter.EXTRA_ELEMENT.equals(element)) {
|
||||
server.extras = parseSettings(xpp, SettingsExporter.EXTRA_ELEMENT);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
private List<ImportedIdentity> parseIdentities(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
List<ImportedIdentity> identities = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.IDENTITY_ELEMENT.equals(element)) {
|
||||
if (identities == null) {
|
||||
identities = new ArrayList<>();
|
||||
}
|
||||
|
||||
ImportedIdentity identity = parseIdentity(xpp);
|
||||
identities.add(identity);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return identities;
|
||||
}
|
||||
|
||||
private ImportedIdentity parseIdentity(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
ImportedIdentity identity = new ImportedIdentity();
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) {
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
||||
identity.name = getText(xpp);
|
||||
} else if (SettingsExporter.EMAIL_ELEMENT.equals(element)) {
|
||||
identity.email = getText(xpp);
|
||||
} else if (SettingsExporter.DESCRIPTION_ELEMENT.equals(element)) {
|
||||
identity.description = getText(xpp);
|
||||
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
||||
identity.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
private List<ImportedFolder> parseFolders(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
List<ImportedFolder> folders = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.FOLDER_ELEMENT.equals(element)) {
|
||||
if (folders == null) {
|
||||
folders = new ArrayList<>();
|
||||
}
|
||||
|
||||
ImportedFolder folder = parseFolder(xpp);
|
||||
folders.add(folder);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
private ImportedFolder parseFolder(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
ImportedFolder folder = new ImportedFolder();
|
||||
|
||||
folder.name = xpp.getAttributeValue(null, SettingsExporter.NAME_ATTRIBUTE);
|
||||
|
||||
folder.settings = parseSettings(xpp, SettingsExporter.FOLDER_ELEMENT);
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private String getText(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
int eventType = xpp.next();
|
||||
if (eventType != XmlPullParser.TEXT) {
|
||||
return "";
|
||||
}
|
||||
return xpp.getText();
|
||||
}
|
||||
|
||||
private void skipToEndTag(XmlPullParser xpp, String endTag) throws XmlPullParserException, IOException {
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
eventType = xpp.next();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
package com.fsck.k9.preferences;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
@ -15,7 +12,6 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.AccountPreferenceSerializer;
|
||||
import com.fsck.k9.Core;
|
||||
|
@ -30,9 +26,6 @@ import com.fsck.k9.mail.ServerSettings;
|
|||
import com.fsck.k9.mailstore.SpecialLocalFoldersCreator;
|
||||
import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
|
||||
import kotlinx.datetime.Clock;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
|
@ -59,7 +52,8 @@ public class SettingsImporter {
|
|||
|
||||
try {
|
||||
// Parse the import stream but don't save individual settings (overview=true)
|
||||
Imported imported = parseSettings(inputStream, false, null, true);
|
||||
SettingsFileParser settingsFileParser = new SettingsFileParser();
|
||||
Imported imported = settingsFileParser.parseSettings(inputStream, false, null, true);
|
||||
|
||||
// If the stream contains global settings the "globalSettings" member will not be null
|
||||
boolean globalSettings = (imported.globalSettings != null);
|
||||
|
@ -116,7 +110,8 @@ public class SettingsImporter {
|
|||
List<AccountDescriptionPair> importedAccounts = new ArrayList<>();
|
||||
List<AccountDescription> erroneousAccounts = new ArrayList<>();
|
||||
|
||||
Imported imported = parseSettings(inputStream, globalSettings, accountUuids, false);
|
||||
SettingsFileParser settingsFileParser = new SettingsFileParser();
|
||||
Imported imported = settingsFileParser.parseSettings(inputStream, globalSettings, accountUuids, false);
|
||||
|
||||
Preferences preferences = Preferences.getPreferences();
|
||||
Storage storage = preferences.getStorage();
|
||||
|
@ -568,402 +563,6 @@ public class SettingsImporter {
|
|||
editor.putString(key, value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static Imported parseSettings(InputStream inputStream, boolean globalSettings, List<String> accountUuids,
|
||||
boolean overview) throws SettingsImportExportException {
|
||||
|
||||
if (!overview && accountUuids == null) {
|
||||
throw new IllegalArgumentException("Argument 'accountUuids' must not be null.");
|
||||
}
|
||||
|
||||
try {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
//factory.setNamespaceAware(true);
|
||||
XmlPullParser xpp = factory.newPullParser();
|
||||
|
||||
InputStreamReader reader = new InputStreamReader(inputStream);
|
||||
xpp.setInput(reader);
|
||||
|
||||
Imported imported = null;
|
||||
int eventType = xpp.getEventType();
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (SettingsExporter.ROOT_ELEMENT.equals(xpp.getName())) {
|
||||
imported = parseRoot(xpp, globalSettings, accountUuids, overview);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
if (imported == null || (overview && imported.globalSettings == null && imported.accounts == null)) {
|
||||
throw new SettingsImportExportException("Invalid import data");
|
||||
}
|
||||
|
||||
return imported;
|
||||
} catch (Exception e) {
|
||||
throw new SettingsImportExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void skipToEndTag(XmlPullParser xpp, String endTag) throws XmlPullParserException, IOException {
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
eventType = xpp.next();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getText(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
int eventType = xpp.next();
|
||||
if (eventType != XmlPullParser.TEXT) {
|
||||
return "";
|
||||
}
|
||||
return xpp.getText();
|
||||
}
|
||||
|
||||
private static Imported parseRoot(XmlPullParser xpp, boolean globalSettings, List<String> accountUuids,
|
||||
boolean overview) throws XmlPullParserException, IOException, SettingsImportExportException {
|
||||
|
||||
Imported result = new Imported();
|
||||
|
||||
String fileFormatVersionString = xpp.getAttributeValue(null, SettingsExporter.FILE_FORMAT_ATTRIBUTE);
|
||||
validateFileFormatVersion(fileFormatVersionString);
|
||||
|
||||
String contentVersionString = xpp.getAttributeValue(null, SettingsExporter.VERSION_ATTRIBUTE);
|
||||
result.contentVersion = validateContentVersion(contentVersionString);
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ROOT_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.GLOBAL_ELEMENT.equals(element)) {
|
||||
if (overview || globalSettings) {
|
||||
if (result.globalSettings == null) {
|
||||
if (overview) {
|
||||
result.globalSettings = new ImportedSettings();
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
} else {
|
||||
result.globalSettings = parseSettings(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
Timber.w("More than one global settings element. Only using the first one!");
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.GLOBAL_ELEMENT);
|
||||
Timber.i("Skipping global settings");
|
||||
}
|
||||
} else if (SettingsExporter.ACCOUNTS_ELEMENT.equals(element)) {
|
||||
if (result.accounts == null) {
|
||||
result.accounts = parseAccounts(xpp, accountUuids, overview);
|
||||
} else {
|
||||
Timber.w("More than one accounts element. Only using the first one!");
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int validateFileFormatVersion(String versionString) throws SettingsImportExportException {
|
||||
if (versionString == null) {
|
||||
throw new SettingsImportExportException("Missing file format version");
|
||||
}
|
||||
|
||||
int version;
|
||||
try {
|
||||
version = Integer.parseInt(versionString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SettingsImportExportException("Invalid file format version: " + versionString);
|
||||
}
|
||||
|
||||
if (version != SettingsExporter.FILE_FORMAT_VERSION) {
|
||||
throw new SettingsImportExportException("Unsupported file format version: " + versionString);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private static int validateContentVersion(String versionString) throws SettingsImportExportException {
|
||||
if (versionString == null) {
|
||||
throw new SettingsImportExportException("Missing content version");
|
||||
}
|
||||
|
||||
int version;
|
||||
try {
|
||||
version = Integer.parseInt(versionString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SettingsImportExportException("Invalid content version: " + versionString);
|
||||
}
|
||||
|
||||
if (version < 1) {
|
||||
throw new SettingsImportExportException("Unsupported content version: " + versionString);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private static ImportedSettings parseSettings(XmlPullParser xpp, String endTag)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
ImportedSettings result = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.VALUE_ELEMENT.equals(element)) {
|
||||
String key = xpp.getAttributeValue(null, SettingsExporter.KEY_ATTRIBUTE);
|
||||
String value = getText(xpp);
|
||||
|
||||
if (result == null) {
|
||||
result = new ImportedSettings();
|
||||
}
|
||||
|
||||
if (result.settings.containsKey(key)) {
|
||||
Timber.w("Already read key \"%s\". Ignoring value \"%s\"", key, value);
|
||||
} else {
|
||||
result.settings.put(key, value);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, ImportedAccount> parseAccounts(XmlPullParser xpp, List<String> accountUuids,
|
||||
boolean overview) throws XmlPullParserException, IOException {
|
||||
|
||||
Map<String, ImportedAccount> accounts = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ACCOUNTS_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.ACCOUNT_ELEMENT.equals(element)) {
|
||||
if (accounts == null) {
|
||||
accounts = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
ImportedAccount account = parseAccount(xpp, accountUuids, overview);
|
||||
|
||||
if (account == null) {
|
||||
// Do nothing - parseAccount() already logged a message
|
||||
} else if (!accounts.containsKey(account.uuid)) {
|
||||
accounts.put(account.uuid, account);
|
||||
} else {
|
||||
Timber.w("Duplicate account entries with UUID %s. Ignoring!", account.uuid);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private static ImportedAccount parseAccount(XmlPullParser xpp, List<String> accountUuids, boolean overview)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
String uuid = xpp.getAttributeValue(null, SettingsExporter.UUID_ATTRIBUTE);
|
||||
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
} catch (Exception e) {
|
||||
skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT);
|
||||
Timber.w("Skipping account with invalid UUID %s", uuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
ImportedAccount account = new ImportedAccount();
|
||||
account.uuid = uuid;
|
||||
|
||||
if (overview || accountUuids.contains(uuid)) {
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.ACCOUNT_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
||||
account.name = getText(xpp);
|
||||
} else if (SettingsExporter.INCOMING_SERVER_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
||||
} else {
|
||||
account.incoming = parseServerSettings(xpp, SettingsExporter.INCOMING_SERVER_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.OUTGOING_SERVER_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
||||
} else {
|
||||
account.outgoing = parseServerSettings(xpp, SettingsExporter.OUTGOING_SERVER_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
} else {
|
||||
account.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
}
|
||||
} else if (SettingsExporter.IDENTITIES_ELEMENT.equals(element)) {
|
||||
account.identities = parseIdentities(xpp);
|
||||
} else if (SettingsExporter.FOLDERS_ELEMENT.equals(element)) {
|
||||
if (overview) {
|
||||
skipToEndTag(xpp, SettingsExporter.FOLDERS_ELEMENT);
|
||||
} else {
|
||||
account.folders = parseFolders(xpp);
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
} else {
|
||||
skipToEndTag(xpp, SettingsExporter.ACCOUNT_ELEMENT);
|
||||
Timber.i("Skipping account with UUID %s", uuid);
|
||||
}
|
||||
|
||||
// If we couldn't find an account name use the UUID
|
||||
if (account.name == null) {
|
||||
account.name = uuid;
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
private static ImportedServer parseServerSettings(XmlPullParser xpp, String endTag)
|
||||
throws XmlPullParserException, IOException {
|
||||
ImportedServer server = new ImportedServer();
|
||||
|
||||
server.type = xpp.getAttributeValue(null, SettingsExporter.TYPE_ATTRIBUTE);
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && endTag.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.HOST_ELEMENT.equals(element)) {
|
||||
server.host = getText(xpp);
|
||||
} else if (SettingsExporter.PORT_ELEMENT.equals(element)) {
|
||||
server.port = getText(xpp);
|
||||
} else if (SettingsExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) {
|
||||
server.connectionSecurity = getText(xpp);
|
||||
} else if (SettingsExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) {
|
||||
String text = getText(xpp);
|
||||
server.authenticationType = AuthType.valueOf(text);
|
||||
} else if (SettingsExporter.USERNAME_ELEMENT.equals(element)) {
|
||||
server.username = getText(xpp);
|
||||
} else if (SettingsExporter.CLIENT_CERTIFICATE_ALIAS_ELEMENT.equals(element)) {
|
||||
server.clientCertificateAlias = getText(xpp);
|
||||
} else if (SettingsExporter.PASSWORD_ELEMENT.equals(element)) {
|
||||
server.password = getText(xpp);
|
||||
} else if (SettingsExporter.EXTRA_ELEMENT.equals(element)) {
|
||||
server.extras = parseSettings(xpp, SettingsExporter.EXTRA_ELEMENT);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
private static List<ImportedIdentity> parseIdentities(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
List<ImportedIdentity> identities = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.IDENTITIES_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.IDENTITY_ELEMENT.equals(element)) {
|
||||
if (identities == null) {
|
||||
identities = new ArrayList<>();
|
||||
}
|
||||
|
||||
ImportedIdentity identity = parseIdentity(xpp);
|
||||
identities.add(identity);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return identities;
|
||||
}
|
||||
|
||||
private static ImportedIdentity parseIdentity(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
ImportedIdentity identity = new ImportedIdentity();
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.IDENTITY_ELEMENT.equals(xpp.getName()))) {
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.NAME_ELEMENT.equals(element)) {
|
||||
identity.name = getText(xpp);
|
||||
} else if (SettingsExporter.EMAIL_ELEMENT.equals(element)) {
|
||||
identity.email = getText(xpp);
|
||||
} else if (SettingsExporter.DESCRIPTION_ELEMENT.equals(element)) {
|
||||
identity.description = getText(xpp);
|
||||
} else if (SettingsExporter.SETTINGS_ELEMENT.equals(element)) {
|
||||
identity.settings = parseSettings(xpp, SettingsExporter.SETTINGS_ELEMENT);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
private static List<ImportedFolder> parseFolders(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
List<ImportedFolder> folders = null;
|
||||
|
||||
int eventType = xpp.next();
|
||||
while (!(eventType == XmlPullParser.END_TAG && SettingsExporter.FOLDERS_ELEMENT.equals(xpp.getName()))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String element = xpp.getName();
|
||||
if (SettingsExporter.FOLDER_ELEMENT.equals(element)) {
|
||||
if (folders == null) {
|
||||
folders = new ArrayList<>();
|
||||
}
|
||||
|
||||
ImportedFolder folder = parseFolder(xpp);
|
||||
folders.add(folder);
|
||||
} else {
|
||||
Timber.w("Unexpected start tag: %s", xpp.getName());
|
||||
}
|
||||
}
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
private static ImportedFolder parseFolder(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||
ImportedFolder folder = new ImportedFolder();
|
||||
|
||||
folder.name = xpp.getAttributeValue(null, SettingsExporter.NAME_ATTRIBUTE);
|
||||
|
||||
folder.settings = parseSettings(xpp, SettingsExporter.FOLDER_ELEMENT);
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private static String getAccountDisplayName(ImportedAccount account) {
|
||||
String name = account.name;
|
||||
if (TextUtils.isEmpty(name) && account.identities != null && account.identities.size() > 0) {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package com.fsck.k9.preferences
|
||||
|
||||
import app.k9mail.core.android.testing.RobolectricTest
|
||||
import assertk.all
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.containsExactly
|
||||
import assertk.assertions.extracting
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.key
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import java.util.UUID
|
||||
import org.junit.Test
|
||||
|
||||
class SettingsFileParserTest : RobolectricTest() {
|
||||
private val parser = SettingsFileParser()
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should return accounts`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf("1")
|
||||
|
||||
val results = parser.parseSettings(inputStream, true, accountUuids, true)
|
||||
|
||||
assertThat(results.accounts).isNotNull().all {
|
||||
hasSize(1)
|
||||
key(accountUuid).all {
|
||||
prop(ImportedAccount::uuid).isEqualTo(accountUuid)
|
||||
prop(ImportedAccount::name).isEqualTo("Account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should return identities`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
<identities>
|
||||
<identity>
|
||||
<email>user@gmail.com</email>
|
||||
</identity>
|
||||
</identities>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf("1")
|
||||
|
||||
val results = parser.parseSettings(inputStream, true, accountUuids, true)
|
||||
|
||||
assertThat(results.accounts).isNotNull().all {
|
||||
hasSize(1)
|
||||
key(accountUuid).all {
|
||||
prop(ImportedAccount::uuid).isEqualTo(accountUuid)
|
||||
prop(ImportedAccount::identities).isNotNull()
|
||||
.extracting(ImportedIdentity::email).containsExactly("user@gmail.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should parse incoming server authentication type`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
<incoming-server>
|
||||
<authentication-type>CRAM_MD5</authentication-type>
|
||||
</incoming-server>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf(accountUuid)
|
||||
|
||||
val results = parser.parseSettings(inputStream, true, accountUuids, false)
|
||||
|
||||
assertThat(results.accounts)
|
||||
.isNotNull()
|
||||
.key(accountUuid)
|
||||
.prop(ImportedAccount::incoming)
|
||||
.isNotNull()
|
||||
.prop(ImportedServer::authenticationType)
|
||||
.isEqualTo(AuthType.CRAM_MD5)
|
||||
}
|
||||
}
|
|
@ -4,21 +4,16 @@ import android.content.Context
|
|||
import assertk.all
|
||||
import assertk.assertFailure
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.containsExactly
|
||||
import assertk.assertions.extracting
|
||||
import assertk.assertions.first
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEmpty
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.isTrue
|
||||
import assertk.assertions.key
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.K9RobolectricTest
|
||||
import com.fsck.k9.Preferences
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import java.util.UUID
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
@ -107,93 +102,6 @@ class SettingsImporterTest : K9RobolectricTest() {
|
|||
}.isInstanceOf<SettingsImportExportException>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should return accounts`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf("1")
|
||||
|
||||
val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true)
|
||||
|
||||
assertThat(results.accounts).isNotNull().all {
|
||||
hasSize(1)
|
||||
key(accountUuid).all {
|
||||
prop(ImportedAccount::uuid).isEqualTo(accountUuid)
|
||||
prop(ImportedAccount::name).isEqualTo("Account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should return identities`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
<identities>
|
||||
<identity>
|
||||
<email>user@gmail.com</email>
|
||||
</identity>
|
||||
</identities>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf("1")
|
||||
|
||||
val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true)
|
||||
|
||||
assertThat(results.accounts).isNotNull().all {
|
||||
hasSize(1)
|
||||
key(accountUuid).all {
|
||||
prop(ImportedAccount::uuid).isEqualTo(accountUuid)
|
||||
prop(ImportedAccount::identities).isNotNull()
|
||||
.extracting(ImportedIdentity::email).containsExactly("user@gmail.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseSettings() should parse incoming server authentication type`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
val inputStream =
|
||||
"""
|
||||
<k9settings format="1" version="1">
|
||||
<accounts>
|
||||
<account uuid="$accountUuid">
|
||||
<name>Account</name>
|
||||
<incoming-server>
|
||||
<authentication-type>CRAM_MD5</authentication-type>
|
||||
</incoming-server>
|
||||
</account>
|
||||
</accounts>
|
||||
</k9settings>
|
||||
""".trimIndent().byteInputStream()
|
||||
val accountUuids = listOf(accountUuid)
|
||||
|
||||
val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, false)
|
||||
|
||||
assertThat(results.accounts)
|
||||
.isNotNull()
|
||||
.key(accountUuid)
|
||||
.prop(ImportedAccount::incoming)
|
||||
.isNotNull()
|
||||
.prop(ImportedServer::authenticationType)
|
||||
.isEqualTo(AuthType.CRAM_MD5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importSettings() should disable accounts needing passwords`() {
|
||||
val accountUuid = UUID.randomUUID().toString()
|
||||
|
|
Loading…
Reference in a new issue