From 3a9eabf50ba83906a3e94300d58b24eec5f68768 Mon Sep 17 00:00:00 2001 From: Karim Mreisi Date: Sat, 14 Jan 2023 16:44:13 +0100 Subject: [PATCH] fix: support host names with _ Use a JSON to properly encapsule different computer addresses and their port, instead of using "_" as separator. Fix usage of '_' in computer host names / domain names. --- .../computers/ComputerDatabaseManager.java | 95 +++++++++----- .../computers/LegacyDatabaseReader3.java | 123 ++++++++++++++++++ 2 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/limelight/computers/LegacyDatabaseReader3.java diff --git a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java index e3d23c3f..d96d1d76 100644 --- a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java +++ b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java @@ -9,7 +9,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; +import com.limelight.LimeLog; import com.limelight.nvstream.http.ComputerDetails; +import com.limelight.nvstream.http.LimelightCryptoProvider; import com.limelight.nvstream.http.NvHTTP; import android.content.ContentValues; @@ -18,18 +20,29 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + public class ComputerDatabaseManager { - private static final String COMPUTER_DB_NAME = "computers3.db"; + private static final String COMPUTER_DB_NAME = "computers4.db"; private static final String COMPUTER_TABLE_NAME = "Computers"; private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; private static final String ADDRESSES_COLUMN_NAME = "Addresses"; + private interface AddressFields { + String LOCAL = "local"; + String REMOTE = "remote"; + String MANUAL = "manual"; + String IPv6 = "ipv6"; + + String ADDRESS = "address"; + String PORT = "port"; + } + private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress"; private static final String SERVER_CERT_COLUMN_NAME = "ServerCert"; - private static final char ADDRESS_DELIMITER = ';'; - private static final char PORT_DELIMITER = '_'; - private SQLiteDatabase computerDb; public ComputerDatabaseManager(Context c) { @@ -64,24 +77,52 @@ public class ComputerDatabaseManager { for (ComputerDetails computer : oldComputers) { updateComputer(computer); } + oldComputers = LegacyDatabaseReader3.migrateAllComputers(c); + for (ComputerDetails computer : oldComputers) { + updateComputer(computer); + } } public void deleteComputer(ComputerDetails details) { computerDb.delete(COMPUTER_TABLE_NAME, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{details.uuid}); } + public static JSONObject tupleToJson(ComputerDetails.AddressTuple tuple) throws JSONException { + if (tuple == null) { + return null; + } + JSONObject json = new JSONObject(); + json.put(AddressFields.ADDRESS, tuple.address); + json.put(AddressFields.PORT, tuple.port); + + return json; + } + + public static ComputerDetails.AddressTuple tupleFromJson(JSONObject json) throws JSONException { + if (json == null) { + return null; + } + + return new ComputerDetails.AddressTuple( + json.getString(AddressFields.ADDRESS), json.getInt(AddressFields.PORT)); + } + public boolean updateComputer(ComputerDetails details) { ContentValues values = new ContentValues(); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid); values.put(COMPUTER_NAME_COLUMN_NAME, details.name); - StringBuilder addresses = new StringBuilder(); - addresses.append(details.localAddress != null ? splitTupleToAddress(details.localAddress) : ""); - addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? splitTupleToAddress(details.remoteAddress) : ""); - addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? splitTupleToAddress(details.manualAddress) : ""); - addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? splitTupleToAddress(details.ipv6Address) : ""); + try { + JSONObject addresses = new JSONObject(); + addresses.put(AddressFields.LOCAL, tupleToJson((details.localAddress))); + addresses.put(AddressFields.REMOTE, tupleToJson((details.remoteAddress))); + addresses.put(AddressFields.MANUAL, tupleToJson((details.manualAddress))); + addresses.put(AddressFields.IPv6, tupleToJson((details.ipv6Address))); + values.put(ADDRESSES_COLUMN_NAME, addresses.toString()); + } catch (JSONException e) { + LimeLog.warning("JSON error, failed to write computer address information, " + e.getMessage()); + } - values.put(ADDRESSES_COLUMN_NAME, addresses.toString()); values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress); try { if (details.serverCert != null) { @@ -105,36 +146,20 @@ public class ComputerDatabaseManager { return input; } - private static ComputerDetails.AddressTuple splitAddressToTuple(String input) { - if (input == null) { - return null; - } - - String[] parts = input.split(""+PORT_DELIMITER, -1); - if (parts.length == 1) { - return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT); - } - else { - return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1])); - } - } - - private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) { - return tuple.address+PORT_DELIMITER+tuple.port; - } - private ComputerDetails getComputerFromCursor(Cursor c) { ComputerDetails details = new ComputerDetails(); details.uuid = c.getString(0); details.name = c.getString(1); - - String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1); - - details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0])); - details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1])); - details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2])); - details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3])); + try { + JSONObject addresses = new JSONObject(c.getString(2)); + details.localAddress = tupleFromJson(addresses.getJSONObject(AddressFields.LOCAL)); + details.remoteAddress = tupleFromJson(addresses.getJSONObject(AddressFields.REMOTE)); + details.manualAddress = tupleFromJson(addresses.getJSONObject(AddressFields.MANUAL)); + details.ipv6Address = tupleFromJson(addresses.getJSONObject(AddressFields.IPv6)); + } catch (JSONException e) { + LimeLog.warning("JSON error, failed to read computer address information, " + e.getMessage()); + } // External port is persisted in the remote address field if (details.remoteAddress != null) { diff --git a/app/src/main/java/com/limelight/computers/LegacyDatabaseReader3.java b/app/src/main/java/com/limelight/computers/LegacyDatabaseReader3.java new file mode 100644 index 00000000..aca6d1e1 --- /dev/null +++ b/app/src/main/java/com/limelight/computers/LegacyDatabaseReader3.java @@ -0,0 +1,123 @@ +package com.limelight.computers; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; + +import com.limelight.nvstream.http.ComputerDetails; +import com.limelight.nvstream.http.NvHTTP; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.LinkedList; +import java.util.List; + +public class LegacyDatabaseReader3 { + private static final String COMPUTER_DB_NAME = "computers3.db"; + private static final String COMPUTER_TABLE_NAME = "Computers"; + + private static final char ADDRESS_DELIMITER = ';'; + private static final char PORT_DELIMITER = '_'; + + private static String readNonEmptyString(String input) { + if (input.isEmpty()) { + return null; + } + + return input; + } + + private static ComputerDetails.AddressTuple splitAddressToTuple(String input) { + if (input == null) { + return null; + } + + String[] parts = input.split(""+PORT_DELIMITER, -1); + if (parts.length == 1) { + return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT); + } + else { + return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1])); + } + } + + private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) { + return tuple.address+PORT_DELIMITER+tuple.port; + } + + private static ComputerDetails getComputerFromCursor(Cursor c) { + ComputerDetails details = new ComputerDetails(); + + details.uuid = c.getString(0); + details.name = c.getString(1); + + String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1); + + details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0])); + details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1])); + details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2])); + details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3])); + + // External port is persisted in the remote address field + if (details.remoteAddress != null) { + details.externalPort = details.remoteAddress.port; + } + else { + details.externalPort = NvHTTP.DEFAULT_HTTP_PORT; + } + + details.macAddress = c.getString(3); + + try { + byte[] derCertData = c.getBlob(4); + + if (derCertData != null) { + details.serverCert = (X509Certificate) CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(derCertData)); + } + } catch (CertificateException e) { + e.printStackTrace(); + } + + // This signifies we don't have dynamic state (like pair state) + details.state = ComputerDetails.State.UNKNOWN; + + return details; + } + + public static List getAllComputers(SQLiteDatabase computerDb) { + try (final Cursor c = computerDb.rawQuery("SELECT * FROM "+COMPUTER_TABLE_NAME, null)) { + LinkedList computerList = new LinkedList<>(); + while (c.moveToNext()) { + ComputerDetails details = getComputerFromCursor(c); + + // If a critical field is corrupt or missing, skip the database entry + if (details.uuid == null) { + continue; + } + + computerList.add(details); + } + + return computerList; + } + } + + public static List migrateAllComputers(Context c) { + try (final SQLiteDatabase computerDb = SQLiteDatabase.openDatabase( + c.getDatabasePath(COMPUTER_DB_NAME).getPath(), + null, SQLiteDatabase.OPEN_READONLY) + ) { + // Open the existing database + return getAllComputers(computerDb); + } catch (SQLiteException e) { + return new LinkedList(); + } finally { + // Close and delete the old DB + c.deleteDatabase(COMPUTER_DB_NAME); + } + } +}