Repository: StackExchange/StackExchange.Redis

Issue:
client library metadata should validate contents
The `client setinfo lib-name` and `client setinfo lib-ver` sub-commands are picky:

``` txt
127.0.0.1:6379> client setinfo lib-name "a b"
(error) ERR lib-name cannot contain spaces, newlines or special characters.
127.0.0.1:6379> client setinfo lib-name "a-b"
OK
```

and

``` txt
127.0.0.1:6379> client setinfo lib-ver "a b"
(error) ERR lib-ver cannot contain spaces, newlines or special characters.
127.0.0.1:6379> client setinfo lib-ver "a-b"
OK
```

This causes the library metadata to *not be set at all*; suggestion: fix this at the library level. We already perform `client setname` validation, removing invalid characters; however, I propose to replace any groups of invalid characters with a single `-` instead, to retain semantic grouping. I *do not* currently propose to change how `client setname` sanitizes anything, although that could also be considered.


Hints:

Patch:
diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 4a4b1c3fc..2098349e2 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -7,7 +7,8 @@ Current package versions:
 | [![StackExchange.Redis](https://img.shields.io/nuget/v/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/vpre/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis MyGet](https://img.shields.io/myget/stackoverflow/vpre/StackExchange.Redis.svg)](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) |
 
 ## Unreleased
-No pending unreleased changes.
+
+- Fix [#2653](https://github.com/StackExchange/StackExchange.Redis/issues/2653): Client library metadata should validate contents ([#2654](https://github.com/StackExchange/StackExchange.Redis/pull/2654) by mgravell)
 
 ## 2.7.20
 
diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs
index 97230f85f..a5471b5d5 100644
--- a/src/StackExchange.Redis/ServerEndPoint.cs
+++ b/src/StackExchange.Redis/ServerEndPoint.cs
@@ -26,7 +26,7 @@ internal sealed partial class ServerEndPoint : IDisposable
     {
         internal volatile ServerEndPoint? Primary;
         internal volatile ServerEndPoint[] Replicas = Array.Empty<ServerEndPoint>();
-        private static readonly Regex nameSanitizer = new Regex("[^!-~]", RegexOptions.Compiled);
+        private static readonly Regex nameSanitizer = new Regex("[^!-~]+", RegexOptions.Compiled);
 
         private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal);
 
@@ -1024,6 +1024,8 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log)
                         // it, they should set SetClientLibrary to false, not set the name to empty string)
                         libName = config.Defaults.LibraryName;
                     }
+
+                    libName = ClientInfoSanitize(libName);
                     if (!string.IsNullOrWhiteSpace(libName))
                     {
                         msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
@@ -1032,7 +1034,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log)
                         await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
                     }
 
-                    var version = Utils.GetLibVersion();
+                    var version = ClientInfoSanitize(Utils.GetLibVersion());
                     if (!string.IsNullOrWhiteSpace(version))
                     {
                         msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
@@ -1091,6 +1093,8 @@ private void SetConfig<T>(ref T field, T value, [CallerMemberName] string? calle
                 Multiplexer?.ReconfigureIfNeeded(EndPoint, false, caller!);
             }
         }
+        internal static string ClientInfoSanitize(string? value)
+            => string.IsNullOrWhiteSpace(value) ? ""  : nameSanitizer.Replace(value!.Trim(), "-");
 
         private void ClearMemoized()
         {


Test patch:
diff --git a/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs b/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs
new file mode 100644
index 000000000..973a01da5
--- /dev/null
+++ b/tests/StackExchange.Redis.Tests/Issues/Issue2653.cs
@@ -0,0 +1,16 @@
+﻿using Xunit;
+
+namespace StackExchange.Redis.Tests.Issues;
+
+public class Issue2653
+{
+    [Theory]
+    [InlineData(null, "")]
+    [InlineData("", "")]
+    [InlineData("abcdef", "abcdef")]
+    [InlineData("abc.def", "abc.def")]
+    [InlineData("abc d \t  ef", "abc-d-ef")]
+    [InlineData("  abc\r\ndef\n", "abc-def")]
+    public void CheckLibraySanitization(string input, string expected)
+        => Assert.Equal(expected, ServerEndPoint.ClientInfoSanitize(input));
+}