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()));