From 440f1d4fa36c3f175f5e8ca10a80fb8c921c9951 Mon Sep 17 00:00:00 2001 From: Albert Vaca Cintora Date: Sun, 19 May 2024 10:02:28 +0000 Subject: [PATCH] Filter device name Following the spec: https://invent.kde.org/network/kdeconnect-meta/-/blob/master/protocol.md?ref_type=heads#kdeconnectidentity --- res/values/strings.xml | 2 +- .../BluetoothBackend/BluetoothLinkProvider.kt | 14 +++++++++++++ .../Backends/LanBackend/LanLinkProvider.java | 11 ++++++++++ src/org/kde/kdeconnect/DeviceInfo.kt | 8 ++++++- .../kde/kdeconnect/Helpers/DeviceHelper.kt | 9 +++++++- .../UserInterface/SettingsFragment.java | 10 ++++++--- tests/org/kde/kdeconnect/DeviceTest.java | 21 +++++++++++++++++++ 7 files changed, 69 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e4cba47d..8d4452b4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -183,7 +183,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted Pair requested Pair succeeded 🔑 %1s - Pairing request from "%1s" + Pairing request from \'%1s\' Receiving file from %1s> Receiving %1$d file from %2$s Receiving %1$d files from %2$s diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.kt b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.kt index 11460fa5..054d8516 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.kt +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.kt @@ -22,6 +22,7 @@ import android.util.Log import org.apache.commons.io.IOUtils import org.kde.kdeconnect.Backends.BaseLinkProvider import org.kde.kdeconnect.Device +import org.kde.kdeconnect.DeviceInfo import org.kde.kdeconnect.DeviceInfo.Companion.fromIdentityPacketAndCert import org.kde.kdeconnect.Helpers.DeviceHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper @@ -44,6 +45,12 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() { @Throws(CertificateException::class) private fun addLink(identityPacket: NetworkPacket, link: BluetoothLink) { + + if (!DeviceInfo.isValidIdentityPacket(identityPacket)) { + Log.w("KDE/LanLinkProvider", "Invalid identity packet received.") + return + } + val deviceId = identityPacket.getString("deviceId") Log.i("BluetoothLinkProvider", "addLink to $deviceId") val oldLink = visibleDevices[deviceId] @@ -369,6 +376,13 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() { socket.close() return } + + if (!DeviceInfo.isValidIdentityPacket(identityPacket)) { + Log.w("KDE/LanLinkProvider", "Invalid identity packet received.") + connection.close() + return + } + Log.i("BTLinkProvider/Client", "Received identity packet") val myId = DeviceHelper.getDeviceId(context) if (identityPacket.getString("deviceId") == myId) { diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index f616623f..2181acde 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -125,6 +125,12 @@ public class LanLinkProvider extends BaseLinkProvider { String message = new String(packet.getData(), Charsets.UTF_8); final NetworkPacket identityPacket = NetworkPacket.unserialize(message); + + if (!DeviceInfo.isValidIdentityPacket(identityPacket)) { + Log.w("KDE/LanLinkProvider", "Invalid identity packet received."); + return; + } + final String deviceId = identityPacket.getString("deviceId"); if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) { Log.e("KDE/LanLinkProvider", "Expecting an UDP identity packet"); @@ -192,6 +198,11 @@ public class LanLinkProvider extends BaseLinkProvider { @WorkerThread private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted) throws IOException { + if (!DeviceInfo.isValidIdentityPacket(identityPacket)) { + Log.w("KDE/LanLinkProvider", "Invalid identity packet received."); + return; + } + String myId = DeviceHelper.getDeviceId(context); final String deviceId = identityPacket.getString("deviceId"); if (deviceId.equals(myId)) { diff --git a/src/org/kde/kdeconnect/DeviceInfo.kt b/src/org/kde/kdeconnect/DeviceInfo.kt index f416c06c..59b08453 100644 --- a/src/org/kde/kdeconnect/DeviceInfo.kt +++ b/src/org/kde/kdeconnect/DeviceInfo.kt @@ -10,6 +10,7 @@ import android.content.Context import android.content.SharedPreferences import android.util.Base64 import androidx.core.content.ContextCompat +import org.kde.kdeconnect.Helpers.DeviceHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper import org.kde.kdeconnect_tp.R import java.security.cert.Certificate @@ -90,7 +91,7 @@ class DeviceInfo( with(identityPacket) { DeviceInfo( id = getString("deviceId"), // Redundant: We could read this from the certificate instead - name = getString("deviceName", "unknown"), + name = DeviceHelper.filterName(getString("deviceName", "unknown")), type = DeviceType.fromString(getString("deviceType", "desktop")), certificate = certificate, protocolVersion = getInt("protocolVersion"), @@ -98,6 +99,11 @@ class DeviceInfo( outgoingCapabilities = getStringSet("outgoingCapabilities") ) } + + @JvmStatic + fun isValidIdentityPacket(identityPacket: NetworkPacket): Boolean = with(identityPacket) { + DeviceHelper.filterName(getString("deviceName", "")).isNotBlank() && getString("deviceId", "").isNotBlank() + } } } diff --git a/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt index 83ad76ce..bdc5c0cc 100644 --- a/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt +++ b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt @@ -38,6 +38,9 @@ object DeviceHelper { private const val DEVICE_DATABASE = "https://storage.googleapis.com/play_public/supported_devices.csv" + private val NAME_INVALID_CHARACTERS_REGEX = "[\"',;:.!?()\\[\\]<>]".toRegex() + const val MAX_DEVICE_NAME_LENGTH = 32 + private val isTablet: Boolean by lazy { val config = Resources.getSystem().configuration //This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE @@ -119,8 +122,9 @@ object DeviceHelper { } fun setDeviceName(context: Context, name: String) { + val filteredName = filterName(name) val preferences = PreferenceManager.getDefaultSharedPreferences(context) - preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, name).apply() + preferences.edit().putString(KEY_DEVICE_NAME_PREFERENCE, filteredName).apply() } fun initializeDeviceId(context: Context) { @@ -160,4 +164,7 @@ object DeviceHelper { PluginFactory.getOutgoingCapabilities() ) } + + @JvmStatic + fun filterName(input: String): String = input.replace(NAME_INVALID_CHARACTERS_REGEX, "").take(MAX_DEVICE_NAME_LENGTH) } diff --git a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java index a095e2a0..c273224a 100644 --- a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java @@ -7,14 +7,13 @@ package org.kde.kdeconnect.UserInterface; import android.Manifest; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.text.TextUtils; +import android.text.InputFilter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,6 +32,7 @@ import androidx.preference.TwoStatePreference; import com.google.android.material.snackbar.Snackbar; +import org.apache.commons.lang3.StringUtils; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; @@ -66,6 +66,10 @@ public class SettingsFragment extends PreferenceFragmentCompat { renameDevice.setKey(DeviceHelper.KEY_DEVICE_NAME_PREFERENCE); renameDevice.setSelectable(true); renameDevice.setOnBindEditTextListener(TextView::setSingleLine); + renameDevice.setOnBindEditTextListener(editText -> editText.setFilters(new InputFilter[] { + (source, start, end, dest, dstart, dend) -> DeviceHelper.filterName(source.subSequence(start, end).toString()), + new InputFilter.LengthFilter(DeviceHelper.MAX_DEVICE_NAME_LENGTH), + })); String deviceName = DeviceHelper.getDeviceName(context); renameDevice.setTitle(R.string.settings_rename); renameDevice.setSummary(deviceName); @@ -76,7 +80,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { renameDevice.setOnPreferenceChangeListener((preference, newValue) -> { String name = (String) newValue; - if (TextUtils.isEmpty(name)) { + if (StringUtils.isBlank(name)) { if (getView() != null) { Snackbar snackbar = Snackbar.make(getView(), R.string.invalid_device_name, Snackbar.LENGTH_LONG); int currentTheme = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; diff --git a/tests/org/kde/kdeconnect/DeviceTest.java b/tests/org/kde/kdeconnect/DeviceTest.java index 2dd64579..6b10b708 100644 --- a/tests/org/kde/kdeconnect/DeviceTest.java +++ b/tests/org/kde/kdeconnect/DeviceTest.java @@ -132,6 +132,27 @@ public class DeviceTest { assertEquals(di.outgoingCapabilities, np.getStringSet("outgoingCapabilities")); } + @Test + public void testIsValidIdentityPacket() { + NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); + assertFalse(DeviceInfo.Companion.isValidIdentityPacket(np)); + + String validName = "MyDevice"; + String validId = "123"; + np.set("deviceName", validName); + np.set("deviceId", validId); + assertTrue(DeviceInfo.Companion.isValidIdentityPacket(np)); + + np.set("deviceName", " "); + assertFalse(DeviceInfo.Companion.isValidIdentityPacket(np)); + np.set("deviceName", "<><><><><><><><><>"); // Only invalid characters + assertFalse(DeviceInfo.Companion.isValidIdentityPacket(np)); + + np.set("deviceName", validName); + np.set("deviceId", " "); + assertFalse(DeviceInfo.Companion.isValidIdentityPacket(np)); + } + @Test public void testDeviceType() { assertEquals(DeviceType.PHONE, DeviceType.fromString(DeviceType.PHONE.toString()));