/** * * Copyright 2017 Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.omemo; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.SortedSet; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.util.CloseableUtil; import org.jivesoftware.smack.util.stringencoder.BareJidEncoder; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jxmpp.jid.BareJid; /** * Like a rocket! * * @author Paul Schaub */ public abstract class FileBasedOmemoStore extends OmemoStore { private final FileHierarchy hierarchy; private static final Logger LOGGER = Logger.getLogger(FileBasedOmemoStore.class.getName()); private static BareJidEncoder bareJidEncoder = new BareJidEncoder.UrlSafeEncoder(); public FileBasedOmemoStore(File basePath) { super(); if (basePath == null) { throw new IllegalStateException("No FileBasedOmemoStoreDefaultPath set in OmemoConfiguration."); } this.hierarchy = new FileHierarchy(basePath); } @Override public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice) throws CorruptedOmemoKeyException { File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); return keyUtil().identityKeyPairFromBytes(readBytes(identityKeyPairPath)); } @Override public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) { File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); writeBytes(identityKeyPairPath, keyUtil().identityKeyPairToBytes(identityKeyPair)); } @Override public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) { File identityKeyPairPath = hierarchy.getIdentityKeyPairPath(userDevice); if (!identityKeyPairPath.delete()) { LOGGER.log(Level.WARNING, "Could not delete OMEMO IdentityKeyPair " + identityKeyPairPath.getAbsolutePath()); } } @Override public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) throws CorruptedOmemoKeyException { File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); byte[] bytes = readBytes(identityKeyPath); return bytes != null ? keyUtil().identityKeyFromBytes(bytes) : null; } @Override public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey t_idKey) { File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); writeBytes(identityKeyPath, keyUtil().identityKeyToBytes(t_idKey)); } @Override public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) { File identityKeyPath = hierarchy.getContactsIdentityKeyPath(userDevice, contactsDevice); if (!identityKeyPath.delete()) { LOGGER.log(Level.WARNING, "Could not delete OMEMO identityKey " + identityKeyPath.getAbsolutePath()); } } @Override public SortedSet localDeviceIdsOf(BareJid localUser) { SortedSet deviceIds = new TreeSet<>(); File userDir = hierarchy.getUserDirectory(localUser); File[] list = userDir.listFiles(); for (File d : (list != null ? list : new File[] {})) { if (d.isDirectory()) { try { deviceIds.add(Integer.parseInt(d.getName())); } catch (NumberFormatException e) { // ignore } } } return deviceIds; } @Override public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) { File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice); writeLong(lastMessageReceived, date.getTime()); } @Override public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice) { File lastMessageReceived = hierarchy.getLastMessageReceivedDatePath(userDevice, contactsDevice); Long date = readLong(lastMessageReceived); return date != null ? new Date(date) : null; } @Override public void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) { File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice); writeLong(lastDeviceIdPublished, date.getTime()); } @Override public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) { File lastDeviceIdPublished = hierarchy.getLastDeviceIdPublicationDatePath(userDevice, contactsDevice); Long date = readLong(lastDeviceIdPublished); return date != null ? new Date(date) : null; } @Override public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) { File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice); writeLong(lastSignedPreKeyRenewal, date.getTime()); } @Override public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) { File lastSignedPreKeyRenewal = hierarchy.getLastSignedPreKeyRenewal(userDevice); Long date = readLong(lastSignedPreKeyRenewal); return date != null ? new Date(date) : null; } @Override public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) { File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); byte[] bytes = readBytes(preKeyPath); if (bytes != null) { try { return keyUtil().preKeyFromBytes(bytes); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e); } } return null; } @Override public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) { File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); writeBytes(preKeyPath, keyUtil().preKeyToBytes(t_preKey)); } @Override public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) { File preKeyPath = hierarchy.getPreKeyPath(userDevice, preKeyId); if (!preKeyPath.delete()) { LOGGER.log(Level.WARNING, "Deleting OMEMO preKey " + preKeyPath.getAbsolutePath() + " failed."); } } @Override public TreeMap loadOmemoPreKeys(OmemoDevice userDevice) { File preKeyDirectory = hierarchy.getPreKeysDirectory(userDevice); TreeMap preKeys = new TreeMap<>(); if (preKeyDirectory == null) { return preKeys; } File[] keys = preKeyDirectory.listFiles(); for (File f : keys != null ? keys : new File[0]) { byte[] bytes = readBytes(f); if (bytes != null) { try { T_PreKey p = keyUtil().preKeyFromBytes(bytes); preKeys.put(Integer.parseInt(f.getName()), p); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize preKey from bytes.", e); } } } return preKeys; } @Override public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); byte[] bytes = readBytes(signedPreKeyPath); if (bytes != null) { try { return keyUtil().signedPreKeyFromBytes(bytes); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize signed preKey from bytes.", e); } } return null; } @Override public TreeMap loadOmemoSignedPreKeys(OmemoDevice userDevice) { File signedPreKeysDirectory = hierarchy.getSignedPreKeysDirectory(userDevice); TreeMap signedPreKeys = new TreeMap<>(); if (signedPreKeysDirectory == null) { return signedPreKeys; } File[] keys = signedPreKeysDirectory.listFiles(); for (File f : keys != null ? keys : new File[0]) { byte[] bytes = readBytes(f); if (bytes != null) { try { T_SigPreKey p = keyUtil().signedPreKeyFromBytes(bytes); signedPreKeys.put(Integer.parseInt(f.getName()), p); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize signed preKey.", e); } } } return signedPreKeys; } @Override public void storeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId, T_SigPreKey signedPreKey) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); writeBytes(signedPreKeyPath, keyUtil().signedPreKeyToBytes(signedPreKey)); } @Override public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) { File signedPreKeyPath = new File(hierarchy.getSignedPreKeysDirectory(userDevice), Integer.toString(signedPreKeyId)); if (!signedPreKeyPath.delete()) { LOGGER.log(Level.WARNING, "Deleting signed OMEMO preKey " + signedPreKeyPath.getAbsolutePath() + " failed."); } } @Override public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) { File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); byte[] bytes = readBytes(sessionPath); if (bytes != null) { try { return keyUtil().rawSessionFromBytes(bytes); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e); } } return null; } @Override public HashMap loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) { File contactsDirectory = hierarchy.getContactsDir(userDevice, contact); HashMap sessions = new HashMap<>(); String[] devices = contactsDirectory.list(); for (String deviceId : devices != null ? devices : new String[0]) { int id; try { id = Integer.parseInt(deviceId); } catch (NumberFormatException e) { continue; } OmemoDevice device = new OmemoDevice(contact, id); File session = hierarchy.getContactsSessionPath(userDevice, device); byte[] bytes = readBytes(session); if (bytes != null) { try { T_Sess s = keyUtil().rawSessionFromBytes(bytes); sessions.put(id, s); } catch (IOException e) { LOGGER.log(Level.WARNING, "Could not deserialize raw session.", e); } } } return sessions; } @Override public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session) { File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); writeBytes(sessionPath, keyUtil().rawSessionToBytes(session)); } @Override public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) { File sessionPath = hierarchy.getContactsSessionPath(userDevice, contactsDevice); if (!sessionPath.delete()) { LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + sessionPath.getAbsolutePath() + " failed."); } } @Override public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) { File contactsDirectory = hierarchy.getContactsDir(userDevice, contact); String[] devices = contactsDirectory.list(); for (String deviceId : devices != null ? devices : new String[0]) { int id = Integer.parseInt(deviceId); OmemoDevice device = new OmemoDevice(contact, id); File session = hierarchy.getContactsSessionPath(userDevice, device); if (!session.delete()) { LOGGER.log(Level.WARNING, "Deleting raw OMEMO session " + session.getAbsolutePath() + "failed."); } } } @Override public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) { File session = hierarchy.getContactsSessionPath(userDevice, contactsDevice); return session.exists(); } @Override public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) { OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList(); if (contact == null) { throw new IllegalArgumentException("Contact can not be null."); } // active File activeDevicesPath = hierarchy.getContactsActiveDevicesPath(userDevice, contact); Set active = readIntegers(activeDevicesPath); if (active != null) { cachedDeviceList.getActiveDevices().addAll(active); } // inactive File inactiveDevicesPath = hierarchy.getContactsInactiveDevicesPath(userDevice, contact); Set inactive = readIntegers(inactiveDevicesPath); if (inactive != null) { cachedDeviceList.getInactiveDevices().addAll(inactive); } return cachedDeviceList; } @Override public void storeCachedDeviceList(OmemoDevice userDevice, BareJid contact, OmemoCachedDeviceList contactsDeviceList) { if (contact == null) { return; } File activeDevices = hierarchy.getContactsActiveDevicesPath(userDevice, contact); writeIntegers(activeDevices, contactsDeviceList.getActiveDevices()); File inactiveDevices = hierarchy.getContactsInactiveDevicesPath(userDevice, contact); writeIntegers(inactiveDevices, contactsDeviceList.getInactiveDevices()); } @Override public void purgeOwnDeviceKeys(OmemoDevice userDevice) { File deviceDirectory = hierarchy.getUserDeviceDirectory(userDevice); deleteDirectory(deviceDirectory); } private static void writeLong(File target, long i) { if (target == null) { LOGGER.log(Level.WARNING, "Could not write long to null-path."); return; } try { FileHierarchy.createFile(target); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not create file.", e); return; } DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); out.writeLong(i); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write longs to file.", e); } finally { CloseableUtil.maybeClose(out, LOGGER); } } private static Long readLong(File target) { if (target == null) { LOGGER.log(Level.WARNING, "Could not read long from null-path."); return null; } Long l; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); l = in.readLong(); } catch (FileNotFoundException e) { l = null; } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not read long from file.", e); return null; } finally { CloseableUtil.maybeClose(in, LOGGER); } return l; } private static void writeBytes(File target, byte[] bytes) { if (target == null) { LOGGER.log(Level.WARNING, "Could not write bytes to null-path."); return; } // Create file try { FileHierarchy.createFile(target); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not create file.", e); return; } DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); out.write(bytes); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write bytes to file.", e); } finally { CloseableUtil.maybeClose(out, LOGGER); } } private static byte[] readBytes(File target) { if (target == null) { LOGGER.log(Level.WARNING, "Could not read bytes from null-path."); return null; } byte[] b = null; DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); b = new byte[(int) target.length()]; in.read(b); } catch (FileNotFoundException e) { b = null; } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not read bytes from file.", e); } finally { CloseableUtil.maybeClose(in, LOGGER); } return b; } private static void writeIntegers(File target, Set integers) { if (target == null) { LOGGER.log(Level.WARNING, "Could not write integers to null-path."); return; } DataOutputStream out = null; try { out = new DataOutputStream(new FileOutputStream(target)); for (int i : integers) { out.writeInt(i); } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write integers to file.", e); } finally { CloseableUtil.maybeClose(out, LOGGER); } } private static Set readIntegers(File target) { if (target == null) { LOGGER.log(Level.WARNING, "Could not read integers from null-path."); return null; } HashSet integers = new HashSet<>(); DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(target)); try { while (true) { integers.add(in.readInt()); } } catch (EOFException e) { // Reached end of the list. } } catch (FileNotFoundException e) { integers = null; } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not read integers.", e); } finally { CloseableUtil.maybeClose(in, LOGGER); } return integers; } /** * One day... *sheds a tear* * TODO Use methods below once Smack's minimum Android API level is 19 or higher */ /* private static void writeLong(File target, long i) throws IOException { if (target == null) { throw new IOException("Could not write long to null-path."); } FileHierarchy.createFile(target); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { out.writeLong(i); } } private static Long readLong(File target) throws IOException { if (target == null) { throw new IOException("Could not read long from null-path."); } if (!target.exists() || !target.isFile()) { return null; } try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { return in.readLong(); } } private static void writeBytes(File target, byte[] bytes) throws IOException { if (target == null) { throw new IOException("Could not write bytes to null-path."); } // Create file FileHierarchy.createFile(target); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { out.write(bytes); } } private static byte[] readBytes(File target) throws IOException { if (target == null) { throw new IOException("Could not read bytes from null-path."); } if (!target.exists() || !target.isFile()) { return null; } byte[] b = new byte[(int) target.length()]; try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { in.read(b); } return b; } private static void writeIntegers(File target, Set integers) throws IOException { if (target == null) { throw new IOException("Could not write integers to null-path."); } FileHierarchy.createFile(target); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(target))) { for (int i : integers) { out.writeInt(i); } } } private static Set readIntegers(File target) throws IOException { if (target == null) { throw new IOException("Could not write integers to null-path."); } if (!target.exists() || !target.isFile()) { return null; } HashSet integers = new HashSet<>(); try (DataInputStream in = new DataInputStream(new FileInputStream(target))) { while (true) { try { integers.add(in.readInt()); } catch (EOFException e) { break; } } } return integers; } */ /** * Delete a directory with all subdirectories. * @param root */ public static void deleteDirectory(File root) { File[] currList; Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { if (stack.lastElement().isDirectory()) { currList = stack.lastElement().listFiles(); if (currList != null && currList.length > 0) { for (File curr : currList) { stack.push(curr); } } else { stack.pop().delete(); } } else { stack.pop().delete(); } } } /** * This class represents the directory structure of the FileBasedOmemoStore. * The directory looks as follows: * * OMEMO_Store/ * 'romeo@montague.lit'/ //Our bareJid * ... * 'juliet@capulet.lit'/ //Our other bareJid * '13371234'/ //deviceId * identityKeyPair //Our identityKeyPair * lastSignedPreKeyRenewal //Date of when the signedPreKey was last renewed. * preKeys/ //Our preKeys * '1' * '2' * ... * signedPreKeys/ //Our signedPreKeys * '1' * '2' * ... * contacts/ * 'romeo@capulet.lit'/ //Juliets contact Romeo * activeDevice //List of Romeos active devices * inactiveDevices //List of his inactive devices * 'deviceId'/ //Romeos deviceId * identityKey //Romeos identityKey * session //Our session with romeo * trust //Records about the trust in romeos device * (lastReceivedMessageDate) //Only, for our own other devices: * //date of the last received message * */ public static class FileHierarchy { static final String STORE = "OMEMO_Store"; static final String CONTACTS = "contacts"; static final String IDENTITY_KEY = "identityKey"; static final String IDENTITY_KEY_PAIR = "identityKeyPair"; static final String PRE_KEYS = "preKeys"; static final String LAST_MESSAGE_RECEVIED_DATE = "lastMessageReceivedDate"; static final String LAST_DEVICEID_PUBLICATION_DATE = "lastDeviceIdPublicationDate"; static final String SIGNED_PRE_KEYS = "signedPreKeys"; static final String LAST_SIGNED_PRE_KEY_RENEWAL = "lastSignedPreKeyRenewal"; static final String SESSION = "session"; static final String DEVICE_LIST_ACTIVE = "activeDevices"; static final String DEVICE_LIST_INAVTIVE = "inactiveDevices"; static final String MESSAGE_COUNTER = "messageCounter"; File basePath; FileHierarchy(File basePath) { this.basePath = basePath; basePath.mkdirs(); } File getStoreDirectory() { return createDirectory(basePath, STORE); } File getUserDirectory(OmemoDevice userDevice) { return getUserDirectory(userDevice.getJid()); } File getUserDirectory(BareJid bareJid) { return createDirectory(getStoreDirectory(), bareJidEncoder.encode(bareJid)); } File getUserDeviceDirectory(OmemoDevice userDevice) { return createDirectory(getUserDirectory(userDevice.getJid()), Integer.toString(userDevice.getDeviceId())); } File getContactsDir(OmemoDevice userDevice) { return createDirectory(getUserDeviceDirectory(userDevice), CONTACTS); } File getContactsDir(OmemoDevice userDevice, BareJid contact) { return createDirectory(getContactsDir(userDevice), bareJidEncoder.encode(contact)); } File getContactsDir(OmemoDevice userDevice, OmemoDevice contactsDevice) { return createDirectory(getContactsDir(userDevice, contactsDevice.getJid()), Integer.toString(contactsDevice.getDeviceId())); } File getIdentityKeyPairPath(OmemoDevice userDevice) { return new File(getUserDeviceDirectory(userDevice), IDENTITY_KEY_PAIR); } File getPreKeysDirectory(OmemoDevice userDevice) { return createDirectory(getUserDeviceDirectory(userDevice), PRE_KEYS); } File getPreKeyPath(OmemoDevice userDevice, int preKeyId) { return new File(getPreKeysDirectory(userDevice), Integer.toString(preKeyId)); } File getLastMessageReceivedDatePath(OmemoDevice userDevice, OmemoDevice device) { return new File(getContactsDir(userDevice, device), LAST_MESSAGE_RECEVIED_DATE); } File getLastDeviceIdPublicationDatePath(OmemoDevice userDevice, OmemoDevice device) { return new File(getContactsDir(userDevice, device), LAST_DEVICEID_PUBLICATION_DATE); } File getSignedPreKeysDirectory(OmemoDevice userDevice) { return createDirectory(getUserDeviceDirectory(userDevice), SIGNED_PRE_KEYS); } File getLastSignedPreKeyRenewal(OmemoDevice userDevice) { return new File(getUserDeviceDirectory(userDevice), LAST_SIGNED_PRE_KEY_RENEWAL); } File getContactsIdentityKeyPath(OmemoDevice userDevice, OmemoDevice contactsDevice) { return new File(getContactsDir(userDevice, contactsDevice), IDENTITY_KEY); } File getContactsSessionPath(OmemoDevice userDevice, OmemoDevice contactsDevice) { return new File(getContactsDir(userDevice, contactsDevice), SESSION); } File getContactsActiveDevicesPath(OmemoDevice userDevice, BareJid contact) { return new File(getContactsDir(userDevice, contact), DEVICE_LIST_ACTIVE); } File getContactsInactiveDevicesPath(OmemoDevice userDevice, BareJid contact) { return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE); } File getDevicesMessageCounterPath(OmemoDevice userDevice, OmemoDevice otherDevice) { return new File(getContactsDir(userDevice, otherDevice), MESSAGE_COUNTER); } private static File createFile(File f) throws IOException { File p = f.getParentFile(); createDirectory(p); f.createNewFile(); return f; } private static File createDirectory(File dir, String subdir) { File f = new File(dir, subdir); return createDirectory(f); } private static File createDirectory(File f) { if (f.exists() && f.isDirectory()) { return f; } f.mkdirs(); return f; } } /** * Convert {@link BareJid BareJids} to Strings using the legacy {@link BareJid#toString()} method instead of the * proper, url safe {@link BareJid#asUrlEncodedString()} method. * While it is highly advised to use the new format, you can use this method to stay backwards compatible to data * sets created by the old implementation. */ @SuppressWarnings("deprecation") public static void useLegacyBareJidEncoding() { bareJidEncoder = new BareJidEncoder.LegacyEncoder(); } }