Prechádzať zdrojové kódy

use Regex Source Generator for .NET 7+ (#1401)

* use Regex Source Generator for .NET 7+

fixes #1131

* use shorter pattern for GeneratedRegex
mus65 1 rok pred
rodič
commit
23323aa217

+ 0 - 7
src/Renci.SshNet/.editorconfig

@@ -33,13 +33,6 @@ dotnet_diagnostic.S2589.severity = none
 
 dotnet_diagnostic.S2372.severity = none
 
-#### SYSLIB diagnostics ####
-
-# SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time
-#
-# TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented.
-dotnet_diagnostic.SYSLIB1045.severity = none
-
 #### StyleCop Analyzers rules ####
 
 # SA1123: Do not place regions within elements

+ 20 - 6
src/Renci.SshNet/Connection/HttpConnector.cs

@@ -29,8 +29,25 @@ namespace Renci.SshNet.Connection
     ///   </item>
     /// </list>
     /// </remarks>
-    internal sealed class HttpConnector : ProxyConnector
+    internal sealed partial class HttpConnector : ProxyConnector
     {
+        private const string HttpResponsePattern = @"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$";
+        private const string HttpHeaderPattern = @"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?";
+
+#if NET7_0_OR_GREATER
+        private static readonly Regex HttpResponseRegex = GetHttpResponseRegex();
+        private static readonly Regex HttpHeaderRegex = GetHttpHeaderRegex();
+
+        [GeneratedRegex(HttpResponsePattern)]
+        private static partial Regex GetHttpResponseRegex();
+
+        [GeneratedRegex(HttpHeaderPattern)]
+        private static partial Regex GetHttpHeaderRegex();
+#else
+        private static readonly Regex HttpResponseRegex = new Regex(HttpResponsePattern, RegexOptions.Compiled);
+        private static readonly Regex HttpHeaderRegex = new Regex(HttpHeaderPattern, RegexOptions.Compiled);
+#endif
+
         public HttpConnector(ISocketFactory socketFactory)
             : base(socketFactory)
         {
@@ -38,9 +55,6 @@ namespace Renci.SshNet.Connection
 
         protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
         {
-            var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
-            var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
-
             SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format(CultureInfo.InvariantCulture,
                                                                                 "CONNECT {0}:{1} HTTP/1.0\r\n",
                                                                                 connectionInfo.Host,
@@ -71,7 +85,7 @@ namespace Renci.SshNet.Connection
 
                 if (statusCode is null)
                 {
-                    var statusMatch = httpResponseRe.Match(response);
+                    var statusMatch = HttpResponseRegex.Match(response);
                     if (statusMatch.Success)
                     {
                         var httpStatusCode = statusMatch.Result("${statusCode}");
@@ -86,7 +100,7 @@ namespace Renci.SshNet.Connection
                 }
 
                 // continue on parsing message headers coming from the server
-                var headerMatch = httpHeaderRe.Match(response);
+                var headerMatch = HttpHeaderRegex.Match(response);
                 if (headerMatch.Success)
                 {
                     var fieldName = headerMatch.Result("${fieldName}");

+ 12 - 4
src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

@@ -19,11 +19,19 @@ namespace Renci.SshNet.Connection
     /// <remarks>
     /// https://tools.ietf.org/html/rfc4253#section-4.2.
     /// </remarks>
-    internal sealed class ProtocolVersionExchange : IProtocolVersionExchange
+    internal sealed partial class ProtocolVersionExchange : IProtocolVersionExchange
     {
         private const byte Null = 0x00;
+        private const string ServerVersionPattern = "^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$";
 
-        private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+#if NET7_0_OR_GREATER
+        private static readonly Regex ServerVersionRegex = GetServerVersionRegex();
+
+        [GeneratedRegex(ServerVersionPattern, RegexOptions.ExplicitCapture)]
+        private static partial Regex GetServerVersionRegex();
+#else
+        private static readonly Regex ServerVersionRegex = new Regex(ServerVersionPattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+#endif
 
         /// <summary>
         /// Performs the SSH protocol version exchange.
@@ -57,7 +65,7 @@ namespace Renci.SshNet.Connection
                     throw CreateServerResponseDoesNotContainIdentification(bytesReceived);
                 }
 
-                var identificationMatch = ServerVersionRe.Match(line);
+                var identificationMatch = ServerVersionRegex.Match(line);
                 if (identificationMatch.Success)
                 {
                     return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),
@@ -104,7 +112,7 @@ namespace Renci.SshNet.Connection
                     throw CreateServerResponseDoesNotContainIdentification(bytesReceived);
                 }
 
-                var identificationMatch = ServerVersionRe.Match(line);
+                var identificationMatch = ServerVersionRegex.Match(line);
                 if (identificationMatch.Success)
                 {
                     return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),

+ 20 - 5
src/Renci.SshNet/Netconf/NetConfSession.cs

@@ -9,10 +9,11 @@ using Renci.SshNet.Common;
 
 namespace Renci.SshNet.NetConf
 {
-    internal sealed class NetConfSession : SubsystemSession, INetConfSession
+    internal sealed partial class NetConfSession : SubsystemSession, INetConfSession
     {
         private const string Prompt = "]]>]]>";
-
+        private const string LengthPattern = @"\n#(?<length>\d+)\n";
+        private const string ReplyPattern = @"\n##\n";
         private readonly StringBuilder _data = new StringBuilder();
         private bool _usingFramingProtocol;
         private EventWaitHandle _serverCapabilitiesConfirmed = new AutoResetEvent(initialState: false);
@@ -20,6 +21,20 @@ namespace Renci.SshNet.NetConf
         private StringBuilder _rpcReply = new StringBuilder();
         private int _messageId;
 
+#if NET7_0_OR_GREATER
+        private static readonly Regex LengthRegex = GetLengthRegex();
+        private static readonly Regex ReplyRegex = GetReplyRegex();
+
+        [GeneratedRegex(LengthPattern)]
+        private static partial Regex GetLengthRegex();
+
+        [GeneratedRegex(ReplyPattern)]
+        private static partial Regex GetReplyRegex();
+#else
+        private static readonly Regex LengthRegex = new Regex(LengthPattern, RegexOptions.Compiled);
+        private static readonly Regex ReplyRegex = new Regex(ReplyPattern, RegexOptions.Compiled);
+#endif
+
         /// <summary>
         /// Gets NetConf server capabilities.
         /// </summary>
@@ -145,7 +160,7 @@ namespace Renci.SshNet.NetConf
 
                 for (; ; )
                 {
-                    var match = Regex.Match(chunk.Substring(position), @"\n#(?<length>\d+)\n");
+                    var match = LengthRegex.Match(chunk.Substring(position));
                     if (!match.Success)
                     {
                         break;
@@ -157,9 +172,9 @@ namespace Renci.SshNet.NetConf
                 }
 
 #if NET7_0_OR_GREATER
-                if (Regex.IsMatch(chunk.AsSpan(position), @"\n##\n"))
+                if (ReplyRegex.IsMatch(chunk.AsSpan(position)))
 #else
-                if (Regex.IsMatch(chunk.Substring(position), @"\n##\n"))
+                if (ReplyRegex.IsMatch(chunk.Substring(position)))
 #endif // NET7_0_OR_GREATER
                 {
                     _ = _rpcReplyReceived.Set();

+ 11 - 2
src/Renci.SshNet/PrivateKeyFile.cs

@@ -62,10 +62,19 @@ namespace Renci.SshNet
     /// </list>
     /// </para>
     /// </remarks>
-    public class PrivateKeyFile : IPrivateKeySource, IDisposable
+    public partial class PrivateKeyFile : IPrivateKeySource, IDisposable
     {
-        private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k<keyName> PRIVATE KEY *-+",
+        private const string PrivateKeyPattern = @"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k<keyName> PRIVATE KEY *-+";
+
+#if NET7_0_OR_GREATER
+        private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex();
+
+        [GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
+        private static partial Regex GetPrivateKeyRegex();
+#else
+        private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern,
                                                                   RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
+#endif
 
         private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
         private Key _key;

+ 27 - 7
src/Renci.SshNet/ScpClient.cs

@@ -32,11 +32,31 @@ namespace Renci.SshNet
     public partial class ScpClient : BaseClient
     {
         private const string Message = "filename";
-        private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", RegexOptions.Compiled);
+        private const string FileInfoPattern = @"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)";
+        private const string DirectoryInfoPattern = @"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)";
+        private const string TimestampPattern = @"T(?<mtime>\d+) 0 (?<atime>\d+) 0";
+
+#if NET7_0_OR_GREATER
+        private static readonly Regex FileInfoRegex = GetFileInfoRegex();
+        private static readonly Regex DirectoryInfoRegex = GetDirectoryInfoRegex();
+        private static readonly Regex TimestampRegex = GetTimestampRegex();
+
+        [GeneratedRegex(FileInfoPattern)]
+        private static partial Regex GetFileInfoRegex();
+
+        [GeneratedRegex(DirectoryInfoPattern)]
+        private static partial Regex GetDirectoryInfoRegex();
+
+        [GeneratedRegex(TimestampPattern)]
+        private static partial Regex GetTimestampRegex();
+#else
+        private static readonly Regex FileInfoRegex = new Regex(FileInfoPattern, RegexOptions.Compiled);
+        private static readonly Regex DirectoryInfoRegex = new Regex(DirectoryInfoPattern, RegexOptions.Compiled);
+        private static readonly Regex TimestampRegex = new Regex(TimestampPattern, RegexOptions.Compiled);
+#endif
+
         private static readonly byte[] SuccessConfirmationCode = { 0 };
         private static readonly byte[] ErrorConfirmationCode = { 1 };
-        private static readonly Regex DirectoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", RegexOptions.Compiled);
-        private static readonly Regex TimestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0", RegexOptions.Compiled);
 
         private IRemotePathTransformation _remotePathTransformation;
         private TimeSpan _operationTimeout;
@@ -458,7 +478,7 @@ namespace Renci.SshNet
                 SendSuccessConfirmation(channel); // Send reply
 
                 var message = ReadString(input);
-                var match = FileInfoRe.Match(message);
+                var match = FileInfoRegex.Match(message);
 
                 if (match.Success)
                 {
@@ -757,7 +777,7 @@ namespace Renci.SshNet
                     continue;
                 }
 
-                var match = DirectoryInfoRe.Match(message);
+                var match = DirectoryInfoRegex.Match(message);
                 if (match.Success)
                 {
                     SendSuccessConfirmation(channel); // Send reply
@@ -784,7 +804,7 @@ namespace Renci.SshNet
                     continue;
                 }
 
-                match = FileInfoRe.Match(message);
+                match = FileInfoRegex.Match(message);
                 if (match.Success)
                 {
                     // Read file
@@ -814,7 +834,7 @@ namespace Renci.SshNet
                     continue;
                 }
 
-                match = TimestampRe.Match(message);
+                match = TimestampRegex.Match(message);
                 if (match.Success)
                 {
                     // Read timestamp