From d4ffb1734bb3dae1e6381c8aee634631e5d17b70 Mon Sep 17 00:00:00 2001 From: Marius Thesing Date: Fri, 17 May 2024 15:26:53 +0200 Subject: [PATCH 1/2] use Regex Source Generator for .NET 7+ fixes #1131 --- src/Renci.SshNet/.editorconfig | 7 --- src/Renci.SshNet/Connection/HttpConnector.cs | 34 +++++++++++--- .../Connection/ProtocolVersionExchange.cs | 19 ++++++-- src/Renci.SshNet/Netconf/NetConfSession.cs | 33 +++++++++++-- src/Renci.SshNet/PrivateKeyFile.cs | 18 +++++-- src/Renci.SshNet/ScpClient.cs | 47 ++++++++++++++++--- 6 files changed, 126 insertions(+), 32 deletions(-) diff --git a/src/Renci.SshNet/.editorconfig b/src/Renci.SshNet/.editorconfig index f7f9047a4..9e17f2e36 100644 --- a/src/Renci.SshNet/.editorconfig +++ b/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 diff --git a/src/Renci.SshNet/Connection/HttpConnector.cs b/src/Renci.SshNet/Connection/HttpConnector.cs index afbaf0f01..c40c91130 100644 --- a/src/Renci.SshNet/Connection/HttpConnector.cs +++ b/src/Renci.SshNet/Connection/HttpConnector.cs @@ -29,8 +29,33 @@ namespace Renci.SshNet.Connection /// /// /// - internal sealed class HttpConnector : ProxyConnector + internal sealed partial class HttpConnector : ProxyConnector { + private const string HttpResponsePattern = @"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$"; + private const string HttpHeaderPattern = @"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?"; + +#if NET7_0_OR_GREATER + [GeneratedRegex(HttpResponsePattern)] + private static partial Regex HttpResponseRegex(); + + [GeneratedRegex(HttpHeaderPattern)] + private static partial Regex HttpHeaderRegex(); +#else + private static readonly Regex HttpResponseRe = new Regex(HttpResponsePattern, RegexOptions.Compiled); + + private static Regex HttpResponseRegex() + { + return HttpResponseRe; + } + + private static readonly Regex HttpHeaderRe = new Regex(HttpHeaderPattern, RegexOptions.Compiled); + + private static Regex HttpHeaderRegex() + { + return HttpHeaderRe; + } +#endif + public HttpConnector(ISocketFactory socketFactory) : base(socketFactory) { @@ -38,9 +63,6 @@ public HttpConnector(ISocketFactory socketFactory) protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket) { - var httpResponseRe = new Regex(@"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$"); - var httpHeaderRe = new Regex(@"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?"); - SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format(CultureInfo.InvariantCulture, "CONNECT {0}:{1} HTTP/1.0\r\n", connectionInfo.Host, @@ -71,7 +93,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke if (statusCode is null) { - var statusMatch = httpResponseRe.Match(response); + var statusMatch = HttpResponseRegex().Match(response); if (statusMatch.Success) { var httpStatusCode = statusMatch.Result("${statusCode}"); @@ -86,7 +108,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke } // 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}"); diff --git a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs index b14da93c0..0dea7c4ee 100644 --- a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs +++ b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs @@ -19,11 +19,22 @@ namespace Renci.SshNet.Connection /// /// https://tools.ietf.org/html/rfc4253#section-4.2. /// - internal sealed class ProtocolVersionExchange : IProtocolVersionExchange + internal sealed partial class ProtocolVersionExchange : IProtocolVersionExchange { private const byte Null = 0x00; + private const string ServerVersionPattern = "^SSH-(?[^-]+)-(?.+?)([ ](?.+))?$"; - private static readonly Regex ServerVersionRe = new Regex("^SSH-(?[^-]+)-(?.+?)([ ](?.+))?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); +#if NET7_0_OR_GREATER + [GeneratedRegex(ServerVersionPattern, RegexOptions.ExplicitCapture)] + private static partial Regex ServerVersionRegex(); +#else + private static readonly Regex ServerVersionRe = new Regex(ServerVersionPattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + + private static Regex ServerVersionRegex() + { + return ServerVersionRe; + } +#endif /// /// Performs the SSH protocol version exchange. @@ -57,7 +68,7 @@ public SshIdentification Start(string clientVersion, Socket socket, TimeSpan tim throw CreateServerResponseDoesNotContainIdentification(bytesReceived); } - var identificationMatch = ServerVersionRe.Match(line); + var identificationMatch = ServerVersionRegex().Match(line); if (identificationMatch.Success) { return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"), @@ -104,7 +115,7 @@ public async Task StartAsync(string clientVersion, Socket soc throw CreateServerResponseDoesNotContainIdentification(bytesReceived); } - var identificationMatch = ServerVersionRe.Match(line); + var identificationMatch = ServerVersionRegex().Match(line); if (identificationMatch.Success) { return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"), diff --git a/src/Renci.SshNet/Netconf/NetConfSession.cs b/src/Renci.SshNet/Netconf/NetConfSession.cs index 317b99c1c..1b3aee13a 100644 --- a/src/Renci.SshNet/Netconf/NetConfSession.cs +++ b/src/Renci.SshNet/Netconf/NetConfSession.cs @@ -9,10 +9,11 @@ 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#(?\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,28 @@ internal sealed class NetConfSession : SubsystemSession, INetConfSession private StringBuilder _rpcReply = new StringBuilder(); private int _messageId; +#if NET7_0_OR_GREATER + [GeneratedRegex(LengthPattern)] + private static partial Regex LengthRegex(); + + [GeneratedRegex(ReplyPattern)] + private static partial Regex ReplyRegex(); +#else + private static readonly Regex LengthRe = new Regex(LengthPattern, RegexOptions.Compiled); + + private static Regex LengthRegex() + { + return LengthRe; + } + + private static readonly Regex ReplyRe = new Regex(ReplyPattern, RegexOptions.Compiled); + + private static Regex ReplyRegex() + { + return ReplyRe; + } +#endif + /// /// Gets NetConf server capabilities. /// @@ -145,7 +168,7 @@ protected override void OnDataReceived(byte[] data) for (; ; ) { - var match = Regex.Match(chunk.Substring(position), @"\n#(?\d+)\n"); + var match = LengthRegex().Match(chunk.Substring(position)); if (!match.Success) { break; @@ -157,9 +180,9 @@ protected override void OnDataReceived(byte[] data) } #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(); diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index 68408536a..f91b821d0 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -62,11 +62,23 @@ namespace Renci.SshNet /// /// /// - public class PrivateKeyFile : IPrivateKeySource, IDisposable + public partial class PrivateKeyFile : IPrivateKeySource, IDisposable { - private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?[A-Z0-9-]+),(?[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k PRIVATE KEY *-+", + private const string PrivateKeyPattern = @"^-+ *BEGIN (?\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?[A-Z0-9-]+),(?[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k PRIVATE KEY *-+"; + +#if NET7_0_OR_GREATER + [GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)] + private static partial Regex PrivateKeyRegex(); +#else + private static readonly Regex PrivateKeyRe = new Regex(PrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); + private static Regex PrivateKeyRegex() + { + return PrivateKeyRe; + } +#endif + private readonly List _hostAlgorithms = new List(); private Key _key; private bool _isDisposed; @@ -180,7 +192,7 @@ private void Open(Stream privateKey, string passPhrase) using (var sr = new StreamReader(privateKey)) { var text = sr.ReadToEnd(); - privateKeyMatch = PrivateKeyRegex.Match(text); + privateKeyMatch = PrivateKeyRegex().Match(text); } if (!privateKeyMatch.Success) diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs index 7aa57c62b..222f5e953 100644 --- a/src/Renci.SshNet/ScpClient.cs +++ b/src/Renci.SshNet/ScpClient.cs @@ -32,11 +32,44 @@ namespace Renci.SshNet public partial class ScpClient : BaseClient { private const string Message = "filename"; - private static readonly Regex FileInfoRe = new Regex(@"C(?\d{4}) (?\d+) (?.+)", RegexOptions.Compiled); + private const string FileInfoPattern = @"C(?\d{4}) (?\d+) (?.+)"; + private const string DirectoryInfoPattern = @"D(?\d{4}) (?\d+) (?.+)"; + private const string TimestampPattern = @"T(?\d+) 0 (?\d+) 0"; + +#if NET7_0_OR_GREATER + [GeneratedRegex(FileInfoPattern)] + private static partial Regex FileInfoRegex(); + + [GeneratedRegex(DirectoryInfoPattern)] + private static partial Regex DirectoryInfoRegex(); + + [GeneratedRegex(TimestampPattern)] + private static partial Regex TimestampRegex(); +#else + private static readonly Regex FileInfoRe = new Regex(FileInfoPattern, RegexOptions.Compiled); + + private static Regex FileInfoRegex() + { + return FileInfoRe; + } + + private static readonly Regex DirectoryInfoRe = new Regex(DirectoryInfoPattern, RegexOptions.Compiled); + + private static Regex DirectoryInfoRegex() + { + return DirectoryInfoRe; + } + + private static readonly Regex TimestampRe = new Regex(TimestampPattern, RegexOptions.Compiled); + + private static Regex TimestampRegex() + { + return TimestampRe; + } +#endif + private static readonly byte[] SuccessConfirmationCode = { 0 }; private static readonly byte[] ErrorConfirmationCode = { 1 }; - private static readonly Regex DirectoryInfoRe = new Regex(@"D(?\d{4}) (?\d+) (?.+)", RegexOptions.Compiled); - private static readonly Regex TimestampRe = new Regex(@"T(?\d+) 0 (?\d+) 0", RegexOptions.Compiled); private IRemotePathTransformation _remotePathTransformation; private TimeSpan _operationTimeout; @@ -458,7 +491,7 @@ public void Download(string filename, Stream destination) SendSuccessConfirmation(channel); // Send reply var message = ReadString(input); - var match = FileInfoRe.Match(message); + var match = FileInfoRegex().Match(message); if (match.Success) { @@ -757,7 +790,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - var match = DirectoryInfoRe.Match(message); + var match = DirectoryInfoRegex().Match(message); if (match.Success) { SendSuccessConfirmation(channel); // Send reply @@ -784,7 +817,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - match = FileInfoRe.Match(message); + match = FileInfoRegex().Match(message); if (match.Success) { // Read file @@ -814,7 +847,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - match = TimestampRe.Match(message); + match = TimestampRegex().Match(message); if (match.Success) { // Read timestamp From 438c1f972d0d2bdcfdbb71657a53f1561e6fc889 Mon Sep 17 00:00:00 2001 From: Marius Thesing Date: Fri, 17 May 2024 23:00:32 +0200 Subject: [PATCH 2/2] use shorter pattern for GeneratedRegex --- src/Renci.SshNet/Connection/HttpConnector.cs | 26 ++++-------- .../Connection/ProtocolVersionExchange.cs | 15 +++---- src/Renci.SshNet/Netconf/NetConfSession.cs | 28 +++++-------- src/Renci.SshNet/PrivateKeyFile.cs | 13 +++--- src/Renci.SshNet/ScpClient.cs | 41 +++++++------------ 5 files changed, 44 insertions(+), 79 deletions(-) diff --git a/src/Renci.SshNet/Connection/HttpConnector.cs b/src/Renci.SshNet/Connection/HttpConnector.cs index 82e6f7001..a05222fe3 100644 --- a/src/Renci.SshNet/Connection/HttpConnector.cs +++ b/src/Renci.SshNet/Connection/HttpConnector.cs @@ -35,25 +35,17 @@ internal sealed partial class HttpConnector : ProxyConnector private const string HttpHeaderPattern = @"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?"; #if NET7_0_OR_GREATER + private static readonly Regex HttpResponseRegex = GetHttpResponseRegex(); + private static readonly Regex HttpHeaderRegex = GetHttpHeaderRegex(); + [GeneratedRegex(HttpResponsePattern)] - private static partial Regex HttpResponseRegex(); + private static partial Regex GetHttpResponseRegex(); [GeneratedRegex(HttpHeaderPattern)] - private static partial Regex HttpHeaderRegex(); + private static partial Regex GetHttpHeaderRegex(); #else - private static readonly Regex HttpResponseRe = new Regex(HttpResponsePattern, RegexOptions.Compiled); - - private static Regex HttpResponseRegex() - { - return HttpResponseRe; - } - - private static readonly Regex HttpHeaderRe = new Regex(HttpHeaderPattern, RegexOptions.Compiled); - - private static Regex HttpHeaderRegex() - { - return HttpHeaderRe; - } + 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) @@ -93,7 +85,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke if (statusCode is null) { - var statusMatch = HttpResponseRegex().Match(response); + var statusMatch = HttpResponseRegex.Match(response); if (statusMatch.Success) { var httpStatusCode = statusMatch.Result("${statusCode}"); @@ -108,7 +100,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke } // continue on parsing message headers coming from the server - var headerMatch = HttpHeaderRegex().Match(response); + var headerMatch = HttpHeaderRegex.Match(response); if (headerMatch.Success) { var fieldName = headerMatch.Result("${fieldName}"); diff --git a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs index 0dea7c4ee..ca207eb88 100644 --- a/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs +++ b/src/Renci.SshNet/Connection/ProtocolVersionExchange.cs @@ -25,15 +25,12 @@ internal sealed partial class ProtocolVersionExchange : IProtocolVersionExchange private const string ServerVersionPattern = "^SSH-(?[^-]+)-(?.+?)([ ](?.+))?$"; #if NET7_0_OR_GREATER + private static readonly Regex ServerVersionRegex = GetServerVersionRegex(); + [GeneratedRegex(ServerVersionPattern, RegexOptions.ExplicitCapture)] - private static partial Regex ServerVersionRegex(); + private static partial Regex GetServerVersionRegex(); #else - private static readonly Regex ServerVersionRe = new Regex(ServerVersionPattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture); - - private static Regex ServerVersionRegex() - { - return ServerVersionRe; - } + private static readonly Regex ServerVersionRegex = new Regex(ServerVersionPattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture); #endif /// @@ -68,7 +65,7 @@ public SshIdentification Start(string clientVersion, Socket socket, TimeSpan tim throw CreateServerResponseDoesNotContainIdentification(bytesReceived); } - var identificationMatch = ServerVersionRegex().Match(line); + var identificationMatch = ServerVersionRegex.Match(line); if (identificationMatch.Success) { return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"), @@ -115,7 +112,7 @@ public async Task StartAsync(string clientVersion, Socket soc throw CreateServerResponseDoesNotContainIdentification(bytesReceived); } - var identificationMatch = ServerVersionRegex().Match(line); + var identificationMatch = ServerVersionRegex.Match(line); if (identificationMatch.Success) { return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"), diff --git a/src/Renci.SshNet/Netconf/NetConfSession.cs b/src/Renci.SshNet/Netconf/NetConfSession.cs index 1b3aee13a..aca2f8e04 100644 --- a/src/Renci.SshNet/Netconf/NetConfSession.cs +++ b/src/Renci.SshNet/Netconf/NetConfSession.cs @@ -22,25 +22,17 @@ internal sealed partial class NetConfSession : SubsystemSession, INetConfSession 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 LengthRegex(); + private static partial Regex GetLengthRegex(); [GeneratedRegex(ReplyPattern)] - private static partial Regex ReplyRegex(); + private static partial Regex GetReplyRegex(); #else - private static readonly Regex LengthRe = new Regex(LengthPattern, RegexOptions.Compiled); - - private static Regex LengthRegex() - { - return LengthRe; - } - - private static readonly Regex ReplyRe = new Regex(ReplyPattern, RegexOptions.Compiled); - - private static Regex ReplyRegex() - { - return ReplyRe; - } + private static readonly Regex LengthRegex = new Regex(LengthPattern, RegexOptions.Compiled); + private static readonly Regex ReplyRegex = new Regex(ReplyPattern, RegexOptions.Compiled); #endif /// @@ -168,7 +160,7 @@ protected override void OnDataReceived(byte[] data) for (; ; ) { - var match = LengthRegex().Match(chunk.Substring(position)); + var match = LengthRegex.Match(chunk.Substring(position)); if (!match.Success) { break; @@ -180,9 +172,9 @@ protected override void OnDataReceived(byte[] data) } #if NET7_0_OR_GREATER - if (ReplyRegex().IsMatch(chunk.AsSpan(position))) + if (ReplyRegex.IsMatch(chunk.AsSpan(position))) #else - if (ReplyRegex().IsMatch(chunk.Substring(position))) + if (ReplyRegex.IsMatch(chunk.Substring(position))) #endif // NET7_0_OR_GREATER { _ = _rpcReplyReceived.Set(); diff --git a/src/Renci.SshNet/PrivateKeyFile.cs b/src/Renci.SshNet/PrivateKeyFile.cs index 630dfd7bd..2a7749986 100644 --- a/src/Renci.SshNet/PrivateKeyFile.cs +++ b/src/Renci.SshNet/PrivateKeyFile.cs @@ -67,16 +67,13 @@ public partial class PrivateKeyFile : IPrivateKeySource, IDisposable private const string PrivateKeyPattern = @"^-+ *BEGIN (?\w+( \w+)*) PRIVATE KEY *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?[A-Z0-9-]+),(?[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k PRIVATE KEY *-+"; #if NET7_0_OR_GREATER + private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex(); + [GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)] - private static partial Regex PrivateKeyRegex(); + private static partial Regex GetPrivateKeyRegex(); #else - private static readonly Regex PrivateKeyRe = new Regex(PrivateKeyPattern, + private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture); - - private static Regex PrivateKeyRegex() - { - return PrivateKeyRe; - } #endif private readonly List _hostAlgorithms = new List(); @@ -192,7 +189,7 @@ private void Open(Stream privateKey, string passPhrase) using (var sr = new StreamReader(privateKey)) { var text = sr.ReadToEnd(); - privateKeyMatch = PrivateKeyRegex().Match(text); + privateKeyMatch = PrivateKeyRegex.Match(text); } if (!privateKeyMatch.Success) diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs index f82ca2141..8dd42a215 100644 --- a/src/Renci.SshNet/ScpClient.cs +++ b/src/Renci.SshNet/ScpClient.cs @@ -37,35 +37,22 @@ public partial class ScpClient : BaseClient private const string TimestampPattern = @"T(?\d+) 0 (?\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 FileInfoRegex(); + private static partial Regex GetFileInfoRegex(); [GeneratedRegex(DirectoryInfoPattern)] - private static partial Regex DirectoryInfoRegex(); + private static partial Regex GetDirectoryInfoRegex(); [GeneratedRegex(TimestampPattern)] - private static partial Regex TimestampRegex(); + private static partial Regex GetTimestampRegex(); #else - private static readonly Regex FileInfoRe = new Regex(FileInfoPattern, RegexOptions.Compiled); - - private static Regex FileInfoRegex() - { - return FileInfoRe; - } - - private static readonly Regex DirectoryInfoRe = new Regex(DirectoryInfoPattern, RegexOptions.Compiled); - - private static Regex DirectoryInfoRegex() - { - return DirectoryInfoRe; - } - - private static readonly Regex TimestampRe = new Regex(TimestampPattern, RegexOptions.Compiled); - - private static Regex TimestampRegex() - { - return TimestampRe; - } + 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 }; @@ -491,7 +478,7 @@ public void Download(string filename, Stream destination) SendSuccessConfirmation(channel); // Send reply var message = ReadString(input); - var match = FileInfoRegex().Match(message); + var match = FileInfoRegex.Match(message); if (match.Success) { @@ -790,7 +777,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - var match = DirectoryInfoRegex().Match(message); + var match = DirectoryInfoRegex.Match(message); if (match.Success) { SendSuccessConfirmation(channel); // Send reply @@ -817,7 +804,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - match = FileInfoRegex().Match(message); + match = FileInfoRegex.Match(message); if (match.Success) { // Read file @@ -847,7 +834,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI continue; } - match = TimestampRegex().Match(message); + match = TimestampRegex.Match(message); if (match.Success) { // Read timestamp