Skip to content

Commit 23323aa

Browse files
authored
use Regex Source Generator for .NET 7+ (#1401)
* use Regex Source Generator for .NET 7+ fixes #1131 * use shorter pattern for GeneratedRegex
1 parent aa70718 commit 23323aa

File tree

6 files changed

+90
-31
lines changed

6 files changed

+90
-31
lines changed

src/Renci.SshNet/.editorconfig

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,6 @@ dotnet_diagnostic.S2589.severity = none
3333

3434
dotnet_diagnostic.S2372.severity = none
3535

36-
#### SYSLIB diagnostics ####
37-
38-
# SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time
39-
#
40-
# TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented.
41-
dotnet_diagnostic.SYSLIB1045.severity = none
42-
4336
#### StyleCop Analyzers rules ####
4437

4538
# SA1123: Do not place regions within elements

src/Renci.SshNet/Connection/HttpConnector.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,32 @@ namespace Renci.SshNet.Connection
2929
/// </item>
3030
/// </list>
3131
/// </remarks>
32-
internal sealed class HttpConnector : ProxyConnector
32+
internal sealed partial class HttpConnector : ProxyConnector
3333
{
34+
private const string HttpResponsePattern = @"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$";
35+
private const string HttpHeaderPattern = @"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?";
36+
37+
#if NET7_0_OR_GREATER
38+
private static readonly Regex HttpResponseRegex = GetHttpResponseRegex();
39+
private static readonly Regex HttpHeaderRegex = GetHttpHeaderRegex();
40+
41+
[GeneratedRegex(HttpResponsePattern)]
42+
private static partial Regex GetHttpResponseRegex();
43+
44+
[GeneratedRegex(HttpHeaderPattern)]
45+
private static partial Regex GetHttpHeaderRegex();
46+
#else
47+
private static readonly Regex HttpResponseRegex = new Regex(HttpResponsePattern, RegexOptions.Compiled);
48+
private static readonly Regex HttpHeaderRegex = new Regex(HttpHeaderPattern, RegexOptions.Compiled);
49+
#endif
50+
3451
public HttpConnector(ISocketFactory socketFactory)
3552
: base(socketFactory)
3653
{
3754
}
3855

3956
protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socket socket)
4057
{
41-
var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
42-
var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
43-
4458
SocketAbstraction.Send(socket, SshData.Ascii.GetBytes(string.Format(CultureInfo.InvariantCulture,
4559
"CONNECT {0}:{1} HTTP/1.0\r\n",
4660
connectionInfo.Host,
@@ -71,7 +85,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke
7185

7286
if (statusCode is null)
7387
{
74-
var statusMatch = httpResponseRe.Match(response);
88+
var statusMatch = HttpResponseRegex.Match(response);
7589
if (statusMatch.Success)
7690
{
7791
var httpStatusCode = statusMatch.Result("${statusCode}");
@@ -86,7 +100,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke
86100
}
87101

88102
// continue on parsing message headers coming from the server
89-
var headerMatch = httpHeaderRe.Match(response);
103+
var headerMatch = HttpHeaderRegex.Match(response);
90104
if (headerMatch.Success)
91105
{
92106
var fieldName = headerMatch.Result("${fieldName}");

src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@ namespace Renci.SshNet.Connection
1919
/// <remarks>
2020
/// https://tools.ietf.org/html/rfc4253#section-4.2.
2121
/// </remarks>
22-
internal sealed class ProtocolVersionExchange : IProtocolVersionExchange
22+
internal sealed partial class ProtocolVersionExchange : IProtocolVersionExchange
2323
{
2424
private const byte Null = 0x00;
25+
private const string ServerVersionPattern = "^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$";
2526

26-
private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+?)([ ](?<comments>.+))?$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
27+
#if NET7_0_OR_GREATER
28+
private static readonly Regex ServerVersionRegex = GetServerVersionRegex();
29+
30+
[GeneratedRegex(ServerVersionPattern, RegexOptions.ExplicitCapture)]
31+
private static partial Regex GetServerVersionRegex();
32+
#else
33+
private static readonly Regex ServerVersionRegex = new Regex(ServerVersionPattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
34+
#endif
2735

2836
/// <summary>
2937
/// Performs the SSH protocol version exchange.
@@ -57,7 +65,7 @@ public SshIdentification Start(string clientVersion, Socket socket, TimeSpan tim
5765
throw CreateServerResponseDoesNotContainIdentification(bytesReceived);
5866
}
5967

60-
var identificationMatch = ServerVersionRe.Match(line);
68+
var identificationMatch = ServerVersionRegex.Match(line);
6169
if (identificationMatch.Success)
6270
{
6371
return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),
@@ -104,7 +112,7 @@ public async Task<SshIdentification> StartAsync(string clientVersion, Socket soc
104112
throw CreateServerResponseDoesNotContainIdentification(bytesReceived);
105113
}
106114

107-
var identificationMatch = ServerVersionRe.Match(line);
115+
var identificationMatch = ServerVersionRegex.Match(line);
108116
if (identificationMatch.Success)
109117
{
110118
return new SshIdentification(GetGroupValue(identificationMatch, "protoversion"),

src/Renci.SshNet/Netconf/NetConfSession.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,32 @@
99

1010
namespace Renci.SshNet.NetConf
1111
{
12-
internal sealed class NetConfSession : SubsystemSession, INetConfSession
12+
internal sealed partial class NetConfSession : SubsystemSession, INetConfSession
1313
{
1414
private const string Prompt = "]]>]]>";
15-
15+
private const string LengthPattern = @"\n#(?<length>\d+)\n";
16+
private const string ReplyPattern = @"\n##\n";
1617
private readonly StringBuilder _data = new StringBuilder();
1718
private bool _usingFramingProtocol;
1819
private EventWaitHandle _serverCapabilitiesConfirmed = new AutoResetEvent(initialState: false);
1920
private EventWaitHandle _rpcReplyReceived = new AutoResetEvent(initialState: false);
2021
private StringBuilder _rpcReply = new StringBuilder();
2122
private int _messageId;
2223

24+
#if NET7_0_OR_GREATER
25+
private static readonly Regex LengthRegex = GetLengthRegex();
26+
private static readonly Regex ReplyRegex = GetReplyRegex();
27+
28+
[GeneratedRegex(LengthPattern)]
29+
private static partial Regex GetLengthRegex();
30+
31+
[GeneratedRegex(ReplyPattern)]
32+
private static partial Regex GetReplyRegex();
33+
#else
34+
private static readonly Regex LengthRegex = new Regex(LengthPattern, RegexOptions.Compiled);
35+
private static readonly Regex ReplyRegex = new Regex(ReplyPattern, RegexOptions.Compiled);
36+
#endif
37+
2338
/// <summary>
2439
/// Gets NetConf server capabilities.
2540
/// </summary>
@@ -145,7 +160,7 @@ protected override void OnDataReceived(byte[] data)
145160

146161
for (; ; )
147162
{
148-
var match = Regex.Match(chunk.Substring(position), @"\n#(?<length>\d+)\n");
163+
var match = LengthRegex.Match(chunk.Substring(position));
149164
if (!match.Success)
150165
{
151166
break;
@@ -157,9 +172,9 @@ protected override void OnDataReceived(byte[] data)
157172
}
158173

159174
#if NET7_0_OR_GREATER
160-
if (Regex.IsMatch(chunk.AsSpan(position), @"\n##\n"))
175+
if (ReplyRegex.IsMatch(chunk.AsSpan(position)))
161176
#else
162-
if (Regex.IsMatch(chunk.Substring(position), @"\n##\n"))
177+
if (ReplyRegex.IsMatch(chunk.Substring(position)))
163178
#endif // NET7_0_OR_GREATER
164179
{
165180
_ = _rpcReplyReceived.Set();

src/Renci.SshNet/PrivateKeyFile.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,19 @@ namespace Renci.SshNet
6262
/// </list>
6363
/// </para>
6464
/// </remarks>
65-
public class PrivateKeyFile : IPrivateKeySource, IDisposable
65+
public partial class PrivateKeyFile : IPrivateKeySource, IDisposable
6666
{
67-
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 *-+",
67+
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 *-+";
68+
69+
#if NET7_0_OR_GREATER
70+
private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex();
71+
72+
[GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
73+
private static partial Regex GetPrivateKeyRegex();
74+
#else
75+
private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern,
6876
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
77+
#endif
6978

7079
private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
7180
private Key _key;

src/Renci.SshNet/ScpClient.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,31 @@ namespace Renci.SshNet
3232
public partial class ScpClient : BaseClient
3333
{
3434
private const string Message = "filename";
35-
private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", RegexOptions.Compiled);
35+
private const string FileInfoPattern = @"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)";
36+
private const string DirectoryInfoPattern = @"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)";
37+
private const string TimestampPattern = @"T(?<mtime>\d+) 0 (?<atime>\d+) 0";
38+
39+
#if NET7_0_OR_GREATER
40+
private static readonly Regex FileInfoRegex = GetFileInfoRegex();
41+
private static readonly Regex DirectoryInfoRegex = GetDirectoryInfoRegex();
42+
private static readonly Regex TimestampRegex = GetTimestampRegex();
43+
44+
[GeneratedRegex(FileInfoPattern)]
45+
private static partial Regex GetFileInfoRegex();
46+
47+
[GeneratedRegex(DirectoryInfoPattern)]
48+
private static partial Regex GetDirectoryInfoRegex();
49+
50+
[GeneratedRegex(TimestampPattern)]
51+
private static partial Regex GetTimestampRegex();
52+
#else
53+
private static readonly Regex FileInfoRegex = new Regex(FileInfoPattern, RegexOptions.Compiled);
54+
private static readonly Regex DirectoryInfoRegex = new Regex(DirectoryInfoPattern, RegexOptions.Compiled);
55+
private static readonly Regex TimestampRegex = new Regex(TimestampPattern, RegexOptions.Compiled);
56+
#endif
57+
3658
private static readonly byte[] SuccessConfirmationCode = { 0 };
3759
private static readonly byte[] ErrorConfirmationCode = { 1 };
38-
private static readonly Regex DirectoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", RegexOptions.Compiled);
39-
private static readonly Regex TimestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0", RegexOptions.Compiled);
4060

4161
private IRemotePathTransformation _remotePathTransformation;
4262
private TimeSpan _operationTimeout;
@@ -458,7 +478,7 @@ public void Download(string filename, Stream destination)
458478
SendSuccessConfirmation(channel); // Send reply
459479

460480
var message = ReadString(input);
461-
var match = FileInfoRe.Match(message);
481+
var match = FileInfoRegex.Match(message);
462482

463483
if (match.Success)
464484
{
@@ -757,7 +777,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI
757777
continue;
758778
}
759779

760-
var match = DirectoryInfoRe.Match(message);
780+
var match = DirectoryInfoRegex.Match(message);
761781
if (match.Success)
762782
{
763783
SendSuccessConfirmation(channel); // Send reply
@@ -784,7 +804,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI
784804
continue;
785805
}
786806

787-
match = FileInfoRe.Match(message);
807+
match = FileInfoRegex.Match(message);
788808
if (match.Success)
789809
{
790810
// Read file
@@ -814,7 +834,7 @@ private void InternalDownload(IChannelSession channel, Stream input, FileSystemI
814834
continue;
815835
}
816836

817-
match = TimestampRe.Match(message);
837+
match = TimestampRegex.Match(message);
818838
if (match.Success)
819839
{
820840
// Read timestamp

0 commit comments

Comments
 (0)